Case study · A2Pay

A wallet, card-acceptance and lending platform — designed, built and run by us.

A2Pay operates a merchant and consumer financial platform across South Africa. Lakshaya is its engineering team: we architected the system, wrote it, integrated it into the national payment rails, and we run it in production today.

Money enters the platform through card acceptance and inbound bank credits, moves across an internal ledger that handles fees, holds and reversals, and leaves through four separate payment rails. Merchants and consumers onboard through a verification workflow that clears identity, bank-account, FICA and company-registry checks before a wallet can ever be funded.

Nothing about that is a single integration. It is a ledger that must always balance, a bank file that must be byte-perfect, an onboarding journey that must satisfy a regulator, and a set of third parties that each fail in their own way. That is the work.

  • Two .NET 9 service APIs — wallet & payments, lending & origination
  • Three web applications — back office, point of sale, mobile onboarding
  • Four payment rails behind one ledger
  • Three environments at parity, defined entirely in Terraform

Engagement at a glance

Client
A2Pay
Relationship
Architect, build, operate
Backend
.NET 9 · clean architecture · CQRS
Front end
Angular 19 · three applications
Data
SQL Server · versioned migrations
Cloud
Azure · Terraform · containers
Identity
OIDC · permission-based authz
Assurance
Unit, integration & contract tests
4
Payment rails integrated behind a single wallet ledger
320B
Fixed-length bank records built, sequenced and reconciled automatically
10s
Dispatch and reconciliation cycle for outbound bank files
100%
Infrastructure as code across QA, staging and production

What we built

Four systems that have to agree with each other.

Wallet & transaction ledger

Customer, merchant, fee and system wallets with sequenced wallet codes, fee accounting, statements and searchable transaction history. Every money movement carries an idempotency token, so a retried request can never double-debit, and every failure path has an explicit reversal.

  • Fee & system wallets
  • Idempotency tokens
  • Reversals
  • Statements

Payment rails & bank files

PayShap proxy payments with remittance information, duplicate-submit protection and a background status monitor; real-time credit; one-day EFT; and host-to-host batch files in fixed-length 320-byte records delivered over SFTP — with acknowledgements, full and partial rejections, duplicates and unpaids all parsed and matched back to the ledger.

  • PayShap
  • Real-time
  • One-day EFT
  • CPS host-to-host

Onboarding, KYC & merchant verification

A custom workflow engine drives wallet onboarding and merchant enrolment as typed state machines with permission groups, fault recovery and step-by-step audit history. Identity, bank-account, FICA and company-registry checks run inside the flow, with name-match scoring and every third-party response persisted.

  • Workflow engine
  • IDV & FICA
  • Bank account verification
  • Registry validation

Card acceptance & lending

Tap on Phone acceptance turning an NFC handset into a terminal, sub-merchant gateway onboarding and webhook-driven settlement into the wallet. Alongside it, a lending module covering applications, affordability, two-stage approval, agreements, disbursement, billing and claims.

  • Tap on Phone
  • Sub-merchant onboarding
  • Affordability & approvals
  • Collections

The hard parts

Where the engineering actually went.

Any team can move a balance between two rows. These are the problems that decide whether a payments platform survives its second year.

Reconciliation
CONTINUOUS

Batch rails answer asynchronously, in files, hours later, and sometimes reject only part of what you sent. A background service dispatches outbound files on a ten-second cycle and ingests every response type the bank produces, updating individual payments rather than whole batches — so a single rejected item never strands the other 999.

Exactly-once money
INVARIANT

A timeout is not a failure — it is an unknown. Idempotency tokens, an explicit payment state machine and status polling let the platform re-ask "what happened to this payment?" safely, instead of guessing and paying twice. Declined refunds and failed transactions reverse cleanly, once.

Partner failure
ISOLATED

Every external provider sits behind its own typed client with retry, exponential backoff, timeout and full request/response audit logging. A scheme outage or a 500 from a verification provider degrades one capability for a few minutes; it does not take the platform down or corrupt the ledger.

Re-platforming
IN FLIGHT

The estate has been moved from a self-managed Kubernetes deployment onto Azure-native services described entirely in Terraform — WAF-protected ingress, private networking, managed secrets and configuration, and identical QA, staging and production definitions. No environment is a hand-built snowflake.

The difference between a demo and a payments platform is everything that happens after the happy path — the rejected file, the timed-out proxy, the duplicate submit, the reversal at 17:59 on a Friday. That is the part we engineer first.
Neil Cameron · Chief Executive Officer, Lakshaya