fix(checkout): clear tax ID when country change coalesces with another address edit - #14260
Closed
detail-app[bot] wants to merge 1 commit into
Closed
detail-app[bot] wants to merge 1 commit into
detail-app[bot] wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
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.
Detail bug report: View on Detail
Summary
Related Issue: polarsource/feedback#483
The checkout form's debounced address watcher failed to clear the tax ID when a billing country change was coalesced with another address-field edit, causing a misleading "Invalid tax ID" error and a form/server desync for returning customers with a saved tax ID.
What
clients/packages/checkout/src/components/CheckoutForm.tsx: in thewatcher'selse if (name.startsWith('customer_billing_address'))branch, clear the tax ID (payload + form field + error) when the coalesced update carries a country that differs from the last persisted checkout country.clients/packages/checkout/src/components/CheckoutForm.test.tsx: two regression tests covering the coalesced clear and the no-over-fire guard.clients/.changeset/checkout-coalesced-country-tax-id.md: patch changeset for the published@polar-sh/checkoutpackage.Why
The form syncs address edits to
PATCH /v1/checkouts/client/{id}through a single 500 ms-debouncedwatchcallback. The watcher branches on the last field name that changed within the debounce window. Commit7ece780fb5("fix(checkout): clear tax ID when billing country changes") addedcustomer_tax_id: nullclearing only to thename === 'customer_billing_address.country'branch.When a returning customer changes billing country and then edits another address field (e.g. line1) within 500 ms, the debounce fires carrying the later field's
nameand takes the siblingname.startsWith('customer_billing_address')branch — sending the new country withoutcustomer_tax_id: null. The server keeps the stale tax ID, re-validates it against the new country, and rejects the PATCH with422 "Invalid tax ID". The transaction rolls back server-side, but the form keeps the new country, leaving a transient desync and a field error pointing at the tax-ID field instead of the customer's intended country change.How
The tax-ID clearing is decoupled from the
name-based branching. In the coalesced branch, the guard compares the new country againstcheckout.customer_billing_address?.country— the last persisted server country — rather than the live watchedcountryvalue. By the time the coalesced watcher fires, the component has already re-rendered with the new country, so the closed-overcountryequalsnewCountryand a naivecountry !== newCountryguard would be a no-op for the very race being fixed. The persisted checkout country only advances on a successful PATCH, so it is still the old country in the coalesced case and the trip is detected. When the country actually changed and a tax ID is present, the branch sendscustomer_tax_id: null, clears the field error, and resets the form field — the same side effects the dedicated country-branch already performs.A comment documents why the guard uses the persisted checkout country, since the correctness of the comparison against
checkout.customer_billing_address?.country(rather than the livecountry) is the non-obvious core of the fix.Checklist
pnpm --filter @polar-sh/checkout typecheck,oxlint,oxfmt --check)Testing
Unit tests —
pnpm --filter @polar-sh/checkout exec vitest run src/components/CheckoutForm.test.tsxpasses 37/37 (35 pre-existing + 2 new):clears the tax ID when a country change is coalesced with a line1 edit within the debounce window— the core regression: a country change followed by a line1 edit within 500 ms produces a single coalescedupdatecarrying both the new country/line1 andcustomer_tax_id: null, with the form field reset to''.does not clear the tax ID when only a non-country address field is edited (no country change)— guard: editing line1 alone (country unchanged) must not addcustomer_tax_idto the payload or reset the form field, ensuring the new clear is gated on a real country change rather than unconditional.Both new tests were confirmed to fail against the pre-fix source (the coalesced-clear test fails because
customer_tax_idis absent from the payload) and pass with the fix.Full checkout suite + coverage —
pnpm --filter @polar-sh/checkout testpasses 455/455 across 34 files; coverage (Statements 82.64% / Branches 73.21% / Functions 86.1% / Lines 83.24%) stays above the package's 80/80/80/70 thresholds.Provider error-surfacing —
CheckoutFormProvider.test.tsxpasses 26/26; thePolarRequestValidationError→ field-levelsetErrormapping (the path that renders a server "Invalid tax ID" as a field error) is unchanged.Server tax-ID re-validation —
pytest tests/checkout/test_service.py::TestUpdate::test_invalid_tax_idpasses 3/3. The server still rejects a country update with nocustomer_tax_idwhen the stale tax ID is invalid for the new country; the fix is purely client-side (it stops the client sending that rejected shape on a real country change).Typecheck, lint, format, build —
tsc --noEmit,oxlint,oxfmt --check, and thetsupbuild all pass cleanly.End-to-end browser verification (not run) — The intended check was to drive the live checkout form with Playwright via the
verifier-webskill: open a Stripe-keyed checkout for a returning customer with a saved tax ID, change country to US and edit line1 within the debounce window, then confirm no "Invalid tax ID" error appears, the tax-ID field clears, the country persists server-side (verified in the DB), and the checkout still completes with a Stripe test card. I could not run this in the local sandbox: thedev dockerweb container (the Next.js checkout UI host) fails to start with a host cgroup v2 limitation (cannot enter cgroupv2 "/sys/fs/cgroup/user/workload" with domain controllers -- it is in domain threaded mode, reproduced on initial bring-up and explicit restart), and the Playwright MCP browser tooling the skill requires is not available in this environment. The API half of the stack is healthy (/healthzreturns ok). The aspects this e2e would verify are otherwise covered by the unit suites (client payload shape) and the server test (tax-ID re-validation); the browser e2e should be re-run on a host with a working cgroup setup before release.Automatic Fixes PRs can be configured here.