Skip to content

fix: Make a customer's first card their default payment method - #119

Merged
mattdjenkinson merged 4 commits into
mainfrom
fix/first-card-default
Oct 20, 2026
Merged

mattdjenkinson merged 4 commits into
mainfrom
fix/first-card-default

Conversation

@mattdjenkinson

Copy link
Copy Markdown
Contributor

Summary

Fixes #118. A customer's first card could finish setup without becoming the billing account's default, which left the organization stuck short of onboarding. The portal designates the card default as soon as it turns Active. The BillingAccount webhook checked the card through the manager's cache, which can lag the API by about a second, so it rejected the request as if the card were still AwaitingConfirmation. Nothing retried.

Either change below closes the bug on its own. Together they also cover a browser that leaves before it designates the card.

  • The webhook reads the card from the API server. defaultPaymentMethodRef is now validated with mgr.GetAPIReader(), as the BillingArrangement webhook already does. It costs one GET on a rare write. The comment on ValidateBillingAccountDefaultPaymentMethodRef claimed it already read the live API, so that's corrected too.
  • The controller sets the default for a first card. When an account has no default and exactly one Active payment method, the BillingAccount controller makes that card the default.
    • An existing default is never replaced.
    • With several Active cards and no default, the customer chooses.
    • The patch carries the account's resourceVersion, so it can't overwrite a default someone set at the same moment.
  • New index: PaymentMethods are indexed by billing account for the controller's lookup.

Behaviour changes

  • If staff clear the default on an account with a single Active card, the controller sets it straight back. The portal isn't affected: it removes a card by deleting it, never by clearing the default first.
  • The existing payment-method-lifecycle e2e still passes. The controller may now set the default before the test links it, and applying the same ref is a no-op. I updated that step's description.

Tests

  • Webhook unit test: the cache holds the card as AwaitingConfirmation while the API server has it Active. Without the fix it fails with the same message as the production denial.
  • Envtest specs:
    • the first Active card becomes the default
    • an existing default stays when another card turns Active
    • no default is picked when several cards are Active
  • New payment-method-default chainsaw test: covers the same cases on a real cluster, including setting a card Active and immediately designating it default. On kind the cache usually catches up within milliseconds, so the unit test is the reliable guard against a regression.

go test ./... (including envtest) and golangci-lint pass locally. The chainsaw tests haven't been run locally, so CI is their first run.

Not in this PR

The PaymentMethod delete webhook also checks the account's default through the cache. A card deleted moments after it became the default could get past that check. It's the same class of bug, so I'm leaving it for a follow-up.

A first card could finish setup without becoming the billing account's
default (#118). The portal designates the card default as soon as it
turns Active, but the BillingAccount webhook read the card through the
manager's cache, which can lag the API by about a second, and rejected
it as still AwaitingConfirmation. Nothing retried.

- Validate defaultPaymentMethodRef against the API server instead of
  the cache, as the BillingArrangement webhook already does.
- Have the BillingAccount controller set the default when the account
  has none and exactly one Active payment method. An existing default
  is never replaced, and with several Active cards the customer
  chooses. The patch is optimistically locked so it can't overwrite a
  default set concurrently.
- Index PaymentMethods by billing account for the lookup.
- Add a webhook test with a stale cache, envtest specs for the
  defaulting, and a payment-method-default chainsaw test that sets a
  card Active and immediately designates it default.

Fixes #118
@mattdjenkinson
mattdjenkinson requested review from JoseSzycho, savme and scotwells and removed request for savme and scotwells October 13, 2026 21:18
@mattdjenkinson mattdjenkinson self-assigned this Oct 13, 2026
@mattdjenkinson

Copy link
Copy Markdown
Contributor Author

@JoseSzycho @scotwells could you take a look at this please?

@JoseSzycho

Copy link
Copy Markdown
Contributor

@mattdjenkinson sure, taking a look in the next minutes

@mattdjenkinson
mattdjenkinson merged commit e73d053 into main Oct 20, 2026
8 of 9 checks passed
@mattdjenkinson
mattdjenkinson deleted the fix/first-card-default branch October 20, 2026 10:13
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.

First card can fail to become the default payment method

2 participants