feat(deleter): email the org owners when a delete forfeits tokens - #1880
feat(deleter): email the org owners when a delete forfeits tokens#1880whoAbhishekSah wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Coverage Report for CI Build 32462394554Coverage increased (+0.09%) to 49.004%Details
Uncovered Changes
Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
81dcec9 to
2da80b2
Compare
2da80b2 to
4b0e063
Compare
4b0e063 to
8a25833
Compare
8a25833 to
a4245d6
Compare
a4245d6 to
539a84a
Compare
539a84a to
875a56e
Compare
875a56e to
680e712
Compare
680e712 to
de13f71
Compare
de13f71 to
b99483b
Compare
rohilsurana
left a comment
There was a problem hiding this comment.
Stack-aware review (base org-delete-preflight-blockers). The retry issue in the first comment is the one worth fixing; the rest are smaller robustness and cleanup notes. One efficiency note that spans functions: customerService.List(OrgID) now runs three times per delete (ensureDeletable, collectForfeitNotice, deleteCustomers) and GetBalance runs twice per account, where one list and one balance read per account would do.
b99483b to
ad25f29
Compare
ad25f29 to
5ac8345
Compare
|
On the review-body efficiency note: fixed across the stack. DeleteOrganization now lists the billing accounts once and passes them down (blocker check, notice collection, teardown), the balances are read once in the notice collection and reused by the blocker check and the forfeit audit, and the plan lookups share one per-delete cache between the blocker pass and the cancel pass. The exported DeleteCustomers still reads for itself when called directly. |
rohilsurana
left a comment
There was a problem hiding this comment.
Re-review after the fix commits. The retry recovery, the non-fatal owner lookup, the no-owner warn, parse-once, and the rename all look good. The new recoverForfeitFromAudit has a couple of reconciliation gaps worth a look, and one owner case is still uncovered.
5ac8345 to
8534ce3
Compare
8534ce3 to
d50ff18
Compare
Deleting an organization forfeits any unused tokens. The owners now get an email saying how many tokens were left, how many of those came from purchases (complimentary grants count as spent first, refunded purchases are taken back out), and that support can transfer the purchased amount. The subject and body come from the billing config (billing.token_forfeit_notice.subject/body) as Go templates with .Amount, .Purchased, .Org, .User, and .DeletedBy, falling back to plain built-in text — the same pattern the PAT expiry alerts use. The notice is collected before teardown (balances, purchased shares, and owners are gone after) and sent only after the delete fully succeeded; a retry that lost the balances mid-teardown recovers the amounts from the audit records the earlier attempt wrote. The owner lookup is best-effort so the delete never depends on the policy machinery, a forfeit with no reachable owner logs a warning, and the per-account numbers thread into the teardown so the forfeit audit records (amount plus purchased) match what the pre-check read without reading balances twice. Send and render failures are logged per owner and never fail the delete. The org lookup at the start switched from Get to GetRaw so a disabled org keeps its title in the email and stays deletable without a special case.
d50ff18 to
81d4d9d
Compare
Part of #1837. Stacked on #1857; the check RPC follows in #1893/#1894.
Deleting an organization forfeits any unused tokens (the client confirms that with the user before calling). This PR adds the second half of that flow: the org owners get an email saying how many tokens were left and that support can transfer the purchased amount to their bank account.
How it works:
billing.token_forfeit_notice.subjectandbilling.token_forfeit_notice.body. Both are Go templates with.Amount(tokens forfeited),.Purchased(the share of it that was bought),.Org(the deleted organization),.User(the owner receiving the mail), and.DeletedBy(who ran the delete, when known). Empty config falls back to plain built-in text. This is the same pattern the PAT expiry alert emails use.billing_transactionswith a source (system.buyfor purchases,system.starterfor plan starter grants,system.awardedfor awards). Spending is not attributed to a source, so complimentary tokens are counted as spent first: the purchased share is the smaller of the balance and everything ever bought. Accounts holding starter or awarded grants get the split; when everything was purchased the share equals the whole amount and the email shows one number.app.billing.tokens.forfeited) now carriespurchasednext toamount— the transaction rows are deleted with the org, so the audit row is what support settles from later.ListOrganizationUsersuses) and every owner gets the mail, through the sharedapp.mailerdialer.not_foundfirst, so nobody gets the mail twice. A retry that lost the balances mid-teardown recovers the amounts from the forfeit audit records the earlier attempt wrote (needs thedbaudit sink). Send and render failures are logged per owner and never fail the API call.Side change: the org lookup at the start of the delete switched from
GettoGetRaw, so a disabled org keeps its title for the email and stays deletable without theErrDisabledspecial case.🤖 Generated with Claude Code