Skip to content

feat(store): billing account delete hides the account's token transactions with the row - #1999

Merged
rohilsurana merged 10 commits into
mainfrom
feat/billing-account-delete-hides-transactions
Oct 22, 2026
Merged

rohilsurana merged 10 commits into
mainfrom
feat/billing-account-delete-hides-transactions

Conversation

@rohilsurana

@rohilsurana rohilsurana commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Summary

Deleting a billing account now hides the account's token transactions and records what was forfeited on the account's billing_customer.deleted audit record.

An account that owes tokens cannot be deleted. The API answers with failed precondition.

Changes

  • The delete in the repository hides the account's live token transactions. It writes the forfeited amount and the purchased share into the audit record metadata, in the same transaction.
  • The delete is rolled back if the hidden transactions add up to a debt.
  • The service refuses the delete when the balance is below zero, and when the account has live subscriptions, invoices or checkouts. Both checks run before the Stripe call, so a refused delete never touches Stripe. The live records check uses a new HasLiveRecords repository method.
  • The API maps the new ErrHasTokenDebt error to failed precondition.
  • Org delete no longer writes its own forfeit event and no longer deletes the credit transactions. The account delete does both.
  • When an org delete is retried, the owner notice recovers the forfeit of accounts that an earlier attempt already deleted. It reads the billing_customer.deleted records. Only records written by an org delete count, so an account deleted on its own is not added to the notice. The record carries org_teardown: true for this.
  • The app.billing.tokens.forfeited audit log event is removed, together with its entries in the webhook docs and the admin webhook event list. The event has shipped since v0.116.0. It was only an audit log entry and the forfeit now lives on the audit record, so it will no longer be sent as a webhook event.
  • The unused DeleteByAccountID on the credit service and repository is removed.

Technical Details

  • One SQL statement hides the transactions and returns the totals. It only touches rows where account_id is the customer id, never the system account.
  • The purchased share is the net of rows with source system.buy, kept between zero and the forfeited amount.
  • The debt check and the live records check each run twice. The service checks before the Stripe call. The repository checks again inside the transaction, where the customer row is locked, so a race cannot hide a debt or a live record.
  • Audit records written before this change have no org_teardown flag, so a retry that started before the deploy recovers nothing.
  • A gap remains. A credit that passed the account check just before the delete can still land on the deleted account. It is tracked in A credit can be added to a billing account that is being deleted #2010.

Test Plan

  • go build ./...
  • go vet on the touched packages
  • go test ./internal/store/postgres/ ./billing/... ./core/deleter/... ./internal/api/v1beta1connect/
  • golangci-lint run on the touched packages, 0 issues
  • gofmt on the changed files
  • Each commit was written test first and the new tests were seen failing before the code

SQL Safety

  • Values go in through goqu.Record and goqu.Ex or bind parameters
  • Callers of ToSQL forward the params
  • No ? placeholders inside quoted literals in goqu.L
  • No new nolint annotations

@vercel

vercel Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
frontier Ready Ready Preview Oct 20, 2026 12:16pm UTC

@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Summary

Summary by CodeRabbit

  • Bug Fixes

    • Customer account deletion is blocked when the account has token debt or live billing records, helping prevent deletion when outstanding balances or billing activity remain.
    • When deletion succeeds, token totals are recorded with the deletion and can be recovered for organization deletion notices, including after a retry.
    • Billing activity is handled safely during deletion, preserving transaction history while excluding deleted records from active billing checks.
  • Changes

    • The forfeited-tokens webhook event is no longer listed among supported webhook events.

Billing-customer deletion now checks token debt and live records, hides transactions, and records token totals. Organization delete notices recover those totals from billing-customer deletion audit records. Organization teardown no longer separately deletes credit transactions or writes token-forfeiture audit records.

Changes: Billing deletion and token recovery

Layer / File(s) Summary
Billing deletion rules and persistence
billing/customer/..., internal/store/postgres/billing_customer_repository.go, internal/store/postgres/billing_customer_repository_pg_test.go
Deletion accepts options, hides transactions, rejects net debt, checks live records, and records token metadata. PostgreSQL tests cover deletion, rollback, and live-record checks.
Customer deletion guards and API contracts
billing/customer/service.go, internal/api/v1beta1connect/..., billing/customer/service_test.go
The customer service checks balance and live records before deletion. API contracts and error mapping use the updated deletion signature.
Organization teardown and audit-service wiring
cmd/serve.go, core/deleter/service.go, billing/credit/..., internal/store/postgres/billing_transactions_repository.go, core/deleter/service_test.go
The cascade deleter receives an audit-record service. Teardown passes OrgTeardown: true and removes separate credit-transaction deletion.
Delete-notice recovery from audit records
core/deleter/delete_notice.go, core/deleter/mocks/audit_record_service.go, core/audit/audit.go, docs/content/docs/reference/webhook.mdx, web/sdk/admin/utils/webhook-events.ts
Delete notices recover token totals from billing-customer deletion records. The legacy token-forfeiture event is removed from event declarations and webhook lists.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Suggested reviewers: amangit07, whoabhisheksah

Merge Risk: 🟡 Moderate · up to fc474

Large deletion histories can understate forfeited tokens on retry, and a concurrent credit can remain live after its account is deleted. Resolve or explicitly accept these risks before merging.

Pre-merge checks: Passed 2
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coveralls

coveralls commented Oct 14, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 39420937993

Coverage increased (+0.05%) to 55.627%

Details

  • Coverage increased (+0.05%) from the base build.
  • Patch coverage: 12 uncovered changes across 4 files (66 of 78 lines covered, 84.62%).
  • 1 coverage regression across 1 file.

Uncovered Changes

File Changed Covered %
internal/store/postgres/billing_customer_repository.go 39 33 84.62%
billing/customer/service.go 16 12 75.0%
cmd/serve.go 1 0 0.0%
internal/api/v1beta1connect/billing_customer.go 1 0 0.0%
Total (7 files) 78 66 84.62%

Coverage Regressions

1 previously-covered line in 1 file lost coverage.

File Lines Losing Coverage Coverage
billing/credit/service.go 1 64.96%

Coverage Stats

Coverage Status
Relevant Lines: 41863
Covered Lines: 23287
Line Coverage: 55.63%
Coverage Strength: 19.3 hits per line

💛 - Coveralls

@rohilsurana
rohilsurana force-pushed the feat/billing-account-delete-hides-transactions branch from 4f3a2c0 to 88f48ee Compare October 14, 2026 06:27
@rohilsurana
rohilsurana force-pushed the feat/billing-rows-soft-delete branch from be61aad to 49f5fe5 Compare October 14, 2026 09:03
@rohilsurana
rohilsurana force-pushed the feat/billing-account-delete-hides-transactions branch from 88f48ee to 55510df Compare October 14, 2026 09:04
@rohilsurana
rohilsurana marked this pull request as ready for review October 14, 2026 10:45
@rohilsurana
rohilsurana force-pushed the feat/billing-rows-soft-delete branch from 49f5fe5 to 170adbe Compare October 16, 2026 08:44
@rohilsurana
rohilsurana force-pushed the feat/billing-account-delete-hides-transactions branch from 55510df to 5a6a5bc Compare October 16, 2026 10:04
@rohilsurana
rohilsurana force-pushed the feat/billing-rows-soft-delete branch from 170adbe to c2b82b8 Compare October 16, 2026 10:04
@rohilsurana
rohilsurana force-pushed the feat/billing-rows-soft-delete branch from c2b82b8 to e625872 Compare October 16, 2026 10:20
@rohilsurana
rohilsurana force-pushed the feat/billing-account-delete-hides-transactions branch from 5a6a5bc to 0286c5f Compare October 16, 2026 10:21
Comment thread billing/customer/service.go Resolved
Base automatically changed from feat/billing-rows-soft-delete to main October 19, 2026 09:12
@rohilsurana
rohilsurana force-pushed the feat/billing-account-delete-hides-transactions branch from 5d7f7fc to 7f9267e Compare October 19, 2026 09:13
@rohilsurana
rohilsurana requested a review from AmanGIT07 October 19, 2026 09:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository: raystack/frontier/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e8cc6bc4-0fe1-4ef3-ac62-1754a045a56d
📥 Commits

Reviewing files that changed from the base of the PR and between 4e266f1 and 7f9267e.

📒 Files selected for processing: 18
  • billing/customer/errors.go
  • billing/customer/mocks/repository.go
  • billing/customer/service.go
  • billing/customer/service_test.go
  • cmd/serve.go
  • core/audit/audit.go
  • core/deleter/delete_notice.go
  • core/deleter/delete_notice_test.go
  • core/deleter/mocks/audit_record_service.go
  • core/deleter/mocks/credit_service.go
  • core/deleter/service.go
  • core/deleter/service_test.go
  • docs/content/docs/reference/webhook.mdx
  • internal/api/v1beta1connect/billing_errors.go
  • internal/api/v1beta1connect/billing_errors_test.go
  • internal/store/postgres/billing_customer_repository.go
  • internal/store/postgres/billing_customer_repository_pg_test.go
  • web/sdk/admin/utils/webhook-events.ts
💤 Files with no reviewable changes: 4
  • docs/content/docs/reference/webhook.mdx
  • core/deleter/mocks/credit_service.go
  • web/sdk/admin/utils/webhook-events.ts
  • core/audit/audit.go

Comment thread core/deleter/delete_notice.go Resolved
Comment thread core/deleter/delete_notice.go Resolved
Comment thread core/deleter/service.go Resolved
@AmanGIT07

Copy link
Copy Markdown
Contributor

The description still says the forfeit goes to app.billing.tokens.forfeited after the delete and that core/deleter is not changed. Can we update it to the new flow, including that this removes app.billing.tokens.forfeited, which has shipped since v0.116.0?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository: raystack/frontier/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b586b7de-26b5-4bc9-9f41-9ab440e0527f
📥 Commits

Reviewing files that changed from the base of the PR and between 7f9267e and fc474d7.

📒 Files selected for processing: 19
  • billing/credit/mocks/transaction_repository.go
  • billing/credit/service.go
  • billing/customer/customer.go
  • billing/customer/mocks/repository.go
  • billing/customer/service.go
  • billing/customer/service_test.go
  • cmd/serve.go
  • core/deleter/delete_notice.go
  • core/deleter/delete_notice_test.go
  • core/deleter/mocks/customer_service.go
  • core/deleter/service.go
  • core/deleter/service_test.go
  • internal/api/v1beta1connect/billing_customer.go
  • internal/api/v1beta1connect/interfaces.go
  • internal/api/v1beta1connect/mocks/customer_service.go
  • internal/store/postgres/billing_customer_repository.go
  • internal/store/postgres/billing_customer_repository_pg_test.go
  • internal/store/postgres/billing_transactions_repository.go
  • internal/store/postgres/billing_transactions_repository_pg_test.go
💤 Files with no reviewable changes: 4
  • internal/store/postgres/billing_transactions_repository.go
  • billing/credit/service.go
  • internal/store/postgres/billing_transactions_repository_pg_test.go
  • billing/credit/mocks/transaction_repository.go

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.

Comment thread core/deleter/delete_notice.go Resolved
@rohilsurana
rohilsurana merged commit 8053fb3 into main Oct 22, 2026
8 checks passed
@rohilsurana
rohilsurana deleted the feat/billing-account-delete-hides-transactions branch October 22, 2026 04:42

This branch was successfully deployed

1 active deployment
Preview — fc474d71 Deployed Oct 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants