DEV Community

Cover image for When Stripe Was a Pipe Dream, We Built Our Own Checkout in 120 Lines
pinkie zwane
pinkie zwane

Posted on

When Stripe Was a Pipe Dream, We Built Our Own Checkout in 120 Lines

The Problem We Were Actually Solving

Late 2024, the company launched an education platform for a country where Stripe, PayPal, Gumroad, and Payhip were either blocked or region-restricted. The product team insisted on selling ebooks directly inside the app instead of redirecting users to third-party stores. The CFO said no external checkout meant no payment fees, but the legal team warned that PCI compliance and bank integrations would take six months and half a million dollars.

We needed a checkout that lived inside our React SPA, respected local payment methods like M-Pesa and bank transfers, and didnt route users outside the application. Everything else was failure.

What We Tried First (And Why It Failed)

We started with a simple hosted checkout that popped a new window to a bank redirect. It worked for 30 % of users, but the rest closed the popup or lost the session when the bank returned via deep link. Mobile Safari and Android WebView treated the popup as a second tab, so cookies, sessions, and the JWT were gone.

Next we tried an iframe sandboxed to our domain. Safaris Intelligent Tracking Prevention immediately dropped the iframe after three seconds of inactivity, breaking the payment flow. Bank pages inside the iframe were blocked by popup blockers on desktop, and mobile browsers killed the iframe when switching apps to approve M-Pesa.

Then we looked at merchant-of-record services. They required us to route payments through their servers, which added 4 % to 7 % in fees and introduced a middleman we couldnt debug. Support tickets piled up because the error messages were opaque and outside our control.

Every off-the-shelf solution assumed global payment rails; none spoke local languages and banking UX patterns.

The Architecture Decision

We decided to treat the payment form as part of the application and bypass tokenized gateways entirely. The checkout lived inside a React portal that rendered above a full-screen modal. We collected payment details in plain text, encrypted them client-side with RSA-OAEP using a public key we rotated weekly, and posted the ciphertext directly to our Node API. The backend decrypted with the private key, then vaulted the card data in an HSM-backed store and created an invoice. When the invoice was paid—via bank callback, webhook, or admin action—we unlocked the ebook.

Key trade-offs we accepted:

  • We became a payment facilitator in the eyes of regulators, which meant yearly audits and transaction logging.
  • We lost Stripes dispute handling, so we built a small admin panel to review chargebacks and upload evidence manually.
  • We gained control: if a banks redirect failed, we could show a local USSD code or WhatsApp payment link inside the same modal without leaving the app.

The entire flow stayed inside a single next.js route, used no external iframes, and worked on 2G networks with flaky connectivity. We shipped it in two weeks, not six months.

What The Numbers Said After

In the first quarter, 87 % of checkouts completed inside the app versus 30 % with the hosted popup. Mobile bounce rate dropped from 42 % to 12 %. The HSM decryption added 60 ms to the cold-start payment request, but the median stayed under 200 ms. Monthly PCI audit costs were $12k versus the $470k quote from the payment facilitator. Two months later, the local bank reached out to white-label our checkout for their own merchants—proof we had solved a problem they had ignored.

What I Would Do Differently

I would never ship the RSA-OAEP public key in client-side JavaScript again. One stolen public key plus enough captured ciphertexts could let an attacker reconstruct messages offline. Next time, Id move the RSA encryption to a Cloudflare Worker edge function with a short-lived public key that rotated every hour. Id also add a server-side tokenization layer that returns a one-time-use payment token instead of raw card data, even though it complicates the bookkeeping.

Finally, Id insist on a real-time compliance chatbot in the admin panel. Our manual chargeback review process became a bottleneck during holidays; the bot could triage obvious fraud and free human reviewers for edge cases.

Top comments (0)