DEV Community

Cover image for The Platform Lock-In Grind: How We Sold Books in 12 Countries Stripe Couldnt Reach
Lillian Dube
Lillian Dube

Posted on

The Platform Lock-In Grind: How We Sold Books in 12 Countries Stripe Couldnt Reach

The problem wasnt our product; it was the platforms. We launched our technical deep-dive ebook series in 2024, expecting Stripe and Gumroad to handle the heavy lifting. By week three it was clear: Stripe declined 47% of our traffic from Angola, Gumroad blocked IPN callbacks from Belarus without notice, and PayPals new KYC rules froze funds in Nigeria for 14 days. The platforms werent refusing our business—they were refusing entire countries based on arbitrary compliance thresholds. Our revenue died not because our ebooks sucked, but because the payment rails did.

We tried every hosted checkout first: Stripe Checkout, Gumroad embed, Payhips Paywall. All three failed the same way—either the country block list grew daily or the callbacks vanished into static-filled logs. The error messages were polite but final: Server refused connection from AS12345, IPN delivery failure: no response received, KYC32 timeout in country NG. We spent two weeks rewriting checkout flows to use manual payment instructions, then realized emailing bank details wasnt scalable beyond ten customers. The real failure wasnt code; it was geography. Hosted platforms make the optimistic assumption that every country has a stable banking network and a compliant processor. Ours didnt.

So we built a microservice we called LedgerCharge—an ugly, verbose name for something simple: a headless payment orchestrator that never relied on a single gateway. We kept Stripe for US/EU traffic but routed Angola, Belarus, and Nigeria through Flutterwaves sandbox endpoint (which actually worked in those countries), M-Pesas Daraja API for Kenya, and PayTabs for UAE. The service used a priority-weighted router: if Flutterwave returned HTTP 429, we tried PayTabs; if PayTabs returned 400, we tried M-Pesa. Each country had a fallback list, and we stored the routing decisions in a Postgres table called country_route_prefs with a 5-minute TTL cache. We deployed it on Fly.ios shared-cpu-1x instances because we didnt want to explain to a finance team why our AWS bill tripled overnight. The tradeoff was obvious: more moving parts meant more failure modes, but also more countries served. We accepted the extra p99 latency of 800ms for international calls because declining a customer was worse than waiting an extra second.

After six months the numbers told a story neither Stripe nor Gumroad had shared. Flutterwave handled 32% of global volume across three countries, M-Pesa 22%, PayTabs 18%, and Stripe 28%. The remaining 0.3% came from niche processors like PayU South Africa and PayKiosk. Our average authorization rate across all routes hit 83%, up from 42% when we relied solely on Stripe. The error rate dropped from 2.1% to 0.04% because the router automatically evicted failing endpoints. The worst outage occurred when Flutterwaves sandbox switched to production keys and started rejecting every card—our routers circuit breaker kicked in after 5 seconds, rerouted to PayTabs, and customers never noticed. We logged every metric in Prometheus with labels for country, route, and error type; the Grafana dashboard became the first thing our finance team looked at each morning. The cost? Three extra DevOps hours a week to manage API key rotation and a 0.0002% increase in failed transactions due to race conditions in our retry logic. We called it acceptable.

If I could go back, Id rip out LedgerCharges fallback router and replace it with a proper saga-based orchestrator. The current design uses a simple priority list, which means if M-Pesa is down we retry every other route before we log the failure. That adds unnecessary latency. A saga would let us mark the transaction as failed immediately when the primary route dies, then compensate by notifying customers via WhatsApp instead of email. The other lesson is to never trust sandbox endpoints again—Flutterwaves sandbox accepted test cards but rejected real ones because the sandbox keys werent provisioned for live traffic. We lost $1,200 in test transactions before we noticed. Live test environments need real card numbers, not sandbox placeholders. Finally, dont deploy on Fly.ios shared instances if you care about consistent latency. Our 800ms p99 became 1.2s during peak hours, and customers in Angola noticed. Next time well pay the $20/month for Flys dedicated-cpu-1x instances and sleep better.

Top comments (0)