5.8 KiB
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.
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 |
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
{
"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)
{
"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
{
"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
{
"action": "approve",
"debitAccount": "<internalAccountId>",
"requestId": "<trxn_hash>",
"amount": 1.03,
"currency": "MVR",
"channel": "token",
"otp": "<TOTP>"
}
Expected response:
{
"success": true,
"code": 0,
"payload": {
"merchant": "Merchant Name",
"amount": "1.03",
"currency": "MVR"
}
}
On failure:
{
"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
debitAccountfield takes the internal UUID from the dashboard response, not the displayed account number. See Dashboard 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_tokenfrom OAuth Token Exchange - TOTP seed enrolled via BML app
- Account
internalIdfrom Dashboard