A Stripe create that Stripe answered with a server error is retried for a day under a key Stripe will only answer with that error #312
Closed
opened 2026-10-31 01:01:36 +00:00 by cgalo5758
·
1 comment
Labels
Clear labels
accessibility
area/billing
area/catalog
area/discourse
area/domains
area/entitlements
area/fedwiki
area/identity
area/integrations
area/licensing
area/member-ui
area/meta
area/operator-ui
area/ops
area/testing
duplicate
good-first-issue
invalid
privacy
security
upstream
wontfix
A barrier for people using assistive technology or a keyboard alone.
The Stripe mirror, checkout, subscriptions, invoices, fulfillment.
Products, prices, plan ladders, purchasability.
The Discourse integration.
The domains registry, claims, placements, the certificate ask.
Entitlement sets, rules, grants, pools, provisioning.
The Federated Wiki integration and farm sync.
Sign-in, sessions, persons, organizations, workspaces, roles.
The provider registry, outbox and webhooks in general.
Licenses, the contributor agreement, SPDX headers.
Member pages.
The repository itself, its contributing guide, CI, the tracker and the workflow.
Operator pages, forms, lists, the design system.
Deployment, configuration, migrations, workflows, instance settings.
The test stack, screens, lint, walkthroughs.
Closed because another issue already covers it.
Small, self-contained, and explained enough to be a first contribution.
Closed because it is not a ticket for this repository.
Touches what a person's data reveals.
Touches authentication, authorization, secrets or data exposure.
Waits on another repository or project before it can move.
Closed because it will not be done, with the reason in the last comment.
kind
bug
The software does something other than what it promises; closed when it again does what it promises.
kind
debt
Code, tests or tooling to clean up with nothing visible changing; closed when they are cleaner.
kind
design
A question to settle before work can be defined; closed when the decision is written down.
kind
docs
Documentation that is wrong or missing; closed when it says the right thing.
kind
enhancement
Something the software does not do yet; closed when it does.
priority
critical
Blocks the active milestone or harms members now.
priority
high
Next in line inside the active milestone.
priority
low
Inside the active milestone, when nothing else is left.
priority
medium
Inside the active milestone, after the high ones.
Milestone
No items
No Milestone
Public launch
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: wiki-cafe/member-console#312
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What happens
The console sends some writes to Stripe through a queue, the outbox: a row written in the same database transaction as the change that needs it, executed in the background, retried for up to a day, and then set aside as failed ("dead-lettered") for an operator. Today it carries the product and price creates that Sync to Stripe starts on a product's page. Each create carries an idempotency key, a value that tells Stripe two requests are one, so a retry after a lost answer gets the first object back instead of a second one. The key is the same on every attempt.
Stripe saves its first answer to a key once it has started executing the request, a 500 included, and returns that answer to every later request with the key for at least 24 hours. The outbox treats a 500 like a network fault and retries it: about 150 attempts over the day, each answered with the same saved 500, after which the entry is dead-lettered. All that day the product's readiness panel says the price is syncing and "this clears once the sync lands", and the product cannot be bought. After the dead-letter, the panel offers Retry, which puts the same entry back in the queue, so it sends the same key and, while Stripe keeps the key, gets the same 500.
A 500 does not say whether Stripe created the object, so sending the create again under a new key could make a second product or price.
What should happen
A create that Stripe answers with its own 500 stops at once and is dead-lettered with a reason that says the outcome is unknown. Retry creates the object only after the console has found whether Stripe already made it; the console's create puts the product's or price's id in the object's metadata, so an object Stripe made is found and mapped, not created twice. A gateway's error page (502, 503 or 504 without a Stripe error body) is still retried, because Stripe saved nothing for it.
Where
internal/integrations/stripe/workflows/outbox.go(classifyStripeError,outboxIdempotencyKey),internal/integrations/stripe/catalogsync/catalogsync.go(the Retry that puts a dead-lettered entry back in the queue)Why it matters
After one server error at Stripe, a product cannot be sold for at least a day while the console repeats a request that cannot succeed, and the Retry offered to the operator repeats it again.
The outbox is the console's queue of Stripe actions: a row written in the same database transaction as the change that needs it, executed in the background, retried for up to a day, and then set aside as failed (dead-lettered) for an operator. An idempotency key is a value that tells Stripe two requests are one.
A product or price create that Stripe answers with its own server error (Stripe's error object with a status of 500 or above) is now set aside after that one attempt, with the reason "Stripe answered with a server error; whether it created the price is unknown", because Stripe answers every later request with the same idempotency key from the saved error, and a server error does not say whether the object was made. The product's readiness panel shows the failed sync at once instead of "syncing" for a day. A gateway's error page, which carries no Stripe error object, and a request that gets no answer are still retried.
Retry now counts each time it sends an entry back, and the entry's create then carries a new idempotency key. Before that create, the entry lists the products, or the prices of its product, that Stripe made since an hour before the entry was written, through Stripe's list, which shows what Stripe has written, rather than its search, which lags. It maps the oldest one whose metadata carries the console's product or price id, a price also with the same amount, currency and interval, and creates the object only when none does. An entry never sent back keeps the key it always had. Each create also ends with its attempt's two-minute deadline.
After a failed sync, the readiness panel's line ends at the reason, without "Use Retry to run it again."; the second line under the control, which repeated it, is gone; and the control, which read "Retry Stripe sync", now reads "Find or create in Stripe".
Commit:
16aad935