billing: add tenant billing contact fields and per-tenant controller#2902
Open
jshearer wants to merge 1 commit intojshearer/billing_graphqlfrom
Open
billing: add tenant billing contact fields and per-tenant controller#2902jshearer wants to merge 1 commit intojshearer/billing_graphqlfrom
jshearer wants to merge 1 commit intojshearer/billing_graphqlfrom
Conversation
dd24bcb to
2d02827
Compare
2d02827 to
769438a
Compare
cc73e15 to
2184f2e
Compare
769438a to
4203194
Compare
183cb12 to
e3ba37e
Compare
4203194 to
583eeca
Compare
ca57448 to
98a8373
Compare
Customers cannot self-serve billing email changes today. Every request requires manual Stripe intervention. This adds admin-editable billing contact fields on `tenants`, a GraphQL mutation for managing them, and a per-tenant controller that reconciles the Stripe-backed subset to Stripe asynchronously. * `billing_email`, `billing_name`, `billing_address` columns on `tenants`. Insert trigger creates a controller task per tenant, update trigger wakes the controller when `billing_email` or `billing_address` change. Existing tenants are backfilled from `stripe.customers` CDC data. * `setBillingContact` mutation writes the DB and returns immediately. `TenantBilling.contact` query field reads from the DB. No Stripe call in the request path. * `BillingProvider::update_customer_billing_profile` for updating Stripe `Customer.email` and `Customer.address`. * `TenantController` executor (`TaskType(12)`) with a `billing_contact` sub-controller that compares DB desired state against actual Stripe state and reconciles on mismatch, with retry backoff. * `createBillingSetupIntent` and `billing-integrations` customer creation now prefer `billing_email` from the tenants table over the JWT user's email when creating new Stripe customers.
583eeca to
4d347ab
Compare
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.
Summary
Customers cannot self-serve billing email changes today. Every request requires manual Stripe intervention. This adds admin-editable billing contact fields on
tenants, a GraphQL mutation for managing them, and a per-tenant controller that reconciles the Stripe-backed subset to Stripe asynchronously.billing_email,billing_name, andbilling_addressfields totenants, letting admins self-serve billing contact changes through a newsetBillingContactGraphQL mutation instead of requesting manual Stripe editsTenantControllerautomation (the first tenant-scoped automation) that reconciles DB-authoritative billing contact data to Stripe asynchronouslycreateBillingSetupIntent,billing-integrations) to prefer tenant-managed billing email over JWT claims when creating new Stripe customersHow it works
The mutation writes Postgres and returns. A trigger wakes the tenant's controller task, which reads current DB state, compares against the Stripe customer, and calls
update_customer_billing_profileif they differ. Tenants without a Stripe customer store billing data in the DB; the controller treats "no customer" as a no-op, and customer-creation paths wake the controller afterward.The controller follows the same sub-controller composition pattern as
LiveSpecControllerExecutor:TenantControllerStatecontains a nestedBillingContactStatusmanaged by thebilling_contactsub-module, so future tenant automations can be added as additional sub-controllers.Migration
20260429120000_tenant_controller_billing_contact.sql:wake_tenant_controllercreates the task on-demand ifcontroller_task_idis null, sosetBillingContactor customer-creation paths work without a pre-existing task row.billing_emailandbilling_addressfrom CDC-syncedstripe.customersso existing data matches Stripe without triggering reconciliation. Only tenants that received billing data from this backfill get a controller task row; the rest get one on first use.Testing
I tested this e2e in a local stack with a testmode Stripe API key