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.

280 lines (279 loc) 13.9 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] = "5d250c4a-9efe-418d-9ca5-2faf7b697d3b", e._sentryDebugIdIdentifier = "sentry-dbid-5d250c4a-9efe-418d-9ca5-2faf7b697d3b"); } catch (e) {} import { o as createLogger } from "./translation-BFxyJ1c5.js"; import { d as keysOf } from "./useAnalyticsContext-BVFDMrVE.js"; import { E as dataMissingRemediationsToFieldsMap, h as businessDetailsApiKeyMapping, r as remediationHasFileUploadField, s as individualApiKeyMapping } from "./processCapabilities-DlZY9-Jc.js"; import { t as TrustedFieldsProviders } from "./trusted-fields-provider-BHbX2hsp.js"; import { t as formDebugInfo } from "./debugStore-bZ_bkJeS.js"; //#region src/utils/addLinkedFields.ts var linkedFieldsMap = { "additionalInformation.vatNumber": ["additionalInformation.exemptedFromVat", "additionalInformation.vatAbsenceReason"] }; /** * This function is used to process fields linked to other fields that have custom rules that can return either REQUIRED or OPTIONAL * depending on the value of their links. An example of this is the Vat number, this will be REQUIRED when exemption is false but OPTIONAL when exemption is true. * This needs to be done due to functions like isFieldRequiredToRemediateDataMissingError, which do not take into account the custom rules and instead mark * these fields as required regardless of the rule. * * @param field - Field being processed * @param formId - Form this field is associated to * @param customRules - The rules associated to the form flow * @param scenarioConfiguration - The static configuration returned from the scenario, i.e. L1, L, etc * @param dataMissingErrors - Missing data problems on the LE response * @param formFieldsWithExistingData - The current fields that have existing data within the LE response */ function calculateLinkedRequiredFields({ field, formId, customRules, scenarioConfiguration, dataMissingErrors, formFieldsWithExistingData }) { const tldsField = makeTLDSFieldName(formId, field); if (Boolean(linkedFieldsMap[tldsField])) { const linkedFields = linkedFieldsMap[tldsField]; const fieldSettings = scenarioConfiguration?.[field]; const isFieldRequiredBySettings = fieldSettings && isFieldRequired(fieldSettings, customRules); const isFieldPopulated = formFieldsWithExistingData?.includes(field); const isFieldNeededToRemediateDataMissingError = isFieldRequiredToRemediateDataMissingError(formId, field, dataMissingErrors); const areLinkedFieldsPopulated = linkedFields?.some((linkedField) => { const linkedFieldKey = linkedField.split(".")[1]; return formFieldsWithExistingData?.includes(linkedFieldKey); }); if (!(isFieldRequiredBySettings || isFieldPopulated || isFieldNeededToRemediateDataMissingError || areLinkedFieldsPopulated)) return { isRequiredFromLinkedField: false, isOptionalFromLinkedField: false }; switch (field) { case "vatNumber": { const processedRule = customRules["countryUsesVat"]?.(); return { isRequiredFromLinkedField: processedRule === "REQUIRED", isOptionalFromLinkedField: processedRule === "OPTIONAL" }; } default: break; } } const rootLinkedField = Object.entries(linkedFieldsMap).find(([, linkedFields]) => linkedFields?.includes(tldsField))?.[0]; if (!rootLinkedField) return { isRequiredFromLinkedField: false, isOptionalFromLinkedField: false }; const rootFieldKey = rootLinkedField.split(".")[1]; const rootFieldSettings = scenarioConfiguration?.[rootFieldKey]; const isRootFieldRequired = rootFieldSettings && isFieldRequired(rootFieldSettings, customRules); const isRootFieldPopulated = formFieldsWithExistingData?.includes(rootFieldKey); const areLinkedFieldsPopulated = linkedFieldsMap[rootLinkedField]?.some((linkedField) => { const fieldKey = linkedField.split(".")[1]; return formFieldsWithExistingData?.includes(fieldKey); }); const isRootFieldRequiredToRemediateDataMissingError = isFieldRequiredToRemediateDataMissingError(formId, rootFieldKey, dataMissingErrors); if (!(isRootFieldRequired || isRootFieldPopulated || areLinkedFieldsPopulated || isRootFieldRequiredToRemediateDataMissingError)) return { isRequiredFromLinkedField: false, isOptionalFromLinkedField: false }; switch (field) { case "exemptedFromVat": { const processedRule = customRules["isVatExempt"]?.(); return { isRequiredFromLinkedField: processedRule === "REQUIRED", isOptionalFromLinkedField: processedRule === "OPTIONAL" }; } case "vatAbsenceReason": { const processedRule = customRules["isVatExempt"]?.(); return { isRequiredFromLinkedField: processedRule === "REQUIRED", isOptionalFromLinkedField: processedRule === "OPTIONAL" }; } default: return { isRequiredFromLinkedField: false, isOptionalFromLinkedField: false }; } } //#endregion //#region src/core/process-field-configurations.ts var logger = createLogger(); var composePerScenarioFormConfigurations = (scenarios = [], configurations) => scenarios.reduce((composedConfiguration, scenarioName) => ({ ...composedConfiguration, ...configurations[scenarioName] }), {}); var augmentWithCountryConfigs = (countrySpecificConfig, baseFormConfig) => { if (!countrySpecificConfig) return baseFormConfig; return keysOf(countrySpecificConfig).reduce((mergedConfig, field) => ({ ...mergedConfig, [field]: { ...baseFormConfig[field], ...countrySpecificConfig[field] } }), baseFormConfig); }; var evaluateRules = (fieldSettings, customRules) => { const ruleNames = [fieldSettings.rule, ...fieldSettings.rules ?? []].filter(Boolean); return ruleNames.map((ruleName) => { switch (ruleName) { case "REQUIRED": return () => "REQUIRED"; case "OPTIONAL": return () => "OPTIONAL"; default: { const ruleFn = customRules[ruleName]; if (!ruleFn) { logger.error(`Rule with name '${ruleName}' is not defined`); return () => void 0; } return ruleFn; } } }).map((rule, index) => ({ ruleName: ruleNames[index], result: rule() })); }; var isFieldRequired = (fieldSettings, customRules, reasons) => { const ruleResults = evaluateRules(fieldSettings, customRules); if (reasons) ruleResults.forEach(({ ruleName, result }) => { reasons.push(`Rule '${ruleName}': ${result ?? "undefined"}`); }); return ruleResults.every(({ result }) => result === "REQUIRED"); }; var isFieldOptional = (fieldSettings, customRules, reasons) => { const ruleResults = evaluateRules(fieldSettings, customRules); if (reasons) ruleResults.forEach(({ ruleName, result }) => { reasons.push(`Rule '${ruleName}': ${result ?? "undefined"}`); }); return ruleResults.every(({ result }) => result === "OPTIONAL"); }; /** * function: getPropsFromScenarios * * Configurations are stored as a flat list of fields and their properties.(see identity.ts), they * should be agnostic of how forms are structured and implemented. While parsing the configurations * the form structure information will be used to generate props specific to the passed form. * This also means the field names in configurations and forms should match. * * @returns value Eg: * ``` * { * requiredFields: { * personalDetails: ['firstName', 'lastName', 'idNumber', 'residencyCountry' ], * contactDetails: ['phoneNumber', 'email'] * }, * optionalFields: { * contactDetails: ['email'] * }, * labels: { * personalDetails: { * firstName: 'givenName', * lastName: 'surName' * } * }, * validators: { * phoneNumber: { * validate: (value, context?) => boolean; * errorMessage?: string | ErrorMessageObject; * modes: 'blur' | 'input'; * } * } * * } * ``` */ function getPropsFromConfigurations({ scenarioConfiguration, forms, remediationActions, dataMissingErrors, fieldsWithExistingData, customRules, legalEntityType, legalEntityTrustedFields, remediationFieldAsOptional = false }) { const requiredFields = {}; const optionalFields = {}; const allFields = {}; const obscuredFields = {}; const trustedFields = {}; const fieldDebugStore = {}; formDebugInfo.value = {}; if (!scenarioConfiguration) return; const readonly = ["legalFormDescription"]; /** * Converts the LEM model trusted mappings to the component form field mappings */ const trustedFieldMappings = legalEntityTrustedFields?.reduce((acc, trusted) => { switch (trusted.provider) { case TrustedFieldsProviders.KOMPANY: if (legalEntityType === "organization") return [...acc, ...trusted.fields.map((tf) => businessDetailsApiKeyMapping[tf])]; return acc; case TrustedFieldsProviders.SINGPASS: if (legalEntityType === "organization") { const orgFields = trusted.fields.filter((field) => field.split(".")[0] === "organization"); return [...acc, ...orgFields.map((tf) => businessDetailsApiKeyMapping[tf])]; } if (legalEntityType === "individual") { const individualFields = trusted.fields.filter((field) => field.split(".")[0] === "individual"); return [...acc, ...individualFields.map((tf) => individualApiKeyMapping[tf])]; } return acc; default: return acc; } }, []); Object.entries(forms).forEach(([_formId, formDetails]) => { const formId = _formId; const formFieldsWithExistingData = fieldsWithExistingData.filter((field) => field.startsWith(formId)).map((field) => field.slice(field.lastIndexOf(".") + 1)); if (trustedFieldMappings) trustedFields[formId] = getFormTrustedFields(formId, trustedFieldMappings); formDetails.fields?.forEach((_field) => { const field = _field; const fieldName = `${formId}.${field}`; const reasons = []; const fieldSettings = scenarioConfiguration[field]; const isRequiredFromFieldConfig = fieldSettings && isFieldRequired(fieldSettings, customRules, reasons); const isOptionalFromFieldConfig = fieldSettings && isFieldOptional(fieldSettings, customRules); const isFieldRequiredByRemediationAction = showRemediationField(field, remediationActions); if (isFieldRequiredByRemediationAction) reasons.push("Required by remediation action"); const isFieldNeededToRemediateDataMissingError = isFieldRequiredToRemediateDataMissingError(formId, field, dataMissingErrors); if (isFieldNeededToRemediateDataMissingError) reasons.push("Required to remediate dataMissing error"); const isRequiredByRemediationAction = !remediationFieldAsOptional && isFieldRequiredByRemediationAction; const isOptionalByRemediationAction = remediationFieldAsOptional && isFieldRequiredByRemediationAction; const isRequiredFieldPopulated = !isOptionalFromFieldConfig && formFieldsWithExistingData.includes(field); if (isRequiredFieldPopulated) reasons.push("Required because field is already populated"); const { isRequiredFromLinkedField, isOptionalFromLinkedField } = calculateLinkedRequiredFields({ field, formId, customRules, scenarioConfiguration, dataMissingErrors, formFieldsWithExistingData }); if (isRequiredFromLinkedField) reasons.push("Required by linked field"); if (isOptionalFromLinkedField) reasons.push("Optional by linked field"); const isRequired = (isRequiredFromFieldConfig || isRequiredByRemediationAction || isFieldNeededToRemediateDataMissingError || isRequiredFieldPopulated || isRequiredFromLinkedField) && !isOptionalFromLinkedField; if (isRequired && !readonly.includes(field)) { requiredFields[formId] = requiredFields[formId] ? [...requiredFields[formId], field] : [field]; allFields[formId] = allFields[formId] ? [...allFields[formId], field] : [field]; } const isOptional = !isRequired && (isOptionalFromFieldConfig || isOptionalByRemediationAction || isOptionalFromLinkedField); if (isOptional) { optionalFields[formId] = optionalFields[formId] ? [...optionalFields[formId], field] : [field]; allFields[formId] = allFields[formId] ? [...allFields[formId], field] : [field]; } fieldDebugStore[fieldName] = { status: isRequired ? "REQUIRED" : isOptional ? "OPTIONAL" : "HIDDEN", reasons }; if (fieldSettings?.obscured) obscuredFields[formId] = obscuredFields[formId] ? [...obscuredFields[formId], field] : [field]; }); }); formDebugInfo.value = fieldDebugStore; return { requiredFields, optionalFields, allFields, obscuredFields, trustedFields }; } /** * The following field is made a visible "required" field if : * - is a remediation action field AND is a document upload field * @param field - field key * @param remediationActions - array of remediation actions */ var showRemediationField = (field, remediationActions) => remediationActions.some((rem) => rem.fields?.includes(field) && remediationHasFileUploadField(rem)); var makeTLDSFieldName = (formId, fieldName) => { if (fieldName === "proofOfOrganizationTaxInfo") return `${formId}.taxDocument`; return `${formId}.${fieldName}`; }; var isFieldRequiredToRemediateDataMissingError = (formId, field, dataMissingErrors) => { return dataMissingErrors.flatMap((error) => error.remediatingActions).filter((remediatingAction) => !!remediatingAction && remediatingAction.code in dataMissingRemediationsToFieldsMap).flatMap((remediatingAction) => dataMissingRemediationsToFieldsMap[remediatingAction.code]).filter((missingField) => Boolean(missingField)).map((missingField) => makeTLDSFieldName(formId, missingField)).includes(makeTLDSFieldName(formId, field)); }; var getFormTrustedFields = (formId, trustedFields) => trustedFields.filter((field) => field.split(".")[0] === formId).map((field) => field.split(".").filter((item) => formId !== item).join(".")); //#endregion export { composePerScenarioFormConfigurations as n, getPropsFromConfigurations as r, augmentWithCountryConfigs as t };