Card rails are the default assumption in most payment integrations. Across the markets MakersMark serves, cards are the exception — and that changes the architecture, not just the checkout screen.
M-Pesa, MTN MoMo, and Airtel Money move more everyday transactions across the markets we build for than any card network. Treating mobile money as a bolt-on "alternative payment method" produces a checkout flow that fights its own users.
Card payments confirm in milliseconds; a mobile money payment often confirms via a webhook or USSD callback seconds to minutes later. MakersMark's payment core is built around that asynchrony from the start — pending states, idempotent reconciliation, and a ledger that doesn't assume "paid" means "confirmed right now."
sealed interface PaymentStatus
object Pending : PaymentStatus
object Confirmed : PaymentStatus
object Failed : PaymentStatus
fun onWebhook(event: MobileMoneyWebhook) {
ledger.reconcile(
reference = event.reference,
status = if (event.success) Confirmed else Failed,
) // reconcile() is idempotent on 'reference'
}We integrated M-Pesa, MTN MoMo, Airtel Money, Flutterwave, Paystack, and Monnify behind one internal payments interface, so the rest of the platform — payouts, refunds, subscriptions — never has to know which rail a given transaction moved through.
Model every payment as PENDING until an explicit confirmation event lands — a webhook, a poll, or a USSD callback — never assume synchronous success just because a rail usually confirms fast. Reconciliation should be idempotent, so a duplicated webhook can't double-credit a ledger.
Design the payments core around your slowest, least certain rail, not your fastest. Everything else gets easier to add once the ledger already assumes payments arrive out of order.
Tell us about your system. We respond within one business day with whether we're the right studio for it — and a rough path forward.