Skip to content

Commit

Permalink
Validating postal code when billingAddressMode is partial (#1924)
Browse files Browse the repository at this point in the history
* feat: validating zipcode

* feat: added e2e tests

* fix: setting validation to null if no partial mode

* fix: removed log
  • Loading branch information
ribeiroguilherme authored Jan 3, 2023
1 parent 8fad8fc commit 18d6625
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 28 deletions.
3 changes: 3 additions & 0 deletions packages/e2e/tests/_models/CardComponent.page.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ export default class CardPage extends BasePage {
this.addressLabel = Selector(`${BASE_EL} .adyen-checkout__field--street .adyen-checkout__label`);
this.addressInput = Selector(`${BASE_EL} .adyen-checkout__field--street .adyen-checkout__input--street`);

this.postalCodeInput = Selector(`${BASE_EL} .adyen-checkout__field--postalCode .adyen-checkout__input--postalCode`);
this.postalCodeErrorText = Selector(`${BASE_EL} .adyen-checkout__field--postalCode .adyen-checkout__error-text`);

this.houseNumberLabelWithFocus = Selector(`${BASE_EL} .adyen-checkout__field--houseNumberOrName .adyen-checkout__label--focused`);

// Country dropdown
Expand Down
20 changes: 20 additions & 0 deletions packages/e2e/tests/cards/avs/avs.partial.clientScripts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const CLIENTSCRIPT_PARTIAL_AVS_WITH_COUNTRY = `
window.cardConfig = {
billingAddressRequired: true,
billingAddressMode: 'partial',
data: {
billingAddress: {
country: 'BR'
}
}
};
`;

const CLIENTSCRIPT_PARTIAL_AVS_WITHOUT_COUNTRY = `
window.cardConfig = {
billingAddressRequired: true,
billingAddressMode: 'partial'
};
`;

export { CLIENTSCRIPT_PARTIAL_AVS_WITH_COUNTRY, CLIENTSCRIPT_PARTIAL_AVS_WITHOUT_COUNTRY };
41 changes: 41 additions & 0 deletions packages/e2e/tests/cards/avs/avs.partial.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CARDS_URL } from '../../pages';
import { start } from '../../utils/commonUtils';
import { REGULAR_TEST_CARD } from '../utils/constants';
import CardComponentPage from '../../_models/CardComponent.page';
import { CLIENTSCRIPT_PARTIAL_AVS_WITH_COUNTRY, CLIENTSCRIPT_PARTIAL_AVS_WITHOUT_COUNTRY } from './avs.partial.clientScripts';

const TEST_SPEED = 1;
const INVALID_POSTALCODE = 'aaaaaaaaaa';

let cardPage = null;

fixture`Card with Partial AVS`.page(CARDS_URL).beforeEach(() => {
cardPage = new CardComponentPage();
});

test('should validate Postal Code if property data.billingAddress.country is provided', async t => {
await start(t, 2000, TEST_SPEED);

await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD);
await cardPage.cardUtils.fillDateAndCVC(t);

await t.typeText(cardPage.postalCodeInput, INVALID_POSTALCODE);
await t.click(cardPage.payButton);

await t.expect(cardPage.postalCodeErrorText.innerText).contains('Invalid format. Expected format: 99999999');
}).clientScripts({ content: CLIENTSCRIPT_PARTIAL_AVS_WITH_COUNTRY });

test('should not validate Postal Code if property data.billingAddress.country is not provided', async t => {
await t.setNativeDialogHandler(() => true);
await start(t, 2000, TEST_SPEED);

await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD);
await cardPage.cardUtils.fillDateAndCVC(t);

await t.typeText(cardPage.postalCodeInput, INVALID_POSTALCODE);
await t.click(cardPage.payButton);

// Check the value of the alert text
const history = await t.getNativeDialogHistory();
await t.expect(history[0].text).eql('Authorised');
}).clientScripts({ content: CLIENTSCRIPT_PARTIAL_AVS_WITHOUT_COUNTRY });
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getAddressHandler, getAutoJumpHandler, getErrorPanelHandler, getFocusHa
import { InstallmentsObj } from './components/Installments/Installments';
import { TouchStartEventObj } from './components/types';
import classNames from 'classnames';
import { getPartialAddressValidationRules } from '../../../internal/Address/validate';

const CardInput: FunctionalComponent<CardInputProps> = props => {
const sfp = useRef(null);
Expand Down Expand Up @@ -75,6 +76,8 @@ const CardInput: FunctionalComponent<CardInputProps> = props => {
const showBillingAddress = props.billingAddressMode !== AddressModeOptions.none && props.billingAddressRequired;

const partialAddressSchema = handlePartialAddressMode(props.billingAddressMode);
// Keeps the value of the country set initially by the merchant, before the Address Component mutates it
const partialAddressCountry = useRef<string>(partialAddressSchema && props.data?.billingAddress?.country);

const [storePaymentMethod, setStorePaymentMethod] = useState(false);
const [billingAddress, setBillingAddress] = useState<AddressData>(showBillingAddress ? props.data.billingAddress : null);
Expand Down Expand Up @@ -427,10 +430,11 @@ const CardInput: FunctionalComponent<CardInputProps> = props => {
// For Store details
handleOnStoreDetails={setStorePaymentMethod}
// For Address
billingAddress={billingAddress}
handleAddress={handleAddress}
billingAddressRef={billingAddressRef}
billingAddress={billingAddress}
billingAddressValidationRules={partialAddressSchema && getPartialAddressValidationRules(partialAddressCountry.current)}
partialAddressSchema={partialAddressSchema}
handleAddress={handleAddress}
//
iOSFocusedField={iOSFocusedField}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const CardFieldsWrapper = ({
billingAddressRequired,
billingAddressRequiredFields,
billingAddressAllowedCountries,
billingAddressValidationRules = null,
brandsConfiguration,
enableStoreDetails,
hasCVC,
Expand Down Expand Up @@ -171,6 +172,7 @@ export const CardFieldsWrapper = ({
allowedCountries={billingAddressAllowedCountries}
requiredFields={billingAddressRequiredFields}
ref={billingAddressRef}
validationRules={billingAddressValidationRules}
specifications={partialAddressSchema}
iOSFocusedField={iOSFocusedField}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Language from '../../../../language/Language';
import { BinLookupResponse, BrandConfiguration, CardBrandsConfiguration, CardConfiguration, DualBrandSelectElement } from '../../types';
import { PaymentAmount } from '../../../../types';
import { AddressData, PaymentAmount } from '../../../../types';
import { InstallmentOptions } from './components/types';
import { ValidationResult } from '../../../internal/PersonalDetails/types';
import { CVCPolicyType, DatePolicyType } from '../../../internal/SecuredFields/lib/types';
Expand Down Expand Up @@ -34,7 +34,7 @@ export interface CardInputErrorState {

export interface CardInputDataState {
holderName?: string;
billingAddress?: object;
billingAddress?: AddressData;
socialSecurityNumber?: string;
taxNumber?: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
SSN_CARD_NAME_BOTTOM,
SSN_CARD_NAME_TOP
} from './layouts';
import { StringObject } from '../../../internal/Address/types';
import { AddressSpecifications, StringObject } from '../../../internal/Address/types';
import { PARTIAL_ADDRESS_SCHEMA } from '../../../internal/Address/constants';
import { InstallmentsObj } from './components/Installments/Installments';
import { SFPProps } from '../../../internal/SecuredFields/SFP/types';
Expand Down Expand Up @@ -205,6 +205,6 @@ export const extractPropsForSFP = (props: CardInputProps) => {
} as SFPProps; // Can't set as return type on fn or it will complain about missing, mandatory, props
};

export const handlePartialAddressMode = (addressMode: AddressModeOptions) => {
return addressMode == AddressModeOptions.partial ? PARTIAL_ADDRESS_SCHEMA : [];
export const handlePartialAddressMode = (addressMode: AddressModeOptions): AddressSpecifications | null => {
return addressMode == AddressModeOptions.partial ? PARTIAL_ADDRESS_SCHEMA : null;
};
1 change: 1 addition & 0 deletions packages/lib/src/components/internal/Address/Address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export default function Address(props: AddressProps) {

Address.defaultProps = {
countryCode: null,
validationRules: null,
data: {},
onChange: () => {},
visibility: 'editable',
Expand Down
2 changes: 1 addition & 1 deletion packages/lib/src/components/internal/Address/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const ADDRESS_SPECIFICATIONS: AddressSpecifications = {
}
};

export const PARTIAL_ADDRESS_SCHEMA = {
export const PARTIAL_ADDRESS_SCHEMA: AddressSpecifications = {
default: {
labels: {
[POSTAL_CODE]: 'zipCode'
Expand Down
61 changes: 41 additions & 20 deletions packages/lib/src/components/internal/Address/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ const createPatternByDigits = (digits: number) => {
};
};

const validatePostalCode = (val: string, countryCode: string, validatorRules: ValidatorRules) => {
if (countryCode) {
// Dynamically create errorMessage
(validatorRules.postalCode as ValidatorRule).errorMessage = {
translationKey: 'invalidFormatExpects',
translationObject: {
values: {
format: countrySpecificFormatters[countryCode]?.postalCode.format || null
}
}
};

if (isEmpty(val)) return null;

const pattern = postalCodePatterns[countryCode]?.pattern;
return pattern ? pattern.test(val) : !!val; // No pattern? Accept any, filled, value.
}
// Default rule
return isEmpty(val) ? null : true;
};

const postalCodePatterns = {
AT: createPatternByDigits(4),
AU: createPatternByDigits(4),
Expand Down Expand Up @@ -52,32 +73,32 @@ const postalCodePatterns = {
US: createPatternByDigits(5)
};

/**
* Validates only postalCode property. As the partial address form does not have the country selector, the country value
* must be informed beforehand and can't be picked up from the form context
*
* @param country - Country that will be used to validate postal code
*/
export const getPartialAddressValidationRules = (country: string): ValidatorRules => {
const validationRules: ValidatorRules = {
postalCode: {
modes: ['blur'],
validate: val => {
return validatePostalCode(val, country, validationRules);
},
errorMessage: ERROR_CODES[ERROR_MSG_INCOMPLETE_FIELD]
}
};
return validationRules;
};

export const getAddressValidationRules = (specifications): ValidatorRules => {
const addressValidationRules: ValidatorRules = {
postalCode: {
modes: ['blur'],
validate: (val, context) => {
const country = context.state.data.country;

// Country specific rule
if (country) {
// Dynamically create errorMessage
(addressValidationRules.postalCode as ValidatorRule).errorMessage = {
translationKey: 'invalidFormatExpects',
translationObject: {
values: {
format: countrySpecificFormatters[country]?.postalCode.format || null
}
}
};

if (isEmpty(val)) return null;

const pattern = postalCodePatterns[country]?.pattern;
return pattern ? pattern.test(val) : !!val; // No pattern? Accept any, filled, value.
}
// Default rule
return isEmpty(val) ? null : true;
return validatePostalCode(val, country, addressValidationRules);
},
errorMessage: ERROR_CODES[ERROR_MSG_INCOMPLETE_FIELD]
},
Expand Down

0 comments on commit 18d6625

Please sign in to comment.