fix(membership): surface checkout errors as a flash banner (BUNYIP-187) #211

Merged
YousifShkara merged 1 commit from fix/BUNYIP-187-surface-checkout-errors into main 2026-06-24 09:59:55 +02:00
Owner

The Subscribe button on /membership was a silent failure surface:
bunyip-web/src/handlers/dashboard.rs::membership_subscribe matched the
api response with a _ => redirect_cookies("/membership", ...) arm that
swallowed every error - a 400 / 5xx / network drop landed the user back
on the page with NO indication of what went wrong. The api's actual
error (e.g. price_id: No active price configured, the silent-400
gotcha catalogued as gotcha 3 in BUNYIP-A-5) was being thrown away.

What landed:

  • membership_subscribe now captures the calls::checkout result. On
    Err(e) it redirects to /membership?error=<urlenc(e.user_message())>;
    on Ok with a non-Stripe URL it redirects with a "Checkout returned
    an invalid URL" message. Only the real https://checkout.stripe.com/
    URL still 302s straight to Stripe (unchanged happy path).

  • New humanise_checkout_error maps the two known raw messages from
    bunyip-api/src/handlers/membership.rs ("No active price configured",
    "Stripe is not configured") to operator-actionable copy that points
    at the configuration step. Unknown messages pass through verbatim so
    the operator still gets useful information instead of a sanitised
    wall.

  • New MembershipQuery { ok, error } plumbed into the membership GET
    handler (mirrors the existing SettingsQuery shape). The page
    renders the flash banner above the existing past_due warning using
    the same error_box + clamp_msg primitives the settings page
    already uses, so the visual treatment is consistent.

This is the user-visible symptom behind BUNYIP-170 ("Subscribe button
doesnt work in production"); closing this ticket lets BUNYIP-170 close
as a duplicate.

#BUNYIP-187

The Subscribe button on `/membership` was a silent failure surface: `bunyip-web/src/handlers/dashboard.rs::membership_subscribe` matched the api response with a `_ => redirect_cookies("/membership", ...)` arm that swallowed every error - a 400 / 5xx / network drop landed the user back on the page with NO indication of what went wrong. The api's actual error (e.g. `price_id: No active price configured`, the silent-400 gotcha catalogued as gotcha 3 in BUNYIP-A-5) was being thrown away. What landed: * `membership_subscribe` now captures the `calls::checkout` result. On `Err(e)` it redirects to `/membership?error=<urlenc(e.user_message())>`; on `Ok` with a non-Stripe URL it redirects with a "Checkout returned an invalid URL" message. Only the real `https://checkout.stripe.com/` URL still 302s straight to Stripe (unchanged happy path). * New `humanise_checkout_error` maps the two known raw messages from `bunyip-api/src/handlers/membership.rs` ("No active price configured", "Stripe is not configured") to operator-actionable copy that points at the configuration step. Unknown messages pass through verbatim so the operator still gets useful information instead of a sanitised wall. * New `MembershipQuery { ok, error }` plumbed into the `membership` GET handler (mirrors the existing `SettingsQuery` shape). The page renders the flash banner above the existing `past_due` warning using the same `error_box` + `clamp_msg` primitives the settings page already uses, so the visual treatment is consistent. This is the user-visible symptom behind BUNYIP-170 ("Subscribe button doesnt work in production"); closing this ticket lets BUNYIP-170 close as a duplicate. #BUNYIP-187
fix(membership): surface checkout errors as a flash banner (BUNYIP-187)
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 17s
Check / fmt + clippy + build + tests (pull_request) Successful in 2m43s
Create release / Create release from merged PR (pull_request) Has been skipped
46e90e6e16
The Subscribe button on `/membership` was a silent failure surface:
`bunyip-web/src/handlers/dashboard.rs::membership_subscribe` matched the
api response with a `_ => redirect_cookies("/membership", ...)` arm that
swallowed every error - a 400 / 5xx / network drop landed the user back
on the page with NO indication of what went wrong. The api's actual
error (e.g. `price_id: No active price configured`, the silent-400
gotcha catalogued as gotcha 3 in BUNYIP-A-5) was being thrown away.

What landed:

* `membership_subscribe` now captures the `calls::checkout` result. On
  `Err(e)` it redirects to `/membership?error=<urlenc(e.user_message())>`;
  on `Ok` with a non-Stripe URL it redirects with a "Checkout returned
  an invalid URL" message. Only the real `https://checkout.stripe.com/`
  URL still 302s straight to Stripe (unchanged happy path).

* New `humanise_checkout_error` maps the two known raw messages from
  `bunyip-api/src/handlers/membership.rs` ("No active price configured",
  "Stripe is not configured") to operator-actionable copy that points
  at the configuration step. Unknown messages pass through verbatim so
  the operator still gets useful information instead of a sanitised
  wall.

* New `MembershipQuery { ok, error }` plumbed into the `membership` GET
  handler (mirrors the existing `SettingsQuery` shape). The page
  renders the flash banner above the existing `past_due` warning using
  the same `error_box` + `clamp_msg` primitives the settings page
  already uses, so the visual treatment is consistent.

This is the user-visible symptom behind BUNYIP-170 ("Subscribe button
doesnt work in production"); closing this ticket lets BUNYIP-170 close
as a duplicate.

#BUNYIP-187
YousifShkara deleted branch fix/BUNYIP-187-surface-checkout-errors 2026-06-24 09:59:55 +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!211
No description provided.