forked from shihaam/thijooree
update docs
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
# 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
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```json
|
||||
{
|
||||
"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_token` from [OAuth Token Exchange](03-oauth-token.md)
|
||||
- TOTP seed enrolled via BML app (same seed used for login 2FA)
|
||||
- `cardId` from the dashboard — see [Dashboard](04-dashboard.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
[← Foreign Limits](11-foreign-limits.md) · [Next → QR Payment](13-qr-payment.md)
|
||||
Reference in New Issue
Block a user