🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

feat(checkout): add delete of checkout records by billing account - #1841

Merged
whoAbhishekSah merged 2 commits into
mainfrom
fix/checkout-delete-method
Aug 5, 2026
Merged

feat(checkout): add delete of checkout records by billing account#1841
whoAbhishekSah merged 2 commits into
mainfrom
fix/checkout-delete-method

Conversation

@whoAbhishekSah

Copy link
Copy Markdown
Member

Part of #1835. Bottom of the org-delete cleanup stack.

Checkout rows hold a foreign key to the billing customer row, and nothing could remove them. That FK is what blocks the hard delete of a billing account during org delete.

Changes

  • DeleteByCustomerID on the checkout Postgres repository.
  • DeleteByCustomer on the checkout service.

No behavior change: nothing calls these yet. The deleter starts using them later in the stack. Checkout sessions on the billing provider are not touched; they expire on their own.

🤖 Generated with Claude Code

Checkout rows hold a foreign key to the billing customer row, and
nothing could remove them, which blocked the hard delete of a billing
account. Sessions on the billing provider are not touched; they expire
on their own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview Aug 5, 2026 2:47am

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3fde52c1-4580-4c43-be38-e13e559e837f

📥 Commits

Reviewing files that changed from the base of the PR and between ad5e9a5 and 9deb1dc.

📒 Files selected for processing (2)
  • billing/checkout/service.go
  • internal/store/postgres/billing_checkout_repository.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added the ability to delete checkout records associated with a specific customer.
    • Deletion is limited to local checkout records and does not affect provider-side sessions.

Walkthrough

The checkout interfaces now support deleting records by customer ID. The PostgreSQL repository executes the customer-filtered deletion with timeout and error wrapping. The service delegates the operation without modifying provider-side sessions.

Changes

Checkout record deletion

Layer / File(s) Summary
Repository deletion implementation
internal/store/postgres/billing_checkout_repository.go
BillingCheckoutRepository.DeleteByCustomerID deletes all checkout records for the specified customer and wraps query-generation and database errors.
Service deletion contract and delegation
billing/checkout/service.go
The repository and service interfaces expose customer-scoped deletion. Service.DeleteByCustomer delegates to the repository without changing provider-side sessions.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

  • raystack/frontier#1838 — Directly overlaps by adding DeleteByCustomerID to the checkout service and PostgreSQL repository.

Suggested reviewers: amangit07, rohilsurana

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
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.

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.

@whoAbhishekSah

Copy link
Copy Markdown
Member Author

End-to-end testing of the whole stack

Posting here because this is the PR that lands on main, so it carries the whole series.

Tested at the tip of the stack, which contains all six PRs: #1841#1842#1843#1844#1838#1839. That covers checkout delete, credit delete, kyc delete, the retryable subscription teardown, the deleter changes, and the new checkout audit records.

Run against a real Stripe test-mode account, so every provider call actually went to Stripe. No mock and no recorded cassettes.

Setup

  • Fresh Postgres and SpiceDB databases, both migrated from scratch.
  • Platform permissions, roles, platform users and preferences applied with frontier reconcile.
  • A Stripe test-mode key in billing.stripe_key.
  • One plan created through CreatePlan ($10/month, 100 start credits). Frontier registered the product and price in Stripe.
  • Seven orgs seeded through the normal RPCs to cover the different billing shapes. Billing accounts, usage, checkout sessions and KYC records were all created the way a client would create them.
  • Every delete was driven through FrontierService/DeleteOrganization.
  • After each delete I checked three places: the local tables, the auditlogs rows, and the Stripe API directly.

Results

Case What the org had Expected Result
Invoice guard Online account, 1 paid invoice, 1 subscription, 1 checkout Refused, nothing removed Pass. Org, subscription and checkout all untouched
Simple account Online account, nothing else Deleted Pass. Customer shows deleted: true in Stripe
Full billing data 2 checkouts, 2 credit rows, KYC record Deleted, 1 audit record per checkout Pass. Both rows gone, 2 audit records written
Provider drift 1 checkout, 2 credit rows; customer deleted in Stripe behind Frontier's back Deleted anyway Pass. resource_missing treated as already gone
Active subscription Active subscription + paid invoice Refused while the invoice exists Pass. After clearing the invoice row the delete ran and the subscription went to canceled in Stripe
Offline account Offline account (no provider id), 2 credit rows, KYC record Deleted with no provider calls Pass
Failure then retry 2 checkouts, 2 credit rows, KYC record. A database trigger blocked the billing account delete Fails safely, retry finishes Pass, details below

Extra checks:

  • Orphan sweep across the whole database after all deletes: 0 orphaned checkouts, subscriptions, invoices, KYC rows and billing accounts.
  • Unit tests pass for all five changed packages.

What the retry case showed

I forced a realistic mid-teardown failure with a trigger on billing_customers. The behaviour matched what the code comments claim:

  • Checkouts and credit rows were removed and the audit records were written.
  • The org row survived and kept its owner policy, so the org was still owned and still reachable.
  • After removing the trigger, the same call finished the delete.
  • The checkout audit count stayed at 2. Retrying does not write duplicate records, because the checkout list is empty the second time.

Audit record contents

Each app.billing.checkout.deleted record carried the Stripe session id (cs_test_...), the customer id, the plan id, the state and the payment status. Six checkouts were removed across the test orgs and six records were written, one each. So the sessions can still be traced on the provider after the local rows are gone, which is the point of #1839.

Two issues found, neither introduced by this stack

1. A refused delete looks like a crash to the caller. When the invoice guard blocks a delete, the client gets:

{"code":"internal","message":"internal server error"}

The real reason never reaches the caller. internal/api/v1beta1connect/deleter.go wraps every error as CodeInternal, so deleter.ErrDeleteNotAllowed is lost and a normal business rule reads as a server fault. Mapping that error to CodeFailedPrecondition would fix it. Can raise this separately.

2. Credit ledger rows on the system account stay behind. Each usage debit writes two rows: one on the customer account and one on the system account (00000000-0000-0000-0000-000000000000). DeleteByAccountID clears the customer side only, so the system-side rows remain and still carry descriptions naming orgs that no longer exist. Nothing breaks and the customer delete is not blocked, since there is no foreign key on billing_transactions.account_id. Flagging it because it leaves the ledger one-sided. It may well be intended for accounting history.

@rohilsurana rohilsurana left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed as part of the stack (#1841 through #1839). The logic looks good: parameterized delete, the customer_id column is indexed, and there is no existing mock for the checkout Repository, so nothing needed regenerating. One small nit inline.

Comment thread internal/store/postgres/billing_checkout_repository.go Outdated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@whoAbhishekSah
whoAbhishekSah merged commit c977583 into main Aug 5, 2026
11 of 14 checks passed
@whoAbhishekSah
whoAbhishekSah deleted the fix/checkout-delete-method branch August 5, 2026 03:36
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.

2 participants