forked from thijooree/android
177 lines
5.1 KiB
Markdown
177 lines
5.1 KiB
Markdown
# PayMV QR Format
|
|
|
|
Documents the decimal TLV QR format used by the PayMV/Favara payment network in the Maldives — both generation (to receive payment) and parsing (to initiate a transfer by scanning).
|
|
|
|
---
|
|
|
|
## Encoding
|
|
|
|
PayMV QRs use a **decimal TLV** encoding — not binary BER-TLV. Every field is represented as ASCII text:
|
|
|
|
```
|
|
<2-digit decimal tag><2-digit decimal length><value>...
|
|
```
|
|
|
|
Example — tag `59`, value `"AHMED ALI"` (9 chars):
|
|
```
|
|
5909AHMED ALI
|
|
```
|
|
|
|
Tags and lengths are always exactly 2 decimal digits. Fields are concatenated directly with no separator.
|
|
|
|
---
|
|
|
|
## Root-Level Tags
|
|
|
|
| Tag | Field | Notes |
|
|
|---|---|---|
|
|
| `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 |
|
|
| `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 |
|
|
| `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 |
|
|
|
|
---
|
|
|
|
## Merchant Account Info — Tag `26` Sub-Tags
|
|
|
|
| Sub-Tag | Field | Example value |
|
|
|---|---|---|
|
|
| `00` | Domain | `"mv.favara.mpqr"` |
|
|
| `01` | Acquirer BIC | `"MALBMVMV"` (see table below) |
|
|
| `02` | Acquirer BIC (repeated) | Same as `01` |
|
|
| `03` | Account number | Beneficiary account number |
|
|
| `05` | Mobile number | E.164 format (e.g. `"+9607654321"`); optional |
|
|
| `10` | Network indicator | `"IPAY"` |
|
|
|
|
### Acquirer BIC Mapping
|
|
|
|
| Bank | Acquirer BIC |
|
|
|---|---|
|
|
| BML | `MALBMVMV` |
|
|
| MIB | `MADVMVMV` |
|
|
| Fahipay | `FAHIMVMV` |
|
|
|
|
---
|
|
|
|
## Additional Data — Tag `62` Sub-Tags
|
|
|
|
| Sub-Tag | Field | Notes |
|
|
|---|---|---|
|
|
| `05` | Reference / bill number | 9 random uppercase alphanumeric characters |
|
|
| `08` | Payment purpose | Free-form text entered by the payee |
|
|
|
|
---
|
|
|
|
## Supplementary Data — Tag `80` Sub-Tags
|
|
|
|
| Sub-Tag | Field | Notes |
|
|
|---|---|---|
|
|
| `00` | Domain | `"mv.favara.mpqr"` |
|
|
| `01` | Timestamp | ISO 8601 format: `"yyyy-MM-dd'T'HH:mm:ss.00000"` |
|
|
|
|
---
|
|
|
|
## CRC-16
|
|
|
|
The checksum uses **CRC-16/CCITT-FALSE** (polynomial `0x1021`, initial value `0xFFFF`). The CRC is computed over the entire payload string up to and including `"6304"`, then the 4-digit uppercase hex result is appended.
|
|
|
|
```python
|
|
def crc16(data: str) -> str:
|
|
crc = 0xFFFF
|
|
for c in data:
|
|
crc ^= (ord(c) & 0xFF) << 8
|
|
for _ in range(8):
|
|
if crc & 0x8000:
|
|
crc = ((crc << 1) & 0xFFFF) ^ 0x1021
|
|
else:
|
|
crc = (crc << 1) & 0xFFFF
|
|
return format(crc, '04X')
|
|
```
|
|
|
|
---
|
|
|
|
## Generating a Receive-Payment QR
|
|
|
|
To create a QR that others can scan to pay you:
|
|
|
|
```
|
|
00 02 01 ← Format indicator
|
|
01 02 11 ← Static QR
|
|
26 <len> ← Merchant account info
|
|
00 15 mv.favara.mpqr
|
|
01 08 MALBMVMV ← Acquirer BIC (BML example)
|
|
02 08 MALBMVMV ← Repeated
|
|
03 <len> <accountNumber>
|
|
05 <len> <+960XXXXXXX> ← Optional phone
|
|
10 04 IPAY
|
|
52 04 0000 ← MCC
|
|
53 03 462 ← MVR
|
|
54 <len> <amount> ← Omit tag entirely if open-amount
|
|
58 02 MV
|
|
59 <len> <name up to 25 chars>
|
|
62 <len>
|
|
05 09 <9 random alphanum chars> ← Reference
|
|
08 <len> <purpose text>
|
|
80 <len>
|
|
00 15 mv.favara.mpqr
|
|
01 <len> <yyyy-MM-dd'T'HH:mm:ss.00000> ← Timestamp
|
|
6304<CRC>
|
|
```
|
|
|
|
---
|
|
|
|
## Parsing a PayMV QR (Incoming Scan)
|
|
|
|
When scanning a QR code, extract the relevant fields:
|
|
|
|
| Field | TLV path | Used for |
|
|
|---|---|---|
|
|
| Account number | root→`26`→`03` | Transfer destination |
|
|
| Amount | root→`54` | Pre-fill transfer amount (may be absent) |
|
|
| Merchant name | root→`59` | Display recipient name |
|
|
| Purpose | root→`62`→`08` | Pre-fill transfer remarks |
|
|
|
|
---
|
|
|
|
## Extracting a BML Gateway URL from a Combined QR
|
|
|
|
Combined QRs (e.g. Fahipay card QRs that embed a BML gateway payment URL) encode the BML URL at a fixed TLV path:
|
|
|
|
```
|
|
root tag 35 → sub-tag 20 → sub-sub-tag 01
|
|
```
|
|
|
|
The value at sub-sub-tag `01` is a full `https://pay.bml.com.mv/app/...` URL. Extract it and hand off to the [BML QR Payment flow](../bmlapi/13-qr-payment.md).
|
|
|
|
Plain BML QR codes (not combined) start with `https://pay.bml.com.mv/app/` directly.
|
|
|
|
---
|
|
|
|
## Example Payload
|
|
|
|
Static QR for account `7700000000123`, holder `"AHMED ALI"`, open amount, purpose `"Rent"`:
|
|
|
|
```
|
|
000201010211268...520400005303462
|
|
5802MV5909AHMED ALI6225050912345ABCDEF0804Rent
|
|
80...63044A2B
|
|
```
|
|
|
|
(values abbreviated for clarity — actual tags are concatenated with no whitespace)
|
|
|
|
---
|
|
|
|
|
|
|
|
---
|
|
|
|
[← README](README.md) **Next →** [Account Parser Architecture](19-parsers.md)
|