244 lines
5.8 KiB
Markdown
244 lines
5.8 KiB
Markdown
# QR Payment
|
|
|
|
BML supports QR-based payments via the PayMV network. There are two QR types — static merchant QRs (no preset amount) and gateway QRs (amount preset by merchant). Both are paid via the same 3-step TOTP-authenticated flow.
|
|
|
|
---
|
|
|
|
## QR Code Types
|
|
|
|
| Type code | Name | Amount |
|
|
|---|---|---|
|
|
| `QRS` | Static QR | `0.00` — user enters amount |
|
|
| `QRR` | Gateway / dynamic QR | Preset by merchant |
|
|
|
|
---
|
|
|
|
## QR Code Formats
|
|
|
|
BML QR codes appear in two formats.
|
|
|
|
### 1. Plain URL QR
|
|
|
|
```
|
|
https://pay.bml.com.mv/app/<base64-encoded-url>
|
|
```
|
|
|
|
The entire URL is base64-encoded and passed directly to the payrequest lookup API.
|
|
|
|
### 2. Combined EMV-style QR
|
|
|
|
Used in Fahipay/PayMV combo QRs that embed multiple payment networks. The BML gateway URL is embedded as a TLV value at a fixed path.
|
|
|
|
TLV path: **root tag `35` → sub-tag `20` → sub-sub-tag `01`**
|
|
|
|
The value at tag `01` is the full `https://pay.bml.com.mv/app/...` URL.
|
|
|
|
---
|
|
|
|
## PayMV QR Format (TLV)
|
|
|
|
PayMV QRs (static, PayMV-native) use a decimal TLV encoding (not BER-TLV):
|
|
|
|
```
|
|
<2-digit decimal tag><2-digit decimal length><value>...
|
|
```
|
|
|
|
### Root-level tags (key fields for scanning)
|
|
|
|
| Tag | Field |
|
|
|---|---|
|
|
| `26` | Merchant account information (container) |
|
|
| `54` | Transaction amount |
|
|
| `59` | Merchant / recipient name |
|
|
| `62` | Additional data (container) |
|
|
|
|
### Sub-tags
|
|
|
|
| Parent | Tag | Field |
|
|
|---|---|---|
|
|
| `26` | `03` | Account number |
|
|
| `62` | `08` | Payment purpose / reference |
|
|
|
|
> For the full PayMV QR format spec including generation (receive-payment QRs), acquirer BIC mapping, CRC algorithm, and all tags — see [PayMV QR Format](../thijooree/18-paymv-qr-format.md).
|
|
|
|
---
|
|
|
|
## Step 1 — Resolve QR to Merchant Details
|
|
|
|
### Endpoint
|
|
|
|
```
|
|
GET https://www.bankofmaldives.com.mv/internetbanking/api/mobile/walletpayments/payrequest/{base64Url}
|
|
```
|
|
|
|
`{base64Url}` is the full QR URL (e.g. `https://pay.bml.com.mv/app/...`) base64-encoded with standard encoding (with padding).
|
|
|
|
### Headers
|
|
|
|
| Header | Value |
|
|
|---|---|
|
|
| `Authorization` | `Bearer <access_token>` |
|
|
| `User-Agent` | `bml-mobile-banking/348 ({manufacturer}; Android {version}; {model})` |
|
|
| `x-app-version` | `2.1.44.348` |
|
|
|
|
```bash
|
|
curl --request GET \
|
|
--url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/walletpayments/payrequest/<base64Url>' \
|
|
--header 'Authorization: Bearer <access_token>' \
|
|
--header 'User-Agent: bml-mobile-banking/348 ({manufacturer}; Android {version}; {model})' \
|
|
--header 'x-app-version: 2.1.44.348'
|
|
```
|
|
|
|
### Response
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"payload": {
|
|
"trxn_hash": "<base64Url>",
|
|
"narrative1": "Merchant Name",
|
|
"narrative2": "Address Line 1",
|
|
"narrative3": "Address Line 2",
|
|
"amount": "1.03",
|
|
"currency": "MVR"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Response Fields
|
|
|
|
| Field | Description |
|
|
|---|---|
|
|
| `trxn_hash` | The base64 URL — used as `requestId` in payment steps |
|
|
| `narrative1` | Merchant name |
|
|
| `narrative2` | Merchant address line 1 |
|
|
| `narrative3` | Merchant address line 2 |
|
|
| `amount` | Payment amount (`"0.00"` for static QRS) |
|
|
| `currency` | Currency code (typically `"MVR"`) |
|
|
|
|
---
|
|
|
|
## Step 2 — Pay (3-Step TOTP Flow)
|
|
|
|
All three steps POST to the same endpoint:
|
|
|
|
```
|
|
POST https://www.bankofmaldives.com.mv/internetbanking/api/mobile/walletpayments/pay
|
|
```
|
|
|
|
### Headers
|
|
|
|
| Header | Value |
|
|
|---|---|
|
|
| `Authorization` | `Bearer <access_token>` |
|
|
| `User-Agent` | `bml-mobile-banking/348 ({manufacturer}; Android {version}; {model})` |
|
|
| `x-app-version` | `2.1.44.348` |
|
|
| `Content-Type` | `application/json` |
|
|
| `Accept` | `application/json` |
|
|
|
|
### Step 2a — Initiate (no channel)
|
|
|
|
```json
|
|
{
|
|
"action": "approve",
|
|
"debitAccount": "<internalAccountId>",
|
|
"requestId": "<trxn_hash>",
|
|
"amount": 1.03,
|
|
"currency": "MVR"
|
|
}
|
|
```
|
|
|
|
Expected response: `{ "success": true, "code": 99 }` (OTP required)
|
|
|
|
> **Note:** This step may be skipped. The app proceeds directly to Step 2b if the gateway already indicates OTP is required.
|
|
|
|
### Step 2b — Request OTP Channel
|
|
|
|
```json
|
|
{
|
|
"action": "approve",
|
|
"debitAccount": "<internalAccountId>",
|
|
"requestId": "<trxn_hash>",
|
|
"amount": 1.03,
|
|
"currency": "MVR",
|
|
"channel": "token"
|
|
}
|
|
```
|
|
|
|
Expected response: `{ "success": true, "code": 22 }` (OTP generated)
|
|
|
|
### Step 2c — Confirm with TOTP
|
|
|
|
```json
|
|
{
|
|
"action": "approve",
|
|
"debitAccount": "<internalAccountId>",
|
|
"requestId": "<trxn_hash>",
|
|
"amount": 1.03,
|
|
"currency": "MVR",
|
|
"channel": "token",
|
|
"otp": "<TOTP>"
|
|
}
|
|
```
|
|
|
|
Expected response:
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"code": 0,
|
|
"payload": {
|
|
"merchant": "Merchant Name",
|
|
"amount": "1.03",
|
|
"currency": "MVR"
|
|
}
|
|
}
|
|
```
|
|
|
|
On failure:
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "Payment failed"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Request Fields
|
|
|
|
| Field | Type | Description |
|
|
|---|---|---|
|
|
| `action` | `string` | Always `"approve"` |
|
|
| `debitAccount` | `string` | Internal account UUID (not the display account number) — from dashboard `internalId` field |
|
|
| `requestId` | `string` | The `trxn_hash` from the payrequest lookup |
|
|
| `amount` | `number` | Payment amount as a number (e.g. `1.03`) |
|
|
| `currency` | `string` | Currency code (e.g. `"MVR"`) |
|
|
| `channel` | `string` | `"token"` — present in steps 2b and 2c only |
|
|
| `otp` | `string` | TOTP code — present in step 2c only |
|
|
|
|
> The `debitAccount` field takes the internal UUID from the dashboard response, **not** the displayed account number. See [Dashboard](04-dashboard.md) for the account object structure.
|
|
|
|
---
|
|
|
|
## OTP
|
|
|
|
The OTP is a standard TOTP (RFC 6238, SHA-1, 30-second window, 6 digits) derived from the stored BML authenticator seed — the same seed used for login 2FA.
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
- Valid `access_token` from [OAuth Token Exchange](03-oauth-token.md)
|
|
- TOTP seed enrolled via BML app
|
|
- Account `internalId` from [Dashboard](04-dashboard.md)
|
|
|
|
---
|
|
|
|
|
|
|
|
---
|
|
|
|
[← Tap-to-Pay](12-tap-to-pay.md)
|