Stripe & Firebase Integration
Archived (2026): This page documents the retired browser checkout stack. CONTRABAND now sells through Steam. The marketing site no longer runs epilogue or soundtrack purchases. The api/ folder in the repo remains as reference code, not an active store for visitors.
During the web prototype, monetization was a small-but-tricky problem: prove purchases server-side, sync entitlements across devices, and still let guests buy before creating an account. Stripe Checkout + Firebase Firestore + four Vercel Functions solved it.
Today: Steam owns commerce
Game keys, DLC, soundtrack, refunds, and regional pricing now live on Steam. This site is wiki/lore/press only — no “Buy epilogue” buttons, no Stripe return URLs on the homepage, no entitlement unlock flow for new players here.
If you are integrating payments today, start with Monetization Philosophy for product context, then read the archived technical sections below only as historical reference.
Browser-era stack (archived)
Everything below describes circa 2024–2025 behavior when /play was live. Wording is past tense on purpose.
Requirements we had to meet
- Server-side verification — client “purchased” flags were spoofable.
- Cross-device sync — buy on phone, unlock on laptop after sign-in.
- Offline tolerance — game ran offline except at purchase/sync moments.
- Guest purchases — buy first, link to Firebase account later.
SKUs included four paid epilogues ($4.99 each), a bundle ($12.99), and occasional $2.99–$2.99 cosmetics defined in api/checkout.js.
Four Vercel Functions
checkout.js— created a Stripe Checkout session and returned the hosted payment URL.stripe-webhook.js— oncheckout.session.completed, wrote entitlements to Firestore (UID or guest token).verify-purchase.js— on game load, returned entitlements; client cached to localStorage.link-purchase.js— moved guest-token purchases onto a new Firebase UID after sign-up.
Total: ~280 lines of Node.js plus a shared Stripe/Firebase admin init module.
Firestore data model
Collection users (keyed by Firebase UID):
entitlements— purchased product IDsguestTokens— tokens linked after guest checkoutpurchaseHistory— audit trail with Stripe session IDs
Guest-only purchases lived in guestEntitlements until link-purchase.js merged them.
Client purchase flow
- Player clicked “Buy epilogue” in the web game UI.
- Client called
checkout.jswith product ID + auth/guest token. - Browser redirected to Stripe-hosted checkout.
- Webhook wrote entitlement; client polled
verify-purchase.json return. - Epilogue scenes unlocked once Firestore confirmed the SKU.
Typical round-trip: 8–12 seconds (mostly Stripe UI). Server work was sub-500 ms.
Cache vs authority
localStorage cached the last server-confirmed entitlement list for offline UX. Firestore stayed authoritative — tampering with localStorage could fake UI state, not serve paid narrative payloads without a server check.
Pitfalls we hit
- Webhook race — poll verify-purchase for ~30s after Stripe return.
- Lost guest tokens — recovery via Stripe receipt / session ID.
- Unsigned webhooks — always verify Stripe signatures.
What still transfers
Even though Steam replaced this stack, the pattern holds for any web SKU: authoritative server ledger + client cache + never trust the browser for paid content gates. Stripe + Firebase + Vercel was cheap (<$10/mo at indie scale) and maintainable by one developer — a good fit for the browser prototype, not the final PC storefront.