WiraPay: Payments for the ike.work products
Card & M-Pesa checkout, one-off or recurring—hosted or by API
Project Overview
WiraPay is the shared payment rail behind my other products. It wraps Paystack (Kenya) into a small, well-tested service that any of my apps can call to charge card or M-Pesa, as a one-off payment or a subscription, through either a hosted checkout or a machine-to-machine API. Roamgap and the Mara simulator are its first clients—which is why "how does Roamgap take payments?" and "how will I sell prints?" both point back here.
The design goal was a payment service I could trust and reuse: domain logic that's dependency-injected and unit-tested, thin route handlers, and reconcile-from-source as the safety net, so a dropped webhook never means a wrong balance.
Status
Built end-to-end—spec, tests, and app—with both Supabase projects provisioned and the deploy path ready on Firebase App Hosting. It is not yet live: WiraPay's underlying Paystack business account is going through verification, so there is no live secret key yet. Everything downstream—including Roamgap's paid tier and, later, print sales—switches on the moment that clears.
What It Does
- Hosted checkout. A public
/pay/{endpoint}page—one-off or subscription, with recurrence disclosure—plus an in-page M-Pesa STK push that charges on the page with no redirect. - Machine-to-machine API. Bearer-key endpoints to create and verify invoices and subscriptions, so a client app never touches card data.
- Subscriptions, dual-rail. The payer picks the channel: card becomes an auto-renewing Paystack subscription; M-Pesa becomes a "renewing pass" where WiraPay owns the period clock and reminds before it lapses.
- Signed outbound webhooks. WiraPay POSTs signed
invoice.*/subscription.*events to a client's URL within seconds of settlement; clients verify the signature, dedupe, then reconcile viaverify—the webhook is a nudge, never trusted state. - One-off consumables. The same rail sells metered units (credit packs, token bundles), granted idempotently on the settlement webhook.
Technology Stack
- App: Next.js (App Router) on Firebase App Hosting (Cloud Run,
europe-west4). - Data: Supabase (PostgreSQL,
eu-central-1) with deny-by-default row-level security; the server uses a service-role key. - Payments: Paystack (Kenya)—card + M-Pesa; HMAC-SHA512 inbound signatures, HMAC-SHA256 outbound.
- Jobs: Cloud Scheduler crons for outbound webhook delivery, subscription reconcile / reminders, and scheduled price migration.
- Architecture: a pure, unit-tested
src/lib+src/core(DI services) with a thinsrc/serverwiring layer—165 Vitest tests, plus a contract → API → webhook → component → browser-E2E test plan.
Skills Demonstrated
- Payments engineering—one-off and recurring billing across two channels (card + M-Pesa), with the correctness concerns (idempotency, signature verification, reconcile-from-source) treated as first-class.
- Service design for reuse—a dependency-injected core with in-memory fakes, thin handlers, and a provisioning CLI, so new client apps onboard against a stable contract.
- Production infrastructure—Firebase App Hosting / Cloud Run, Secret Manager, scheduled jobs, and a documented deploy + go-live checklist.
- System-of-systems thinking—a single payment rail that several of my products (Roamgap, Mara, and a coming print store) depend on.
WiraPay is built and provisioned, awaiting Paystack business verification before it goes live at wirapay.ike.work.