welcome.mdbilling-crypto.mdbilling.md04-auth-session.mdJourney flow#
Generated from the authored steps below — click a node to jump to that section.
Signup, crypto invoice issuance, the paywall, and signed settlement are each
real, but they are not one shipped customer funnel. Signup routes through
onboarding. /billing links to /billing/crypto without creating an invoice,
so it lands on the pending-issuance notice. Tests issue invoices through the BFF
API, then prove the paywall and settlement seams directly.
Personas#
- New customer — creates an account and completes onboarding before any protected billing surface.
- Signed-in plan buyer — needs a
proorpremiuminvoice bound to the server price book and their user id. - Crypto payer — receives an asset-specific non-custodial target and, for issued/less-decentralized rails, acknowledges the disclosure.
- Settlement watcher/operator — submits a signed on-chain confirmation and expects one plan mutation plus a verifiable receipt.
Pre-conditions#
/welcomeis public; protected/billingand/billing/cryptoroutes require an authenticated session.- Production quoting requires a bound non-custodial payment runtime. Without
one, methods are marked inactive and quote creation fails closed with
503; no address is fabricated. - The settlement receiver requires a configured HMAC secret and a bound receipt signer.
- A plan invoice derives amount/currency from the BFF price book. Client values may only echo that price and mismatches are rejected.
Steps#
1. Create the account and finish the auth handoff#
WelcomeAuthPanel posts the real signup contract through the auth client,
tracks viewed/submitted/completed/failed funnel events, and sends new accounts
to the onboarding route. A sanitized redirect is preserved through onboarding;
signup does not immediately mint a paid entitlement or crypto invoice.
2. Reach the current billing boundary#
/billing states that crypto is the wired rail and links to /billing/crypto.
That link carries no invoice id. Missing, malformed, over-length, unknown,
expired, cancelled, or confirmed invoices render PendingIssuanceNotice with
return/support actions. There is no customer upgrade tile that calls the quote
API and redirects with the issued id.
3. Issue an invoice through the BFF seam#
POST /v1/payments/crypto/quote is session-optional, but only an authenticated,
plan-bearing quote can grant that purchaser a plan at settlement. It checks
crisis suppression, runtime availability, supported asset, tenant, canonical
plan price, and the issuance disclosure gate. Only after the provisioner returns
a real unique target does the BFF persist the pending invoice and return the web
DTO.
Tier-B/Tier-C or centrally issued assets require acknowledgement before quote issuance. The rendered paywall independently asks for its visible legal acknowledgement before revealing the already-issued target.
4. Render and pay the pending invoice#
/billing/crypto?invoice=<id> fetches only a pending DTO, displays fiat and
chain amounts, expiry, disclosure version, trust tier, address/QR, copy, and
billing support. The asset picker changes visible radio/network choices, but the
page provides no onChainSelected callback; it does not re-quote or replace the
original invoice target.
The customer sends funds outside the app. The page does not poll invoice status or display confirmation depth.
5. Confirm through the signed settlement receiver#
The deploy-bound watcher posts exact raw JSON with timestamped HMAC, invoice,
transaction, block hash, and block height. The BFF rejects missing config,
invalid/stale signatures, malformed payloads, unknown invoices, and unavailable
receipt signing. A valid pending invoice becomes confirmed, receives an
Ed25519-signed receipt, and—when purchaser, plan, currency, and book price all
match—updates the persisted customer plan. Replay is acknowledged without
re-driving the grant.
This route does not publish a customer-facing
payment.invoice.confirmed → payment.invoice.settled event chain. Those bridge
contracts exist elsewhere, but the shipped BFF settlement path performs one
direct pending-to-confirmed transition.
6. Re-enter after settlement#
A fresh profile read sees the upgraded plan, and a fresh invoice fetch reports
confirmed. The paywall loader intentionally refuses non-pending invoices, so
reopening the URL shows the generic “could not find invoice” pending notice and
never leaks the stale payment address. There is no success receipt view or
“Continue to the gated surface” handoff.
Post-conditions#
- A provisioned invoice is snapshot-durable, purchaser-scoped in member list reads, and never persisted before a real receiving target exists.
- A valid signed settlement stores chain facts and receipt, confirms the invoice, and conditionally updates the purchaser's plan.
- Re-delivery is idempotent and a confirmed address is no longer rendered as payable.
- The end-user signup → issue → pay → success/return journey remains incomplete despite strong API/paywall/settlement coverage.
Failure modes#
- No invoice from
/billing— the primary customer link ends atPendingIssuanceNotice. - No payment runtime — quote returns
503 payments_not_configuredand methods are inactive. - Crisis frame active — quote returns
403 crisis_suppressedbefore showing a payment prompt. - Price/disclosure mismatch — quote fails closed before target issuance.
- Invalid settlement — status, tx id, receipt, and plan remain unchanged.
- Below-book settlement — money can confirm, but entitlement is withheld and the invoice is flagged for support.
- No live status UI — an already-open paywall does not notice settlement; only a new navigation changes what is rendered.
- Picker ambiguity — changing asset/network controls does not mutate the issued invoice.
E2E coverage#
apps/oshun/web/e2e/auth-entry-flows.spec.tscovers signup/auth redirect and onboarding entry separately.apps/oshun/web/e2e/billing-index.spec.tscovers protected billing entry, rail links, support, and responsive targets.apps/oshun/web/e2e/billing-crypto-paywall.spec.tsissues deterministic E2E quotes over real HTTP, drives BTC/USDC paywall and disclosure states, posts signed settlement, reads the plan/ledger/receipt boundary, rejects bad settlements, and proves no stale address after confirm.apps/oshun/web/e2e/entitlements-aaa-bff.spec.tsprotects an adjacent entitlement gate, not the missing invoice-creation UI.- Coverage depth: partial. The component seams are deep; provider watching, customer issuance, live status/success, and gated-surface return are absent.
Per-view files touched#
customer/00-public/welcome.md— real signup and onboarding handoff.customer/09-account/billing.md— rail entry and missing issuance join.customer/09-account/billing-crypto.md— pending notice, disclosure, address, and support.shell/04-auth-session.md— protected routes and post-settlement plan reads.
Cross-references#
aaa-upgrade-entitlement-bounce.md— adjacent tier gate/Studio handoff.onboarding-ten-steps.md— mandatory new-account continuation after signup.- Sources:
WelcomeAuthPanel.tsx,app/billing/page.tsx,app/billing/crypto/CryptoPaywall.tsx,routes/domain-stubs.ts, andpayments/settlement-route.tsunderapps/oshun.
Open questions#
- Which customer plan surface should create the quote, handle the issuance disclosure, and redirect to the pending invoice?
- Should the paywall poll/subscribe to status and expose the signed receipt plus a sanitized return target after confirmation?
- Should asset/network controls be removed for a fixed invoice or wired to an explicit cancel-and-requote flow?
- How should bridge-level confirmed/settled events compose with the direct BFF transition without double-granting entitlement?