@adyen/kyc-components
Version:
This guide assumes that you have already an account with Adyen. A legalEntity needs to be created, and you need to have a `legalEntityId` to instatiate a Component.
596 lines (595 loc) • 23.6 kB
JavaScript
try {
let e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {}, n = new e.Error().stack;
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "e6bf234a-1401-437b-8b94-8f28a6060db3", e._sentryDebugIdIdentifier = "sentry-dbid-e6bf234a-1401-437b-8b94-8f28a6060db3");
} catch (e) {}
import { a as Icon, r as useTranslation } from "./translation-BFxyJ1c5.js";
import { n as httpGet, r as httpPost, s as useApiContext } from "./http-D1NDkBxF.js";
import { t as useAnalyticsContext, u as entriesOf } from "./useAnalyticsContext-BVFDMrVE.js";
import { t as useToggleContext } from "./useToggleContext-DaQUBF8O.js";
import { a as translateTranslatable } from "./utils-B807QaDx.js";
import { p as getMaxLengthByFieldAndCountry } from "./validatorUtils-DRapRJ6z.js";
import { t as CountryCodes } from "./country-code-CX5KqMBr.js";
import { t as useDataset } from "./useDataset-ZHrWhmsh.js";
import { t as datasetIdentifier } from "./datasetUtil-Zd4TCTDn.js";
import { t as createFormUtils } from "./formUtils-DCvL3uZG.js";
import { c as COUNTRIES_WITH_STATES_DATASET, o as CONDENSED_ADDRESS_FIELDS, r as getKeyForField, s as COUNTRIES_WITH_HOUSE_NUMBER_FIRST, t as getAddressSchemaForCountry } from "./utils-CfTVU2Hq.js";
import { a as validateNotEmptyOnBlur } from "./patternValidators-BaQxw3ki.js";
import { a as addressFormatters, i as preventPoBoxValidation, l as useAddressCleanseImperatively, n as addressValidationRulesV4, o as countrySpecificFormatters, t as addressValidationRules } from "./validate-qd_17no4.js";
import { t as Field } from "./Field-pcJkjIG_.js";
import { t as Select } from "./Select-CcSRI-H0.js";
import { r as useDebouncedCallback } from "./debouncedInputEvent-Dxv4-RAv.js";
import { t as useForm } from "./useForm-pUkvCLc9.js";
import { t as InputText } from "./InputText-C30dZxS4.js";
import { n as CountryField } from "./CountryField-Dh4DfjBf.js";
import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
import { useQuery } from "@tanstack/preact-query";
//#region src/api/providersConfiguration/useProviderStatus.ts
var getProvidersStatus = async (legalEntityId, baseUrl, data) => {
return httpPost({
baseUrl,
path: `legalEntities/${legalEntityId}/configurations/providers/status`
}, data);
};
/**
* Asks the backend if 3rd party providers like Loqate and Onfido are available.
*
* Bank verification providers are not included, as these are handled by Open Banking.
*/
var useProviderStatus = (data, options) => {
const { rootLegalEntityId, baseUrl } = useApiContext();
return useQuery({
queryKey: ["providersStatus", data],
queryFn: () => getProvidersStatus(rootLegalEntityId.value, baseUrl.value, data),
...options
});
};
//#endregion
//#region src/components/Business/forms/AdditionalInformation/utils.ts
/**
* Parses the nested trusted fields on a legal entity e.g. ['registrationAddress.address', 'registrationAddress.postalCode']
*
* @param field - field in formSchema
* @param data - data in useForm
* @param trustedFields - trustedFields on legal entity
* @returns An array of the nested field keys that are trusted
*/
var getNestedTrustedFields = (field, data, trustedFields) => {
if (trustedFields && data?.[field] && typeof data[field] === "object") return trustedFields.reduce((fields, trustedField) => {
const splitField = trustedField.split(".");
if (splitField[0] === field) return [...fields, splitField[1]];
return fields;
}, []);
return [];
};
var mapAddressData = (verifiedBusiness) => ({
address: verifiedBusiness.street ?? "",
otherAddressInformation: verifiedBusiness.street2 ?? "",
city: verifiedBusiness.city ?? "",
postalCode: verifiedBusiness.postalCode ?? "",
stateOrProvince: verifiedBusiness.state ?? ""
});
var isDateOlderThanAYear = (dateString) => {
if (!dateString) return false;
const inputDate = new Date(dateString);
if (isNaN(inputDate.getTime())) {
console.error(`Error: Invalid date format or value for: ${dateString}`);
return false;
}
const oneYearAgo = /* @__PURE__ */ new Date();
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
return inputDate.getTime() < oneYearAgo.getTime();
};
//#endregion
//#region src/api/address/useRetrieveAddress.ts
var retrieveAddress = async (legalEntityId, baseUrl, addressId) => {
return httpGet({
baseUrl,
path: `legalEntities/${legalEntityId}/addresses/${addressId}`
});
};
/**
* @param addressId
* @param options additional options passed to Tanstack Query, eg; refetchInterval for polling
*/
var useRetrieveAddress = (addressId, options) => {
const { rootLegalEntityId, baseUrl } = useApiContext();
return useQuery({
queryKey: ["retrieveAddress", addressId],
queryFn: () => retrieveAddress(rootLegalEntityId.value, baseUrl.value, addressId),
...options
});
};
//#endregion
//#region src/api/address/useSearchAddress.ts
var searchAddress = async (legalEntityId, baseUrl, data) => {
return httpPost({
baseUrl,
path: `legalEntities/${legalEntityId}/addresses`
}, data);
};
/**
* @param data
* @param options additional options passed to Tanstack Query, eg; refetchInterval for polling
*/
var useSearchAddress = (data, options) => {
const { rootLegalEntityId, baseUrl } = useApiContext();
return useQuery({
queryKey: ["searchAddress", data],
queryFn: () => searchAddress(rootLegalEntityId.value, baseUrl.value, data),
...options
});
};
//#endregion
//#region src/components/Shared/forms/Address/SearchAddressItem.tsx
var getFormattedText = (highlightItems, text) => {
const formattedText = [];
let lastHighlightIndex = 0;
if (highlightItems[0] === "") return [text];
highlightItems.forEach((highlightValue) => {
const highlightRange = highlightValue.split("-");
const highlightInit = parseInt(highlightRange[0]);
const highlightEnd = parseInt(highlightRange[1]);
if (lastHighlightIndex < highlightInit) {
const remain = text.substring(lastHighlightIndex, highlightInit);
formattedText.push(`${remain}`);
}
const boldText = text.substring(highlightInit, highlightEnd);
formattedText.push(/* @__PURE__ */ jsx("span", {
className: "adyen-kyc-dropdown-search-element__description--highlight",
children: boldText
}));
lastHighlightIndex = highlightEnd;
});
if (lastHighlightIndex < text.length) formattedText.push(`${text.substring(lastHighlightIndex)}`);
return formattedText;
};
var getHighlightedText = ({ text = "", highlight = "", description = "" }) => {
let formattedText = [];
if (highlight === "") if (description) formattedText.push(`${text}, ${description}`);
else formattedText.push(text);
else {
const highlightItems = highlight.split(";");
formattedText = getFormattedText(highlightItems[0].split(","), text);
if (highlightItems.length > 1) {
formattedText.push(`, `);
formattedText.push(/* @__PURE__ */ jsx(Fragment, { children: getFormattedText(highlightItems[1].split(","), description) }));
} else if (description) formattedText.push(`, ${description}`);
}
return formattedText;
};
var SearchAddressItem = (item) => {
const { t } = useTranslation("common");
const { addressAmount } = item;
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
className: "adyen-kyc-dropdown-search-element__description",
children: getHighlightedText(item)
}), addressAmount && /* @__PURE__ */ jsxs("div", {
className: "adyen-kyc-dropdown-search-element__addresses",
children: [
addressAmount,
" ",
/* @__PURE__ */ jsxs("span", {
className: "adyen-kyc-dropdown-search-element__addresses--text",
children: [" ", t(($) => $["addresses"])]
}),
/* @__PURE__ */ jsx(Icon, {
name: "arrow-right",
className: "adyen-kyc-dropdown-search-element__addresses--icon"
})
]
})] });
};
//#endregion
//#region src/components/Shared/forms/Address/SearchAddress.tsx
var SearchAddress = ({ addressText, country, labelKey, errorMessage, isValid, readOnly, optional, handleBlur, onAddressTextChange, onAutofillAddress }) => {
const { t } = useTranslation("common");
const userEvents = useAnalyticsContext();
const [textToSearch, setTextToSearch] = useState();
const [showSearchList, setShowSearchList] = useState(false);
const [selectedAddress, setSelectedAddress] = useState();
const [drilldownContainerId, setDrilldownContainerId] = useState();
const debouncedSetTextToSearch = useDebouncedCallback(setTextToSearch, 500);
const searchAddressQuery = useSearchAddress({
text: textToSearch,
country,
container: drilldownContainerId
}, { enabled: showSearchList && Boolean(textToSearch) && Boolean(country) });
const retrieveAddressQuery = useRetrieveAddress(selectedAddress?.id ?? "", { enabled: Boolean(selectedAddress) });
const results = useMemo(() => searchAddressQuery.data?.results?.map(formatAddressResult) ?? [], [searchAddressQuery.data?.results]);
useEffect(() => {
if (retrieveAddressQuery.data) {
userEvents.addPageEvent("Success", {
actionType: "select",
actionLevel: "field",
returnType: "backend",
returnValue: "retrieveAddress"
});
onAutofillAddress(retrieveAddressQuery.data);
}
}, [retrieveAddressQuery.data]);
const onSelectItem = useCallback((selected) => {
const selectedResult = results.find((i) => i.id === selected.id);
if (!selectedResult) return;
if (selectedResult.type === "Address") {
setSelectedAddress(selected);
setDrilldownContainerId(void 0);
} else {
setDrilldownContainerId(selectedResult.id);
setTextToSearch(`${selectedResult.text}`);
setTimeout(() => setShowSearchList(true));
}
}, [results]);
const items = useMemo(() => results.map((item) => ({
...item,
name: item.text
})), [results]);
return /* @__PURE__ */ jsx(Field, {
name: "address",
label: translateTranslatable(t, labelKey),
errorMessage,
isValid,
optional,
children: (childProps) => /* @__PURE__ */ jsx(Select, {
...childProps,
name: "address",
filterable: "prefiltered",
items,
selected: selectedAddress,
loading: searchAddressQuery.isLoading || retrieveAddressQuery.isLoading,
onSelect: onSelectItem,
getSearchItem: SearchAddressItem,
behaviour: "textIsData",
text: addressText,
setText: (text) => {
onAddressTextChange(text);
debouncedSetTextToSearch(text);
},
showList: showSearchList,
setShowList: setShowSearchList,
readonly: Boolean(readOnly),
placeholder: t(($) => $["address__placeHolder"]),
onBlur: handleBlur
})
});
};
var formatAddressResult = (item) => {
if (item.description.includes("Addresses")) {
const splitDescription = item.description.split(" - ");
if (splitDescription.length === 1) return {
...item,
description: "",
addressAmount: splitDescription[0].replace("Addresses", "")
};
return {
...item,
description: splitDescription[0],
addressAmount: splitDescription[1].replace("Addresses", "")
};
}
return item;
};
//#endregion
//#region src/components/Shared/fields/StateField/fieldConfig.ts
var defaultFieldMetadata = {
label: "stateOrProvince",
validators: validateNotEmptyOnBlur
};
var defaultFieldConfig = {
[CountryCodes.Australia]: {
label: "state",
validators: validateNotEmptyOnBlur
},
[CountryCodes.Brazil]: {
label: "state",
validators: validateNotEmptyOnBlur
},
[CountryCodes.Canada]: {
label: "provinceOrTerritory",
validators: validateNotEmptyOnBlur
},
[CountryCodes.NewZealand]: {
label: "region",
validators: validateNotEmptyOnBlur
},
[CountryCodes.UnitedStates]: ({ isBusiness }) => ({
label: isBusiness ? "whereIsYourBusinessRegistered" : "state",
validators: validateNotEmptyOnBlur
})
};
/**
* Returns the placeholder text for state field based on country.
*/
var getStatePlaceholderText = (t, { country }) => {
switch (country) {
case CountryCodes.Australia:
case CountryCodes.Brazil: return t(($) => $["selectState"]);
case CountryCodes.Canada: return t(($) => $["selectProvinceOrTerritory"]);
case CountryCodes.UnitedStates: return t(($) => $[`selectAState`]);
default: return t(($) => $["stateOrProvince__placeHolder"]);
}
};
//#endregion
//#region src/components/Shared/fields/StateField/StateField.tsx
var STATE_FIELD = ["stateOrProvince"];
function StateField({ data, valid, errors, labels, readonly, handleChangeFor, selectedCountry, handleBlur }) {
const { t } = useTranslation("common");
const { dataset: states, loaded } = useDataset(datasetIdentifier.state(selectedCountry), !selectedCountry || !COUNTRIES_WITH_STATES_DATASET.includes(selectedCountry));
const labelKey = getKeyForField("stateOrProvince", selectedCountry);
if (!loaded || !states.length) return null;
return /* @__PURE__ */ jsx(Field, {
name: "stateOrProvince",
label: labels?.stateOrProvince || t(($) => $[labelKey]),
errorMessage: errors.stateOrProvince,
isValid: valid.stateOrProvince,
dataTestId: "stateOrProvince",
children: (childProps) => /* @__PURE__ */ jsx(Select, {
...childProps,
name: "stateOrProvince",
selected: data.stateOrProvince,
placeholder: getStatePlaceholderText(t, { country: selectedCountry }),
items: states,
readonly: readonly && !!data.stateOrProvince,
onChange: handleChangeFor("stateOrProvince"),
onBlur: handleBlur
})
});
}
//#endregion
//#region src/components/Shared/forms/Address/AddressField.tsx
/**
* USAGE: Specifically defined as a util to provide a wrapper for fields created within the Address component
*
* NOT TO BE USED: if you just want to add a Country or State dropdown outside of an Address component
* - then you should implement <CountryField> or <StateField> directly
*/
function AddressField({ schema, data, valid, errors, fieldProblems, fieldName, trimOnBlur, hideField, maxlength, readOnly, optional, handleChangeFor, handleBlur }) {
const { t } = useTranslation("common");
const { data: providerStatus } = useProviderStatus({ providers: ["address"] });
const addressSearchEnabled = providerStatus?.statuses?.address?.enabled ?? false;
const formUtils = createFormUtils({ data }, t);
const errorMessage = formUtils.getErrorMessage(fieldName, errors ?? {}, fieldProblems);
const value = data[fieldName];
const labelKey = getKeyForField(fieldName, data.country);
const setAddressFieldFromFoundAddress = useCallback(({ houseNumberOrName, street, line1 }) => {
const hasHouseNumber = !!houseNumberOrName;
if (!street && line1) return handleChangeFor("address", "input")(line1);
if (hasHouseNumber) {
const formattedAddress = (data.country ? COUNTRIES_WITH_HOUSE_NUMBER_FIRST.includes(data.country) : false) ? `${houseNumberOrName} ${street}` : `${street} ${houseNumberOrName}`;
return handleChangeFor("address", "input")(formattedAddress);
}
return handleChangeFor("address", "input")(street);
}, [data.country, handleChangeFor]);
const onAutofillAddress = useCallback((foundAddress) => {
schema?.forEach((field) => {
switch (field) {
case "country":
handleChangeFor("country", "input")(foundAddress.country ?? " ");
break;
case "address":
setAddressFieldFromFoundAddress(foundAddress);
break;
case "otherAddressInformation":
handleChangeFor("otherAddressInformation", "blur")(foundAddress.street2 ?? " ");
break;
case "city":
handleChangeFor("city", "input")(foundAddress.city ?? " ");
break;
case "postalCode":
handleChangeFor("postalCode", "input")(foundAddress.postalCode ?? " ");
break;
case "stateOrProvince":
handleChangeFor("stateOrProvince", "input")(foundAddress.stateOrProvince ?? " ");
break;
default: break;
}
});
}, [
handleChangeFor,
schema,
setAddressFieldFromFoundAddress
]);
if (hideField) return null;
switch (fieldName) {
case "address": return addressSearchEnabled ? /* @__PURE__ */ jsx(SearchAddress, {
addressText: data.address,
country: data.country,
labelKey,
errorMessage,
isValid: valid?.address,
readOnly,
optional,
handleBlur: (e) => {
handleChangeFor("address", "blur")(data.address);
handleBlur?.(e);
},
onAddressTextChange: handleChangeFor("address", "input"),
onAutofillAddress
}) : /* @__PURE__ */ jsx(Field, {
name: fieldName,
label: t(($) => $[labelKey]),
errorMessage,
isValid: valid?.[fieldName],
optional,
children: (childProps) => /* @__PURE__ */ jsx(InputText, {
...childProps,
name: fieldName,
value,
onInput: handleChangeFor(fieldName, "input"),
onBlur: (e) => {
handleChangeFor(fieldName, "blur")(e);
handleBlur?.(e);
},
maxLength: maxlength,
"aria-required": true,
"aria-invalid": !valid?.[fieldName],
trimOnBlur,
readonly: Boolean(readOnly)
})
});
case "country": return /* @__PURE__ */ jsx(CountryField, {
data: { country: value },
valid: { country: valid?.country },
errors: { country: errorMessage },
labels: { country: formUtils.getLabel(fieldName, labelKey) },
readonly: Boolean(readOnly),
handleChangeFor,
handleBlur
});
case "stateOrProvince": return /* @__PURE__ */ jsx(StateField, {
data: { stateOrProvince: value },
valid: { stateOrProvince: valid?.stateOrProvince },
errors: { stateOrProvince: errorMessage },
labels: { stateOrProvince: formUtils.getLabel(fieldName, labelKey) },
readonly: Boolean(readOnly),
selectedCountry: data.country,
handleChangeFor,
handleBlur
});
default: return /* @__PURE__ */ jsx(Field, {
name: fieldName,
label: t(($) => $[labelKey]),
errorMessage,
isValid: valid?.[fieldName],
optional,
children: (childProps) => /* @__PURE__ */ jsx(InputText, {
...childProps,
name: fieldName,
value: fieldName === "postalCode" ? value?.toUpperCase() : value,
onInput: handleChangeFor(fieldName, "input"),
onBlur: (e) => {
handleChangeFor(fieldName, "blur")(e);
handleBlur?.(e);
},
maxLength: maxlength,
"aria-required": true,
"aria-invalid": !valid?.[fieldName],
trimOnBlur,
readonly: Boolean(readOnly)
})
});
}
}
//#endregion
//#region src/components/Shared/forms/Address/Address.tsx
function Address({ data: dataProp, labels, readOnly, hideCountry, fieldValidationErrors: propFieldValidationErrors, allFields, requiredFields, optionalFields = ["otherAddressInformation"], readOnlyFields, obscuredFields, trustedFields, shouldValidate, onChange, verifiedAddress, condensed, handleFieldChange, errors: errorProp, valid: validProp, addressType }) {
const { t } = useTranslation("common");
const { isFeatureEnabled } = useToggleContext();
const isStrictNameAndAddressValidationEnabled = isFeatureEnabled("StrictNameAndAddressValidationV4");
const preventPoBoxes = addressType === "operationalAddress";
const schema = useMemo(() => {
if (requiredFields?.length) return requiredFields?.includes("stateOrProvince") || dataProp?.country && COUNTRIES_WITH_STATES_DATASET.includes(dataProp?.country) ? requiredFields : requiredFields?.filter((field) => field !== "stateOrProvince");
return dataProp?.country && COUNTRIES_WITH_STATES_DATASET.includes(dataProp?.country) ? getAddressSchemaForCountry(dataProp?.country) : getAddressSchemaForCountry(dataProp?.country).filter((ad) => ad !== "stateOrProvince");
}, [dataProp?.country, requiredFields]);
const rules = useMemo(() => addressValidationRules(dataProp?.country), [dataProp?.country]);
const rulesV4 = useMemo(() => addressValidationRulesV4(dataProp?.country, t, isFeatureEnabled), [dataProp?.country, t]);
const cleanseAddress = useAddressCleanseImperatively();
const asyncRules = useMemo(() => preventPoBoxes ? preventPoBoxValidation(cleanseAddress) : void 0, [cleanseAddress, preventPoBoxes]);
const form = useForm({
schema,
defaultData: dataProp,
formatters: addressFormatters,
rules: isStrictNameAndAddressValidationEnabled ? rulesV4 : rules,
asyncRules,
fieldProblems: propFieldValidationErrors,
optionalFields,
obscuredFields,
trustedFields,
shouldValidate
});
let { schema: formSchema, data: formData, errors: formErrors, valid: formValid, fieldProblems: formFieldProblems, handleChangeFor } = form;
const { isValid } = form;
if (handleFieldChange && requiredFields && propFieldValidationErrors) {
const allFields = [...requiredFields, ...optionalFields];
const fieldsInOrder = getAddressSchemaForCountry(dataProp?.country).filter((field) => allFields.includes(field));
handleChangeFor = handleFieldChange;
formData = dataProp ?? {};
formErrors = errorProp;
formValid = validProp;
formFieldProblems = propFieldValidationErrors;
formSchema = fieldsInOrder;
}
/**
* Updates local useForm data with either the verified address data or the default data
*/
useEffect(() => {
if (verifiedAddress) entriesOf(verifiedAddress).forEach(([key, value]) => {
if (formData[key] !== value) handleChangeFor(key, "input")(value);
});
}, [verifiedAddress]);
const formUtils = createFormUtils({
data: dataProp,
labels,
readOnly,
allFields,
requiredFields: schema,
optionalFields,
readOnlyFields,
trustedFields
}, t);
useEffect(() => {
if (hideCountry && dataProp?.country && dataProp.country !== formData.country) schema.forEach((field) => {
return field === "country" ? handleChangeFor("country", "input")(dataProp.country) : handleChangeFor(field, "input")(" ");
});
}, [dataProp?.country, hideCountry]);
useEffect(() => {
onChange?.({
schema: formSchema,
data: formData,
valid: formValid,
errors: formErrors,
fieldProblems: formFieldProblems,
isValid
});
}, [
formData,
formErrors,
formFieldProblems,
formSchema,
formValid,
isValid
]);
const createField = (fieldName) => {
if (!formUtils.isRequiredField(fieldName) && !formUtils.isOptionalField(fieldName)) return null;
const hideField = fieldName === "country" && hideCountry;
return /* @__PURE__ */ jsx(AddressField, {
schema: formSchema,
data: formData,
valid: formValid,
errors: formErrors,
fieldProblems: formFieldProblems,
fieldName,
maxlength: getMaxLengthByFieldAndCountry(countrySpecificFormatters, fieldName, formData.country, true),
hideField,
readOnly: formUtils.isReadOnly(fieldName) || formUtils.isTrusted(fieldName),
optional: formUtils.isOptionalField(fieldName),
trimOnBlur: true,
handleChangeFor
}, fieldName);
};
if (condensed && formData?.country) {
const condensedFields = CONDENSED_ADDRESS_FIELDS[formData.country] ?? [];
switch (formData?.country) {
case "US": return /* @__PURE__ */ jsxs("fieldset", {
"aria-label": t(($) => $[addressType]),
className: "adyen-kyc-address",
children: [formSchema.filter((field) => !condensedFields.includes(field)).map((filteredField) => createField(filteredField)), condensedFields.length && /* @__PURE__ */ jsx("div", {
className: "adyen-kyc-address__condensed-fields",
children: condensedFields.map((field) => createField(field))
})]
});
default: return /* @__PURE__ */ jsx("fieldset", {
"aria-label": t(($) => $[addressType]),
className: "adyen-kyc-address",
children: formSchema.map((field) => createField(field))
});
}
}
return /* @__PURE__ */ jsx("fieldset", {
"aria-label": t(($) => $[addressType]),
className: "adyen-kyc-address",
children: formSchema.map((field) => createField(field))
});
}
//#endregion
export { defaultFieldMetadata as a, mapAddressData as c, defaultFieldConfig as i, useProviderStatus as l, STATE_FIELD as n, getNestedTrustedFields as o, StateField as r, isDateOlderThanAYear as s, Address as t };