Yesterday I shipped the Paywall Interceptor and Database Migration. Today was all about the complex monetization logic and making the app feel snappy without hammering the database.
What's shipped:
- Smart Cache Strategy - BillingPage now forces a full refresh on every mount (critical for financial data). All other pages (Dashboard, Lists, Details) use a manual refresh button with a 5-minute cache. This drastically reduces Turso read costs and prevents UI flashes.
- Plan Upgrade/Downgrade with Proration - Implemented the complex webhook logic to detect plan changes. When a user upgrades/downgrades via Stripe Customer Portal, the system automatically calculates and grants proportional credits based on the remaining days in the billing cycle.
- Turso Sync Fixes - Fixed the "stream not found" errors on writes.
- End-to-End Payment Testing - Validated 11 critical scenarios including credit purchases, subscription lifecycle, webhook idempotency (preventing double credits), and webhook security.
Key decisions:
- Manual vs Auto Refresh: Instead of auto-refreshing every page (which would spam the Turso replica sync), I added a manual refresh button for Dashboard and Lists. Billing remains auto-refreshed. This gives users control while saving API calls.
- Proration Logic: Instead of just giving full plan credits on upgrade, I calculated (days_remaining / total_days) * new_plan_credits. This is fairer and aligns with how Stripe handles monetary proration.
- Webhook Idempotency: Proved that our stripe_webhook_events table successfully prevents duplicate processing when Stripe resends events (tested via Stripe CLI resend).
What's left:
- Scenario 6 deep dive (Paywall edge cases with expired credits)
- Final frontend polish
- Production deploy (Render + Turso)
8 days to launch.