← All projects

Multi-Tenant Wallet & Payments Infrastructure

From debugging silent payment failures to architecting a reconciliation-first wallet system across five payment providers.

Period
2023 — 2025
Role
Backend Engineer — owned design and implementation
  • Node.js
  • Express.js
  • MySQL
  • Redis
  • Stripe
  • Razorpay
  • Paystack
  • PawaPay
  • Peach Payments

Overview

A wallet and payments layer for a multi-tenant mobility SaaS platform, supporting white-labeled deployments across multiple markets. The system handles passenger charges, driver credits, pending-balance settlement, and real-time reconciliation across five payment providers.

The Problem

The work began as a production incident: funds were being deducted from passengers but never credited to drivers. The debit and credit legs of a transaction were not atomic — a failure after the debit left money in limbo, with no reconciliation process to detect or repair the inconsistency.

Fixing the bug required tracing transaction flows through a large, interlinked codebase — and made it clear the platform needed a wallet architecture designed around correctness, not just a patch.

Requirements

  • Every transaction must be traceable end-to-end: who was charged, who was credited, and when
  • Inconsistencies must be detectable automatically, not discovered via user complaints
  • Multi-tenant: each white-labeled client operates in isolation with its own provider configuration
  • Extensible to new payment providers without touching core wallet logic
  • Support for pending balances and delayed settlement flows

Architecture

The wallet is modeled as a ledger: state transitions are recorded as transaction entries rather than direct balance mutations, so the balance is always derivable and auditable. Provider integrations sit behind a common interface, keeping provider-specific quirks (webhook formats, settlement timing, currency handling) out of core wallet logic.

Real-time reconciliation compares internal ledger state against provider records, surfacing mismatches for automated or manual repair before users notice them.

Trade-offs

  • Ledger-style records add write volume and storage versus mutating a single balance row — accepted, because auditability is the point of a payments system
  • A common provider interface means the integration layer targets the lowest common denominator; provider-specific optimizations require deliberate escape hatches
  • Real-time reconciliation adds operational cost, traded against the far higher cost of silent money loss

Challenges

The hardest part was not writing new code but understanding existing behavior: payment flows crossed multiple repositories and services, and the original incident could only be fixed safely by mapping every path that touched wallet state.

Extending to African-market providers (Paystack, PawaPay, Peach Payments) surfaced assumptions baked in from Stripe and Razorpay — settlement timing, webhook reliability, and currency handling all differed enough to force the abstraction to earn its keep.

Results

  • Transaction inconsistencies reduced by 20% after the reconciliation-first redesign
  • Payment layer extended from two providers to five without changes to core wallet logic
  • White-labeled tenants onboard with provider configuration, not code changes

Lessons Learned

Payments code is only as good as its failure paths. The happy path was never the problem — designing for the partial-failure states (debit succeeded, credit did not) is the actual work.

Reconciliation is not an afterthought or a monthly batch job; treating it as a first-class, real-time system component is what turns 'we think balances are right' into 'we know'.