Skip to content

fix(checkout): preserve full billing address when country changes - #14038

Open
detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-checkout-preserve-full-billing-address-when-co-e70f69
Open

fix(checkout): preserve full billing address when country changes#14038
detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-checkout-preserve-full-billing-address-when-co-e70f69

Conversation

@detail-app

@detail-app detail-app Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Detail bug report: View on Detail

Summary

Related Issue: polarsource/feedback#417

When a user changes the billing country dropdown in the checkout form, the watcher sent only { country: newCountry } to the server instead of the full billing address. The server replaces the entire customer_billing_address on update, so all other fields (line1, city, postal_code, state) were dropped from server state — causing incorrect tax calculations and address data loss on page refresh.

What

  • CheckoutForm.tsx: The country-change watcher now spreads ...customerBillingAddress (all existing form address fields) and overrides country: newCountry, mirroring the pattern already used by the other-address-field watcher branch. When the state field is reset (switching between fixed-state countries like US/CA), state: '' is included in the payload so the server receives the reset value consistent with the form UI.
  • CheckoutForm.test.tsx: Updated the existing "resets state field when country changes" test to assert the update() payload includes all address fields (not just country). Added a new test verifying all fields are sent when changing between two non-fixed-state countries (no state reset).

Why

The country-change watcher was inconsistent with the other address field watchers: the country path sent only { country } while the other fields spread the full customerBillingAddress. Because the server does a full replace (checkout.customer_billing_address = checkout_update.customer_billing_address), sending only country truncated the stored address. This caused:

  • Incorrect tax — tax APIs (Stripe Tax / Numeral) received only the country, missing postal_code/state/city needed for jurisdiction-accurate rates.
  • Address loss on refresh — after a page refresh, the form re-initialized from the truncated server state, losing all address fields except country.

The bug was introduced in ba33eac70d (PR #4060), which added the watcher pattern for address field changes.

How

The fix is a single client-side change: spread customerBillingAddress in the country-change payload, the same way the other-address-field branch already does. No server-side changes are needed — the server's full-replace semantics are correct when the client sends the full address. The shouldResetState condition was extracted into a variable to avoid duplicating the state-reset check between the resetField call and the payload construction.

Testing

  • Unit tests: All 31 CheckoutForm tests pass, including the updated "resets state field when country changes" (now asserts the PATCH payload contains all address fields with state: '') and the new "sends all billing address fields when country changes without state reset" (verifies fields are preserved when switching SE → DE). The full checkout package suite (389 tests) passes with no regressions.
  • Typecheck and format: tsc --noEmit and oxfmt --check both pass clean.
  • Server-side tests: The full checkout test suite (456 tests) passes, confirming the server's address-replacement and tax-calculation flow works correctly with the full payload — no server changes were needed.
  • E2E (browser): Drove a live checkout with Playwright against the local stack (API server + Next.js web app). Filled billing address fields, changed the country dropdown, and verified: (1) the PATCH request sent to the server included the full address (line1, postal_code, city, state, country), (2) after page refresh the form fields were still populated from server state, and (3) the server API confirmed the full address was stored. Also tested with a paid product — the client correctly sent the full address on both US and FR country changes (confirmed via request capture); server-side persistence couldn't be fully verified for the paid product because the dev Stripe key is a placeholder, but a direct API test on the free product confirmed the server stores and returns the full address on country change.

Checklist

  • This PR addresses a single concern (one bug fix, one feature, one refactor)
  • The diff is reasonably sized and easy to review
  • New functionality is covered by tests
  • Linting and type checking pass (uv run task lint && uv run task lint_types)
  • No unrelated changes or drive-by fixes are included

Automatic Fixes PRs can be configured here.

Review in cubic

@detail-app
detail-app Bot requested a review from sebastianekstrom as a code owner August 29, 2026 00:32
@detail-app
detail-app Bot requested a review from frankie567 August 29, 2026 00:32
@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
orbit Ready Ready Preview Aug 29, 2026 12:36am
polar-test Ready Ready Preview Aug 29, 2026 12:36am

Request Review

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