gopay-merchant-qris-gateway: add topic — GoPay/GoBiz merchant→QRIS gateway reverse-engineering audit (gopay-api-gateaway, gobiz-payment, QrisMerchantID, pinned commits)
This commit is contained in:
parent
d512cd7514
commit
376a811476
6 changed files with 589 additions and 0 deletions
59
gopay-merchant-qris-gateway/index.md
Normal file
59
gopay-merchant-qris-gateway/index.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
title: "GoPay Merchant QRIS gateways — how a merchant account becomes an unofficial payment gateway"
|
||||
description: "Reverse-engineering audit of three open-source repos (gopay-api-gateaway, gobiz-payment, QrisMerchantID): how a GoPay/GoBiz merchant's own QRIS + private portal APIs are automated into a self-hosted 'payment gateway' — dynamic QRIS via EMVCo TLV, payment confirmation by polling the merchant's own transaction feed, and the HAR-capture/APK-analysis methods used to reverse engineer it. Pinned at Oct 2026 commits."
|
||||
status: concluded
|
||||
category: technical
|
||||
tags: [gopay, gobiz, qris, payment-gateway, reverse-engineering, emvco, har-capture, apk-analysis, api-replay, indonesia, fintech, unofficial-api, gojek]
|
||||
draft: true
|
||||
created: 2026-10-15
|
||||
updated: 2026-10-15
|
||||
---
|
||||
|
||||
# GoPay Merchant QRIS gateways
|
||||
|
||||
**Research question:** How do tools like `gopay-api-gateaway`, `gobiz-payment`, and `QrisMerchantID` turn a GoPay/GoBiz merchant account into a "payment gateway API" — how does the mechanism actually work, and how were the underlying APIs reverse engineered?
|
||||
|
||||
**Short answer [verified].** There is no fake gateway and no money-routing trick. All three repos automate **two things the merchant can already do with their own account**: (1) **render** a dynamic QRIS for an order — done fully offline by editing the merchant's own static QRIS string (EMVCo TLV: inject the amount into tag 54, recompute CRC16); and (2) **detect** the matching incoming payment by polling the merchant's own transaction feed through GoBiz's private web-portal API, authenticated as the merchant. Settlement still happens on GoPay's normal QRIS rails (buyer's wallet/bank app → merchant's GoPay/GoBiz balance). The "gateway" is presentation + reconciliation automation around the merchant's own account — which is why it works without any licensed payment provider (PJP), and why it sits in a ToS/compliance gray zone.
|
||||
|
||||
**How it was reverse engineered [verified].** Mostly *gray-box*, not exploits: the APIs were recovered by capturing the merchant's own web-portal sessions (HAR exports shared between researchers), replaying them request-by-request ("mirror the browser, byte-for-byte"), reading older open-source repos, and extracting endpoint inventories from the public Android APKs (apktool/androguard/strings on the Flutter app). The QR half needed no reverse engineering at all — EMVCo QRIS is a public spec. The ongoing maintenance burden is **client-version drift**: the portal appears to gate requests on an `X-AppVersion` build string and has moved its auth endpoints, so replay clients must track portal releases (observed sequence v3.107 → v3.119 → v3.122 → v3.125 between Jun–Oct 2026).
|
||||
|
||||
## Repos audited
|
||||
|
||||
| Repo | What it is | Lang · license | Stars (captured 2026-10-15) | Audit note |
|
||||
|---|---|---|---|---|
|
||||
| [S3] `ahmadzakiyox/gopay-api-gateaway` | Self-hosted Express gateway: REST API + hosted QRIS checkout page | Node.js · MIT | 233 ★ / 84 forks | `server.js` readable; `login.js` + `sessionManager.js` **obfuscated** |
|
||||
| [S2] `kavionn/gobiz-payment` | Library: `GoPayMerchant` client + polling watcher | Node.js · MIT | 73 ★ / 29 forks | Fully readable; primary code reference |
|
||||
| [S1] `AlfinAI/QrisMerchantID` | Python SDK + **the public RE knowledge base** (APK analyses, HAR verification reports, reproduction guide) | Python · MIT | 12 ★ / 2 forks | Rich documentation; parts explicitly runtime-unverified |
|
||||
|
||||
## Key findings
|
||||
|
||||
1. **[verified] The "gateway" never touches money.** It renders QRIS (offline string edit) and confirms payments (own-account polling). No PJP, no acquiring, no fund flow. [S6]
|
||||
2. **[verified] Auth = GoID portal replay.** OTP or email+password against `/goid/login/request` + `/goid/token` with copied browser fingerprint headers; JWE bearer tokens, cached on disk and re-obtained on expiry. [S7][S10]
|
||||
3. **[verified] Payment confirmation = polling the merchant's own feed.** `GET api.gojekapi.com/merchant-analytics/v2/merchants/transactions` (primary) or `POST api.gobiz.co.id/journals/search` (fallback/detail), every ~6 s while a checkout is active; match amount + time; dedupe by transaction id. [S2][S6]
|
||||
4. **[verified] Anti-double-claim is a known hard problem** (two buyers paying the same nominal). Implemented: claim scoping by a unique `trx_id` + dedupe by `transaction_id`. Recommended additionally: add a unique Rp1–99 code to the billed amount. [S3][S6]
|
||||
5. **[verified] Reverse engineering was HAR-driven.** Four captured sessions are documented (up to 80 MB / 1063 entries each); replay clients were diffed against capture data header-by-header. Live-credential HARs are treated as crown jewels: gitignored, never committed, masked in reports. [S6][S7]
|
||||
6. **[verified] The APK analysis is a map of what's possible, not what's used.** ~250 endpoints were inventoried statically from GoPay Merchant 2.3.0 + GoFood Merchant 5.49.0; almost all are marked *unverified at runtime*. The subset actually in use is the HAR-verified portal API above. [S8][S12]
|
||||
7. **[reported] Version drift breaks clients regularly.** Documented fixes: phone normalisation (bare national digits only), dropping the `login_type` field, moving auth calls to the portal host, and repeated `X-AppVersion` bumps. Issue trackers show OTP delivery and empty-history reports — maintenance is ongoing. [S7][S13][S14]
|
||||
8. **[verified] Obfuscation sits exactly where it hurts.** The most popular gateway hides precisely the OTP login + session-refresh code (built with `javascript-obfuscator`), so what it does with the merchant's OTP, credentials, and tokens **cannot be audited from source**. [S3]
|
||||
|
||||
## Confidence labels used in this report
|
||||
|
||||
- **[verified]** — read directly in pinned source code, or corroborated by a documented live capture.
|
||||
- **[reported]** — asserted by a source (repo author, capture report); plausible and consistent, not independently confirmed by us.
|
||||
- **[unverified]** — explicitly flagged as untested/unconfirmed in the sources themselves.
|
||||
- **[inference]** — this report's own analysis, not a source claim.
|
||||
|
||||
## Files
|
||||
|
||||
- [[gopay-merchant-qris-gateway/analysis|Analysis — how the mechanism works]]
|
||||
- [[gopay-merchant-qris-gateway/reverse-engineering|Reverse engineering — methods & drift playbook]]
|
||||
- [[gopay-merchant-qris-gateway/repos|Repo audits & comparison]]
|
||||
- [[gopay-merchant-qris-gateway/security|Security, legal & operational risks]]
|
||||
- [[gopay-merchant-qris-gateway/sources|Sources & citations]]
|
||||
|
||||
## Open questions
|
||||
|
||||
1. **[unverified] Unit-handling discrepancy across implementations:** `gobiz.js` and `QrisMerchantID` both divide `gross_amount` by 100 (minor units, i.e. sen), while the gateway's `verifyPayment()` compares the raw field against a rupiah input with no normalisation. Either it was validated against a different response shape, or its `/check-payment` never matches. Resolvable only with a live merchant account. Details in [[gopay-merchant-qris-gateway/analysis|analysis]] §4 (Money units).
|
||||
2. **[unverified] App-side protocol remains traffic-unverified:** the GoPay Merchant app's own API set (realtime WebSocket `raccoon` events, `qris-link` charge endpoints) is inventoried from static analysis only; a realtime (non-polling) watcher on the app protocol is the obvious next build for anyone with a device lab. [S8][S15]
|
||||
3. **[unverified] Request signing:** no static signing secret exists client-side and no signing was observed on the web path; whether app-side calls add per-request signatures is unknown. [S15]
|
||||
4. **[reported] Suspension triggers:** community threads tie account bans to aggressive polling and to transaction spikes; neither is confirmed against any GoPay statement. [S4][S14]
|
||||
Loading…
Add table
Add a link
Reference in a new issue