Fix billing downgrade recharge and suspension race - #2397
Conversation
|
Follow-up audit completed against live head Feedback inventory:
The thermo-nuclear review did find one real retry-state defect in the live implementation. RCA: the definitive Stripe-rejection catch cleared a matching pending operation only when that operation was created by the current request. An identical resumed request had already proven ownership by matching plan + fingerprint, but a Fix in
Verification:
Broader local suite note: the full backend run reached an unrelated shared Elasticsearch limit ( Final GitHub CI for |
10a0595 to
494b772
Compare
494b772 to
fdbe4fc
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fdbe4fc37d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| _logger.LogInformation("Stripe subscription deleted. Customer: {CustomerId} Org: {Organization} Org Name: {OrganizationName}", sub.CustomerId, org.Id, org.Name); | ||
|
|
||
| if (org.BillingChangeDate > DateTime.MinValue && eventCreatedUtc < org.BillingChangeDate) |
There was a problem hiding this comment.
Compare deletions against a Stripe event watermark
When Stripe generates an earlier customer.subscription.updated event and a later deletion before either webhook is delivered, processing the update first sets BillingChangeDate to the current handler time; the deletion's creation time is then earlier than that value and this branch acknowledges but discards the genuinely newer deletion. Because Stripe does not guarantee immediate delivery, the organization can remain active or past due after its subscription was canceled. Track the latest Stripe source-event timestamp separately, or otherwise distinguish local plan transitions from webhook receipt times, and cover queued/out-of-order events.
AGENTS.md reference: AGENTS.md:L74-L74
Useful? React with 👍 / 👎.
Summary
Root cause
Paid-to-free changes canceled the active subscription with default options, so no prorated credit was finalized. A delayed deletion webhook could then overwrite billing state saved by the downgrade or a later upgrade.
Verification
dotnet build Exceptionless.slnx --no-restore -m:1— 0 warnings, 0 errorsOrganizationEndpointTests— 84/84 passedStripeEndpointTests— 6/6 passed, including locally HMAC-signed stale deletion events against both Trialing and Active stateBreaking changes
None.