Live demo stands
Two OpenBitum-powered storefronts running on prod. Both share the
same Logto identity, the same checkout flow, and the same entitlement
pipeline as a real install — they just have demo_mode
toggled on so invoices auto-pay 5 seconds after creation. Nothing here
spends real crypto.
Vercel Commerce skin, swapped from Shopify to OpenBitum's product catalogue. Browse three demo products, click Buy on OpenBitum, land on the OpenBitum hosted checkout, watch the order flip to paid.
Vercel SaaS Starter skin, Stripe replaced with OpenBitum checkout and the bundled auth replaced with @logto/next against auth.openbitum.pw. Sign in, hit the paywall, "subscribe" with crypto, see the dashboard.
What each stand proves
| Stand | What it demonstrates |
|---|---|
| shop.openbitum.pw | A drop-in storefront UI (Vercel Commerce) talking to OpenBitum glue as its backend — product catalogue, single-product invoice creation, redirect-based checkout. |
| demo.openbitum.pw | A SaaS-style sign-in + subscription flow — @logto/next for OIDC, glue checkout for paid tiers, the merchant dashboard pattern. |
| Both | Auto-paid invoices via the demo_mode flag (no real chain wallet activity), entitlement application end-to-end. |
Credentials for hands-on poking
You can sign into either site as the seeded OpenBitum admin:
email: admin@openbitum.pw
password: devsecret123!
The seed also provisions 39 historical e2e buyer users you can browse
in the merchant dashboard at app.openbitum.pw/dashboard/customers.
End-to-end flow (the happy path)
buyer hits shop.openbitum.pw
│
▼
clicks "Buy on OpenBitum" (data-testid="demo-buy-button")
│
▼
redirected to app.openbitum.pw/checkout/<productId>
│
▼ (optional: sign in via Logto)
│
▼
glue POST /api/checkout/initiate
│ - creates Order(status=pending)
│ - calls Bitcart /invoices (real BTC invoice issued —
│ address is derived from the merchant's xPub)
│ - merchant.demo_mode = true → enqueue
│ `simulate-paid` BullMQ job with delay=5000ms
│
▼
buyer sees pay page with crypto address (real but irrelevant in demo)
│ (T+5s)
▼
glue worker `demo-paid-queue` fires
│ - SET orders.status='paid', paid_at=now()
│ - paid_currency='USDC_BASE', tx_hash='0xdemo-…'
│ - enqueue `apply-entitlements` on `entitlements-queue`
│
▼
entitlements worker picks up the order, dispatches based on
product.entitlements[]:
- github_invite → Octokit POST /repos/:owner/:repo/collaborators
- discord_role → Discord bot adds role
- file_download → S3 signed URL stored on applied_entitlements
- logto_role → Logto Management API user-roles binding
- webhook → HMAC-signed POST to the merchant's URL
│
▼
buyer's /account portal at app.openbitum.pw now lists the purchase.
How the storefronts are wired
shop.openbitum.pw (apps/demo-shop/)
Vendored from vercel/commerce.
The Shopify integration at lib/shopify/index.ts was swapped for a
thin adapter that fetches from api.openbitum.pw/api/products and
projects the response into Shopify-shaped types so the existing UI
components keep working unchanged. The Buy button (originally a
Shopify cart-action) now redirects straight at
app.openbitum.pw/checkout/<productId>.
Product covers are placeholder PNGs from placehold.co (Next/Image
refuses to optimize remote SVGs, and picsum 302-redirects to its
fastly CDN which next/image won't follow).
demo.openbitum.pw (apps/demo-saas/)
Vendored from nextjs/saas-starter.
The Stripe + Drizzle stack was stubbed out: lib/db/* returns empty
results, lib/payments/stripe.ts is a throwing placeholder, the
Stripe webhook + checkout routes 410-Gone. The auth surface was
replaced with @logto/next — its own Logto Application
(xnkq9mapfafnolk68vunj) drives sign-in against auth.openbitum.pw
and the cookie session is scoped to demo.openbitum.pw.
The pricing page is a static two-tier card grid where each
"Subscribe with crypto" button points at
app.openbitum.pw/checkout/<productId>.
demo_mode mechanism
| File | What it does |
|---|---|
| packages/db/src/schema/merchants.ts | demo_mode boolean default false column. |
| packages/db/migrations/0003_demo_mode.sql | ALTER TABLE merchants ADD COLUMN IF NOT EXISTS …. |
| apps/glue/src/workers/demo-paid.ts | BullMQ queue demo-paid-queue + worker for the simulate-paid job. Worker logic mirrors the real Bitcart webhook handler: flips order to paid, enqueues apply-entitlements. |
| apps/glue/src/routes/checkout.ts | After INSERT orders … status='pending', if merchant.demoMode === true, enqueue simulate-paid with delay=5_000. |
| apps/glue/src/routes/checkout.ts (sibling) | Public GET /api/checkout/status?orderId= returns { status, paidAt } — used by the checkout/pay page to poll, and by the e2e to assert the auto-pay window. |
E2E coverage
Live prod has three new specs backing the demo stands:
| Spec | What it asserts |
|---|---|
| qa-demo-shop-render-001 | shop.openbitum.pw homepage + product page render with glue products; Buy CTA targets app.openbitum.pw/checkout/<productId>; at least one cover image loads via Next/Image. |
| qa-demo-paid-flow-001 | POST /api/checkout/initiate for a demo-mode merchant's product → poll /api/checkout/status → assert paid within 15 s. |
| qa-demo-shop-buy-flow-001 | Full browser-driven happy path: shop product page → Buy CTA → checkout invoice → paid → /account portal renders for the signed-in buyer. |
| qa-demo-saas-signin-001 | demo.openbitum.pw middleware guard on /dashboard; OIDC redirect chain from /api/logto/sign-in resolves to auth.openbitum.pw with the demo-saas client_id + correct redirect_uri + openid email scopes. |
See also
- Tutorial: Add SSO to your site and manage users — same SSO mechanism, end-user view.
- Tutorial: Put something up for sale — the merchant side of the demo flow.
- Multi-merchant + demo mode — what you do to add a third demo merchant, or operate multiple real merchants.
- Case study: Controlum integration — how a separate Next.js + Go SaaS hooked OpenBitum SSO as a drop-in identity provider.