Skip to content
All projects
Subscription StoreModern Web AppsStatus: Live

SaborCup

Specialty-coffee store — Stripe Billing lifecycle, gift cards, and a 24-model Postgres schema

Screenshot of SaborCup

Full-stack specialty-coffee store (Stripe test mode) — Stripe Billing subscriptions with pause, skip, and plan changes, webhook-minted gift cards, merch, PDF invoices, and a full admin console.

Problem

One-time checkout is the easier half of e-commerce. Subscriptions add lifecycle state — pauses, skips, plan changes, failed payments, dunning — that has to stay correct across webhooks, scheduled jobs, and user actions, and most portfolio projects avoid that complexity entirely.

Goal

Build a complete subscription-commerce store — Stripe Checkout for one-time orders and Stripe Billing for recurring plans — with the operational layer real stores need: webhook-driven state, scheduled jobs with recorded outcomes, transactional email, and separate customer and admin consoles. Then prove it with tests.

What I Built

A fictional specialty-coffee brand (labeled as such on every page; all payments in Stripe test mode). 10 seeded coffees and 15 merch items share one cart, checkout, review, and favorites flow. Three subscription plans ($14/$28/$48 per month) sit alongside per-coffee subscriptions with biweekly-to-monthly cadence changes. Everything after the first charge is the hard part — lifecycle controls, proration, and dunning — and that is where most of the schema and the test surface went.

Key Features

  • Stripe Checkout + Stripe Tax for orders, Stripe Billing for subscriptions — prices, discounts, and gift-card balances re-validated server-side
  • Subscription lifecycle: pause, skip-next-delivery, next-cycle plan changes, cancel/reactivate, and per-coffee subscriptions with cadence switching
  • Dunning — invoice.payment_failed flags the subscription, emails the customer, and surfaces admin triage
  • Gift cards minted by webhook on purchase, redeemed by code at checkout, balance restored on cancellation
  • Volume bundles that stay off ineligible lines — Stripe percent_off coupon for coffee-only carts, per-unit price baking for mixed carts
  • 24-model Postgres schema — dual-nullable product FKs share one commerce flow across coffee and merch; denormalized order items survive product deletion; unique Stripe IDs as a DB-level double-refund backstop
  • 59 pages across four route groups, with scoped error and loading boundaries on the storefront, account, and admin shells
  • One-click demo sign-in at /demo for customer and admin roles — token-gated, fail-closed nightly reset, demo admins fenced off from real roles and secrets
  • Scheduled jobs with CronRun observability — daily renewal reminders (idempotency-keyed), cross-instance claims and outcomes recorded in the database
  • Transactional email on one shared template shell via Resend, with unit-tested templates and a dev preview route
  • PDF invoices rendered server-side from the account dashboard
  • 59 Vitest test files (including a dedicated webhook-idempotency suite) + 4 Playwright E2E specs — hermetic against a guarded database locally, ephemeral Postgres in CI via GitHub Actions

Challenges

  1. Webhooks as the source of truth — checkout, billing, gift-card minting, and dunning all settle through Stripe events, so state stays correct even if the user closes the tab mid-checkout
  2. One commerce flow for two product types — dual-nullable FKs on order items, favorites, and reviews avoid duplicating cart and checkout logic across coffee and merch
  3. Mixed-cart bundle discounts — a single Stripe coupon over-discounts carts containing merch, so mixed carts bake the discount into per-unit prices instead
  4. Cron reliability on a platform with 1-hour logs and no retries — a CronRun model makes the database the evidence for what ran, claimed, and completed
  5. Keeping a public demo safe — token-gated demo auth, fail-closed reset guards, and demo admins that cannot create real admins or reach production secrets

Outcome

Live on Vercel in Stripe test mode with one-click demo accounts for both roles. 644 tests across 59 Vitest files and 4 Playwright E2E specs, run in CI against an ephemeral Postgres. Fictional brand, clearly labeled — nothing is for sale and no orders are fulfilled.

Next Steps

  • Wire the persisted subscriber-discount and skip-limit policies into an actual pricing engine (fields exist, engine does not yet)
  • Hook admin notification preferences to a delivery provider