diff --git a/gopay-merchant-qris-gateway/analysis.md b/gopay-merchant-qris-gateway/analysis.md index 745bddf..36d0d94 100644 --- a/gopay-merchant-qris-gateway/analysis.md +++ b/gopay-merchant-qris-gateway/analysis.md @@ -152,3 +152,23 @@ Two buyers paying the same nominal at the same time is the core race condition. - **Feed anomalies** — field report of `getHistory` returning empty for all ranges; maintainer replied it still works (likely account/data scope, not endpoint death). [S14] - **OTP delivery** — an open issue asks why OTP doesn't arrive; consistent with version-gating/format issues rather than a dead flow. [S14] - **Single point of failure** — every design depends on one portal account's session; suspension or forced logout stalls all automation. No repo implements failover to a second account. [inference] + +## 10. Gateway equivalence — why the seller's backend sees a "gateway API" + +The full loop — **buyer pays by QRIS → seller gets an authoritative confirmation → seller proceeds (fulfil, deliver, activate)** — is precisely the loop a commercial payment gateway exists to serve. A licensed PJP gateway (Midtrans, Xendit, DOKU, …) provides it via three services. This stack emulates all three, sourcing each from a non-gateway surface: + +**1. Payment initiation (the "charge" call).** +- *Gateway:* `POST /charge` → the provider registers a transaction and returns a pay-handle (QR/VA/redirect) with provider-side TTL. +- *Here:* no call and no registration — the handle is **derived offline** from the merchant's own static QR (TLV edit + CRC16). Nothing exists in any provider system until the buyer's app acts on the QR; the 5-minute expiry is local bookkeeping only. + +**2. Status / notification (the webhook).** +- *Gateway:* signed push callback to the store, plus a status-query API. +- *Here:* **pull instead of push** — the tool polls the merchant's own transaction feed (~6 s) and synthesizes the "order paid" event locally. No webhook can be provisioned because the flow has no gateway product behind it; the notification channel is "the merchant can see their own payments", made machine-readable by session replay. + +**3. Order binding (transaction ↔ order, 1:1).** +- *Gateway:* the provider maintains handle ↔ order mapping and guarantees a payment is claimable once. +- *Here:* reconstructed locally — amount + time-window matching + a claim registry (`trx_id` scoping / seen-set dedupe), with the unique-nominal (Rp1–99) trick as the practical binding. This is *why* the double-claim problem exists here at all: it is the gap left by the missing provider-side registry. + +**Net effect.** The store-facing contract is gateway-shaped (create QR → poll/wait → receive "paid" → fulfil). Functionally, for the seller's backend, this **is** a payment gateway API. Structurally it is a **merchant-side reconciliation emulator** built from consumer-grade surfaces — which is also why its failure classes (drift, suspension, claims lost on restart, no delivery guarantees) are exactly the guarantees a real gateway product sells. Coverage is the happy path: paid/pending/expired for QRIS/GOPAY payins; refunds, cancellations, disputes, retryable webhooks and reconciliation reports sit outside the emulation (feeds expose refund statuses, but no audited repo drives those workflows). + +[inference throughout — this section maps the audited mechanics onto standard gateway concepts; the per-leg mechanics are sourced in §3–§8.]