Back to all case studies

Payment infrastructure

Tinker Payments: Making Payments Traceable from Checkout to Ledger

Built with Go and Next.js, Tinker Payments unifies M-Pesa, Paystack, Stripe and many others behind one API, with staged routing, a double-entry ledger, reconciliation, durable webhook recovery and verified merchant onboarding.

My role
Full-stack engineer
Year
2025–2026
Duration
October 2025– Ongoing
Team
3 enginners + 1 product

Impact

A few numbers that show what changed.

1 request
Dashboard usage summary, down from 1 + N requests for N applications
7 checks
Backend-verified setup requirements guiding merchants toward payment readiness
5+ Providers
M-Pesa, Paystack,Stripe among others connected through one payment AP

The challenge

Accepting a payment is only the beginning. A customer can complete checkout before the merchant receives confirmation. A subscription event can arrive after the customer has already cancelled. A provider's settlement statement can disagree with the application's own records. Each gap leaves someone asking the same question: what actually happened to the money?

Tinker Payments was built to answer that question. It brings M-Pesa, Paystack Stripe among other payment providers behind a single API, paired with a dashboard for the teams who integrate and operate payments. I worked across the Go backend and the Next.js frontend, connecting provider integrations to merchant workflows, billing, accounting and recovery.

The project began in October 2023. In December, the frontend became a separate application, and development is ongoing. Over those twelve months, the central engineering problem changed. At first, the challenge was making providers available. As the platform grew, it became keeping every record and decision around a payment consistent and explainable.

That shift shaped every decision that follows.

One integration, without pretending providers are the same

The first step was giving merchants a single place to integrate. A shared gateway interface provides a consistent entry point for payment operations, while provider adapters beneath it translate each gateway's request formats and responses. Provider-specific code stays out of the merchant's integration, and the platform gains a defined place to add the next gateway.

A unified interface, however, should not imply that every provider behaves identically. Currency support, subscription behavior and credential requirements still determine which operations can succeed. Rather than hiding these differences behind a promise of uniformity, the platform treats them as inputs to the payment decision.

That decision grew more sophisticated over time. Routing began with simple rules and failover, then evolved into versioned policies, provider performance scoring, simulation and staged rollouts. A candidate policy moves through 1%, 10%, 50% and finally 100% of eligible traffic, protected by guardrails on approval rate, cost-related margin and latency. At any stage, operators can inspect the policy and roll it back, with the reason recorded.

As a result, every routing change is reviewable and reversible. Simulation and staged rollouts inform the decision; confirming that a policy increases successful payments still depends on measuring it in production.

Making financial records explainable

Routing determines where a payment goes. The harder question is how to account for it once it arrives. In February 2026, I moved the accounting core to a double-entry general ledger. Payments, wallet movements, invoice settlements and corrections need more than a status label. They need records that explain exactly how each balance changed.

The backend enforces that discipline. It validates journal lines, requires every entry to balance and respects accounting periods. Source references ensure that an event already posted resolves to its existing journal rather than creating a duplicate. Reversals generate a corresponding entry linked to the original, so the history of every correction is preserved.

Currency soon became a recurring design constraint. A dashboard total that adds Kenyan shillings to US dollars is misleading unless it applies an explicit exchange rate, so the frontend groups payment volume by currency. On the backend, fixing a currency error meant more than correcting a single payment field: the associated journals and currency accounts had to be corrected alongside it.

Reconciliation is the operational counterpart to the ledger. Provider settlement lines are matched against payment records, and duplicates or mismatched amounts and currencies surface as exceptions for review. The dashboard gives each discrepancy a place to be investigated and resolved.

Situation How the system handles it
A payment event is processed again Source-linked accounting returns the existing journal.
A statement disagrees with a payment Reconciliation flags the difference for review.
Records contain different currencies Totals stay separated, and corrections extend to the accounting records.

The underlying lesson was clear: a financial correction is a workflow, not an edit. When other records depend on a value, changing it in one place is never enough.

Recovering when payment events arrive late

A reliable ledger depends on reliable inputs, and in payments, inputs do not always arrive in order. Provider callbacks are essential, but their arrival order cannot be allowed to define the truth of a subscription. In September, I addressed stale events that could otherwise overwrite a cancellation, along with differences in how Stripe and Paystack handle webhooks.

For managed Stripe billing, a verified callback no longer updates local records directly. Instead, it prompts the backend to retrieve the current state from the provider and confirm application ownership before making any change. Background reconciliation then revisits subscriptions and invoices to recover updates that were missed entirely, while refund and dispute evidence is preserved even when a read returns stale data.

The same principle applies to outbound events. Merchant notifications use a durable outbox: the database commits the business change and its notification payload together, and a separate worker handles delivery. If delivery fails, the payload remains available for retry. Each retry keeps the same event ID, and because delivery is at-least-once, consuming applications deduplicate on that ID.

An unavailable merchant endpoint can therefore delay a payment update, but it cannot erase one. The recovery path is explicit at every step: save the change, retain the intent to deliver, retry, and expose the operational state.

Reducing load without losing billing evidence

As billing and reconciliation expanded, database performance became part of the product design rather than an afterthought. The dashboard originally fetched the list of applications and then made a separate usage-statistics request for each one. I replaced that fan-out with a single owner-scoped usage-summary request. Its totals are maintained transactionally, so the view no longer rescans historical usage logs.

Billing usage also gained its own durable queue. Recording a usage event and acknowledging its queued work happen in the same database transaction. If accounting fails, it can be retried independently of provider calls, and the usage log ID prevents the same event from being accounted for twice.

This design carries a deliberate tradeoff. Billing counters now reflect queued usage asynchronously, and the queue adds writes. To keep that tradeoff visible, health checks surface an aging backlog. Reconciliation work is bounded and leased, allowing multiple workers to claim different subscriptions in parallel, while claims left behind by a crashed worker expire and are recovered.

The immediate gains are fewer dashboard requests and an accounting path that can recover from failure. The effect on production latency and capacity is the next thing to measure under real traffic.

Turning setup into a verified next step

A capable platform only matters if merchants can get started. A broad payments dashboard can leave a new merchant unsure where to begin, so I introduced guided setup built around seven backend-evaluated requirements: identity, business profile, operating mode, financial foundation, application, gateways and webhook delivery.

The frontend highlights the next incomplete step and offers the action needed to complete it. Crucially, completion reflects actual account state, not form submission. Entering a webhook URL, for example, is not enough; the step is marked ready only after a successful delivery to the application's configured endpoint.

Sandbox switching follows the same principle. Before changing environments, the platform prepares the signed-in user's sandbox identity and validates access. Business data stays separate between environments, and if a switch fails, the merchant remains safely in production. The result is a coherent testing journey with a clear boundary between test and live.

Outcomes

Over twelve months, Tinker evolved into a connected API and dashboard for collecting payments, managing subscriptions, explaining financial movements and recovering from missed events. The most significant outcomes in the implementation are:

  • A shared integration surface across M-Pesa, Paystack and Stripe, with routing controls for changing provider selection safely.
  • A traceable financial foundation connecting journals, wallets, invoice settlements and reconciliation exceptions.
  • Durable recovery paths for merchant notifications and usage accounting, with ownership checks and retry behavior.
  • A single usage-summary request replacing the dashboard's application-by-application statistics calls.
  • Seven verified setup checks that turn configuration into a clear next action.

These decisions are backed by regression coverage for ownership isolation, durable replay, stale reversal evidence, worker claims, usage-accounting rollback and setup behavior. That coverage validates the engineering. Customer adoption, processed volume and revenue impact are yet to be measured.

What comes next

The next phase is measurement: merchant time to first confirmed payment, reconciliation exception rates, webhook delivery lag and routing performance under comparable traffic.

If this project taught me one thing, it is that payment infrastructure earns trust when people can understand its records, recover from failures and see what to do next.

Explore Tinker Payments →