# Architecture

```
Merchant ──(1) generate QR──▶ ① Acquirer :34100      stores QR record (PAN, amount?, ref, expiry)
Merchant ◀──(2) QR string──── ① Acquirer
Payer app (③ Issuer :34300): (3) scan → CRC + field checks (BqrCore.decode) → show name/amount
Payer app: (4) confirm → issuer places a hold, assigns txn_id
③ ──(5) pay_request──▶ ② Switch :34200               idempotency, merchant lookup, route
② ──(6) credit_request──▶ ① Acquirer                 match QR record, credit merchant
② ◀──(7) txn_response──── ①
③ ◀──(8) txn_response──── ②                          issuer finalizes debit or releases hold
① ──(9) webhook──▶ Merchant                          only after the credit is confirmed
(10) timeout: ③ → ② status_request → ② reverse_request if still unknown
(11) end of day: reconciliation file
```

## Layout

Each system is one Phoenix app (`BqrAcquirer` + `BqrAcquirerWeb`, same for issuer and switch).
Acquirer and issuer live in the umbrella next to `bqr_core` and `bqr_protocol`; the switch simulator
is a separate Mix project (`bqr_switch_sim/`) that depends only on `bqr_protocol`. Systems talk only
over HTTP/JSON signed with HMAC.

## Rules that matter

* The payer is told "success" only after step 8, never when the switch receives
  the payment.
* CRC detects corruption, not tampering. Tamper protection is server-side:
  active merchant (acquirer_id + merchant_pan), and for dynamic QRs the amount
  and reference must match the stored QR record.
* Every payment has a unique `txn_id`; the switch and acquirer are idempotent.
* A dynamic QR is single use and expires (held by the acquirer, not in the payload).
* PENDING is never final: resolve with status inquiry, then reverse.
* Merchant notification is separate from, and cannot affect, the payment result.

## States

Acquirer: `QR_ISSUED → CREDIT_RECEIVED → CREDITED → NOTIFIED` (+ `FAILED`, `EXPIRED`, `REVERSED`).
Issuer: `HOLD_PLACED → SENT → COMPLETED | RELEASED | UNKNOWN`; `UNKNOWN → COMPLETED | RELEASED` by status inquiry or reversal.
Switch: `RECEIVED → ROUTED → SUCCESS | FAILED | PENDING`; `PENDING → SUCCESS | FAILED` by status inquiry or reversal.
Acquirer credit: `credited | declined`; `credited → reversed` (the QR stays used).

## Plan

0 skeleton · 1 bqr_core · 2 bqr_protocol · 3 acquirer: merchants + QR generation ·
4 switch: registry, /pay, routing, ledger · 5 issuer: accounts, scan page, pay ·
6 status inquiry/reversal/retry, late-arrival guard, fault injection (done) · 7 webhooks, reconciliation, dashboards.
