fix(stripe): name the missing permission, stop faking an empty list #514
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/BUNYIP-516-stripe-permission-errors-visible"
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?
Creating a webhook endpoint from
/admin/stripefailed with "An unexpected error occurred. Please try again later." and the endpoint list underneath reported "No webhook endpoints yet." Both statements were wrong in the same way: Stripe had answered 403more_permissions_requiredbecause the restricted key lacked the Webhook Endpoints permission, and every layer discarded that fact on the way up. The setup docs on that same page asked for five permissions and not this one, so an admin who followed the instructions exactly could not resolve the incident from anything the screen said.Bumps
dunite-stripe(anddunite-stripe-core, byte-identical at both revs, so the graph keeps one copy) to the merge of DUNITE-10, which addsStripeServiceError::Stripe { message, details }carrying the HTTP status and Stripe'serror.code/error.type, and makeslist_webhook_endpointscheck the HTTP status instead of parsing a 403 body intoOk(vec![]). The member-scopedcargo updatemoved exactly those two packages. DUNITE-9 rides along in the same rev and addedrecurring_interval_counttoStripePriceResponse, so the pricing test fixture gains that field.stripe_errmaps the new variant by Stripe error code.more_permissions_required(and any 403) and a rejected key (401,api_key_expired,invalid_api_key) becomeAppError::validation, a 400; every other code keeps the 500. The 4xx is the whole point rather than a detail:ApiError::user_messagecollapses every 5xx to a generic line by design (BUNYIP-477), so a perfect message on a 500 could never have reached the browser. That rule is not loosened, and the only user-facing strings are the bunyip-authored ones built from the newStripePermissionenum. Stripe's ownerror.messageis not carried byStripeErrorDetailsat all, so nothing from Stripe is rendered or logged (BUNYIP-265).stripe_err_for(permission)lets a call site name the permission it needs; all ten Stripe calls inadmin_stripe.rsuse it, and an unattributed failure says so instead of guessing a permission.The three Stripe-backed blocks now take
Resultrather thanOption, so "could not read this list" and "this list is empty" are different renderings: the failure says what bunyip does not know, names the permission that usually explains it, and carries the api request id. The false "No webhook endpoints yet" is the more dangerous half of the bug, because it reads as information rather than as a failure. The Prices form's product picker says why it is empty when the products could not be read, instead of looking like an account with no products.A failed create now redirects back with the reason, the submitted URL and the submitted event text, and the block restates the failure next to the button that produced it. A toast was the wrong surface: it faded after 2.5 seconds and the form came back blank, so retrying meant retyping everything.
The request id shown is bunyip-api's
X-Request-Id, not the error envelope'smeta.request_id: dunite-core mints a fresh id while rendering an error body, so that value correlates with no log line anywhere.ClientIpRootSpanBuildernow records the same id the middleware echoes in that header, so the reference an admin quotes is therequest_idon the api log lines.#BUNYIP-516
Creating a webhook endpoint from `/admin/stripe` failed with "An unexpected error occurred. Please try again later." and the endpoint list underneath reported "No webhook endpoints yet." Both statements were wrong in the same way: Stripe had answered 403 `more_permissions_required` because the restricted key lacked the Webhook Endpoints permission, and every layer discarded that fact on the way up. The setup docs on that same page asked for five permissions and not this one, so an admin who followed the instructions exactly could not resolve the incident from anything the screen said. Bumps `dunite-stripe` (and `dunite-stripe-core`, byte-identical at both revs, so the graph keeps one copy) to the merge of DUNITE-10, which adds `StripeServiceError::Stripe { message, details }` carrying the HTTP status and Stripe's `error.code` / `error.type`, and makes `list_webhook_endpoints` check the HTTP status instead of parsing a 403 body into `Ok(vec![])`. The member-scoped `cargo update` moved exactly those two packages. DUNITE-9 rides along in the same rev and added `recurring_interval_count` to `StripePriceResponse`, so the pricing test fixture gains that field. `stripe_err` maps the new variant by Stripe error code. `more_permissions_required` (and any 403) and a rejected key (401, `api_key_expired`, `invalid_api_key`) become `AppError::validation`, a 400; every other code keeps the 500. The 4xx is the whole point rather than a detail: `ApiError::user_message` collapses every 5xx to a generic line by design (BUNYIP-477), so a perfect message on a 500 could never have reached the browser. That rule is not loosened, and the only user-facing strings are the bunyip-authored ones built from the new `StripePermission` enum. Stripe's own `error.message` is not carried by `StripeErrorDetails` at all, so nothing from Stripe is rendered or logged (BUNYIP-265). `stripe_err_for(permission)` lets a call site name the permission it needs; all ten Stripe calls in `admin_stripe.rs` use it, and an unattributed failure says so instead of guessing a permission. The three Stripe-backed blocks now take `Result` rather than `Option`, so "could not read this list" and "this list is empty" are different renderings: the failure says what bunyip does not know, names the permission that usually explains it, and carries the api request id. The false "No webhook endpoints yet" is the more dangerous half of the bug, because it reads as information rather than as a failure. The Prices form's product picker says why it is empty when the products could not be read, instead of looking like an account with no products. A failed create now redirects back with the reason, the submitted URL and the submitted event text, and the block restates the failure next to the button that produced it. A toast was the wrong surface: it faded after 2.5 seconds and the form came back blank, so retrying meant retyping everything. The request id shown is bunyip-api's `X-Request-Id`, not the error envelope's `meta.request_id`: dunite-core mints a fresh id while rendering an error body, so that value correlates with no log line anywhere. `ClientIpRootSpanBuilder` now records the same id the middleware echoes in that header, so the reference an admin quotes is the `request_id` on the api log lines. #BUNYIP-5165348812b7429b98e487c29b98e487c1c8faf55a0