TapFlow is a portfolio project that simulates card-present payments. This page states what it protects, what it deliberately leaves out, and what a production version would add. It is written to be honest about scope, not to claim compliance.
| Real in TapFlow | Simulated or out of scope |
|---|---|
| Payment state, idempotency, reconciliation, refunds, telemetry, flags | The acquirer, which is a fake inside the backend |
| NFC reader mode and an ISO-DEP exchange with a test card | Card data: no PAN, expiry, track data or EMV cryptogram is ever read |
| Bearer authentication of the terminal | Per-device credentials, key rotation, device attestation |
| TLS-ready HTTP client | Cleartext HTTP is allowed in debug builds only, for the local backend |
- The app never sees card data. The terminal contract returns an opaque
PaymentMethodToken, and itstoString()is redacted, so it cannot leak into logs by accident (ADR 0012). - The NFC reader sends one command: a SELECT of TapFlow's proprietary AID. It never selects a payment application and never reads records. A real bank card tapped on TapFlow only counts as "a card was presented" (ADR 0032).
- The test card answers only its proprietary AID, in the
othercategory, with a random token per tap. It cannot pose as a payment card. - Telemetry carries ids, state names, reasons and cohorts. Only an error's type is recorded, never its message, because messages can contain anything (ADR 0028).
| Threat | Where | Mitigation in TapFlow | Production addition |
|---|---|---|---|
| Customer charged twice through retries or double taps | App ↔ backend | One active payment; idempotency keys generated before sending and enforced by the backend; unknown outcomes reconciled, never retried blindly (ADR 0014, 0015, 0021) | Same design, plus acquirer-side idempotency |
| Replayed or altered request | Network | Key reuse with a different body is rejected (422); amounts and refund limits enforced on the backend | TLS with certificate pinning, request signing |
| Refunding more than was paid | Backend | Limit checked under a row lock, pending refunds count; the device checks the same limit (ADR 0026) | Refund authorization by role, e.g. a manager PIN |
| Stolen terminal token | Device | Static demo token from Gradle properties | Per-device credentials from provisioning, stored in the Android Keystore, rotated and revocable |
| Debug features in production | Build | Developer panel and fault injection only in debug builds; backend debug endpoints on for the demo, off with TAPFLOW_DEBUG=false |
Separate backend deployment without debug routes; build checks that fail if they are present |
| Remote config abused | Backend ↔ app | Authenticated config; typed flags with safe defaults and range checks; versions never go backwards (ADR 0030) | Signed configuration, audited back office with access control |
| A rollout harms payments | Fleet | Automatic rollback on the unknown-rate guardrail (ADR 0031) | Alerting when the guard acts, significance testing |
| Lost or stolen device | Device | App sandbox; the database holds payment ids, amounts and idempotency keys, never card data | Exclude the database from backups, full-disk encryption requirement, remote wipe through device management |
| Relay or tampered NFC exchange | NFC | Nothing valuable is exchanged: the test token cannot pay | A certified reader component, EMV cryptograms verified by the issuer |
| Leaking data through logs | App | Redacted tokens, error types only | Log scrubbing in CI, no payment logs in release builds |
- PCI DSS applies to systems that store, process or transmit cardholder data. TapFlow keeps card data out of the app entirely and works with tokens, which is the main way a merchant application stays out of most of that scope.
- PCI MPoC (Mobile Payments on COTS) is the standard for accepting cards on commercial phones and tablets. It covers the software that reads the card, isolation of card data and PIN entry, attestation of the device and app, and back-end monitoring. A product would get this by integrating a certified Tap to Pay SDK from a payment provider, not by building it.
- In that architecture the TapFlow app would be the merchant application: it starts payments, handles unknown outcomes, reconciles and refunds, using tokens from the certified SDK behind the same
PaymentTerminalcontract.
- Per-device credentials from provisioning, in the Android Keystore, with rotation.
- TLS everywhere, certificate pinning for the payment backend, no cleartext configuration.
- Play Integrity or equivalent attestation before a terminal may take payments.
- Backups exclude the payment database; minimum data retention on the device.
- Debug routes absent from production builds, verified in CI.
- A certified Tap to Pay SDK as the terminal implementation.