Files
android/docs/thijooree/23-tap-to-pay.md
T
shihaam a8cd22cbe1
Auto Tag on Version Change / check-version (push) Failing after 13m32s
update docs
2026-06-13 21:30:12 +05:00

4.1 KiB

Tap to Pay

BML contactless payment via Android Host Card Emulation. The app emulates a BML magnetic-stripe contactless card using a single-use wallet token fetched from the BML Tap-to-Pay API.


Entry Points

Trigger Path
Manifest NFC service launches BmlTapToPayActivity Redirects to MainActivity with TAP_TO_PAY
TAP_TO_PAY intent R.id.nav_pay_with_card + auto_tap_mode=true
"Tap to Pay" button on the Cards screen CardsFragment.setTapMode(true, item)
Dashboard NFC button on a BML card Same

BmlTapToPayActivity (nfc/BmlTapToPayActivity.kt) is a 7-line trampoline — it sets the TAP_TO_PAY action and FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK and finishes.


Pre-flight — NfcPaymentUtil

Located at util/NfcPaymentUtil.kt. Every tap-to-pay entry runs checkAndProceed(context, onReady) first:

  1. NfcAdapter.getDefaultAdapter() — null → "NFC unsupported" dialog
  2. nfcAdapter.isEnabled — false → "Turn on NFC" dialog with a button that opens Settings.ACTION_NFC_SETTINGS
  3. CardEmulation.isDefaultServiceForCategory(component, CATEGORY_PAYMENT) — false → "Set this app as the default payment app" dialog with a button that opens Settings.ACTION_NFC_PAYMENT_SETTINGS
  4. All three pass → onReady() is invoked

If the user has biometrics_transfer_confirm enabled, CardsFragment.showBiometricPromptForTap() runs a BIOMETRIC_STRONG prompt before entering tap mode.


Token Fetch

fetchAndArmToken() (in CardsFragment):

  1. Locate the BML session and OTP seed for the selected card's login
  2. Generate a fresh TOTP via util/Totp
  3. BmlTapToPayClient().fetchTokens(session, internalId, otp) returns a list of BmlWalletToken; the first is used
  4. BmlHostCardEmulatorService.setToken(token) arms the HCE service
  5. BmlHostCardEmulatorService.onTransactionComplete = { success -> ... } — re-enters the carousel and shows a success toast / refresh on success

If any step fails, a toast is shown and tap mode exits.


HCE Service — BmlHostCardEmulatorService

Subclass of HostApduService. Implements the minimal EMV mag-stripe contactless flow:

SELECT PPSE → SELECT AID → GET PROCESSING OPTIONS → READ RECORD

APDU Handling

INS Handler Response
A4 SELECT (data = PPSE name 2PAY.SYS.DDF01) handleSelect FCI containing app entry: tag 4F = AID, tag 87 = priority 01, DF Name 84 = PPSE
A4 SELECT (data = AID bytes from token) handleSelect FCI containing AID, ASCII label (VISA / MASTERCARD / AMEX / BML), PDOL 9F38 = 9F6602 (TTQ 2 bytes)
A8 GPO handleGpo AIP 0080 (mag-stripe mode), AFL 08010100 (SFI=1, record 1-1)
B2 READ RECORD handleReadRecord Tag 70 containing tag 57 Track 2 — fires onTransactionComplete(true)
Anything else 6D00 (INS not supported)

PPSE selection when no active token is set fires launchPromptActivity() which starts BmlTapToPayActivity to bring the user into the app.

Track 2

buildTrack2(token): "${token.token}D${token.expiry}${token.serviceCode}${token.data}", padded with F to even length.

Companion State

Member Use
activeToken: BmlWalletToken? The armed token (volatile)
onTransactionComplete: ((Boolean) -> Unit)? UI callback invoked from READ RECORD or onDeactivated
setToken(token) / clearToken() Called from CardsFragment.fetchAndArmToken() / exitTapMode()
applicationLabel(aidHex) Maps Visa/MC/Amex AIDs to ASCII labels

onDeactivated() fires onTransactionComplete(false) if the reader dropped before GPO was sent.


Failure Modes

  • No NFC, NFC off, or app not the default payment service — handled by NfcPaymentUtil with dialogs that deep-link to Settings
  • Missing OTP seed or session — toast, tap mode exits
  • Token fetch fails — toast, tap mode exits
  • Reader removed before READ RECORD — onDeactivated reports success = false; no toast is shown

 


← Cards     Next → Notifications