Do not key ephemeral per-attempt Stripe creates

FixBillingService
Shipped
August 26, 2026 at 2:54 AM UTC
Author
Kamo
Commit
674fa0f

Round 2 review (Ruling 17) found "is this a create?" is necessary but not sufficient. The question that matters: can this subject legitimately be created more than once inside the key's 24h window, for a reason other than a network retry? Two creates answer yes and were wrongly keyed in round 1: - SetupIntent.create (AccountPaymentMethodService, exposed via createSetupIntent on two controllers, returns the client secret straight to the browser): a stable key meant a second "add a card" attempt the same day replayed the FIRST attempt's already-consumed client secret, silently failing the add. - Session.create (ConsumerCheckoutService): a stable key meant an abandon-and-retry, or a buy-then-upgrade the same plan the same day, replayed the ORIGINAL Checkout Session -- possibly already complete or expired -- handing the customer a dead link. Both are ephemeral, single-use, per-attempt objects, not standing ones. A duplicate there is inert (the unused one just expires); a stale replay is the actual harm, which is the opposite trade from every durable create (Customer, Subscription, Price, Product, Meter), where a duplicate IS the harm this whole task exists to prevent. Removed the key from both; final state is 8 keyed (all durable creates, unchanged), 12 unkeyed (2 ephemeral creates + 10 mutations from round 1). StripeIdempotency's class javadoc now states all three classes -- durable creates keyed, ephemeral creates unkeyed, mutations unkeyed -- with a concrete failure trace for each unkeyed class (the **************** one from round 1, and the second-card-same-day stale-client-secret one for SetupIntent, which is the more surprising of the two). The coverage test now has one regex-based forward net per class (durable creates must carry a key; ephemeral creates and mutations must not) plus a per-file exact count of StripeIdempotency.forKey( pinned to the 8 durable creates, which is what actually catches a key added to any of the local-variable- receiver blind spots. Verified by temporarily reintroducing a key on SetupIntent.create, confirming both the new regex check and the exact-count check fail, then reverting the probe.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing