@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
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] = "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 };