# Self-reconcile — start here

You mint the code, build the QR from it, and match incoming webhooks against
your own database. This is the only payment model GPM Pay has: it reports
transactions, it does not hold order state.

## Run

```bash
npm install express @gpmpay/sdk

export GPMPAY_API_TOKEN=gpm_xxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyy
node server.mjs
```

Scopes needed: `bank-accounts:read` and `webhooks:manage`.

## Wire up the webhook

```bash
# terminal 2
ngrok http 3000

# terminal 3
curl -X POST localhost:3000/setup-webhook \
  -H 'content-type: application/json' \
  -d '{"publicUrl":"https://<id>.ngrok.app"}'
```

The secret is printed to the server console **once**. Export it and restart:

```bash
export GPMPAY_WEBHOOK_SECRET=<printed value>
node server.mjs
```

## Try it

```bash
curl -X POST localhost:3000/checkout \
  -H 'content-type: application/json' \
  -d '{"orderId":"123","amount":50000}'
```

```json
{
  "qrPayload": "00020101021238...",
  "qrImageUrl": "https://img.vietqr.io/image/970422-1234567890-compact.png?amount=50000&addInfo=DH123&accountName=NGUYEN+VAN+A",
  "amount": 50000,
  "transferContent": "DH123",
  "bankName": "MBBank",
  "bankBin": "970422",
  "accountNumber": "1234567890",
  "accountName": "NGUYEN VAN A"
}
```

Simulate the payment (sandbox / localhost only):

```ts
await client.simulator.createTransaction({
  bankAccountId: account.id,
  amount: 50_000,
  transferContent: 'DH123',   // the same code the checkout handed out
});
```

The server logs `✅ DH123 paid 50.000 ₫`.

## What to copy

- **`buildPaymentInstructions({ bankAccount, amount, transferContent })`** — one
  local call, no network. `transferContent` becomes what the payer types, and it
  is the only thing tying a bank transfer back to your order.
- **`express.raw({ type: 'application/json' })` on the webhook route only**, and
  mounted *before* any global `express.json()`.
- **`claimTransaction(payload.id)`** — deliveries retry up to 6 times. Replace
  the `Set` with a unique constraint in your database.
- **The `transferType !== 'in'` guard** — you receive a webhook for *every*
  transaction on the account, including outgoing ones.
- **The amount check.** Nothing compares it for you. The example marks the order
  `UNDERPAID` rather than silently fulfilling it.
- **Integer VND.** `Math.round()` at the boundary; a fractional amount in the QR
  produces a transfer that will not equal your stored total.
- **A regex, not `===`.** Banks normalize the content and some prepend their own
  prefix, so `CODE_REGEX.exec(content)` beats comparing the whole string.

## Two things this example leaves to you

1. **Expiry.** There is no `expiresAt` here — decide how long a `PENDING` order
   stays payable and sweep the stale ones yourself.
2. **Durability.** `orders` and `processedTransactions` are in-memory `Map`/`Set`
   and do not survive a restart or a second instance.
