Security & Trade-offs
Payments demand defense in depth: no single control is trusted on its own, and money only moves when several independent layers agree. This page lists the layers, then gives the recommended UX-vs-security decisions and a phased rollout.
The security layers
1. Transport security
TLS 1.2+ everywhere. mTLS for vendor server-to-server calls; certificate pinning in the user and merchant apps; signed webhooks so vendors can trust callbacks.
2. Authentication
- User: reuses the existing xperise session, plus device binding (a per-install key) so a stolen session can't be replayed from a new device. Payment confirmation adds biometrics/PIN.
- Vendor: an API key + secret; every request is HMAC-signed over method, path, body, and timestamp, with an IP allowlist. Rotate secrets; support two active keys for rotation.
3. Payload & message integrity
QR payloads and card tokens are signed (HMAC/JWT with a key id) and single-use with a short TTL. A nonce + timestamp on each request blocks replay. Never trust an amount that arrived from the client/QR — resolve the canonical value from the server.
4. Transaction integrity (double-spend prevention)
- Idempotency keys on every state-changing call — a retried request is a no-op, not a second charge.
- Double-entry ledger with row locks or optimistic concurrency on the balance, so two concurrent payments can never spend the same funds.
- A strict state machine (see Overview) so a transaction can't be captured twice or refunded beyond its amount.
5. Data at rest
- Credit-card data is handled by 2c2p, not xperise: PANs are captured/tokenized by the 2c2p gateway (with 3DS), so xperise stores only a 2c2p token + last4 and stays out of scope for that data.
- xperise-card numbers live only in a tokenization vault; systems hold token + last4 + hash.
- CVV is never stored; PAN and CVV are never logged.
- Keys in an HSM / KMS; card cryptogram keys live in the card's secure element and never leave it. PII encrypted; least-privilege access; retention limits.
6. Risk & fraud engine
Real-time scoring on velocity (count/amount per window), limits (per-transaction, daily), device/geo anomalies, and blocklists. Its output drives the step-up decision below. Users can freeze a card instantly and report lost to kill the token.
7. Audit & reconciliation
An append-only, immutable audit log of every money movement; scheduled reconciliation of the ledger against transactions and vendor settlements. The ledger is the single source of truth — screens and receipts are derived from it, never the reverse.
Threat → mitigation summary
| Threat | Primary mitigation |
|---|---|
| Replayed QR / tap | Single-use intent + short TTL; tap cryptogram counter verified server-side. |
| Amount tampering | Amount held server-side; app shows the server's value; payload signed. |
| Card cloning | NFC dynamic cryptogram per tap; static token can't authorize alone. |
| Photographed printed QR / stolen card | Token ≠ credential; a second factor (app confirm / PIN) is always required. |
| Vendor impersonation | API key + HMAC + mTLS + IP allowlist. |
| Account takeover | MFA, device binding, biometric confirmation, anomaly detection. |
| Credit-card fraud (2c2p rail) | 3DS / step-up via 2c2p; xperise never holds the PAN; Layer 1 allowance still gates the amount. |
| Overspend beyond policy | Two-layer authorization — the payment is declined unless both the Layer 1 allowance and Layer 2 funding cover it. |
| Shared bearer card misuse | Bearer card (unassigned) is usable by anyone with the card + PIN → CVM is PIN; mitigate with low balance / stored value, tight limits, and monitoring. |
| Double-spend | Idempotency keys + ledger locks + state machine + atomic dual decrement. |
| Data breach | Tokenization, no CVV storage, HSM/KMS, encryption, least privilege. |
OTP & step-up verification
OTP appears in several payment moments — card activation, manual-entry (card-not-present) payments, and any step-up the risk engine triggers. The recommendation is not to build a payment-specific OTP system, but to reuse the existing xperise OTP service — the same one that already powers login and password reset over email, SMS, and Zalo — behind one shared verification API. One service, one set of delivery channels, one place to harden.
One shared service, scoped per purpose
The single OTP service is reused by binding each code to a purpose (scope), so a code can only be spent on the action it was issued for. A login OTP can never approve a payment, and a payment OTP can never approve a different payment.
A verification is requested against a scope + reference, delivered on the account's registered channel, then verified:
{
"scope": "payment_step_up", // login | password_reset | card_activation | payment_step_up
"ref": "TXN_9f2a...", // the exact action being authorized
"channel": "sms", // email | sms | zalo (from the account's registered channels)
"context": { "amountMinor": 150000, "merchant": "Vendor X" }
}
What-you-see-is-what-you-sign (WYSIWYS)
For a payment step-up the OTP message states the amount and merchant, and the code is bound to
that specific transaction ref. So the user approves this payment, not an abstract code an
attacker could redirect to a different charge:
xperise: 482913 approves paying 150,000₫ to Vendor X. Never share this code.
Hardening rules (applied once, for every caller)
| Rule | Value |
|---|---|
| Scope-bound | A code is valid only for its scope + ref; wrong-scope submission is rejected. |
| Single-use | Consumed on first successful verify; cannot be replayed. |
| Short TTL | ~5 minutes; expired codes are dead. |
| Attempt lockout | e.g. 5 wrong tries → invalidate + require re-request. |
| Request throttling | Rate-limit re-sends per account/channel to stop OTP-bombing. |
| Channel = registered only | Sent to the account's known email/phone, never a caller-supplied one. |
How it combines with the other factors
OTP is one rung on the step-up ladder — not the only one. Prefer the lowest-friction factor that meets the risk, and fall back to OTP when a stronger one isn't available:
| Situation | Preferred factor | OTP's role |
|---|---|---|
| User has the app + biometrics | In-app push confirm (biometric) — WYSIWYS, one tap | Fallback if push fails / no biometrics |
| Card activation | Session + OTP to prove identity | Primary identity factor |
| Manual entry (card-not-present) | 3DS-style in-app confirm | Fallback step-up when the user isn't in-app |
| Corporate admin | MFA on the admin account | One MFA factor (with an authenticator preferred for admins) |
Treat OTP as a shared, scope-aware verification primitive reused across the platform. This keeps one delivery infrastructure and one hardening surface instead of two.
Card transaction authentication (CVM)
For xperise card transactions the recorded Cardholder Verification Method (CVM) is a PIN or an OTP — one of the two, never both (configured per card/company; see Transaction authentication). Biometrics, where used, only unlock the app to enter the PIN or approve — they are not the recorded CVM. Low-value taps below the contactless floor limit may skip the CVM entirely.
UX vs security trade-offs
The right lever is risk-based (adaptive) authentication: keep friction low when the transaction is low-risk, and step up only when risk rises. The recommendation:
| Scenario | Lower friction | Higher security | Recommended default |
|---|---|---|---|
| Small tap (under floor limit) | Tap-and-go, no CVM | CVM every tap | Tap-and-go under the contactless floor limit (e.g. 200,000₫); the card's CVM (PIN or OTP) above it. |
| QR pay in app | Auto-confirm | Confirm every time | PIN/OTP on the confirm screen (biometric may unlock the app); it shows the amount. |
| Printed-QR card scan | Approve on token | App confirm + CVM | Always the card's CVM (PIN or OTP); static token alone never authorizes. |
| Manual entry (CNP) | Amount only | Full 3DS-style step-up | Always step-up (app confirm or OTP); restrict to trusted vendors + low limits. |
| New device / new vendor / odd geo | Trust it | Re-verify | Step-up on anomaly, frictionless when the risk score is low. |
| Where the amount lives | In the QR (offline-friendly) | Server-side | Server-side, resolved by the app — tamper-proof. |
Guiding principles:
- Scale friction to risk and amount. Most payments are small and low-risk — make those effortless (a single tap, one biometric) and reserve PIN/OTP/3DS for high amounts, anomalies, and card-not-present.
- Tiered limits (per-transaction, daily, per-mode) cap the blast radius of any single compromise without adding friction to everyday spend.
- Prefer dynamic over static. Dynamic QR on a screen and NFC cryptograms beat static printed codes; offer static QR for convenience but keep it behind a second factor.
- Give users instant control. One-tap freeze and report-lost in the app is both a security control and a trust feature.
Recommended rollout
| Phase | Scope |
|---|---|
| 1 — QR on the wallet | Merchant-presented dynamic QR funded by the company wallet, decrementing monthly limit or cost center; the two-layer authorize→commit; in-app biometric confirm; idempotency; core risk limits; audit log. |
| 2 — xperise card | Issue cards with a card balance; tap (NFC cryptogram) and printed-QR + app-confirm; freeze/lost-card; tap-and-go threshold. |
| 3 — Advanced | Manual entry for trusted vendors with 3DS-style step-up; extend funding combinations (e.g. QR via 2c2p); richer fraud scoring; vendor self-serve settlement. |
The existing company wallet and credit card (2c2p) methods already run; starting the new work with QR on the wallet delivers value fast while exercising the two-layer authorization the xperise card then builds on.
Compliance note
The architecture is hybrid: xperise's own instruments (company wallet, QR, xperise card) are closed-loop, while credit-card payments run through 2c2p. Routing card data through 2c2p keeps the sensitive PAN/CVV out of xperise, minimizing PCI-DSS scope for the credit-card rail (2c2p carries the bulk of that burden; xperise stores only 2c2p tokens + last4). The closed-group xperise card still relies on tokenization, no-CVV-storage, and HSM/KMS key management as above. Depending on jurisdiction, holding company balances/credit may trigger KYC/AML and e-money regulations; confirm with legal before launch.
See Overview, QR-Code Payment, and Card Payment for the flows these layers protect.