know.2nth.ai Technology tech android-hce sa-payments
tech/android-hce · SA Payments · Skill Leaf

Payments for
South Africa.

Building a payment app for SA means hard-coding ZAR EMV tags, complying with POPIA before you write your first log line, surviving load-shedding with pre-provisioned LUKs, and testing on devices the Android docs never mention. This page is everything that doesn't show up in the EMV spec or the Android developer guide but will absolutely fail you in production south of the Limpopo.

Regional ZAR POPIA Load shedding SARB

EMV defaults, regulatory rails, and market reality.

Every EMV transaction carries country-specific data in its TLV tags. Get these wrong and the acquirer rejects the transaction before it reaches the issuer. South Africa also has its own data protection law (POPIA), a payment system regulator (PASA under SARB), and an infrastructure reality — rolling blackouts — that no other major market shares.

This isn't a compliance checklist. It's the set of decisions you make once, hard-code, and never revisit — because changing a currency code or timezone calculation in production means re-certifying with the payment schemes.

Parameter Value EMV tag Notes
Currency code 0710 (ZAR) 5F2A Transaction Currency Code. ISO 4217 numeric. Always 0710 for Rand.
Country code 0710 (ZA) 9F1A Terminal Country Code. ISO 3166 numeric. Same value as currency — coincidence, not a rule.
Timezone SAST (UTC+2) Hard-code Africa/Johannesburg. SA has no daylight saving time. Never use device locale.
// Hard-coded SA payment defaults — never derive from device locale

object SaPaymentDefaults {
    // ISO 4217 numeric: South African Rand
    val CURRENCY_CODE = byteArrayOf(0x07.toByte(), 0x10.toByte())

    // ISO 3166 numeric: South Africa
    val COUNTRY_CODE = byteArrayOf(0x07.toByte(), 0x10.toByte())

    // SAST — no DST, ever
    val TIMEZONE = ZoneId.of("Africa/Johannesburg")

    fun transactionTimestamp(): String {
        val now = ZonedDateTime.now(TIMEZONE)
        // EMV date format: YYMMDD
        val date = now.format(DateTimeFormatter.ofPattern("yyMMdd"))
        // EMV time format: HHmmss
        val time = now.format(DateTimeFormatter.ofPattern("HHmmss"))
        return "$date$time"
    }
}

POPIA, load-shedding, and the device matrix.

Three constraints that shape every architectural decision in a South African payment app. None of them are optional.

POPIA compliance. The Protection of Personal Information Act is South Africa's data protection law. For payment apps, four rules dominate.

Last-4 only in UI

Display at most the last four digits of the DPAN in any user-facing screen, notification, receipt, or analytics event. Full DPAN in logs is a POPIA violation and a PCI DSS finding. Build the masking into your UI layer from day one — retrofitting it is painful.

120-day transaction retention

Transaction data older than 120 days must be purged or anonymised unless you have a documented lawful basis for longer retention (e.g., regulatory requirement, active dispute). "We might need it" is not a lawful basis under POPIA section 14.

72-hour breach notification

If cardholder data is compromised, notify the Information Regulator within 72 hours and affected data subjects "as soon as reasonably possible." Build your incident response playbook before launch. After the breach is too late to figure out who to call.

Cross-border transfer requires lawful basis

If your provisioning server, TSP integration, or analytics pipeline runs outside South Africa, document the lawful basis for transferring personal information across borders. Options: binding corporate rules, data subject consent, or an adequacy finding. Pick one, document it, and be ready to produce it on request.

User data export. POPIA gives data subjects the right to request a copy of their personal information. Build the export endpoint from day one. It's trivial when you have 100 users. It's a scramble when you have 100,000 and the Information Regulator sends a section 23 request.

Load-shedding resilience. South Africa's rolling blackouts take infrastructure offline for 2–4 hour blocks, sometimes multiple times per day. For a payment app, this means your provisioning and LUK replenishment servers may be unreachable. The app must work anyway.

Pre-provision 24h of LUKs

Keep 5–10 LUKs on device at all times. A typical user taps 3–5 times per day. Ten LUKs gives you a full day plus margin. Replenish when inventory drops below threshold, not when you're already at zero.

Offline-first tap path

processCommandApdu must never make a network call. The entire tap flow — SELECT through GENERATE AC — runs from local state. LUKs are already on device. Card metadata is cached. The cryptogram is computed locally. Network is for replenishment, not for taps.

Silent replenishment with backoff

When connectivity returns after a blackout, replenish LUKs with exponential backoff. Don't hammer the server — every other wallet on the network is trying to replenish at the same time. Never show the user a replenishment error unless they're genuinely out of keys and cannot tap.

Device testing matrix. The phones that dominate the South African market are not the phones in the Android documentation. If you only test on Pixel, you'll ship bugs.

Device class SA market position HCE-specific issues
Samsung A-series Dominant mid-market. A15, A25, A55 are volume models. NFC antenna placement varies per model. Tap positioning that works on Pixel may fail on an A15. Test each model's sweet spot.
Huawei (without GMS) Significant installed base. No Google Play Services. No Play Integrity API. Use Android Key Attestation as fallback. Huawei's HMS SafetyDetect is an option but adds SDK dependency. Test AppGallery distribution.
Transsion (Tecno, Itel, Infinix) Massive share in entry-level and prepaid segment. NFC support is inconsistent. Some models have NFC hardware but ship with it disabled in firmware. Check NfcAdapter at runtime, not just manifest capabilities. Many models lack NFC entirely.
Xiaomi / Redmi Growing mid-market share. MIUI's aggressive battery optimisation kills HCE services in the background. Users must manually exempt your app. Document this in onboarding and detect when it hasn't been done.

SA-specific failure modes.

These won't show up in your dev environment. They show up at a Checkers in Soweto at 6pm on a Friday during stage 4 load-shedding.

Device clock set to wrong timezone

Users travel, change settings, or have phones that default to UTC. If you derive transaction timestamps from System.currentTimeMillis() without forcing SAST, your cryptogram's date component won't match the acquirer's expectation. Hard-code Africa/Johannesburg in all timestamp generation.

LUK exhaustion during extended blackouts

Stage 6 load-shedding can mean 6+ hours without connectivity per day. If your replenishment window is too tight, users run out of LUKs. Pre-provision aggressively: 10 LUKs minimum, trigger replenishment at 5 remaining, not at 1.

Transsion NFC false positives

Some Tecno/Infinix devices report NFC support in PackageManager features but the adapter returns null at runtime. Always check NfcAdapter.getDefaultAdapter(context) != null before enabling any HCE UI. Don't trust the manifest alone.

POPIA consent for analytics

If you send transaction metadata (amounts, timestamps, merchant names) to an analytics service outside South Africa, you need explicit consent or a documented lawful basis. Firebase Analytics to a US region counts as cross-border transfer of personal information. Either host analytics in-country or get the consent flow right before launch.

SARB licensing blind spot

SARB (South African Reserve Bank) oversees payment instruments. PASA (Payments Association of South Africa) manages membership for card scheme participants. If your app issues a payment instrument, you may need SARB approval beyond the Visa/Mastercard scheme licence. Engage your legal team early — the regulatory process takes months, not weeks.

SARB, PASA, and who you answer to.

The South African payments ecosystem has specific regulatory bodies that don't exist in most markets. Understanding the hierarchy saves you from building something that's technically functional but legally inoperable.

Body Role When you engage
SARB South African Reserve Bank. Oversees all payment instruments and financial stability. If your app issues or facilitates a payment instrument. Early engagement recommended — approval timeline is 6–12 months.
PASA Payments Association of South Africa. Manages payment system participation and rules. Card scheme participants must be PASA members or operate through a PASA member (your acquiring bank).
Information Regulator Enforces POPIA. Investigates breaches, handles complaints, issues enforcement notices. 72-hour breach notification. Section 23 data subject requests. Compliance audits.
Visa / Mastercard (SA office) Scheme certification and compliance for SA-specific requirements. Certification testing, production go-live approval, ongoing compliance monitoring.

Your acquiring bank is your regulatory shield.

Most wallet startups operate under their acquiring bank's PASA membership and payment system participation. The bank takes regulatory responsibility in exchange for control over your risk management. This is faster than getting your own licences but means the bank can shut you down if your fraud rates spike or your compliance documentation is insufficient.

Where SA context fits in the tree.

Go deeper.