@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.
388 lines (387 loc) • 15.3 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] = "3e18f00e-eff6-402a-9b29-e9df9d72911a", e._sentryDebugIdIdentifier = "sentry-dbid-3e18f00e-eff6-402a-9b29-e9df9d72911a");
} catch (e) {}
import { a as Icon, i as Typography, o as createLogger, r as useTranslation } from "./translation-BFxyJ1c5.js";
import { n as IconButton, r as Loader } from "./Button-oj6H8OrC.js";
import { n as httpGet, r as httpPost, t as httpDelete } from "./http-D1NDkBxF.js";
import { t as useAnalyticsContext } from "./useAnalyticsContext-BVFDMrVE.js";
import { t as StateContext } from "./StateContext-BToC3V53.js";
import { t as getLocalizedIdDocumentTypeOptions } from "./utils-CzW6YqAz.js";
import { useEffect } from "preact/hooks";
import cx from "classnames";
import { jsx, jsxs } from "preact/jsx-runtime";
import "@tanstack/preact-query";
//#region src/api/documents/useCreateDocument.ts
var createDocument = async (context, document, ownerId) => {
const { baseUrl, rootLegalEntityId } = context;
return httpPost({
baseUrl,
path: `legalEntities/${rootLegalEntityId}/documents/${ownerId}`
}, document);
};
//#endregion
//#region src/api/documents/useDeleteDocument.ts
var deleteDocument$1 = async (context, documentId) => {
const { baseUrl, rootLegalEntityId } = context;
return httpDelete({
baseUrl,
path: `legalEntities/${rootLegalEntityId}/documents/${documentId}/`
});
};
//#endregion
//#region src/api/documents/useDocument.ts
var getDocument$1 = async (context, documentId) => {
const { baseUrl, rootLegalEntityId } = context;
return httpGet({
baseUrl,
path: `legalEntities/${rootLegalEntityId}/documents/${documentId}`
});
};
//#endregion
//#region src/api/documents/useUpdateDocument.ts
var updateDocument = async (context, document, documentId, ownerId) => {
const { baseUrl, rootLegalEntityId } = context;
return httpPost({
baseUrl,
path: `legalEntities/${rootLegalEntityId}/documents/${documentId}/${ownerId}`
}, document);
};
//#endregion
//#region src/types/file.ts
var isExistingFile = (file) => "existing" in file && file.existing;
var isNewlyUploadedFile = (file) => !isExistingFile(file);
//#endregion
//#region src/utils/api/documentUtils.ts
var logger = createLogger();
var documentStore = {};
var idTypes = getLocalizedIdDocumentTypeOptions().map((types) => types.id);
var getDocumentService;
var updateDocumentService;
var createDocumentService;
var deleteDocumentService;
var documentApiUtils = (context) => {
if (!getDocumentService || !updateDocumentService || !createDocumentService || !deleteDocumentService) {
getDocumentService = (documentId) => getDocument$1(context, documentId);
updateDocumentService = (document, documentId, ownerId) => updateDocument(context, document, documentId, ownerId);
createDocumentService = (document, ownerId) => createDocument(context, document, ownerId);
if (deleteDocument$1) deleteDocumentService = (documentId) => deleteDocument$1(context, documentId);
}
return {
fetchDocuments,
fetchDocument,
uploadDocuments,
deleteDocument
};
};
var fetchDocuments = async (documentDetails, entityId) => {
if (documentDetails.length === 0) return [];
const documentPromises = [];
documentDetails.forEach((documentDetail) => {
documentPromises.push(fetchDocument(documentDetail));
});
const entityDocuments = await Promise.all(documentPromises);
documentStore[entityId] = [];
entityDocuments.forEach((document) => {
const id = document.owner?.id;
if (id) documentStore[id].push(document);
else logger.error("Document has no `owner.id`", document);
});
return documentStore[entityId];
};
var fetchDocument = async (documentDetail) => {
return {
...await getDocumentService(documentDetail.id),
modificationDate: documentDetail.modificationDate
};
};
var deleteDocument = async (documentId) => deleteDocumentService(documentId);
var uploadDocuments = async (documents, entityId) => {
const uploadPromises = [];
const existingProofOfOwnershipAttachments = [];
documents.forEach((document) => {
const existingDocument = getDocument(entityId, document.type);
if (document.type === "proofOfOwnership" && document.attachments) {
const existingAttachments = document.attachments.filter((att) => !att.content);
document.attachments = document.attachments.filter((att) => att.content);
existingProofOfOwnershipAttachments.push(...existingAttachments);
}
if (hasDocumentChanged(document, entityId) || document.type === "proofOfOwnership" && document.attachments?.length) if (existingDocument && document.type !== "proofOfOwnership") uploadPromises.push(updateDocumentService(document, existingDocument.id, entityId));
else uploadPromises.push(createDocumentService(document, entityId));
});
const uploadedDocuments = await Promise.all(uploadPromises);
if (existingProofOfOwnershipAttachments.length > 0) {
const proofOfOwnershipDoc = uploadedDocuments.find((doc) => doc.type === "proofOfOwnership");
if (proofOfOwnershipDoc) proofOfOwnershipDoc.attachments = [...existingProofOfOwnershipAttachments, ...proofOfOwnershipDoc.attachments ?? []];
}
const uploadedProofOfOwnershipDocuments = uploadedDocuments.filter((doc) => doc.type === "proofOfOwnership");
if (uploadedProofOfOwnershipDocuments.length > 0) {
documentStore[entityId] = documentStore[entityId].filter((doc) => doc.type !== "proofOfOwnership");
const proofOfOwnershipWithModificationDate = uploadedProofOfOwnershipDocuments.map((doc) => ({
...doc,
modificationDate: (/* @__PURE__ */ new Date()).toISOString()
}));
documentStore[entityId].push(...proofOfOwnershipWithModificationDate);
}
updateEntityDocuments(uploadedDocuments, entityId);
return documentStore[entityId];
};
var updateEntityDocuments = (uploadedDocuments, entityId) => {
if (!documentStore[entityId]) documentStore[entityId] = [];
uploadedDocuments.map((doc) => ({
...doc,
modificationDate: (/* @__PURE__ */ new Date()).toISOString()
})).forEach((uploadedDocument) => {
const existingDocumentIndex = documentStore[entityId].findIndex((document) => document.id === uploadedDocument.id);
if (existingDocumentIndex > -1) documentStore[entityId][existingDocumentIndex] = uploadedDocument;
else documentStore[entityId].push(uploadedDocument);
});
};
var getDocument = (entityId, documentType) => {
if (!entityId) return;
return documentStore[entityId]?.find((document) => document.type === documentType);
};
var getDocuments = (entityId, documentType) => {
if (!entityId) return [];
return documentStore[entityId]?.filter((document) => document.type === documentType) ?? [];
};
var getIdDocument = (entityId) => documentStore[entityId]?.find((document) => idTypes.includes(document.type));
var createDocumentRequest = async ({ entityId, entityType, documentType, page1, page2, description, existingDocument }) => {
const pagesToUpload = [page1, page2].filter((page) => page !== void 0 && isNewlyUploadedFile(page));
const [page1ToUpload, page2ToUpload] = pagesToUpload;
const [encodedPage1, encodedPage2] = await Promise.all(pagesToUpload.map(fileToBase64));
if (!encodedPage1) {
if (existingDocument && existingDocument.description !== description) return {
owner: {
id: entityId,
type: entityType
},
type: documentType,
description
};
return;
}
return {
owner: {
id: entityId,
type: entityType
},
type: documentType,
description,
attachments: page1ToUpload && page2ToUpload && encodedPage1 && encodedPage2 ? [createAttachment(encodedPage1, page1ToUpload.name, "front"), createAttachment(encodedPage2, page2ToUpload.name, "back")] : [createAttachment(encodedPage1, page1ToUpload.name)]
};
};
var createAttachment = (content, pageName, pageType) => ({
content,
pageName,
...pageType && { pageType }
});
var hasDocumentChanged = (newDocument, entityId) => {
const existingDocument = getDocument(entityId, newDocument.type);
if (existingDocument) return existingDocument.attachments.some((attachment, index) => attachment.content !== newDocument?.attachments?.[index]?.content) || existingDocument.description !== newDocument?.description;
return true;
};
var fileToBase64 = (file) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const base64String = reader.result.toString().split(";base64,")[1];
resolve(base64String);
};
reader.onerror = (error) => reject(error);
});
var getFileExtention = (fileName) => fileName.split(".").pop();
/**
* Merges existing attachments from API with newly added attachments in UI
* making sure new ones override only corresponding ones based on pageType (front<->front)
* Eg: user selects new front of ID, we keep the back already saved in DB, override front
*
* @param existing Attachments copied from API response
* @param incoming New attachments
* @returns merged attachments with deduped pageTypes
*/
var mergeAttachments = (existing, incoming) => {
if (!existing?.length) return incoming;
if (!incoming?.length) return existing;
const incomingFileTypes = incoming.map((attachment) => attachment.pageType);
return [...existing.filter((attachment) => !incomingFileTypes.includes(attachment.pageType)), ...incoming];
};
/**
* Merges proof of ownership attachments by filtering based on pageName (not pageType)
* since proofOfOwnership attachments may not have pageType
*/
var mergeProofOfOwnershipAttachments = (existing, incoming) => {
if (!existing?.length) return incoming;
if (!incoming?.length) return existing;
const incomingFileNames = incoming.map((attachment) => attachment.pageName);
const filteredExisting = existing.filter((attachment) => !incomingFileNames.includes(attachment.pageName));
return [...incoming, ...filteredExisting].slice(0, 2);
};
//#endregion
//#region src/context/StateContext/StateContextSetter.tsx
function StateContextSetter({ stateRef }) {
if (stateRef.current.setState) return null;
return /* @__PURE__ */ jsx(StateContext.Consumer, { children: (stateContextValue) => {
const { dispatch, setActiveForms } = stateContextValue;
stateRef.current.setState = dispatch;
stateRef.current.setActiveForms = setActiveForms;
return null;
} });
}
//#endregion
//#region src/utils/bytesToSize.ts
function bytesToSize(bytes) {
const sizes = [
"Bytes",
"KB",
"MB",
"GB",
"TB"
];
if (bytes === 0) return "n/a";
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString());
if (i === 0) return `${bytes} ${sizes[i]}`;
return `${(bytes / 1024 ** i).toFixed()} ${sizes[i]}`;
}
//#endregion
//#region src/components/ui/molecules/Dropzone/DropzoneFile.tsx
function DropzoneFile(props) {
const { file, errorMessage, onDelete, loading } = props;
const { t } = useTranslation("common");
const userEvents = useAnalyticsContext();
let documentIcon;
const formatSize = (bytes) => {
const sizes = [
"Bytes",
"KB",
"MB",
"GB",
"TB"
];
if (!bytes || bytes === 0) return "n/a";
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString());
if (i === 0) return `${bytes} ${sizes[i]}`;
return `${(bytes / 1024 ** i).toFixed(1)} ${sizes[i]}`;
};
switch (file.type?.split("/")[0]) {
case "image":
documentIcon = "image";
break;
default: documentIcon = "document";
}
useEffect(() => {
if (!errorMessage) return;
userEvents.addFieldEvent("Encountered error", {
actionType: "change",
field: "dropzone file",
returnType: "validation",
returnValue: errorMessage
});
}, [errorMessage, userEvents]);
return /* @__PURE__ */ jsxs("div", {
className: cx("adyen-kyc-dropzone-file", { "adyen-kyc-dropzone-file--error": errorMessage }),
children: [/* @__PURE__ */ jsxs("div", {
className: "adyen-kyc-dropzone-file__details",
children: [/* @__PURE__ */ jsxs("div", {
className: "adyen-kyc-dropzone-file__labels",
children: [
errorMessage ? /* @__PURE__ */ jsx(Icon, {
testId: "document-invalid",
name: "warning",
className: "adyen-kyc-dropzone-file__icon"
}) : /* @__PURE__ */ jsx(Icon, {
testId: "document-valid",
name: documentIcon,
className: "adyen-kyc-dropzone-file__icon"
}),
/* @__PURE__ */ jsx(Typography, {
variant: "body-stronger",
className: "adyen-kyc-dropzone-file__name",
children: file.name
}),
/* @__PURE__ */ jsx(Typography, {
className: "adyen-kyc-dropzone-file__size",
children: isNewlyUploadedFile(file) && formatSize(file.size)
})
]
}), errorMessage && /* @__PURE__ */ jsx("div", {
className: "adyen-kyc-dropzone-file__error",
children: t(($) => $[errorMessage])
})]
}), loading ? /* @__PURE__ */ jsx("div", {
className: "adyen-kyc-dropzone-file__loading",
children: /* @__PURE__ */ jsx(Loader, { size: "medium" })
}) : errorMessage ? /* @__PURE__ */ jsx(IconButton, {
ariaLabel: t(($) => $["close"]),
icon: "cross",
variant: "tertiary",
onClick: onDelete
}) : /* @__PURE__ */ jsx(IconButton, {
ariaLabel: t(($) => $["deleteDocument"]),
icon: "bin",
variant: "tertiary",
onClick: onDelete
})]
});
}
//#endregion
//#region src/utils/hasDuplicates.ts
var hasDuplicates = (arr) => arr.some((item, currIndex) => arr.indexOf(item) !== currIndex);
//#endregion
//#region src/components/ui/molecules/Dropzone/validate.ts
var defaultFileValidationOptions = {
allowedFileTypes: [
"JPG",
"JPEG",
"PNG",
"PDF"
],
maxNumberOfFiles: 1,
maxSize: 4194304,
isOptional: false
};
var filterOnlyNewlyUploadedFiles = (files) => files.filter(isNewlyUploadedFile);
var fileValidationRules = ({ allowedFileTypes, maxNumberOfFiles, maxSize, isOptional }) => [
{
validate: (files) => {
if (isOptional && (!files || files.length === 0)) return true;
return Boolean(files && files.length > 0);
},
errorMessage: "fieldIsRequired",
modes: ["blur", "input"]
},
{
validate: (files) => {
if (!files) return true;
return files.length <= maxNumberOfFiles;
},
errorMessage: "tooManyFiles",
modes: ["blur", "input"]
},
{
validate: (files) => {
if (!files) return true;
return !hasDuplicates(files.map((file) => file.name));
},
errorMessage: "duplicatedFiles",
modes: ["blur", "input"]
},
{
validate: (files) => {
if (!files) return true;
return filterOnlyNewlyUploadedFiles(files).every((file) => allowedFileTypes.some((filetype) => file.name.toLowerCase().endsWith(filetype.toLowerCase())));
},
errorMessage: "unsupportedFiletype",
modes: ["blur", "input"]
},
{
validate: (files) => {
if (!files) return true;
return filterOnlyNewlyUploadedFiles(files).every((file) => !file.size || file.size < maxSize);
},
errorMessage: "maximumFileSizeExceeded",
modes: ["blur", "input"]
}
];
//#endregion
export { StateContextSetter as a, getDocument as c, getIdDocument as d, mergeAttachments as f, bytesToSize as i, getDocuments as l, fileValidationRules as n, createDocumentRequest as o, mergeProofOfOwnershipAttachments as p, DropzoneFile as r, documentApiUtils as s, defaultFileValidationOptions as t, getFileExtention as u };