feat(admin): move Stripe checkout knobs into DB admin settings (BUNYIP-351) #352

Merged
longjacksonle merged 2 commits from feat/BUNYIP-351-stripe-checkout-db-config into main 2026-07-08 20:42:52 +02:00

What

Phase 3 (final) of BUNYIP-351: move the non-secret Stripe checkout knobs (STRIPE_SUCCESS_URL, STRIPE_CANCEL_URL, BUNYIP_BILLING_TRIAL_PERIOD_DAYS) into the existing stripe_config singleton so an admin can tune them from the Stripe settings page without a redeploy.

Previously these were env-only and StripeConfig::from_db_model always took them from env even when the secrets came from the DB. Now they are nullable success_url / cancel_url / trial_period_days columns; NULL falls back to the env default at load time, matching the DB-overrides-env pattern the Stripe secrets already use.

How

  • Additive migration adding the three nullable columns to stripe_config.
  • The env-default derivation (first CORS_ORIGIN origin for the URLs, 30-day trial fallback) is extracted into shared success_url_from_env / cancel_url_from_env / trial_period_days_from_env helpers, so the runtime StripeConfig and the admin read model (StripeConfigResponse) resolve the fallback through one source and cannot diverge.
  • StripeConfig::from_db_model reads the three columns (DB-over-env). The admin GET/PUT /v1/admin/stripe surface + hot-reload already carry the resolved config through, so a change applies without a restart.
  • Repository update gains the three args (COALESCE = "no change" on NULL); the two other callers (key-rotation re-encrypt, webhook secret auto-save) pass None.
  • Admin request gains success_url / cancel_url / trial_period_days with validation (trial 0-365); bunyip-web Stripe form gains the inputs + a "Checkout" section.

Not moved

free_price_id is intentionally left out: it is already admin-configurable via tier_config (the Tier Settings page). Adding it to stripe_config would create a third source for the same value.

Tests

fmt --check, clippy -D warnings, and the full test --workspace suite are green in the pinned container. A new unit test covers from_db_model resolving DB-over-env for the knobs (DB value wins, NULL falls back to env); the existing Stripe model tests were updated for the new row fields.

Scope

Closes the BUNYIP-351 series: phase 1 (auto-ban) merged, phase 2 (email/SMTP) is #351, this is phase 3. Stripe secrets and tier config were already DB-backed before the series.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CTpxCd1SvpE8sk82aGikkz

## What Phase 3 (final) of BUNYIP-351: move the non-secret Stripe checkout knobs (`STRIPE_SUCCESS_URL`, `STRIPE_CANCEL_URL`, `BUNYIP_BILLING_TRIAL_PERIOD_DAYS`) into the existing `stripe_config` singleton so an admin can tune them from the Stripe settings page without a redeploy. Previously these were env-only and `StripeConfig::from_db_model` always took them from env even when the secrets came from the DB. Now they are nullable `success_url` / `cancel_url` / `trial_period_days` columns; NULL falls back to the env default at load time, matching the DB-overrides-env pattern the Stripe secrets already use. ## How - Additive migration adding the three nullable columns to `stripe_config`. - The env-default derivation (first `CORS_ORIGIN` origin for the URLs, 30-day trial fallback) is extracted into shared `success_url_from_env` / `cancel_url_from_env` / `trial_period_days_from_env` helpers, so the runtime `StripeConfig` and the admin read model (`StripeConfigResponse`) resolve the fallback through one source and cannot diverge. - `StripeConfig::from_db_model` reads the three columns (DB-over-env). The admin `GET`/`PUT /v1/admin/stripe` surface + hot-reload already carry the resolved config through, so a change applies without a restart. - Repository `update` gains the three args (COALESCE = "no change" on NULL); the two other callers (key-rotation re-encrypt, webhook secret auto-save) pass `None`. - Admin request gains `success_url` / `cancel_url` / `trial_period_days` with validation (trial 0-365); bunyip-web Stripe form gains the inputs + a "Checkout" section. ## Not moved `free_price_id` is intentionally left out: it is already admin-configurable via `tier_config` (the Tier Settings page). Adding it to `stripe_config` would create a third source for the same value. ## Tests `fmt --check`, `clippy -D warnings`, and the full `test --workspace` suite are green in the pinned container. A new unit test covers `from_db_model` resolving DB-over-env for the knobs (DB value wins, NULL falls back to env); the existing Stripe model tests were updated for the new row fields. ## Scope Closes the BUNYIP-351 series: phase 1 (auto-ban) merged, phase 2 (email/SMTP) is #351, this is phase 3. Stripe secrets and tier config were already DB-backed before the series. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01CTpxCd1SvpE8sk82aGikkz
feat(admin): move Stripe checkout knobs into DB admin settings (BUNYIP-351)
Some checks failed
E2E / Playwright against deployment (pull_request) Successful in 37s
Check / fmt + clippy + build + tests (pull_request) Has been cancelled
9d5b6cdb3d
The non-secret Stripe checkout knobs (STRIPE_SUCCESS_URL, STRIPE_CANCEL_URL, BUNYIP_BILLING_TRIAL_PERIOD_DAYS) were env-only and always taken from env even when the rest of the Stripe config came from the DB. This adds them to the existing stripe_config singleton (nullable success_url / cancel_url / trial_period_days columns) so an admin can tune them from the Stripe settings page without a redeploy; NULL falls back to the env default at load time, matching the DB-overrides-env pattern the secrets already use.

The env-default derivation (first CORS_ORIGIN origin for the URLs, 30-day trial fallback) is extracted into shared success_url_from_env / cancel_url_from_env / trial_period_days_from_env helpers so the runtime StripeConfig and the admin read model resolve the fallback through one source and cannot diverge. StripeConfig::from_db_model now reads the three columns; the admin GET/PUT surface + hot-reload already carry them through.

free_price_id is intentionally NOT moved here: it is already admin-configurable via tier_config (the Tier Settings page), and adding it to stripe_config would create a third source for the same value.

Adds the additive migration, the model + response fields, the repository update args, the admin request fields + validation (trial 0-365), and the bunyip-web Stripe form inputs. A unit test covers from_db_model resolving DB-over-env for the knobs.

Phase 3 (final) of BUNYIP-351. Auto-ban (phase 1) and email/SMTP (phase 2) landed separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTpxCd1SvpE8sk82aGikkz
Merge branch 'main' into feat/BUNYIP-351-stripe-checkout-db-config
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 24s
Check / fmt + clippy + build + tests (pull_request) Successful in 20m29s
Create release / Create release from merged PR (pull_request) Has been skipped
6c735c980f
longjacksonle scheduled this pull request to auto merge when all checks succeed 2026-07-08 20:22:27 +02:00
longjacksonle deleted branch feat/BUNYIP-351-stripe-checkout-db-config 2026-07-08 20:42:52 +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!352
No description provided.