Browse Source

updates to recurly -> stripe customer upsert script

GitOrigin-RevId: dfe277807f9282804fed5a9cdf86e654a719de96
Simon Gardner 6 tháng trước cách đây
mục cha
commit
4d338efe56

+ 601 - 66
services/web/scripts/helpers/migrate_recurly_customers_to_stripe.helpers.mjs

@@ -2,7 +2,6 @@
 import lodash from 'lodash'
 
 /*
- * This helper can be used by migrate_recurly_customers_to_stripe.mjs
  *
  * This file can be deleted once the Recurly to Stripe migration is complete.
  */
@@ -181,6 +180,30 @@ export const EU_VAT_COUNTRIES = [
   'SK', // Slovakia
 ]
 
+const EU_VAT_PREFIX_OVERRIDES = {
+  GR: 'EL', // Greece uses EL prefix for VAT numbers
+}
+
+function normalizeTaxId(value) {
+  if (!value) return ''
+  return String(value).trim().toUpperCase()
+}
+
+function normalizeTaxIdCompact(value) {
+  return normalizeTaxId(value).replace(/\s+/g, '')
+}
+
+function digitsOnly(value) {
+  return normalizeTaxId(value).replace(/\D+/g, '')
+}
+
+function hasEuVatPrefix(country, taxIdValue) {
+  const normalized = normalizeTaxIdCompact(taxIdValue)
+  if (!normalized) return false
+  const prefix = EU_VAT_PREFIX_OVERRIDES[country] || country
+  return normalized.startsWith(prefix)
+}
+
 function caProvinceFromPostalCode(postalCode) {
   if (!postalCode) return null
   const m = String(postalCode)
@@ -267,6 +290,445 @@ export function getCanadaTaxIdType(taxIdValue, postalCode) {
   return null
 }
 
+/**
+ * Determine the Stripe tax ID type for Australia based on the tax ID value.
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Australia section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'au_abn'|'au_arn'|null}
+ */
+export function getAustraliaTaxIdType(taxIdValue) {
+  const digits = digitsOnly(taxIdValue)
+  if (!digits) return null
+
+  // ABN: 11 digits (example: 12345678912)
+  if (/^\d{11}$/.test(digits)) return 'au_abn'
+
+  // ARN: 12 digits (example: 123456789123)
+  if (/^\d{12}$/.test(digits)) return 'au_arn'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Brazil based on the tax ID value.
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Brazil section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'br_cnpj'|'br_cpf'|null}
+ */
+export function getBrazilTaxIdType(taxIdValue) {
+  const digits = digitsOnly(taxIdValue)
+  if (!digits) return null
+
+  // CNPJ: 14 digits (example: 01.234.456/5432-10)
+  if (/^\d{14}$/.test(digits)) return 'br_cnpj'
+
+  // CPF: 11 digits (example: 123.456.789-87)
+  if (/^\d{11}$/.test(digits)) return 'br_cpf'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Bulgaria (EU VAT vs UIC).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Bulgaria section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'eu_vat'|'bg_uic'|null}
+ */
+export function getBulgariaTaxIdType(taxIdValue) {
+  if (!taxIdValue) return null
+
+  // EU VAT: BG prefix (example: BG0123456789)
+  if (hasEuVatPrefix('BG', taxIdValue)) return 'eu_vat'
+
+  // UIC: 9 digits (example: 123456789)
+  if (/^\d{9}$/.test(digitsOnly(taxIdValue))) return 'bg_uic'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Croatia (EU VAT vs OIB).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Croatia section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'eu_vat'|'hr_oib'|null}
+ */
+export function getCroatiaTaxIdType(taxIdValue) {
+  if (!taxIdValue) return null
+
+  // EU VAT: HR prefix (example: HR12345678912)
+  if (hasEuVatPrefix('HR', taxIdValue)) return 'eu_vat'
+
+  // OIB: 11 digits (example: 12345678901)
+  if (/^\d{11}$/.test(digitsOnly(taxIdValue))) return 'hr_oib'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Germany (EU VAT vs Steuer Nummer).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Germany section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'eu_vat'|'de_stn'|null}
+ */
+export function getGermanyTaxIdType(taxIdValue) {
+  if (!taxIdValue) return null
+
+  // EU VAT: DE prefix (example: DE123456789)
+  if (hasEuVatPrefix('DE', taxIdValue)) return 'eu_vat'
+
+  // Steuer Nummer: 10 digits (example: 1234567890)
+  if (/^\d{10}$/.test(digitsOnly(taxIdValue))) return 'de_stn'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Hungary (EU VAT vs HU tax number).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Hungary section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'eu_vat'|'hu_tin'|null}
+ */
+export function getHungaryTaxIdType(taxIdValue) {
+  if (!taxIdValue) return null
+
+  // EU VAT: HU prefix (example: HU12345678)
+  if (hasEuVatPrefix('HU', taxIdValue)) return 'eu_vat'
+
+  // HU tax number: 8-1-2 digits (example: 12345678-1-23)
+  const normalized = normalizeTaxIdCompact(taxIdValue)
+  if (/^\d{8}-\d-\d{2}$/.test(normalized)) return 'hu_tin'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Japan.
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Japan section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'jp_cn'|'jp_rn'|'jp_trn'|null}
+ */
+export function getJapanTaxIdType(taxIdValue) {
+  const normalized = normalizeTaxIdCompact(taxIdValue)
+  if (!normalized) return null
+
+  // Tax Registration Number: T + 13 digits (example: T1234567891234)
+  if (/^T\d{13}$/.test(normalized)) return 'jp_trn'
+
+  // Corporate Number: 13 digits (example: 1234567891234)
+  if (/^\d{13}$/.test(normalized)) return 'jp_cn'
+
+  // Registered Foreign Businesses: 5 digits (example: 12345)
+  if (/^\d{5}$/.test(normalized)) return 'jp_rn'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Liechtenstein (UID vs VAT).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Liechtenstein section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'li_uid'|'li_vat'|null}
+ */
+export function getLiechtensteinTaxIdType(taxIdValue) {
+  const normalized = normalizeTaxIdCompact(taxIdValue)
+  if (!normalized) return null
+
+  // UID: CHE + 9 digits (example: CHE123456789)
+  if (/^CHE\d{9}$/.test(normalized)) return 'li_uid'
+
+  // VAT: 5 digits (example: 12345)
+  if (/^\d{5}$/.test(digitsOnly(normalized))) return 'li_vat'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Malaysia (FRP, ITN, SST).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Malaysia section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'my_frp'|'my_itn'|'my_sst'|null}
+ */
+export function getMalaysiaTaxIdType(taxIdValue) {
+  const normalized = normalizeTaxIdCompact(taxIdValue)
+  if (!normalized) return null
+
+  // FRP: 8 digits (example: 12345678)
+  if (/^\d{8}$/.test(digitsOnly(normalized))) return 'my_frp'
+
+  // ITN: letter + 10 digits (example: C 1234567890)
+  if (/^[A-Z]\d{10}$/.test(normalized.replace(/\s+/g, ''))) return 'my_itn'
+
+  // SST: A12-3456-78912345
+  if (/^[A-Z]\d{2}-\d{4}-\d{8}$/.test(normalized)) return 'my_sst'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Norway (VAT vs VOEC).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Norway section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'no_vat'|'no_voec'|null}
+ */
+export function getNorwayTaxIdType(taxIdValue) {
+  const normalized = normalizeTaxIdCompact(taxIdValue)
+  if (!normalized) return null
+
+  // VAT: 9 digits + MVA (example: 123456789MVA)
+  if (/^\d{9}MVA$/.test(normalized)) return 'no_vat'
+
+  // VOEC: 7 digits (example: 1234567)
+  if (/^\d{7}$/.test(digitsOnly(normalized))) return 'no_voec'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Poland (EU VAT vs NIP).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Poland section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'eu_vat'|'pl_nip'|null}
+ */
+export function getPolandTaxIdType(taxIdValue) {
+  if (!taxIdValue) return null
+
+  // EU VAT: PL prefix (example: PL1234567890)
+  if (hasEuVatPrefix('PL', taxIdValue)) return 'eu_vat'
+
+  // NIP: 10 digits (example: 1234567890)
+  if (/^\d{10}$/.test(digitsOnly(taxIdValue))) return 'pl_nip'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Romania (EU VAT vs TIN).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Romania section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'eu_vat'|'ro_tin'|null}
+ */
+export function getRomaniaTaxIdType(taxIdValue) {
+  if (!taxIdValue) return null
+
+  // EU VAT: RO prefix (example: RO1234567891)
+  if (hasEuVatPrefix('RO', taxIdValue)) return 'eu_vat'
+
+  // TIN: 13 digits (example: 1234567890123)
+  if (/^\d{13}$/.test(digitsOnly(taxIdValue))) return 'ro_tin'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Russia (INN vs KPP).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Russia section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'ru_inn'|'ru_kpp'|null}
+ */
+export function getRussiaTaxIdType(taxIdValue) {
+  const digits = digitsOnly(taxIdValue)
+  if (!digits) return null
+
+  // INN: 10 digits (example: 1234567891)
+  if (/^\d{10}$/.test(digits)) return 'ru_inn'
+
+  // KPP: 9 digits (example: 123456789)
+  if (/^\d{9}$/.test(digits)) return 'ru_kpp'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Singapore (GST vs UEN).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Singapore section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'sg_gst'|'sg_uen'|null}
+ */
+export function getSingaporeTaxIdType(taxIdValue) {
+  const normalized = normalizeTaxIdCompact(taxIdValue)
+  if (!normalized) return null
+
+  // GST: M + 8 digits + letter (example: M12345678X)
+  if (/^M\d{8}[A-Z]$/.test(normalized)) return 'sg_gst'
+
+  // UEN: 9 digits + letter (example: 123456789F)
+  if (/^\d{9}[A-Z]$/.test(normalized)) return 'sg_uen'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Slovenia (EU VAT vs TIN).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Slovenia section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'eu_vat'|'si_tin'|null}
+ */
+export function getSloveniaTaxIdType(taxIdValue) {
+  if (!taxIdValue) return null
+
+  // EU VAT: SI prefix (example: SI12345678)
+  if (hasEuVatPrefix('SI', taxIdValue)) return 'eu_vat'
+
+  // TIN: 8 digits (example: 12345678)
+  if (/^\d{8}$/.test(digitsOnly(taxIdValue))) return 'si_tin'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Spain (EU VAT vs CIF/NIF).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Spain section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'eu_vat'|'es_cif'|null}
+ */
+export function getSpainTaxIdType(taxIdValue) {
+  const normalized = normalizeTaxIdCompact(taxIdValue)
+  if (!normalized) return null
+
+  // EU VAT: ES prefix (example: ESA1234567Z)
+  if (hasEuVatPrefix('ES', normalized)) return 'eu_vat'
+
+  // CIF/NIF: A12345678 (letter + 7 digits + alnum)
+  if (/^[A-Z]\d{7}[A-Z0-9]$/.test(normalized)) return 'es_cif'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Switzerland (UID vs VAT).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Switzerland section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'ch_uid'|'ch_vat'|null}
+ */
+export function getSwitzerlandTaxIdType(taxIdValue) {
+  const normalized = normalizeTaxIdCompact(taxIdValue)
+  if (!normalized) return null
+
+  const alnum = normalized.replace(/[^A-Z0-9]/g, '')
+
+  // UID: CHE-123.456.789 HR
+  if (/^CHE\d{9}HR$/.test(alnum)) return 'ch_uid'
+
+  // VAT: CHE-123.456.789 MWST
+  if (/^CHE\d{9}MWST$/.test(alnum)) return 'ch_vat'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for the United Kingdom (GB VAT vs EU VAT for NI).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (UK section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'gb_vat'|'eu_vat'|null}
+ */
+export function getUkTaxIdType(taxIdValue) {
+  const normalized = normalizeTaxIdCompact(taxIdValue)
+  if (!normalized) return null
+
+  // Northern Ireland VAT numbers use XI prefix
+  if (/^XI[A-Z0-9]+$/.test(normalized)) return 'eu_vat'
+
+  // GB VAT numbers use GB prefix
+  if (/^GB[A-Z0-9]+$/.test(normalized)) return 'gb_vat'
+
+  return null
+}
+
+/**
+ * Determine the Stripe tax ID type for Uzbekistan (TIN vs VAT).
+ *
+ * Source reference:
+ * - Stripe docs list supported types + example formats (Uzbekistan section)
+ *   https://docs.stripe.com/billing/customer/tax-ids
+ *
+ * @param {string|undefined|null} taxIdValue
+ * @returns {'uz_tin'|'uz_vat'|null}
+ */
+export function getUzbekistanTaxIdType(taxIdValue) {
+  const digits = digitsOnly(taxIdValue)
+  if (!digits) return null
+
+  // VAT: 12 digits (example: 123456789012)
+  if (/^\d{12}$/.test(digits)) return 'uz_vat'
+
+  // TIN: 9 digits (example: 123456789)
+  if (/^\d{9}$/.test(digits)) return 'uz_tin'
+
+  return null
+}
+
 /**
  * Get the Stripe tax ID type for a given country + tax ID value.
  *
@@ -285,79 +747,152 @@ export function getTaxIdType(country, taxIdValue, postalCode) {
 
   const upperCountry = String(country).toUpperCase()
 
+  if (upperCountry === 'EU') {
+    // European One Stop Shop VAT number for non-Union scheme
+    const normalized = normalizeTaxIdCompact(taxIdValue)
+    return /^EU\d+$/.test(normalized) ? 'eu_oss_vat' : null
+  }
+
   // EU VAT
   if (EU_VAT_COUNTRIES.includes(upperCountry)) {
-    return 'eu_vat'
+    // If this country has multiple types, we'll handle it below with a dedicated function.
+    // Otherwise, EU VAT is the only supported type for that country.
+    const euVatOnlyCountries = new Set([
+      'AT',
+      'BE',
+      'CY',
+      'CZ',
+      'DK',
+      'EE',
+      'FI',
+      'FR',
+      'GR',
+      'IE',
+      'IT',
+      'LT',
+      'LU',
+      'LV',
+      'MT',
+      'NL',
+      'PT',
+      'SE',
+      'SK',
+    ])
+    if (euVatOnlyCountries.has(upperCountry)) return 'eu_vat'
   }
 
-  // Canada
-  if (upperCountry === 'CA') {
-    return getCanadaTaxIdType(taxIdValue, postalCode)
-  }
+  // Multi-type countries
+  if (upperCountry === 'CA') return getCanadaTaxIdType(taxIdValue, postalCode)
+  if (upperCountry === 'AU') return getAustraliaTaxIdType(taxIdValue)
+  if (upperCountry === 'BR') return getBrazilTaxIdType(taxIdValue)
+  if (upperCountry === 'BG') return getBulgariaTaxIdType(taxIdValue)
+  if (upperCountry === 'HR') return getCroatiaTaxIdType(taxIdValue)
+  if (upperCountry === 'DE') return getGermanyTaxIdType(taxIdValue)
+  if (upperCountry === 'HU') return getHungaryTaxIdType(taxIdValue)
+  if (upperCountry === 'JP') return getJapanTaxIdType(taxIdValue)
+  if (upperCountry === 'LI') return getLiechtensteinTaxIdType(taxIdValue)
+  if (upperCountry === 'MY') return getMalaysiaTaxIdType(taxIdValue)
+  if (upperCountry === 'NO') return getNorwayTaxIdType(taxIdValue)
+  if (upperCountry === 'PL') return getPolandTaxIdType(taxIdValue)
+  if (upperCountry === 'RO') return getRomaniaTaxIdType(taxIdValue)
+  if (upperCountry === 'RU') return getRussiaTaxIdType(taxIdValue)
+  if (upperCountry === 'SG') return getSingaporeTaxIdType(taxIdValue)
+  if (upperCountry === 'SI') return getSloveniaTaxIdType(taxIdValue)
+  if (upperCountry === 'ES') return getSpainTaxIdType(taxIdValue)
+  if (upperCountry === 'CH') return getSwitzerlandTaxIdType(taxIdValue)
+  if (upperCountry === 'GB') return getUkTaxIdType(taxIdValue)
+  if (upperCountry === 'UZ') return getUzbekistanTaxIdType(taxIdValue)
 
   // Country-specific tax IDs (all Stripe-supported types)
-  // See: https://docs.stripe.com/api/tax_ids/create#create_tax_id-type
+  // See: https://docs.stripe.com/billing/customer/tax-ids
   const countryTaxIdTypes = {
-    // Europe (non-EU)
-    GB: 'gb_vat',
-    // CH: 'ch_vat',
-    // NO: 'no_vat',
-    // IS: 'is_vat',
-    // LI: 'li_uid',
-    // TR: 'tr_tin',
-
-    // // Americas
+    // Africa
+    AO: 'ao_tin',
+    BH: 'bh_vat',
+    BF: 'bf_ifu',
+    BJ: 'bj_ifu',
+    CM: 'cm_niu',
+    CV: 'cv_nif',
+    CD: 'cd_nif',
+    EG: 'eg_tin',
+    ET: 'et_tin',
+    GN: 'gn_nif',
+    KE: 'ke_pin',
+    MA: 'ma_vat',
+    MR: 'mr_nif',
+    NG: 'ng_tin',
+    SN: 'sn_ninea',
+    TZ: 'tz_vat',
+    UG: 'ug_tin',
+    ZA: 'za_vat',
+    ZM: 'zm_tin',
+    ZW: 'zw_tin',
+
+    // Americas
+    AR: 'ar_cuit',
+    BO: 'bo_tin',
+    BS: 'bs_tin',
+    BB: 'bb_tin',
+    CL: 'cl_tin',
+    CO: 'co_nit',
+    CR: 'cr_tin',
+    DO: 'do_rcn',
+    EC: 'ec_ruc',
+    MX: 'mx_rfc',
+    PE: 'pe_ruc',
+    SR: 'sr_fin',
+    SV: 'sv_nit',
     US: 'us_ein',
-    // CA: 'ca_bn', // this is more complex, see getCanadaTaxIdType()
-    // MX: 'mx_rfc',
-    // BR: 'br_cnpj',
-    // CL: 'cl_tin',
-    // CO: 'co_nit',
-    // AR: 'ar_cuit',
-    // BO: 'bo_tin',
-    // CR: 'cr_tin',
-    // DO: 'do_rcn',
-    // EC: 'ec_ruc',
-    // PE: 'pe_ruc',
-    // UY: 'uy_ruc',
-    // VE: 've_rif',
-    // SV: 'sv_nit',
-
-    // // Asia-Pacific
-    // AU: 'au_abn',
-    // NZ: 'nz_gst',
-    // JP: 'jp_cn',
-    // KR: 'kr_brn',
-    // CN: 'cn_tin',
-    // HK: 'hk_br',
-    // TW: 'tw_vat',
-    // SG: 'sg_gst',
-    // MY: 'my_sst',
-    // TH: 'th_vat',
-    // ID: 'id_npwp',
-    // PH: 'ph_tin',
-    // IN: 'in_gst',
-    // VN: 'vn_tin',
-
-    // // Middle East
-    // AE: 'ae_trn',
-    // SA: 'sa_vat',
-    // BH: 'bh_vat',
-    // OM: 'om_vat',
-    // IL: 'il_vat',
-
-    // // Africa
-    // ZA: 'za_vat',
-    // EG: 'eg_tin',
-    // KE: 'ke_pin',
-    // NG: 'ng_tin',
-
-    // // Other
-    // GE: 'ge_vat',
-    // UA: 'ua_vat',
-    // RS: 'rs_pib',
-    // MD: 'md_vat',
-    // AD: 'ad_nrt',
+    UY: 'uy_ruc',
+    VE: 've_rif',
+
+    // Asia-Pacific
+    BD: 'bd_bin',
+    CN: 'cn_tin',
+    HK: 'hk_br',
+    ID: 'id_npwp',
+    IN: 'in_gst',
+    KH: 'kh_tin',
+    KR: 'kr_brn',
+    KZ: 'kz_bin',
+    KG: 'kg_tin',
+    LA: 'la_tin',
+    NZ: 'nz_gst',
+    NP: 'np_pan',
+    PH: 'ph_tin',
+    TH: 'th_vat',
+    TW: 'tw_vat',
+    VN: 'vn_tin',
+
+    // Europe (non-EU)
+    AD: 'ad_nrt',
+    AL: 'al_tin',
+    AM: 'am_tin',
+    AW: 'aw_tin',
+    AZ: 'az_tin',
+    BA: 'ba_tin',
+    BY: 'by_tin',
+    CH: 'ch_vat',
+    GE: 'ge_vat',
+    IS: 'is_vat',
+    LI: 'li_uid',
+    MD: 'md_vat',
+    ME: 'me_pib',
+    MK: 'mk_vat',
+    NO: 'no_vat',
+    RS: 'rs_pib',
+    RU: 'ru_inn',
+    TR: 'tr_tin',
+    UA: 'ua_vat',
+
+    // Middle East
+    AE: 'ae_trn',
+    IL: 'il_vat',
+    OM: 'om_vat',
+    SA: 'sa_vat',
+
+    // Other
+    EU: 'eu_oss_vat',
   }
 
   return countryTaxIdTypes[upperCountry] || null

+ 122 - 0
services/web/scripts/helpers/migrate_recurly_customers_to_stripe.helpers.node.test.mjs

@@ -16,6 +16,26 @@ import {
   coalesceOrEqualOrThrowName,
   coalesceOrThrowVATNumber,
   getCanadaTaxIdType,
+  getAustraliaTaxIdType,
+  getBrazilTaxIdType,
+  getBulgariaTaxIdType,
+  getCroatiaTaxIdType,
+  getGermanyTaxIdType,
+  getHungaryTaxIdType,
+  getJapanTaxIdType,
+  getLiechtensteinTaxIdType,
+  getMalaysiaTaxIdType,
+  getNorwayTaxIdType,
+  getPolandTaxIdType,
+  getRomaniaTaxIdType,
+  getRussiaTaxIdType,
+  getSingaporeTaxIdType,
+  getSloveniaTaxIdType,
+  getSpainTaxIdType,
+  getSwitzerlandTaxIdType,
+  getUkTaxIdType,
+  getUzbekistanTaxIdType,
+  getTaxIdType,
   coalesceOrThrowPaymentMethod,
 } from './migrate_recurly_customers_to_stripe.helpers.mjs'
 
@@ -232,6 +252,108 @@ test('getCanadaTaxIdType returns null when format is unknown/ambiguous', () => {
   assert.equal(getCanadaTaxIdType('PST12345678', null), null)
 })
 
+test('getAustraliaTaxIdType distinguishes ABN vs ARN', () => {
+  assert.equal(getAustraliaTaxIdType('12345678912'), 'au_abn')
+  assert.equal(getAustraliaTaxIdType('123456789123'), 'au_arn')
+})
+
+test('getBrazilTaxIdType distinguishes CNPJ vs CPF', () => {
+  assert.equal(getBrazilTaxIdType('01.234.456/5432-10'), 'br_cnpj')
+  assert.equal(getBrazilTaxIdType('123.456.789-87'), 'br_cpf')
+})
+
+test('getBulgariaTaxIdType distinguishes EU VAT vs UIC', () => {
+  assert.equal(getBulgariaTaxIdType('BG0123456789'), 'eu_vat')
+  assert.equal(getBulgariaTaxIdType('123456789'), 'bg_uic')
+})
+
+test('getCroatiaTaxIdType distinguishes EU VAT vs OIB', () => {
+  assert.equal(getCroatiaTaxIdType('HR12345678912'), 'eu_vat')
+  assert.equal(getCroatiaTaxIdType('12345678901'), 'hr_oib')
+})
+
+test('getGermanyTaxIdType distinguishes EU VAT vs Steuer Nummer', () => {
+  assert.equal(getGermanyTaxIdType('DE123456789'), 'eu_vat')
+  assert.equal(getGermanyTaxIdType('1234567890'), 'de_stn')
+})
+
+test('getHungaryTaxIdType distinguishes EU VAT vs HU tax number', () => {
+  assert.equal(getHungaryTaxIdType('HU12345678'), 'eu_vat')
+  assert.equal(getHungaryTaxIdType('12345678-1-23'), 'hu_tin')
+})
+
+test('getJapanTaxIdType distinguishes TRN, CN, RN', () => {
+  assert.equal(getJapanTaxIdType('T1234567891234'), 'jp_trn')
+  assert.equal(getJapanTaxIdType('1234567891234'), 'jp_cn')
+  assert.equal(getJapanTaxIdType('12345'), 'jp_rn')
+})
+
+test('getLiechtensteinTaxIdType distinguishes UID vs VAT', () => {
+  assert.equal(getLiechtensteinTaxIdType('CHE123456789'), 'li_uid')
+  assert.equal(getLiechtensteinTaxIdType('12345'), 'li_vat')
+})
+
+test('getMalaysiaTaxIdType distinguishes FRP, ITN, SST', () => {
+  assert.equal(getMalaysiaTaxIdType('12345678'), 'my_frp')
+  assert.equal(getMalaysiaTaxIdType('C 1234567890'), 'my_itn')
+  assert.equal(getMalaysiaTaxIdType('A12-3456-78912345'), 'my_sst')
+})
+
+test('getNorwayTaxIdType distinguishes VAT vs VOEC', () => {
+  assert.equal(getNorwayTaxIdType('123456789MVA'), 'no_vat')
+  assert.equal(getNorwayTaxIdType('1234567'), 'no_voec')
+})
+
+test('getPolandTaxIdType distinguishes EU VAT vs NIP', () => {
+  assert.equal(getPolandTaxIdType('PL1234567890'), 'eu_vat')
+  assert.equal(getPolandTaxIdType('1234567890'), 'pl_nip')
+})
+
+test('getRomaniaTaxIdType distinguishes EU VAT vs TIN', () => {
+  assert.equal(getRomaniaTaxIdType('RO1234567891'), 'eu_vat')
+  assert.equal(getRomaniaTaxIdType('1234567890123'), 'ro_tin')
+})
+
+test('getRussiaTaxIdType distinguishes INN vs KPP', () => {
+  assert.equal(getRussiaTaxIdType('1234567891'), 'ru_inn')
+  assert.equal(getRussiaTaxIdType('123456789'), 'ru_kpp')
+})
+
+test('getSingaporeTaxIdType distinguishes GST vs UEN', () => {
+  assert.equal(getSingaporeTaxIdType('M12345678X'), 'sg_gst')
+  assert.equal(getSingaporeTaxIdType('123456789F'), 'sg_uen')
+})
+
+test('getSloveniaTaxIdType distinguishes EU VAT vs TIN', () => {
+  assert.equal(getSloveniaTaxIdType('SI12345678'), 'eu_vat')
+  assert.equal(getSloveniaTaxIdType('12345678'), 'si_tin')
+})
+
+test('getSpainTaxIdType distinguishes EU VAT vs CIF', () => {
+  assert.equal(getSpainTaxIdType('ESA1234567Z'), 'eu_vat')
+  assert.equal(getSpainTaxIdType('A12345678'), 'es_cif')
+})
+
+test('getSwitzerlandTaxIdType distinguishes UID vs VAT', () => {
+  assert.equal(getSwitzerlandTaxIdType('CHE-123.456.789 HR'), 'ch_uid')
+  assert.equal(getSwitzerlandTaxIdType('CHE-123.456.789 MWST'), 'ch_vat')
+})
+
+test('getUkTaxIdType distinguishes GB VAT vs EU VAT (NI)', () => {
+  assert.equal(getUkTaxIdType('GB123456789'), 'gb_vat')
+  assert.equal(getUkTaxIdType('XI123456789'), 'eu_vat')
+})
+
+test('getUzbekistanTaxIdType distinguishes TIN vs VAT', () => {
+  assert.equal(getUzbekistanTaxIdType('123456789'), 'uz_tin')
+  assert.equal(getUzbekistanTaxIdType('123456789012'), 'uz_vat')
+})
+
+test('getTaxIdType handles EU OSS VAT and EU VAT defaults', () => {
+  assert.equal(getTaxIdType('EU', 'EU123456789'), 'eu_oss_vat')
+  assert.equal(getTaxIdType('AT', 'ATU12345678'), 'eu_vat')
+})
+
 test('coalesceOrThrowPaymentMethod throws when payment methods array is empty', () => {
   assert.throws(
     () => coalesceOrThrowPaymentMethod([], 'cus_123', {}),

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 671 - 135
services/web/scripts/recurly/migrate_recurly_customers_to_stripe.mjs


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác