Skip to content

Fix adapter issues found while documenting the billing adapters - #3

Open
saiyam1814 wants to merge 6 commits into
docs/billing-adaptersfrom
fix/adapter-issues
Open

saiyam1814 wants to merge 6 commits into
docs/billing-adaptersfrom
fix/adapter-issues

Conversation

@saiyam1814

Copy link
Copy Markdown
Collaborator

Stacked on #2, so the guides and the behavior they describe ship together. After #2 merges, this PR retargets to main.

Fixes

Stripe: a plan with more than 20 prices stalled all Stripe delivery.
Automatic subscriptions returned a retryable error, so the pipeline retried that tenant forever while every other tenant's usage waited. The error is now permanent: the tenant is logged as rejected, its customer and usage keep flowing, and nobody else is held up.

Metronome: a rate card change rated usage twice.
The new contract was created and the old one left open. vBilling now keeps one open contract per tenant:

  • the new contract starts at UTC midnight, and every other open vBilling contract of the tenant ends there (/v1/contracts/updateEndDate)
  • a contract that would not have started yet, after a same-day flip back, is archived (/v1/contracts/archive, finalized invoices kept)
  • uniqueness keys carry the start date (vbilling:<rate card>@<date>:<tenant hash>), so returning to an earlier rate card works; v0.2.0 keys are recognized and their contracts adopted
  • after a state loss, or on a second replica, the open contract is adopted instead of creating another (/v2/contracts/list)
  • contracts vBilling didn't create are never touched
  • after upgrading, contracts that already overlap are ended at the next midnight, with a warning to check the invoices for the overlap

Metronome: the Stripe link only applied to customers vBilling created.
Customers found by alias or pinned by ID now get the Stripe billing provider configuration when they have none (/v1/getCustomerBillingProviderConfigurations, /v1/setCustomerBillingProviderConfigurations).

Reconcile: sku, region, project and cluster filters always mismatched.
They summed only the matching ledger events, while billing platforms report totals per tenant and metric. Filters now pick the tenant and metric pairs to compare, and each row covers all of that tenant's usage of the metric.

Billing states needed a Stripe or Metronome webhook secret.
Without one, PUT /api/v1/billing-states/{tenant} returned 404, so Lago and custom billing platforms couldn't drive enforcement. The enforcer now always exists; webhooks still need their own secrets.

Rejections that can never succeed were retried forever:

  • a webhook receiver rejecting the catalog event (400, 413 or 422) paused usage delivery; it is now logged and skipped
  • offboarding retried every destination each pass while any one failed, including permanent rejections; each destination's result is now recorded, and a permanent rejection is final
  • Lago treats 404 on terminating a subscription as already done

Tests

New tests cover each fix: the 21-price plan, a permanently rejected tenant in the pipeline, the contract handover across days and on the same day, adopting legacy contracts (with pagination), linking existing customers, filtered reconcile, the rejected catalog, final offboarding, and the Lago 404.

go test -race ./... passes, and go vet is clean. The reconcile test fails on the old code.

The new Metronome requests are checked against request schemas taken from Metronome's published OpenAPI spec and added to testdata. As before, they have not been run against a live Metronome account.

Not changed

  • enforce mode still cannot hold tenant clusters with their own nodes (Private Nodes, Auto Nodes, Standalone). Their pods never reach the control plane cluster, and changing that needs write access inside each tenant cluster. It stays documented as a limit.
  • CustomerResolver is still only consulted by the Stripe and Metronome webhooks, the only inbound billing signals. Other platforms can now use the billing-state API.

🤖 Generated with Claude Code

Automatic subscriptions refused such a plan with a retryable error, so
the pipeline retried that tenant forever and held up Stripe delivery for
every tenant. The error is now permanent: the tenant is logged as
rejected, its customer and usage keep flowing, and everyone else is
unaffected.
A rate card change created a contract on the new rate card and left the
old one open, so usage was rated twice. vBilling now keeps one open
contract per tenant: the new contract starts at UTC midnight and every
other open vBilling contract of the tenant ends there, or is archived if
it would not have started by then. Contracts vBilling did not create are
never touched.

Uniqueness keys carry the start date (vbilling:<rate card>@<date>:<tenant
hash>), so a tenant can return to an earlier rate card; v0.2.0 keys are
recognized and those contracts adopted. A tenant that loses its state
adopts its open contract instead of creating another.

With the Stripe link on, customers that vBilling did not create (found by
alias or pinned by ID) now get the Stripe billing provider configuration
when they have none.

The new requests are validated against Metronome's published OpenAPI
schemas, added to testdata.
Billing platforms report totals per tenant and metric, but a sku, region,
project or cluster filter summed only the matching ledger events, so a
tenant with two SKUs always mismatched. Filters now pick the tenant and
metric pairs to compare, and each row covers all of that tenant's usage
of the metric.
PUT /api/v1/billing-states/{tenant} returned 404 unless a Stripe or
Metronome webhook secret was set, so Lago and custom billing platforms
could not drive enforcement. The enforcer now always exists; webhooks
still need their own secrets.
- A webhook receiver that rejects the catalog event with 400, 413 or 422
  no longer pauses usage delivery: the catalog is informational.
- Offboarding records each destination's result: a permanent rejection
  is final, and destinations that succeeded are not called again while
  another one retries.
- Lago treats 404 on terminating a subscription as already done, and its
  DELETE errors are classified like its other requests.
Stripe plans over 20 prices, Metronome contract handover and Stripe
links, filtered reconciliation, and billing states without webhook
secrets. Also corrects the gpu_profile example in a code comment.

This branch has not been deployed

No deployments
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.

1 participant