diff --git a/package-lock.json b/package-lock.json index 6d2b0fec2..bdc76c7f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "bootstrap": "4.6.1", "classnames": "^2.3.1", "core-js": "^3.23.5", + "country-state-city": "^3.2.1", "form-urlencoded": "^6.0.6", "lodash.camelcase": "^4.3.0", "lodash.snakecase": "^4.1.1", @@ -7252,6 +7253,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/country-state-city": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/country-state-city/-/country-state-city-3.2.1.tgz", + "integrity": "sha512-kxbanqMc6izjhc/EHkGPCTabSPZ2G6eG4/97akAYHJUN4stzzFEvQPZoF8oXDQ+10gM/O/yUmISCR1ZVxyb6EA==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", diff --git a/package.json b/package.json index a8acc8917..262de5087 100755 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "bootstrap": "4.6.1", "classnames": "^2.3.1", "core-js": "^3.23.5", + "country-state-city": "^3.2.1", "form-urlencoded": "^6.0.6", "lodash.camelcase": "^4.3.0", "lodash.snakecase": "^4.1.1", diff --git a/src/payment/checkout/payment-form/CardHolderInformation.test.jsx b/src/payment/checkout/payment-form/CardHolderInformation.test.jsx index 8a37498ce..07481b214 100644 --- a/src/payment/checkout/payment-form/CardHolderInformation.test.jsx +++ b/src/payment/checkout/payment-form/CardHolderInformation.test.jsx @@ -13,16 +13,16 @@ import { createStore } from 'redux'; import CardHolderInformation from './CardHolderInformation'; import PaymentForm from './PaymentForm'; import createRootReducer from '../../../data/reducers'; -import countryStatesMap from './utils/countryStatesMap'; +import { getCountryStatesMap, isPostalCodeRequired } from './utils/form-validators'; import '../../__factories__/userAccount.factory'; jest.mock('@edx/frontend-platform/analytics', () => ({ sendTrackEvent: jest.fn(), })); -jest.mock('./utils/countryStatesMap', () => ({ - __esModule: true, - default: jest.fn(), +jest.mock('./utils/form-validators', () => ({ + getCountryStatesMap: jest.fn(), + isPostalCodeRequired: jest.fn(), })); configureI18n({ @@ -73,7 +73,8 @@ describe('', () => { fireEvent.change(screen.getByLabelText('Country (required)'), { target: { value: 'US' } }); - expect(countryStatesMap).toHaveBeenCalledWith('US'); + expect(getCountryStatesMap).toHaveBeenCalledWith('US'); + expect(isPostalCodeRequired).toHaveBeenCalledWith('US'); }); }); describe('purchasedForOrganization field', () => { diff --git a/src/payment/checkout/payment-form/StateProvinceFormInput.jsx b/src/payment/checkout/payment-form/StateProvinceFormInput.jsx index 8252dc1dc..63565053f 100644 --- a/src/payment/checkout/payment-form/StateProvinceFormInput.jsx +++ b/src/payment/checkout/payment-form/StateProvinceFormInput.jsx @@ -3,16 +3,16 @@ import PropTypes from 'prop-types'; import { Field } from 'redux-form'; import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n'; +import { getCountryStatesMap } from './utils/form-validators'; import FormInput from './FormInput'; import FormSelect from './FormSelect'; -import getStates from './utils/countryStatesMap'; import messages from './StateProvinceFormInput.messages'; class StateProvinceFormInput extends React.Component { getOptions() { const options = []; const { country } = this.props; - const states = getStates(country); + const states = getCountryStatesMap(country); if (states) { options.push([( diff --git a/src/payment/checkout/payment-form/StripePaymentForm.test.jsx b/src/payment/checkout/payment-form/StripePaymentForm.test.jsx index 7918a8696..2a43c9a8e 100644 --- a/src/payment/checkout/payment-form/StripePaymentForm.test.jsx +++ b/src/payment/checkout/payment-form/StripePaymentForm.test.jsx @@ -125,8 +125,27 @@ describe('', () => { lastName: '', address: '', city: '', - country: 'GB', + country: 'AQ', // Antarctica does not have states, postal code not required + optionalField: '', + }, + { + firstName: '', + lastName: '', + address: '', + city: '', + country: 'GB', // United Kingdom has states, state becomes required, postal code is required postalCode: '', + state: '', + optionalField: '', + }, + { + firstName: '', + lastName: '', + address: '', + city: '', + country: 'CA', // Canada state and postal code are required + postalCode: '', + state: '', optionalField: '', }, { @@ -134,7 +153,7 @@ describe('', () => { lastName: '', address: '', city: '', - country: 'US', + country: 'US', // United States state and postal code are required postalCode: '', state: '', optionalField: '', @@ -144,7 +163,7 @@ describe('', () => { lastName: '', address: '', city: '', - country: 'IN', + country: 'IN', // India state is required state: '', optionalField: '', }, diff --git a/src/payment/checkout/payment-form/utils/countryStatesMap.js b/src/payment/checkout/payment-form/utils/countryStatesMap.js deleted file mode 100644 index 339ef782a..000000000 --- a/src/payment/checkout/payment-form/utils/countryStatesMap.js +++ /dev/null @@ -1,115 +0,0 @@ -const COUNTRY_STATES_MAP = { - CA: { - AB: 'Alberta', - BC: 'British Columbia', - MB: 'Manitoba', - NB: 'New Brunswick', - NL: 'Newfoundland and Labrador', - NS: 'Nova Scotia', - NT: 'Northwest Territories', - NU: 'Nunavut', - ON: 'Ontario', - PE: 'Prince Edward Island', - QC: 'Québec', - SK: 'Saskatchewan', - YT: 'Yukon', - }, - IN: { - AN: 'Andaman and Nicobar Islands', - AP: 'Andhra Pradesh', - AR: 'Arunachal Pradesh', - AS: 'Assam', - BR: 'Bihar', - CH: 'Chandigarh', - CT: 'Chhattisgarh', - DN: 'Dadra and Nagar Haveli', - DD: 'Daman and Diu', - DL: 'Delhi', - GA: 'Goa', - GJ: 'Gujarat', - HR: 'Haryana', - HP: 'Himachal Pradesh', - JK: 'Jammu and Kashmir', - JH: 'Jharkhand', - KA: 'Karnataka', - KL: 'Kerala', - LD: 'Lakshadweep', - MP: 'Madhya Pradesh', - MH: 'Maharashtra', - MN: 'Manipur', - ML: 'Meghalaya', - MZ: 'Mizoram', - NL: 'Nagaland', - OR: 'Odisha', - PY: 'Puducherry', - PB: 'Punjab', - RJ: 'Rajasthan', - SK: 'Sikkim', - TN: 'Tamil Nadu', - TG: 'Telangana', - TR: 'Tripura', - UP: 'Uttar Pradesh', - UT: 'Uttarakhand', - WB: 'West Bengal', - }, - US: { - AL: 'Alabama', - AK: 'Alaska', - AZ: 'Arizona', - AR: 'Arkansas', - AA: 'Armed Forces Americas', - AE: 'Armed Forces Europe', - AP: 'Armed Forces Pacific', - CA: 'California', - CO: 'Colorado', - CT: 'Connecticut', - DE: 'Delaware', - DC: 'District Of Columbia', - FL: 'Florida', - GA: 'Georgia', - HI: 'Hawaii', - ID: 'Idaho', - IL: 'Illinois', - IN: 'Indiana', - IA: 'Iowa', - KS: 'Kansas', - KY: 'Kentucky', - LA: 'Louisiana', - ME: 'Maine', - MD: 'Maryland', - MA: 'Massachusetts', - MI: 'Michigan', - MN: 'Minnesota', - MS: 'Mississippi', - MO: 'Missouri', - MT: 'Montana', - NE: 'Nebraska', - NV: 'Nevada', - NH: 'New Hampshire', - NJ: 'New Jersey', - NM: 'New Mexico', - NY: 'New York', - NC: 'North Carolina', - ND: 'North Dakota', - OH: 'Ohio', - OK: 'Oklahoma', - OR: 'Oregon', - PA: 'Pennsylvania', - RI: 'Rhode Island', - SC: 'South Carolina', - SD: 'South Dakota', - TN: 'Tennessee', - TX: 'Texas', - UT: 'Utah', - VT: 'Vermont', - VA: 'Virginia', - WA: 'Washington', - WV: 'West Virginia', - WI: 'Wisconsin', - WY: 'Wyoming', - }, -}; - -export default function getStates(country) { - return country && COUNTRY_STATES_MAP[country.toUpperCase()]; -} diff --git a/src/payment/checkout/payment-form/utils/form-validators.js b/src/payment/checkout/payment-form/utils/form-validators.js index 249a15266..5e2bc208f 100644 --- a/src/payment/checkout/payment-form/utils/form-validators.js +++ b/src/payment/checkout/payment-form/utils/form-validators.js @@ -1,4 +1,18 @@ -import getStates from './countryStatesMap'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { State } from 'country-state-city'; + +export const getCountryStatesMap = (country) => { + const states = State.getStatesOfCountry(country); + + if (!states.length) { + return null; + } + const statesMap = {}; + states.forEach((state) => { + statesMap[state.isoCode] = state.name; + }); + return country && statesMap; +}; // eslint-disable-next-line import/prefer-default-export export function isPostalCodeRequired(selectedCountry) { @@ -39,7 +53,9 @@ export function getRequiredFields(fieldValues, isBulkOrder = false, enableStripe requiredFields.postalCode = postalCode; } - if (getStates(country)) { + // By using the country-state-city library to populate states, every country that + // has states from the ISO 3166-2 list will have states as a required field + if (getCountryStatesMap(country)) { requiredFields.state = state; }