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.

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