forked from thijooree/android
fix BML POS QRs
This commit is contained in:
@@ -28,12 +28,13 @@ Tags and lengths are always exactly 2 decimal digits. Fields are concatenated di
|
||||
| `00` | Format indicator | Always `"01"` |
|
||||
| `01` | Point-of-initiation method | `"11"` = static QR, `"12"` = dynamic QR |
|
||||
| `26` | Merchant account information | Container — see sub-tags below |
|
||||
| `35` | BML/gateway merchant info | Container — present in combined EMV+BML QRs only |
|
||||
| `35` | BML/gateway merchant info | Container — present in combined EMV+BML QRs and in BML POS QRs |
|
||||
| `52` | Merchant category code | `"0000"` (generic) |
|
||||
| `53` | Transaction currency | `"462"` = MVR (ISO 4217 numeric) |
|
||||
| `54` | Transaction amount | Decimal string (e.g. `"1.50"`); absent for open-amount QRs |
|
||||
| `58` | Country code | `"MV"` |
|
||||
| `59` | Merchant / recipient name | Max 25 characters |
|
||||
| `60` | Merchant city / store code | BML POS QRs only |
|
||||
| `62` | Additional data field | Container — see sub-tags below |
|
||||
| `63` | CRC | `6304` prefix + 4-char hex checksum — always last |
|
||||
| `80` | Supplementary data | Container — timestamp and domain |
|
||||
@@ -153,6 +154,67 @@ The value at sub-sub-tag `01` is a full `https://pay.bml.com.mv/app/...` URL. Ex
|
||||
|
||||
Plain BML QR codes (not combined) start with `https://pay.bml.com.mv/app/` directly.
|
||||
|
||||
`PaymvQrParser.bmlQrPayTarget()` covers all of these: it returns the URL for plain URL QRs and for
|
||||
combined QRs, the whole EMV payload for POS QRs (below, recognised by the `mv.com.bml.qtr`
|
||||
supplementary domain), and null for everything else — PayMV QRs keep falling through to `parse()`.
|
||||
|
||||
---
|
||||
|
||||
## BML POS QR (`mv.com.bml.qtr`)
|
||||
|
||||
BML POS terminals emit a third shape — an EMVCo-style dynamic QR whose supplementary data domain
|
||||
(tag `80` → `00`) is `mv.com.bml.qtr` and which has **no tag `26`**, so there is no PayMV account
|
||||
number to transfer to. The same `35` → `20` container is used as in combined QRs, but sub-tag `01`
|
||||
holds a bare reference instead of a URL.
|
||||
|
||||
Example (CRC verified, same CRC-16/CCITT-FALSE as above):
|
||||
|
||||
```
|
||||
00020101021235752071000202013502:215c7b9f15ce4ed28e15697ea976db9902109809724081030874009538520400005303462540436005802MV5911BEST BANANA6006LD044262220510dtyams497d0804POPE80470014mv.com.bml.qtr01252026-09-21T13:33:22.00000630443FA
|
||||
```
|
||||
|
||||
| TLV path | Value | Notes |
|
||||
|---|---|---|
|
||||
| `00` | `01` | Format indicator |
|
||||
| `01` | `12` | Dynamic QR |
|
||||
| `35`→`20`→`00` | `02` | Version / format of the container (combined QRs use the URL form) |
|
||||
| `35`→`20`→`01` | `02:215c7b9f15ce4ed28e15697ea976db99` | Payment reference — `<type>:<32 hex>` |
|
||||
| `35`→`20`→`02` | `9809724081` | Merchant identifier (10 digits) |
|
||||
| `35`→`20`→`03` | `74009538` | Terminal identifier (8 digits) |
|
||||
| `52` | `0000` | MCC |
|
||||
| `53` | `462` | MVR |
|
||||
| `54` | `3600` | Amount — **no decimal point**, unlike PayMV's `"1.50"` |
|
||||
| `58` / `59` / `60` | `MV` / `BEST BANANA` / `LD0442` | Country, merchant name, merchant city/store code |
|
||||
| `62`→`05` | `dtyams497d` | Reference / bill number |
|
||||
| `62`→`08` | `POPE` | Purpose / terminal label |
|
||||
| `80`→`00` | `mv.com.bml.qtr` | Domain — identifies the POS format |
|
||||
| `80`→`01` | `2026-09-21T13:33:22.00000` | Timestamp |
|
||||
|
||||
### Pay-Request Lookup Key
|
||||
|
||||
The lookup key is the **bare reference**, Base64-encoded without padding:
|
||||
|
||||
```
|
||||
GET .../walletpayments/payrequest/MDI6MjE1YzdiOWYxNWNlNGVkMjhlMTU2OTdlYTk3NmRiOTk
|
||||
```
|
||||
|
||||
BML's own app omits the `=` padding, so `BmlQrPayClient.lookupPayRequest()` encodes with
|
||||
`NO_WRAP or NO_PADDING` for every QR type; the padded form resolves too. What the request *must*
|
||||
carry is `accept: application/json` — see
|
||||
[QR Payment → Step 1 headers](../bmlapi/13-qr-payment.md#headers).
|
||||
|
||||
Confirmed against the live API (the example QR had already expired, so BML answered `103` rather
|
||||
than with merchant details — but `103` means the reference itself resolved):
|
||||
|
||||
| Key tried | Response |
|
||||
|---|---|
|
||||
| `02:215c7b9f15ce4ed28e15697ea976db99` | `103` — "The payment request has expired" ✅ recognised |
|
||||
| the whole EMV payload | `112` — "Unsupported payment link" ❌ |
|
||||
| `https://pay.bml.com.mv/app/02:215c…` | `103` — recognised too; the host itself 400s on that path, so the backend must strip the prefix |
|
||||
|
||||
`PaymvQrParser.bmlPayRequestKey()` returns the URL for URL QRs and the reference for POS QRs, so
|
||||
exactly one request is made either way.
|
||||
|
||||
---
|
||||
|
||||
## Example Payload
|
||||
|
||||
Reference in New Issue
Block a user