Skip to content
MARTINEZAI STUDIOS
CONTRABANDEdge of the Fold
THE ORIGINAL REFERENCE COLLECTION

Stripe & Firebase Integration

Original website archive

Preserved from the original edition. Balance and mechanics can differ from the current release. Narrative entries may reveal story events.

Current game & media ↗

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 — on checkout.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 IDs
  • guestTokens — tokens linked after guest checkout
  • purchaseHistory — audit trail with Stripe session IDs

Guest-only purchases lived in guestEntitlements until link-purchase.js merged them.

Client purchase flow

  1. Player clicked “Buy epilogue” in the web game UI.
  2. Client called checkout.js with product ID + auth/guest token.
  3. Browser redirected to Stripe-hosted checkout.
  4. Webhook wrote entitlement; client polled verify-purchase.js on return.
  5. 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.