6.2 KiB
Tap-to-Pay (NFC / HCE)
BML supports contactless NFC payments via Host Card Emulation (HCE). The app fetches single-use payment tokens from the server, then emulates an EMV mag-stripe contactless card using Android's HostApduService.
Overview
1. Fetch tokens → POST /api/mobile/walletpayments/gettoken (TOTP-authenticated)
2. HCE exchange → Android NFC subsystem drives the APDU exchange with the POS terminal
Step 1 — Fetch Payment Tokens
Endpoint
POST https://www.bankofmaldives.com.mv/internetbanking/api/mobile/walletpayments/gettoken
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 |
Three-Step OTP Flow
Token retrieval requires TOTP verification and completes in three POSTs to the same endpoint.
Step 1a — Initiate
{
"type": "track2",
"cardid": "<cardId>",
"quantity": 3
}
Expected response: { "code": 99 } (OTP required)
If "code": 0 is returned directly the payload contains tokens immediately (skip to parsing).
Step 1b — Request OTP Channel
{
"type": "track2",
"cardid": "<cardId>",
"quantity": 3,
"channel": "token"
}
Expected response: { "code": 22 } (OTP generated on BML side; TOTP is used locally)
Step 1c — Submit TOTP
{
"type": "track2",
"cardid": "<cardId>",
"quantity": 3,
"channel": "token",
"otp": "<TOTP>"
}
Expected response: { "code": 0, "payload": [...] }
The OTP is a standard TOTP (RFC 6238, SHA-1, 30-second window, 6 digits) derived from the stored BML authenticator seed.
Token Response
{
"code": 0,
"payload": [
{
"token": "4761360000000000",
"expiry": "2512",
"app_code": "A0000000031010",
"service_code": "000",
"data": "0960919802623742",
"valid_until": "2025-12-01 12:00:00.000"
}
]
}
Token Fields
| Field | Description |
|---|---|
token |
PAN-equivalent single-use token (used as Track 2 primary account number) |
expiry |
Expiry in YYMM format (e.g. "2512" = December 2025) |
app_code |
AID (Application Identifier) hex string — identifies the card network |
service_code |
3-digit service code for Track 2 |
data |
Discretionary data appended to Track 2 |
valid_until |
Server-side expiry timestamp for the token |
AID to Card Network Mapping
| AID prefix | Network |
|---|---|
A0000000031010 |
Visa |
A0000000041010 |
Mastercard |
A000000025... |
Amex |
| (other) | BML |
Step 2 — HCE APDU Exchange
Once a token is set, Android's NFC subsystem routes contactless commands to the app's HostApduService. The flow follows the EMV mag-stripe contactless profile.
APDU Exchange Flow
POS Terminal Android HCE
| |
| SELECT PPSE (INS=A4) |
|--------------------------------------->|
| FCI Template (6F) + 9000 |
|<---------------------------------------|
| |
| SELECT AID (INS=A4) |
|--------------------------------------->|
| FCI Template (6F) + 9000 |
|<---------------------------------------|
| |
| GET PROCESSING OPTIONS (INS=A8) |
|--------------------------------------->|
| Response Message Template (80) + 9000 |
|<---------------------------------------|
| |
| READ RECORD (INS=B2) |
|--------------------------------------->|
| Record Template (70) + 9000 |
|<---------------------------------------|
APDU Command Bytes
| INS | Hex | Command |
|---|---|---|
SELECT |
0xA4 |
Select PPSE or AID |
GET PROCESSING OPTIONS |
0xA8 |
Request AIP + AFL |
READ RECORD |
0xB2 |
Read Track 2 data |
SELECT PPSE Response
PPSE AID: 2PAY.SYS.DDF01 = 325041592E5359532E4444463031
6F <len>
84 <len> 325041592E5359532E4444463031 ← DF Name (PPSE)
A5 <len>
BF0C <len>
61 <len>
4F <len> <AID> ← ADF Name
87 01 01 ← Application Priority Indicator
9000
SELECT AID Response
6F <len>
84 <len> <AID> ← Dedicated File Name
A5 <len>
50 <len> <label-ascii-as-hex> ← Application Label (e.g. "VISA")
9F38 02 9F6602 ← PDOL: TTQ (2 bytes)
9000
The application label is derived from the AID prefix (see mapping table above).
GET PROCESSING OPTIONS Response
80 06 0080 08010100
9000
| Field | Value | Meaning |
|---|---|---|
Tag 80 |
— | Response Message Template 1 |
| AIP | 0080 |
Mag-stripe mode |
| AFL | 08010100 |
SFI=1, records 1–1, 0 offline auth records |
READ RECORD Response
70 <len>
57 <len> <track2-data> ← Track 2 Equivalent Data
9000
Track 2 format:
{token} D {expiry} {serviceCode} {data} [F]
The trailing F nibble is appended when the total length is odd (standard Track 2 padding).
Example from a real token:
4761360000000000 D 2512 000 0960919802623742
→ 4761360000000000D2512000096091980262374 2F (padded)
Status Words
| SW | Meaning |
|---|---|
9000 |
Success |
6F00 |
Generic / unknown error |
6D00 |
Instruction not supported |
TLV Encoding
All APDU responses use BER-TLV encoding. Tags are 1 or 2 bytes (hex string). Length follows DER short/long form:
| Length range | Encoding |
|---|---|
| 0–127 bytes | LL (1 byte) |
| 128–255 bytes | 81 LL (2 bytes) |
| 256–65535 bytes | 82 HH LL (3 bytes) |
Prerequisites
- Valid
access_tokenfrom OAuth Token Exchange - TOTP seed enrolled via BML app (same seed used for login 2FA)
cardIdfrom the dashboard — see Dashboard