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;
}