billing: list invoices per customer instead of per-invoice Stripe search#3054
Draft
GregorShear wants to merge 1 commit into
Draft
billing: list invoices per customer instead of per-invoice Stripe search#3054GregorShear wants to merge 1 commit into
GregorShear wants to merge 1 commit into
Conversation
The invoices GraphQL connection resolves each invoice's Stripe-backed fields (amountDue, status, invoicePdf, hostedInvoiceUrl, paymentDetails) through StripeInvoiceLoader. That loader issued one Stripe Search API call per invoice and fired them concurrently via try_join_all. On pages with many invoices this burst past Stripe's Search rate limit (well below the standard read limit), returning 429 and nulling out the entire tenant query. Every invoice in a connection belongs to one tenant, hence one Stripe customer. The loader now groups the batched keys by customer and issues a single Invoice::list per customer — the standard list endpoint, which is far less aggressively rate-limited than Search — then matches each row to its invoice locally by metadata identity (invoice_type, period_start, period_end). Drafts are still excluded, preserving the prior -status:draft filter. Replaces the BillingProvider::search_invoices trait method with list_invoices(customer_id), updates the Stripe and in-memory implementations, and tags the in-memory test invoice with metadata so local matching resolves it. The billing-integrations publish path keeps its own metadata search and is unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
tenant.billing.invoiceswas returning 429s from Stripe:The
InvoiceGraphQL type's Stripe-backed fields (amountDue,status,invoicePdf,hostedInvoiceUrl,paymentDetails) each resolve throughStripeInvoiceLoader. That loader issued one Stripe Search API call per invoice and fired them all concurrently (try_join_all). The Search API is rate-limited well below the standard read endpoints, so a page with many invoices burst past the limit.try_join_allshort-circuits on the first failure, so a single 429 nulled out the entire tenant query.Fix
Every invoice in a connection belongs to one tenant, hence one Stripe customer.
StripeInvoiceLoadernow:customer_id.Invoice::listper customer — the standard list endpoint, far less aggressively rate-limited than Search — paginating withstarting_after.(invoice_type, period_start, period_end).Drafts remain excluded, preserving the prior
-status:"draft"filter. Result values are unchanged; only the Stripe call pattern changes (N concurrent searches → 1 list call per page).BillingProvider::search_invoicesis replaced bylist_invoices(customer_id); the Stripe and in-memory impls follow. Thebilling-integrationspublish path keeps its own metadata search and is untouched.Testing
cargo test -p control-plane-api invoice— 4 passed.graphql_billing_invoice_stripe_fieldsexercises the full resolver path; its snapshot is unchanged.Not in scope
ChargeDataLoaderstill issues oneretrieve_payment_intentper invoice whenpaymentDetailsis requested. That's on the standard API (~100 rps), so far more forgiving, but still an unbounded concurrent burst — straightforward to bound if needed.Invoice::retrieveinstead of a per-customer list, but isn't necessary to resolve the rate limiting.