Files
Kanishk DudejaandIgnacio Dobronich 5d942e2a46 refactor(billing): allow GB VAT tax IDs for Isle of Man customers (#43640)
### Summary

This PR adds support for Isle of Man customers to use GB VAT tax IDs.
Isle of Man uses the UK VAT system, so Orb requires country: 'GB' for
validation - but the billing address country is IM.

- Adds an IM VAT entry to `TAX_IDS` with a new `taxCountryIso2` override
field, so the UI shows "IM VAT" while sending country: 'GB' to Orb
- Introduces `getEffectiveTaxCountry()` to resolve the correct country
code for Orb
- Introduces `resolveStoredTaxId()` to map stored Orb data back to the
correct UI entry, using billing address country to disambiguate IM vs GB
- Updates all three billing forms to use these utilities: Billing
Settings page, Add Payment Method modal and Update Billing Address modal

### Testing

#### Automated tests

I've added unit tests in this PR

#### Manual tests

You can use the following country/city/postal code combinations to test:

- Isle of Man, Douglas, IM1 2LE
- United Kingdom, Crumlin, BT29 4AA

**Billing Settings page**

- Select Isle of Man as billing country → tax ID dropdown shows "Isle of
Man - IM VAT"
- Select United Kingdom as billing country → tax ID dropdown shows
"United Kingdom - UK VAT" / "United Kingdom - GB VAT" (not "IM VAT")
- Select IM VAT, enter a valid GB VAT number, save → check network tab,
Orb receives country: 'GB'
- Reload page for an IM customer with existing tax ID → "IM VAT" is
pre-selected (not "UK VAT")
- Reload page for a GB customer with existing tax ID → "UK VAT" is
pre-selected (no regression)
- Change country from IM to GB → available tax IDs update, IM VAT no
longer shown

**Add Payment Method modal**

- Open modal for an IM customer with existing tax ID → "IM VAT" is
pre-selected
- Check "I'm purchasing as a business", select Isle of Man in Stripe
address → tax ID dropdown shows "IM VAT"
- Check "I'm purchasing as a business", select United Kingdom in Stripe
address → tax ID dropdown shows "UK VAT" / "GB VAT"
- Submit with IM VAT → check network tab, tax ID is saved with country:
'GB'
- Open modal for a GB customer → no regression, "UK VAT" shown as before

**Update Billing Address modal** 

Appears for paid orgs missing a billing address

- Open modal for an IM customer with existing tax ID → "IM VAT" is
pre-selected
- Open modal for a GB customer with existing tax ID → "UK VAT" is
pre-selected
- Fill in IM address + IM VAT and save → saves correctly, modal closes

---------

Co-authored-by: Ignacio Dobronich <ignacio@dobronich.com>
2026-03-20 21:48:56 +05:30

90 lines
3.0 KiB
TypeScript

import { TAX_IDS } from 'components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.constants'
import {
getEffectiveTaxCountry,
resolveStoredTaxId,
sanitizeTaxIdValue,
} from 'components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.utils'
import { describe, test, expect } from 'vitest'
/**
* We're sanitizing EU tax ids. Stripe expects a prefixed tax id (ATU12345678),
* but users might not realize this and enter only the numbers (12345678) without the
* country code prefix.
*
* Our sanitize function should handle 3 cases:
* 1. take an un-prefixed tax id (12345678) and add the country prefix to it (ATU12345678)
* 2. take a correctly prefixed tax id (ATU12345678) and just pass it through
* 3. take a non-EU tax id and just pass it through
*/
describe('TaxID utils: sanitizeTaxID', () => {
test('should prefix an EU tax ID correctly', () => {
const austriaTaxID = {
id: 'txi_1LoPlYJDPojXS6LNVd1FOTk2',
type: 'eu_vat',
value: '12345678',
name: 'AT VAT',
}
const sanitizedID = sanitizeTaxIdValue(austriaTaxID)
expect(sanitizedID).toBe('ATU12345678')
})
test('should check that EU prefix is correct', () => {
const austriaTaxID = {
id: 'txi_1LoPlYJDPojXS6LNVd1FOTk2',
type: 'eu_vat',
value: 'ATU12345678',
name: 'AT VAT',
}
const sanitizedID = sanitizeTaxIdValue(austriaTaxID)
expect(sanitizedID).toBe('ATU12345678')
})
test('should not prefix an non-EU tax ID', () => {
const unitedStatesID = {
id: 'txi_1LoPlYJDPojXS6LNVd1FOTk2',
type: 'us_ein',
value: '12-3456789',
name: 'US EIN',
}
const sanitizedID = sanitizeTaxIdValue(unitedStatesID)
expect(sanitizedID).toBe('12-3456789')
})
})
describe('TaxID utils: getEffectiveTaxCountry', () => {
test('returns countryIso2 when no override is set', () => {
const ukVat = TAX_IDS.find((t) => t.name === 'UK VAT')!
expect(getEffectiveTaxCountry(ukVat)).toBe('GB')
})
test('returns taxCountryIso2 when override is set', () => {
const imVat = TAX_IDS.find((t) => t.name === 'IM VAT')!
expect(getEffectiveTaxCountry(imVat)).toBe('GB')
})
})
describe('TaxID utils: resolveStoredTaxId', () => {
test('resolves GB customer to UK VAT with billingCountry', () => {
const ukVat = TAX_IDS.find((t) => t.name === 'UK VAT')!
expect(resolveStoredTaxId('gb_vat', 'GB', 'GB')).toBe(ukVat)
})
test('resolves IM customer to IM VAT with billingCountry', () => {
const imVat = TAX_IDS.find((t) => t.name === 'IM VAT')!
expect(resolveStoredTaxId('gb_vat', 'GB', 'IM')).toBe(imVat)
})
test('resolves GB customer to UK VAT without billingCountry', () => {
// Without billingCountry, falls back to countryIso2 match — UK VAT has countryIso2 'GB'
const ukVat = TAX_IDS.find((t) => t.name === 'UK VAT')!
expect(resolveStoredTaxId('gb_vat', 'GB')).toBe(ukVat)
})
test('returns undefined for unknown type', () => {
expect(resolveStoredTaxId('unknown_type', 'XX')).toBeUndefined()
})
})