UNPKG

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