feat(stripe): auto-bootstrap default product on first config save (BUNYIP-189) #212
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/bunyip!212
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/BUNYIP-189-auto-bootstrap-product"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
PMS-A-5 gotcha 3 catalogued the silent-400 trap: a fresh Stripe account
has no products with the
app=<STRIPE_APP_TAG>metadata tag, so thecheckout handler's
list_pricesreturns empty, the api 400s withprice_id: No active price configured, and (until BUNYIP-187) theSubscribe button silently fails. The documented workaround is an out-of-
band
stripe products create -d "metadata[app]=bunyip"+stripe prices create ...sequence, which is a poor onboarding experience on dev andon every new live deployment.
What landed:
New
StripeService::bootstrap_default_product_if_missing(). Idempotentby design: lists app-tagged active prices first and returns
Ok(None)when any exist. Otherwise creates a"{APP_NAME} Membership"product (uses the existing
create_productwhich auto-injects theapp=metadata tag) + a recurringmonth-interval$3 USDprice.Defaults live in env vars so an operator can override without
recompiling:
BUNYIP_DEFAULT_PRICE_AMOUNT_CENTS(default300)BUNYIP_DEFAULT_PRICE_CURRENCY(defaultusd)BUNYIP_DEFAULT_PRICE_INTERVAL(defaultmonth)APP_NAME(defaultBunyipfor the name)Returns
Ok(Some((product, price)))on a fresh bootstrap so thecaller can audit-log what was created.
update_stripe_config(PUT /v1/admin/stripe) calls the bootstrapafter the hot-reload that picks up the new secret key. The audit-log
metadata now carries a
bootstrap: { product_id, price_id }blockon the save that produced the auto-create, so an operator chasing
"where did this product come from" can trace it to the exact config
save. Bootstrap failure does NOT fail the config save (keys are
valid; admin can still create the product manually); the failure is
logged at warn.
Round trip on a fresh test-mode key: PUT /v1/admin/stripe with a real
sk_test_...-> bootstrap fires once, product + price land in Stripetagged with
app=bunyip-> Subscribe button now opens checkout.#BUNYIP-189
PMS-A-5 gotcha 3 catalogued the silent-400 trap: a fresh Stripe account has no products with the `app=<STRIPE_APP_TAG>` metadata tag, so the checkout handler's `list_prices` returns empty, the api 400s with `price_id: No active price configured`, and (until BUNYIP-187) the Subscribe button silently fails. The documented workaround is an out-of- band `stripe products create -d "metadata[app]=bunyip"` + `stripe prices create ...` sequence, which is a poor onboarding experience on dev and on every new live deployment. What landed: * New `StripeService::bootstrap_default_product_if_missing()`. Idempotent by design: lists app-tagged active prices first and returns `Ok(None)` when any exist. Otherwise creates a `"{APP_NAME} Membership"` product (uses the existing `create_product` which auto-injects the `app=` metadata tag) + a recurring `month`-interval `$3 USD` price. Defaults live in env vars so an operator can override without recompiling: - `BUNYIP_DEFAULT_PRICE_AMOUNT_CENTS` (default `300`) - `BUNYIP_DEFAULT_PRICE_CURRENCY` (default `usd`) - `BUNYIP_DEFAULT_PRICE_INTERVAL` (default `month`) - `APP_NAME` (default `Bunyip` for the name) Returns `Ok(Some((product, price)))` on a fresh bootstrap so the caller can audit-log what was created. * `update_stripe_config` (PUT /v1/admin/stripe) calls the bootstrap after the hot-reload that picks up the new secret key. The audit-log metadata now carries a `bootstrap: { product_id, price_id }` block on the save that produced the auto-create, so an operator chasing "where did this product come from" can trace it to the exact config save. Bootstrap failure does NOT fail the config save (keys are valid; admin can still create the product manually); the failure is logged at warn. Round trip on a fresh test-mode key: PUT /v1/admin/stripe with a real `sk_test_...` -> bootstrap fires once, product + price land in Stripe tagged with `app=bunyip` -> Subscribe button now opens checkout. #BUNYIP-189