Phase 2 — Stripe sandbox integration (Checkout Sessions + webhook + OSIS persistence) #3

Open
opened 2026-05-20 19:32:12 +00:00 by mik-tf · 0 comments
Owner

Phase 2 sub-issue tracked under #1. Continues from #2 Phase 1.5 (mycelium proof-of-control login landed in 1b05225).

Scope

  1. Stripe Checkout Session creationPOST /payment/intent?amount=... replaces the stub POST /payment. Creates a sandbox-mode Checkout Session via the stripe (rust-stripe / async-stripe) crate, returns 303 redirect to Stripe-hosted checkout URL. Success URL: /payment/return?session_id={CHECKOUT_SESSION_ID}.
  2. Webhook handlerPOST /webhooks/stripe. Verifies Stripe-Signature HMAC-SHA256 over {timestamp}.{payload} with constant-time compare + tolerance window (delegate to stripe::Webhook::construct_event if the crate exposes it; fall back to manual HMAC otherwise). On checkout.session.completed: dedupe on payment_intent.id against PaymentEvent.external_ref (indexed). If new, append PaymentEvent + increment Billing.credit_balance.
  3. Persistence migrationAppState.users: Arc<Mutex<HashMap<String, UserCredit>>>Arc<OsisOnboarding> (from crates/hero_onboarding_schema/src/onboarding/osis_server_generated.rs:218). Confirms Billing-by-mycelium-address lookup path; likely requires synthetic User row keyed by address → sidBilling row.
  4. Boot-time secrets read — Stripe sandbox keys (STRIPE_SANDBOX_PUBLISHABLE_KEY, STRIPE_SANDBOX_SECRET_KEY, STRIPE_WEBHOOK_SIGNING_KEY) loaded from hero_proc secret store via hero_proc_sdk::HeroProcRPCAPIClient::secret_get. Env-var fallback for dev (per hero_proc_secrets_and_meta skill).
  5. Real --start / --stop — Replaces the s2-002 stub that exits 2 with a redirect to lab service hero_onboarding_server --start. Uses hero_proc_sdk::ActionBuilder + ServiceBuilder for the server + admin daemons.
  6. Provider-trait shape — keep the SDK call site behind a PaymentProvider trait (Stripe + ClickPesa + stub) so Phase 3 ClickPesa lands as a trait impl, not a server-wide refactor. Trait shape decision becomes D-13 if it stabilises mid-session.

Acceptance gate

  • lab build --release --install clean across all 4 crates (CLI + schema + server + admin).
  • lab infocheck green.
  • scripts/smoke_stripe_sandbox.sh exercises:
    • Full Stripe Checkout round-trip with test card 4242 4242 4242 4242 → balance increments
    • Duplicate webhook → balance does NOT increment (idempotency)
    • Signature-tampered webhook → 401
    • Server restart preserves balance (OSIS persistence proof)

Reused / lifted

  • Wire contract: server-creates-then-redirects pattern (the Hero way; Stripe-recommended for first integrations).
  • Idempotency contract: dedupe on Stripe payment_intent.id via the PaymentEvent.external_ref index baked into the s2-002 oschema.
  • In-house implementations are NOT being lifted verbatim: the adjacent freezone_work/znzfreezone_code/ Stripe surface is Yew (frontend) + Rhai (archived, read-only); the Rust SDK call site doesn't exist in that codebase. Phase 2 writes the canonical Rust Stripe SDK call site fresh against the stripe-rust crate + Stripe docs, with stripe::Webhook for signature verification.

Out of scope (Phase 3+)

  • ClickPesa integration (Phase 3).
  • Real production Stripe (live) keys + KYC.
  • UDS + hero_router proxy wiring for the server.
  • Per-node forge billing-record push.
  • Admin surface real user lookup.
Phase 2 sub-issue tracked under [#1](https://forge.ourworld.tf/lhumina_code/hero_onboarding/issues/1). Continues from [#2](https://forge.ourworld.tf/lhumina_code/hero_onboarding/issues/2) Phase 1.5 (mycelium proof-of-control login landed in [`1b05225`](https://forge.ourworld.tf/lhumina_code/hero_onboarding/commit/1b05225)). ## Scope 1. **Stripe Checkout Session creation** — `POST /payment/intent?amount=...` replaces the stub `POST /payment`. Creates a sandbox-mode Checkout Session via the `stripe` (rust-stripe / async-stripe) crate, returns 303 redirect to Stripe-hosted checkout URL. Success URL: `/payment/return?session_id={CHECKOUT_SESSION_ID}`. 2. **Webhook handler** — `POST /webhooks/stripe`. Verifies `Stripe-Signature` HMAC-SHA256 over `{timestamp}.{payload}` with constant-time compare + tolerance window (delegate to `stripe::Webhook::construct_event` if the crate exposes it; fall back to manual HMAC otherwise). On `checkout.session.completed`: dedupe on `payment_intent.id` against `PaymentEvent.external_ref` (indexed). If new, append `PaymentEvent` + increment `Billing.credit_balance`. 3. **Persistence migration** — `AppState.users: Arc<Mutex<HashMap<String, UserCredit>>>` → `Arc<OsisOnboarding>` (from `crates/hero_onboarding_schema/src/onboarding/osis_server_generated.rs:218`). Confirms Billing-by-mycelium-address lookup path; likely requires synthetic `User` row keyed by address → `sid` → `Billing` row. 4. **Boot-time secrets read** — Stripe sandbox keys (`STRIPE_SANDBOX_PUBLISHABLE_KEY`, `STRIPE_SANDBOX_SECRET_KEY`, `STRIPE_WEBHOOK_SIGNING_KEY`) loaded from `hero_proc` secret store via `hero_proc_sdk::HeroProcRPCAPIClient::secret_get`. Env-var fallback for dev (per hero_proc_secrets_and_meta skill). 5. **Real `--start` / `--stop`** — Replaces the s2-002 stub that exits 2 with a redirect to `lab service hero_onboarding_server --start`. Uses `hero_proc_sdk::ActionBuilder` + `ServiceBuilder` for the server + admin daemons. 6. **Provider-trait shape** — keep the SDK call site behind a `PaymentProvider` trait (Stripe + ClickPesa + stub) so Phase 3 ClickPesa lands as a trait impl, not a server-wide refactor. Trait shape decision becomes D-13 if it stabilises mid-session. ## Acceptance gate - `lab build --release --install` clean across all 4 crates (CLI + schema + server + admin). - `lab infocheck` green. - `scripts/smoke_stripe_sandbox.sh` exercises: - Full Stripe Checkout round-trip with test card 4242 4242 4242 4242 → balance increments - Duplicate webhook → balance does NOT increment (idempotency) - Signature-tampered webhook → 401 - Server restart preserves balance (OSIS persistence proof) ## Reused / lifted - **Wire contract**: server-creates-then-redirects pattern (the Hero way; Stripe-recommended for first integrations). - **Idempotency contract**: dedupe on Stripe `payment_intent.id` via the `PaymentEvent.external_ref` index baked into the s2-002 oschema. - **In-house implementations are NOT being lifted verbatim**: the adjacent `freezone_work/znzfreezone_code/` Stripe surface is Yew (frontend) + Rhai (archived, read-only); the Rust SDK call site doesn't exist in that codebase. Phase 2 writes the canonical Rust Stripe SDK call site fresh against the `stripe-rust` crate + Stripe docs, with `stripe::Webhook` for signature verification. ## Out of scope (Phase 3+) - ClickPesa integration (Phase 3). - Real production Stripe (live) keys + KYC. - UDS + hero_router proxy wiring for the server. - Per-node forge billing-record push. - Admin surface real user lookup.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_onboarding#3
No description provided.