<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Epic Dev Log]]></title><description><![CDATA[Epic Dev Log]]></description><link>https://epicdevlog.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6acca63720212c2318ea5a55/5b74ddaf-6fe6-418a-99e5-cce301068a3d.png</url><title>Epic Dev Log</title><link>https://epicdevlog.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 23 Oct 2026 12:03:29 GMT</lastBuildDate><atom:link href="https://epicdevlog.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why Stripe Test Mode Lies: A Solo Dev's 36-Hour Payment Infrastructure Trench Warfare]]></title><description><![CDATA[A solo dev's 36-hour battle debugging Stripe and RevenueCat webhooks for a Kotlin Multiplatform app — and the 3 fixes that saved it.]]></description><link>https://epicdevlog.hashnode.dev/why-stripe-test-mode-lies-a-solo-dev-s-36-hour-payment-infrastructure-trench-warfare</link><guid isPermaLink="true">https://epicdevlog.hashnode.dev/why-stripe-test-mode-lies-a-solo-dev-s-36-hour-payment-infrastructure-trench-warfare</guid><category><![CDATA[Kotlin]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[notes]]></category><category><![CDATA[buildinpublic]]></category><category><![CDATA[indiehacker]]></category><category><![CDATA[stripe]]></category><dc:creator><![CDATA[Epic Devs]]></dc:creator><pubDate>Mon, 12 Oct 2026 09:41:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6acca63720212c2318ea5a55/c04ac82d-8976-48e3-8e83-0340a75b39e5.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Why Stripe Test Mode Lies: A Solo Dev's 36-Hour Payment Infrastructure Trench Warfare</p>
<p>Hey everyone 👋 I'm Usama. By day, I work as a Project Manager. By night, I'm a solo dev building a Kotlin Multiplatform (KMP) notes app called EpicNotes, targeting the "forgotten combo" of Android phones and MacBooks.</p>
<p>The tech stack was hard (KMP, Supabase, local-first sync). But the part that actually broke me was <strong>the payment infrastructure.</strong></p>
<p>I thought wiring up Stripe for the Mac app and RevenueCat for the Android app would take a weekend. Instead, I ended up in a 36-hour debug marathon, buying my own subscriptions, refunding them, watching database rows flip, and debugging at 2 AM after a full day of meetings.</p>
<p>If you are an indie dev about to wire up payments, here are the 3 brutal lessons I learned so you don't have to.</p>
<h2>1. Test Mode is a Sandbox, Not Reality</h2>
<p>Stripe and RevenueCat test environments are great for happy paths, but they lie to you in subtle ways.</p>
<p>During my testing, the Android RevenueCat SDK had a blank API key that nobody noticed until I tried a <em>real</em> purchase with a real credit card. Test mode was perfectly happy passing simulated webhooks, masking the fact that my Android SDK was essentially flying blind.</p>
<p><strong>The fix:</strong> Test with real money. Buy a $2 subscription on your own phone. Refund it. Watch your database. The peace of mind is worth the $2.</p>
<h2>2. Webhook Retries Create "Zombie" Users</h2>
<p>Webhooks are not guaranteed to arrive in order, and they will retry if your server takes more than a few seconds to respond.</p>
<p>I ran into a nightmare scenario where a <code>customer.subscription.updated</code> event arrived <em>before</em> the <code>customer.subscription.deleted</code> event due to network retries. My Supabase database saw the update, flipped the user back to <code>is_pro: true</code>, and completely ignored the subsequent deletion event. I had accidentally built a system that resurrected cancelled subscriptions into "Zombie Pro Users."</p>
<p><strong>The fix:</strong> Never trust the webhook payload blindly. When a webhook arrives, use the <code>subscription_id</code> to make a direct API call back to Stripe/RevenueCat to fetch the live, current state of the subscription.</p>
<h2>3. The "Hostage Paywall" Bug</h2>
<p>I had a bug where the X button on my Mac paywall wouldn't close the window unless the Stripe checkout session actually finished. If a user opened the paywall and changed their mind, they were trapped. I had to literally force-quit the app to get out.</p>
<p>It sounds silly, but when you are deep in the weeds of Kotlin Multiplatform UI state and async Stripe callbacks, you lose sight of the basic UX.</p>
<h2>The Mental Toll of the Side Hustle</h2>
<p>Building a dual-platform SaaS on top of a day job is a feature, not a bug—it forces you to be disciplined. But it also means when the webhooks fail, you aren't just losing company time; you're losing your sleep and your time with your family.</p>
<p>Having a working payment pipe is the most stressful part of the journey, but it's also the moment your hobby officially becomes a business.</p>
<p>I finally got the Stripe and RevenueCat webhooks fully certified and bulletproof, and EpicNotes officially launched this week.</p>
<p>If you want to see the result of those 36 hours of trench warfare, you can check out the app here: <a href="https://epicnotes.app">EpicNotes</a>.</p>
<p>I'd love to hear from other solo devs: What's the most painful infrastructure you've had to wire yourself? Payment processing? Auth? Let me know in the comments!</p>
<p><strong>Want to see the app that caused all this suffering?</strong><br />Check out EpicNotes (Native Mac &amp; Android): <a href="http://epicnotes.app">epicnotes.app</a></p>
]]></content:encoded></item></channel></rss>