Skip to content
~/tariqul.islam
/projects

/projects/payment-layer

2020–2026

One payment layer, twenty-plus gateways

client:
Infix & Aora product families
role:
Developer → TPM
  • Razorpay
  • Mercado Pago
  • Mollie
  • Flutterwave
  • Braintree
  • Coinbase

01 — the problem

What was actually at stake

Products selling on CodeCanyon end up everywhere: a school in Lagos pays differently than a shop in Dhaka or a seller in São Paulo. Every market demands its gateway — each with its own flows, webhooks, refund semantics, and failure modes. Scattered gateway code had already produced the worst class of bug: a webhook handled twice, a subscription marked paid that wasn't.

02 — the architecture

How it was built

A single GatewayContract (intent → charge → webhook → refund) with one adapter per provider — twenty-plus and counting. All webhooks land in one ingress that verifies signatures, deduplicates by event id, and translates provider events into internal payment events; billing logic only ever sees internal events.

The same pattern extends to SMS: Twilio, MSG91, Textlocal, Africa's Talking and others behind a settings layer end-users configure themselves — adding a market became configuration plus one adapter, for payments and messaging alike.

architecture — at a glance
interface
one GatewayContract, 20+ adapters
webhooks
single ingress — verify, dedupe, translate
ledger
internal events as source of truth
sms
same pattern: 5+ SMS gateways, user-configurable

03 — the visuals

What it looks like

Illustrated architecture overview of One payment layer, twenty-plus gateways
fig. 01 — /projects/payment-layer/cover

screenshot pending (NDA-safe crop)

drop /public/images/projects/payment-layer/01.jpg · 1600×900 · see docs/images.md

fig. 02 — screenshot, primary view

screenshot pending (NDA-safe crop)

drop /public/images/projects/payment-layer/02.jpg · 1600×900 · see docs/images.md

fig. 03 — screenshot, detail view

04 — from the codebase

Provider webhooks normalized to internal events

Provider webhooks normalized to internal events
public function ingest(string $provider, Request $request): Response
{
    $adapter = $this->gateways->adapter($provider);

    $event = $adapter->verifyAndParse($request);   // throws on bad signature

    // Dedupe: providers redeliver, our ledger must not.
    if ($this->ledger->seen($provider, $event->externalId)) {
        return response()->noContent();
    }

    // 'payment.captured', 'PAYMENT.SALE.COMPLETED', 'bkash.execute.success'
    // all become the same internal event the billing engine understands.
    $this->ledger->record(
        $event->toInternal()   // e.g. PaymentSucceeded::class
    );

    return response()->noContent();
}

05 — the outcome

What the numbers say

payment gateways behind one contract
20+
SMS gateways, user-configurable
5+
duplicate-webhook billing bugs after the ingress
0

New market entry became an adapter and a config file — not a quarter of rework.

/projects/payment-layer/next

Need something in this neighborhood?

If this case study sounds like your problem — same domain, same scale, same kind of mess — I've already made the expensive mistakes for you. Tell me where you're stuck.