Files
thijooree/docs/bmlapi/13-qr-payment.md
T
2026-09-21 15:26:18 +05:00

7.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.

3. POS QR (mv.com.bml.qtr)

BML POS terminals emit an EMVCo-style dynamic QR with no tag 26 and supplementary domain mv.com.bml.qtr. The same TLV path (352001) holds a bare reference such as 02:215c7b9f15ce4ed28e15697ea976db99 instead of a URL. That bare reference — not the whole payload, which is rejected with code 112 — is what gets Base64-encoded into the payrequest lookup below. The reference does not resolve as a pay.bml.com.mv/app/ short code in a browser; only the API understands it. See PayMV QR Format → BML POS QR.


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 lookup key, base64-encoded with standard encoding — the full QR URL (e.g. https://pay.bml.com.mv/app/...), or for POS QRs the bare 352001 reference. BML's own app omits the = padding; the padded form resolves as well, so the client matches the app rather than relying on either being required.

Headers

Header Value
accept application/jsonrequired in practice, see below
Authorization Bearer <access_token>
User-Agent bml-mobile-banking/348 ({manufacturer}; Android {version}; {model})
x-app-version 2.1.44.348

accept: application/json is not optional. Handled errors (codes 103, 112, …) come back as JSON regardless, but when the route throws, the server renders the Internet Banking HTML login page — under HTTP 200 — instead of a JSON error body. A client without the header then sees a "successful" HTML response it cannot parse. All BML API requests set it in bmlApiRequest().

curl --request GET \
  --url 'https://www.bankofmaldives.com.mv/internetbanking/api/mobile/walletpayments/payrequest/<base64Url>' \
  --header 'accept: application/json' \
  --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")

Failure Responses

success: false comes back with a code and a user-facing message; the client shows BML's own wording and keeps the user on the Transfer screen.

Code Message
103 The payment request has expired
112 Unsupported payment link

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)

When this step is used: the client only calls preInitiatePayment for gateway QRs — QR URLs that begin with https://pay.bml.com.mv/app/ (TransferFragment.kt:349, 1419-1423). For PayMV-native static QRs (QRS), Step 2a is skipped and the flow starts at Step 2b.

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"
  }
}

Currency fallback: if the server's payload.currency is blank, the client falls back to the currency value sent in the request (BmlQrPayClient.kt:148). The same applies to merchant and amount at the UI layer.

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 debitAccount field 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


 


← Tap-to-Pay     Next → Notifications