test(e2e): un-fixme subscribe + billing-portal behind a staging-Stripe gate (BUNYIP-151) #227

Merged
nrupard merged 1 commit from feat/bunyip-151-billing-specs-stripe-gate into main 2026-06-26 00:01:12 +02:00
Owner

What

Un-fixme the two deterministic billing specs (subscribe, billing-portal) behind a staging-Stripe gate, and document why cancel is deferred. Part of BUNYIP-151.

Gate (mirrors the mail-sink pattern)

subscribe + billing-portal now test.skip(!env.stripeSecretKey, ...): they run when the operator provisions E2E_STAGING_STRIPE_SECRET_KEY (set alongside staging Stripe test mode) and skip otherwise, so this is safe to land before staging Stripe exists. The production-apex skip stays as the hard backstop against a real charge.

  • subscribe: POST /membership/subscribe -> assert the 30x redirect targets checkout.stripe.com.
  • billing-portal: read-only GET /checkout/success -> assert it does not error.

cancel deferred (with reasons)

Investigation found cancel is webhook-dependent: /membership/cancel early-returns "No active membership to cancel" unless membership_status is active (bunyip-api/src/handlers/membership.rs), which only flips active via the customer.subscription.created webhook; and the account's stripe_customer_id is not exposed in /me. So a real cancel test needs a setup step that creates a Stripe test-mode subscription on the account's customer (found via the Stripe API by email) and waits for the webhook. That can't be validated until staging Stripe + the webhook are live, so it's deferred rather than shipped blind. The spec comment + dev-docs record exactly what it needs.

Verification

tsc --noEmit clean; subscribe/billing-portal collect as real tests, cancel stays fixme. e2e-only, no app change.

Operator handoff (cannot be done from the agent)

  1. In a Stripe TEST-mode account, create the membership product + a recurring price; note the price id.
  2. Set STRIPE_SECRET_KEY (sk_test_...), STRIPE_WEBHOOK_SECRET (whsec_...), and the price id on staging bunyip-api (admin UI /admin/stripe, or env in the SOPS compose-secrets.yml). Point a Stripe test-mode webhook at the staging webhook route using that whsec_.
  3. Record the sk_test_... as the Forgejo secret E2E_STAGING_STRIPE_SECRET_KEY.

Then subscribe/billing-portal activate on the next staging e2e run.

🤖 Generated with Claude Code

## What Un-fixme the two deterministic billing specs (`subscribe`, `billing-portal`) behind a staging-Stripe gate, and document why `cancel` is deferred. Part of BUNYIP-151. ## Gate (mirrors the mail-sink pattern) `subscribe` + `billing-portal` now `test.skip(!env.stripeSecretKey, ...)`: they run when the operator provisions `E2E_STAGING_STRIPE_SECRET_KEY` (set alongside staging Stripe test mode) and skip otherwise, so this is safe to land before staging Stripe exists. The production-apex skip stays as the hard backstop against a real charge. - `subscribe`: `POST /membership/subscribe` -> assert the 30x redirect targets `checkout.stripe.com`. - `billing-portal`: read-only `GET /checkout/success` -> assert it does not error. ## cancel deferred (with reasons) Investigation found `cancel` is webhook-dependent: `/membership/cancel` early-returns `"No active membership to cancel"` unless `membership_status` is active (`bunyip-api/src/handlers/membership.rs`), which only flips active via the `customer.subscription.created` webhook; and the account's `stripe_customer_id` is not exposed in `/me`. So a real cancel test needs a setup step that creates a Stripe test-mode subscription on the account's customer (found via the Stripe API by email) and waits for the webhook. That can't be validated until staging Stripe + the webhook are live, so it's deferred rather than shipped blind. The spec comment + dev-docs record exactly what it needs. ## Verification `tsc --noEmit` clean; `subscribe`/`billing-portal` collect as real tests, `cancel` stays fixme. e2e-only, no app change. ## Operator handoff (cannot be done from the agent) 1. In a Stripe TEST-mode account, create the membership product + a recurring price; note the price id. 2. Set `STRIPE_SECRET_KEY` (`sk_test_...`), `STRIPE_WEBHOOK_SECRET` (`whsec_...`), and the price id on staging bunyip-api (admin UI `/admin/stripe`, or env in the SOPS `compose-secrets.yml`). Point a Stripe test-mode webhook at the staging webhook route using that `whsec_`. 3. Record the `sk_test_...` as the Forgejo secret `E2E_STAGING_STRIPE_SECRET_KEY`. Then `subscribe`/`billing-portal` activate on the next staging e2e run. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
test(e2e): un-fixme subscribe + billing-portal behind a staging-Stripe gate (BUNYIP-151)
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 1m29s
Check / fmt + clippy + build + tests (pull_request) Successful in 2m18s
Create release / Create release from merged PR (pull_request) Has been skipped
83dece687c
Un-fixme the two deterministic billing specs and gate them on E2E_STRIPE_SECRET_KEY, mirroring the mail-sink pattern: they run when the operator provisions staging Stripe test mode (recorded as E2E_STAGING_STRIPE_SECRET_KEY) and skip otherwise, so this is safe to land before staging Stripe exists. The production-apex skip stays as the hard backstop against a real charge.

- subscribe: POST /membership/subscribe -> assert the 30x redirect targets checkout.stripe.com.
- billing-portal: read-only GET /checkout/success -> assert it does not error.

cancel stays test.fixme, deferred: investigation showed /membership/cancel early-returns "No active membership to cancel" unless membership_status is active (bunyip-api/src/handlers/membership.rs), which only flips active via the customer.subscription.created webhook, and the account's stripe_customer_id is not exposed in /me. So a real cancel test needs a setup step that creates a Stripe test-mode subscription on the account's customer (found via the Stripe API by email) and waits for the webhook. That is webhook-dependent and cannot be validated until staging Stripe + the webhook are live, so it is deferred rather than shipped blind. The spec comment + dev-docs record exactly what it needs.

Docs updated (README status + env table + blocked-specs, .env.example, dev-docs Stripe provisioning steps). tsc --noEmit clean; e2e-only, no app change.

Operator handoff (cannot be done from the agent): create a Stripe TEST-mode product + recurring price; set sk_test_/whsec_ + the price id on staging bunyip-api (admin UI or env); point a test-mode webhook at the staging webhook route; record sk_test_ as the Forgejo secret E2E_STAGING_STRIPE_SECRET_KEY.

#BUNYIP-151

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/bunyip-151-billing-specs-stripe-gate 2026-06-26 00:01:12 +02:00
Sign in to join this conversation.
No reviewers
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
psa-systems/bunyip!227
No description provided.