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.

470 lines (469 loc) 19.5 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] = "1ae6882f-dc40-4e36-a22b-7ba817f64fc2", e._sentryDebugIdIdentifier = "sentry-dbid-1ae6882f-dc40-4e36-a22b-7ba817f64fc2"); } catch (e) {} import { a as Icon, r as useTranslation } from "./translation-BYvhW5zA.js"; import { ft as useAccountHolder, j as useToggleContext, k as Header } from "./resolveEnvironment-DNmu53Rr.js"; import { o as objectsDeepEqual } from "./AnalyticsContext-BFrJmsp0.js"; import { t as getNestedPropertyKeys } from "./getNestedPropertyKeys-Cd8HKupP.js"; import { c as CompanyTypesValues } from "./bafinUtils-BKq-s1Ka.js"; import { n as translateTranslatable } from "./utils-C1buyvRw.js"; import { n as Image } from "./unincorporated-partnership-nnTW3FnG.js"; import { t as ErrorPanel } from "./ErrorPanel-DGlzO81g.js"; import { t as createFormUtils } from "./formUtils-DVc3a6gf.js"; import { t as MaskedInput } from "./MaskedInput-_dYxgb7_.js"; import { A as taxInformationDefaultFieldMetadata, C as getTaxIdNumberOptions, S as getDefaultTaxIdNumberType, k as taxInformationDefaultFieldConfig, n as upsertTaxInfoForCountry, r as TaxIdNumberTypeSelector, t as removeTaxInfoForCountry, v as COUNTRIES_WITH_POSSIBLE_TRUSTEE_TAXID_ABSENT_V4 } from "./utils-C9vhUXDJ.js"; import { t as resolveFieldMetadata } from "./fieldConfigurations-CZCFghnK.js"; import { t as Field } from "./Field-DoXLi6EY.js"; import { t as Select } from "./Select-DUo2rOfa.js"; import { t as Checkbox } from "./Checkbox-D7BRIoQL.js"; import { t as TextArea } from "./TextArea-CT04Dg1N.js"; import { n as StateContextSetter, t as Dropzone } from "./Dropzone-BzDQ_VtL.js"; import { t as useForm } from "./useForm-C5EykRLY.js"; import { lazy, memo } from "preact/compat"; import { useCallback as useCallback$1, useEffect as useEffect$1, useMemo as useMemo$1, useRef as useRef$1, useState as useState$1 } from "preact/hooks"; import cx from "classnames"; import { Fragment as Fragment$1, jsx, jsxs } from "preact/jsx-runtime"; //#region src/utils/getFieldsWithExistingData.ts var getFieldsWithExistingData = (legalEntity, keyMapping, documentMapping, knownProblematicFields = []) => { const nonDocumentFields = getNestedPropertyKeys(legalEntity).map((field) => keyMapping[field]).filter(Boolean).filter((field) => !knownProblematicFields.includes(field)); if (documentMapping && legalEntity.documentDetails?.length) { const documentFields = legalEntity.documentDetails.map((document) => documentMapping[document.type]).filter(Boolean); return [...nonDocumentFields, ...documentFields]; } return nonDocumentFields; }; //#endregion //#region src/utils/mapping/documentGuidanceMap.ts /** * Returns the appropriate prefix for the document guidance image based on api documentType. */ var getDocumentGuidancePrefix = (documentType) => { switch (documentType) { case "passport": return "passport"; case "driversLicense": case "identityCard": return "id"; default: return "document"; } }; //#endregion //#region src/components/ui/molecules/DocumentGuidance/DocumentGuidance.tsx var documentMap = { "document-blurred": lazy(() => import("./document-blurred-iJRUsQR5.js")), "document-cutoff": lazy(() => import("./document-cut-off-DhZYZ56P.js")), "document-expired": lazy(() => import("./document-expired-CDV5yclN.js")), "document-front": lazy(() => import("./document-front-tg-w81rp.js")), "id-blurred": lazy(() => import("./id-blurred-VxdOFc9Z.js")), "id-cutoff": lazy(() => import("./id-cut-off-Lf8yjnWT.js")), "id-front": lazy(() => import("./id-front-C00ss5Ib.js")), "id-glare": lazy(() => import("./id-glare-DyvU9bwI.js")), "passport-blurred": lazy(() => import("./passport-blurred-0Uw8uYSJ.js")), "passport-cutoff": lazy(() => import("./passport-cut-off-BPzI2BwO.js")), "passport-front": lazy(() => import("./passport-front-D6Nky-ni.js")), "passport-glare": lazy(() => import("./passport-glare-DFSFxNy9.js")) }; function DocumentGuidance({ type, className }) { const { t } = useTranslation("ui"); const documentGuidanceItems = useMemo$1(() => { const guidancePrefix = getDocumentGuidancePrefix(type); const guidanceItems = [ { svgName: `${guidancePrefix}-front`, translationKey: "good", valid: true }, { svgName: `${guidancePrefix}-cutoff`, translationKey: "notCutOff" }, { svgName: `${guidancePrefix}-blurred`, translationKey: "notBlurry" }, guidancePrefix === "document" ? { svgName: `document-expired`, translationKey: "notExpired" } : { svgName: `${guidancePrefix}-glare`, translationKey: "noGlare" } ]; return type === "constitutionalDocument" ? guidanceItems.filter((item) => item.svgName !== "document-expired") : guidanceItems; }, [type]); return /* @__PURE__ */ jsx("div", { className: cx("adyen-kyc-document-guidance", className), children: /* @__PURE__ */ jsx("div", { className: "adyen-kyc-document-guidance__container", children: documentGuidanceItems.map((item) => /* @__PURE__ */ jsxs("figure", { className: "adyen-kyc-document-guidance__item", children: [/* @__PURE__ */ jsx(Image, { lazyLoadedImage: documentMap[item.svgName] }), /* @__PURE__ */ jsxs("figcaption", { className: "adyen-kyc-document-guidance__description", children: [item.valid ? /* @__PURE__ */ jsx(Icon, { name: "check", className: "adyen-kyc-document-guidance__icon" }) : /* @__PURE__ */ jsx(Icon, { name: "cross", className: "adyen-kyc-document-guidance__icon adyen-kyc-document-guidance__icon--invalid" }), /* @__PURE__ */ jsx("span", { className: "adyen-kyc-document-guidance__description-text", children: t(($) => $[item.translationKey]) })] })] }, item.translationKey)) }) }); } var DocumentUpload_module_default = { "description-field": "_description-field_1i0mx_1", descriptionField: "_description-field_1i0mx_1" }; //#endregion //#region src/components/Shared/forms/DocumentUpload/DocumentUpload.tsx var DESCRIPTION_CHARACTERS_LIMIT = 200; function DocumentUpload({ documentType, documentField, guidanceHeader, documentTypeSelect, guidanceContent, handleFieldChange, requiredFields, data: dataProp, valid: validProp, errors: errorProp, fieldValidationErrors: propFieldValidationErrors, supportsMultipleUploads, maxNumberOfFiles = 1, label, extraDropzoneDescription, hidePageCount, ...props }) { const { t } = useTranslation("common"); const [document, setDocument] = useState$1(); const form = useForm({ ...props, schema: ["description"], defaultData: dataProp, rules: props.validators, fieldProblems: propFieldValidationErrors }); const { handleChangeFor, triggerValidation, setData } = form; let { data, errors, valid, fieldProblems } = form; const formUtils = createFormUtils(props, t); const stateRef = useRef$1({ setState: null }); const isOptional = !(props?.formVerificationErrors && Object.keys(props?.formVerificationErrors)?.length === 0); useEffect$1(() => { if (isOptional) triggerValidation(); }, [dataProp]); useEffect$1(() => { stateRef.current.setState?.({ type: "addToState", value: { data, valid, errors, fieldProblems, dataStoreId: props.id, schema: requiredFields } }); }, [ data, valid, errors, fieldProblems ]); useEffect$1(() => { setDocument({ [documentField]: dataProp?.[documentField] }); setData("description", dataProp?.description); triggerValidation(); }, [dataProp]); if (handleFieldChange && requiredFields && propFieldValidationErrors) { data = dataProp ?? {}; errors = errorProp; valid = validProp; fieldProblems = propFieldValidationErrors; } const handleInput = (e) => { handleChangeFor("description")(e); if (handleFieldChange) handleFieldChange?.("description")(e); }; return /* @__PURE__ */ jsxs("div", { className: `adyen-kyc-company-document-upload adyen-kyc-company-document-upload--${documentField}`, children: [ /* @__PURE__ */ jsx(StateContextSetter, { owner: props.id, stateRef }), props.heading && /* @__PURE__ */ jsx(Header, { title: props.heading, description: props.description }), /* @__PURE__ */ jsx(ErrorPanel, { verificationErrors: props?.formVerificationErrors, validationErrors: propFieldValidationErrors, formUtils, id: "ariaErrorField" }), formUtils.isRequiredField(documentField) && /* @__PURE__ */ jsxs(Fragment$1, { children: [ guidanceHeader, documentTypeSelect && /* @__PURE__ */ jsx(Field, { name: "documentType", label: t(($) => $["selectDocumentType"]), children: (childProps) => /* @__PURE__ */ jsx(Select, { ...childProps, onChange: (e) => documentTypeSelect.setSelectedType(e.target.value), name: "documentType", placeholder: t(($) => $["selectDocument"]), selected: documentTypeSelect.selectedType, items: documentTypeSelect.options.map(({ id, name }) => ({ id, name: t(($) => $[name]) })) }) }), /* @__PURE__ */ jsx("div", { children: guidanceContent }), /* @__PURE__ */ jsx(DocumentGuidance, { type: documentType, className: "u-margin-bottom-24" }), /* @__PURE__ */ jsx(Dropzone, { defaultData: document ?? data, id: props.id, isOptional: isOptional || formUtils.isOptionalField(documentField), maxSize: documentType === "constitutionalDocument" ? 31457280 : void 0, name: documentField, shouldValidate: props.shouldValidate, handleFieldChange, errors, fieldValidationErrors: fieldProblems, valid, multiple: supportsMultipleUploads, maxNumberOfFiles: maxNumberOfFiles ?? 1, label, extraDropzoneDescription, hidePageCount }) ] }), formUtils.isRequiredField("description") && /* @__PURE__ */ jsx("div", { className: DocumentUpload_module_default.descriptionField, children: /* @__PURE__ */ jsx(Field, { name: "description", label: formUtils.getLabel("descriptionOptional"), errorMessage: formUtils.getErrorMessage("description", errors, fieldProblems), isValid: valid.description, children: (childProps) => /* @__PURE__ */ jsx(TextArea, { ...childProps, name: "description", value: data.description, maxLength: DESCRIPTION_CHARACTERS_LIMIT, onInput: handleInput, "aria-invalid": !valid.description, handleFieldChange: handleInput, errors, fieldValidationErrors: fieldProblems, valid }) }) }) ] }); } var MemoizedDocumentUpload = memo(DocumentUpload, (prevProps, nextProps) => prevProps.shouldValidate === nextProps.shouldValidate && objectsDeepEqual(prevProps.requiredFields, nextProps.requiredFields) && objectsDeepEqual(prevProps.errors, nextProps.errors) && objectsDeepEqual(prevProps.optionalFields, nextProps.optionalFields) && objectsDeepEqual(prevProps.data, nextProps.data) && objectsDeepEqual(prevProps.formVerificationErrors, nextProps.formVerificationErrors) && objectsDeepEqual(prevProps.fieldValidationErrors, nextProps.fieldValidationErrors) && objectsDeepEqual(prevProps.documentTypeSelect, nextProps.documentTypeSelect) && prevProps.documentField === nextProps.documentField && prevProps.name === nextProps.name && prevProps.country === nextProps.country && prevProps.companyType === nextProps.companyType); //#endregion //#region src/components/Shared/fields/TaxInformationField/TaxInformationField.tsx var TAX_INFORMATION_FIELD = [ "taxInformation", "exemptedFromTax", "isUen" ]; /** * Renders a dynamic form field for tax identification information, adapting its display * and behavior based on country and company type. It supports various tax ID input * scenarios, including two specialized handling for US Sole Proprietorships (where SSN option acts as exemption selection) * and Singapore Companies (where registration number (UEN) can be used for tax ID). */ function TaxInformationField({ data, valid, errors, labels, mask, placeholder, readonly, optional, shouldValidate, handleChangeFor, country, canExempt, registrationNumber, entityType, defaultData }) { const { isFeatureEnabled } = useToggleContext(); const { t } = useTranslation("common"); const { accountHolder } = useAccountHolder(); const currentCountryTaxInformation = useMemo$1(() => data?.taxInformation?.find((taxInfo) => taxInfo.country === country) ?? (!data.exemptedFromTax ? defaultData?.find((taxInfo) => taxInfo.country === country) : void 0), [ data?.taxInformation, data.exemptedFromTax, defaultData, country ]); const isUsSoleProp = country === "US" && entityType === CompanyTypesValues.SOLE_PROPRIETORSHIP; const options = getTaxIdNumberOptions(country, entityType); const showTaxIdExemptedOptions = canExempt && (options?.length ?? 0) > 1 || isUsSoleProp; const canSaveAbsentState = isFeatureEnabled("EnableRegistrationAndTaxAbsentStatesV4") && COUNTRIES_WITH_POSSIBLE_TRUSTEE_TAXID_ABSENT_V4.includes(country) && accountHolder === "aTrust"; const exemptionLabel = isUsSoleProp ? t(($) => $["socialSecurityNumber"]) : labels.exemptedFromTax; const shouldRenderInput = isUsSoleProp ? currentCountryTaxInformation?.type === "EIN" : !data.isUen; const taxIdNumberType = useMemo$1(() => { if (data.exemptedFromTax) return "exempted"; if (currentCountryTaxInformation?.type) return currentCountryTaxInformation.type; if (isUsSoleProp) return void 0; return getDefaultTaxIdNumberType(data.taxInformation, country, entityType); }, [ country, currentCountryTaxInformation?.type, data.exemptedFromTax, data.taxInformation, entityType, isUsSoleProp ]); const fieldMetadata = resolveFieldMetadata(taxInformationDefaultFieldConfig[country], { taxIdNumberType: currentCountryTaxInformation?.type, companyType: entityType }, taxInformationDefaultFieldMetadata); const updateTaxInfo = useCallback$1((mode, newEntry) => { const entry = canSaveAbsentState ? { ...newEntry, numberAbsent: newEntry.numberAbsent ?? false } : newEntry; handleChangeFor("taxInformation", mode)(upsertTaxInfoForCountry(data.taxInformation, country, entry)); }, [ canSaveAbsentState, country, data.taxInformation, handleChangeFor ]); const updateTaxType = useCallback$1((type, mode) => { updateTaxInfo(mode, { country, number: currentCountryTaxInformation?.number, type }); }, [ country, currentCountryTaxInformation?.number, updateTaxInfo ]); const resetToDefaultData = useCallback$1(() => { if (defaultData !== void 0) handleChangeFor("taxInformation", "input")(defaultData); }, [defaultData, handleChangeFor]); const removeCountryTaxInfo = useCallback$1(() => { handleChangeFor("taxInformation", "input")(removeTaxInfoForCountry(data.taxInformation, country)); }, [ country, data.taxInformation, handleChangeFor ]); const setUenAsTaxId = useCallback$1((uen) => { updateTaxInfo("blur", { country, number: uen ?? "", type: "UEN" }); }, [country, updateTaxInfo]); const addTaxAbsentStatus = useCallback$1((exempted) => { handleChangeFor("taxInformation", "input")(upsertTaxInfoForCountry(data.taxInformation, country, { country, numberAbsent: exempted })); }, [ country, data.taxInformation, handleChangeFor ]); const handleTaxNumberChange = useCallback$1((e, mode) => { updateTaxInfo(mode, { country, number: e ? typeof e === "string" ? e : e.currentTarget.value : void 0, type: currentCountryTaxInformation?.type ?? (taxIdNumberType !== "exempted" ? taxIdNumberType : void 0) }); }, [ country, currentCountryTaxInformation?.type, taxIdNumberType, updateTaxInfo ]); const handleTaxIdNumberTypeChange = useCallback$1((taxIdType) => { if (!canExempt && taxIdType === "exempted") throw Error(`${country} does not allow tax exemptions`); if (taxIdType === "exempted") { handleChangeFor("exemptedFromTax")(true); updateTaxType(void 0, "input"); return canSaveAbsentState ? addTaxAbsentStatus(true) : removeCountryTaxInfo(); } handleChangeFor("exemptedFromTax")(false); if (country === "SG") if (taxIdType === "UEN") { handleChangeFor("isUen")(true); setUenAsTaxId(registrationNumber); } else { handleChangeFor("isUen")(false); removeCountryTaxInfo(); } updateTaxType(taxIdType, "input"); }, [ addTaxAbsentStatus, canExempt, canSaveAbsentState, country, handleChangeFor, updateTaxType, registrationNumber, removeCountryTaxInfo, setUenAsTaxId ]); const handleExemptedFromTaxChange = useCallback$1((exempted) => { handleChangeFor("exemptedFromTax")(exempted); if (canSaveAbsentState) addTaxAbsentStatus(exempted); else removeCountryTaxInfo(); }, [ addTaxAbsentStatus, canSaveAbsentState, handleChangeFor, removeCountryTaxInfo ]); const prevCountryRef = useRef$1(country); useEffect$1(() => { if (!country || prevCountryRef.current === country) return; prevCountryRef.current = country; const hasSavedTaxId = defaultData?.find((taxId) => taxId.country === country); if (currentCountryTaxInformation && !hasSavedTaxId) return; if (entityType === CompanyTypesValues.SOLE_PROPRIETORSHIP) return; resetToDefaultData(); }, [country]); useEffect$1(() => { if (country === "SG" && data.isUen && registrationNumber && registrationNumber !== currentCountryTaxInformation?.number) setUenAsTaxId(registrationNumber); }, [ registrationNumber, currentCountryTaxInformation?.number, data.isUen, country, setUenAsTaxId ]); const isInitialized = useRef$1(false); if (!isInitialized.current && country === "SG" && !defaultData?.find((taxInfo) => taxInfo.country === country)?.number) { isInitialized.current = true; handleChangeFor("isUen")(false); } return /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx(TaxIdNumberTypeSelector, { country, companyType: entityType, setSelected: handleTaxIdNumberTypeChange, selected: taxIdNumberType, exemptedOptionLabel: showTaxIdExemptedOptions ? exemptionLabel : void 0, isUsSoleProp, errorMessage: isUsSoleProp ? errors.taxInformation : false }), shouldRenderInput && /* @__PURE__ */ jsx(MaskedInput, { type: "text", "aria-required": true, "aria-invalid": !valid.taxInformation, name: "taxInformation", label: labels.taxInformation ?? "", formatGuidance: translateTranslatable(t, fieldMetadata.guidanceText), value: currentCountryTaxInformation?.number ?? "", errorMessage: errors.taxInformation, placeholder, isValid: Boolean(valid.taxInformation) && !!currentCountryTaxInformation, onInput: (e) => handleTaxNumberChange(e, "input"), onBlur: (e) => handleTaxNumberChange(e, "blur"), disabled: data.exemptedFromTax || data.isUen, readonly, optional, shouldValidate, ...mask }), canExempt && !showTaxIdExemptedOptions && /* @__PURE__ */ jsx(Field, { name: "exemptedFromTax", children: (childProps) => /* @__PURE__ */ jsx(Checkbox, { ...childProps, "aria-required": false, "aria-invalid": false, name: "exemptedFromTax", label: labels.exemptedFromTax, value: "exemptedFromTax", checked: Boolean(data.exemptedFromTax), onChange: handleExemptedFromTaxChange }) }) ] }); } //#endregion export { getFieldsWithExistingData as a, DocumentGuidance as i, TaxInformationField as n, MemoizedDocumentUpload as r, TAX_INFORMATION_FIELD as t };