From 2d79041eacf6a8b613f6a3df92af77a8225f511d Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 30 Apr 2026 14:51:32 +0200 Subject: [PATCH 01/13] remove typeDocument from Mexico KYC form --- .../components/Alfredpay/MxnKycFormScreen.tsx | 18 ------------------ .../supported-fiat-currencies.endpoints.ts | 4 ++-- .../shared/src/services/alfredpay/types.ts | 2 +- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/apps/frontend/src/components/Alfredpay/MxnKycFormScreen.tsx b/apps/frontend/src/components/Alfredpay/MxnKycFormScreen.tsx index b1ba119f7..dbab1af23 100644 --- a/apps/frontend/src/components/Alfredpay/MxnKycFormScreen.tsx +++ b/apps/frontend/src/components/Alfredpay/MxnKycFormScreen.tsx @@ -14,7 +14,6 @@ const schema = z.object({ firstName: z.string().min(1), lastName: z.string().min(1), state: z.string().min(1), - typeDocument: z.enum(["INE", "Resident card", "passport"]), zipCode: z.string().min(1) }); @@ -100,23 +99,6 @@ export function MxnKycFormScreen({ onSubmit }: MxnKycFormScreenProps) { {errors.email && {errors.email.message}} -
- - - {errors.typeDocument && {errors.typeDocument.message}} -
-
@@ -139,15 +144,20 @@ export function ColKycFormScreen({ onSubmit }: ColKycFormScreenProps) { - +
+ + + (v ? `+${v.replace(/^\+/, "")}` : v) + })} + /> +
{errors.phoneNumber && {errors.phoneNumber.message}} From d4a0f653412979f967a9c38c1c13805dfa309209 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:17:03 +0000 Subject: [PATCH 03/13] fix(ColKycFormScreen): use ColKycFormValues type and fix phone normalization Agent-Logs-Url: https://github.com/pendulum-chain/vortex/sessions/b3c8ee52-8e6f-4d2a-9417-4c40ef144510 Co-authored-by: ebma <6690623+ebma@users.noreply.github.com> --- apps/frontend/src/components/Alfredpay/ColKycFormScreen.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/components/Alfredpay/ColKycFormScreen.tsx b/apps/frontend/src/components/Alfredpay/ColKycFormScreen.tsx index bb48e23c9..49d765e2f 100644 --- a/apps/frontend/src/components/Alfredpay/ColKycFormScreen.tsx +++ b/apps/frontend/src/components/Alfredpay/ColKycFormScreen.tsx @@ -3,7 +3,6 @@ import { AlfredpayColombiaDocumentType } from "@vortexfi/shared"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { z } from "zod"; -import type { MxnKycFormData } from "../../machines/alfredpayKyc.machine"; import { MenuButtons } from "../MenuButtons"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; @@ -29,7 +28,7 @@ const schema = z type ColKycFormValues = z.infer; interface ColKycFormScreenProps { - onSubmit: (data: MxnKycFormData) => void; + onSubmit: (data: ColKycFormValues) => void; } export function ColKycFormScreen({ onSubmit }: ColKycFormScreenProps) { @@ -154,7 +153,7 @@ export function ColKycFormScreen({ onSubmit }: ColKycFormScreenProps) { placeholder="573000000000" type="tel" {...register("phoneNumber", { - setValueAs: (v: string) => (v ? `+${v.replace(/^\+/, "")}` : v) + setValueAs: (v: string) => (v ? `+${v.replace(/^\+/, "").replace(/\D/g, "")}` : v) })} /> From db9f7d77928d54a92fae955ee6187ab9e9418b89 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Tue, 5 May 2026 20:42:10 +0200 Subject: [PATCH 04/13] Add Mexico KYB Alfredpay types --- .../shared/src/services/alfredpay/types.ts | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/packages/shared/src/services/alfredpay/types.ts b/packages/shared/src/services/alfredpay/types.ts index 93a5a6415..953ee2088 100644 --- a/packages/shared/src/services/alfredpay/types.ts +++ b/packages/shared/src/services/alfredpay/types.ts @@ -411,11 +411,12 @@ export enum AlfredpayKycFileType { // KYB form submission types export enum AlfredpayKybFileType { + TAX_ID_DOCUMENT = "taxIdDocument", ARTICLES_INCORPORATION = "articlesIncorporation", - PROOF_ADDRESS = "proofAddress", - SHAREHOLDER_REGISTRY = "shareholderRegistry" + PROOF_ADDRESS = "proofAddress" } +/** Penny relate-person upload: only docFront + docBack (URI fields are derived server-side). */ export enum AlfredpayKybRelatedPersonFileType { DOC_FRONT = "docFront", DOC_BACK = "docBack" @@ -444,13 +445,42 @@ export interface SubmitKybInformationRequest { relatedPersons: AlfredpayKybRelatedPerson[]; } -export interface AlfredpayKybRelatedPersonResponse { - id: string; - firstName: string; - lastName: string; +export interface SubmitKybInformationResponse { + submissionId: string; } -export interface SubmitKybInformationResponse { +/** Single related-person row from `GET …/customers/{customerId}/kyb/details`. */ +export interface AlfredpayKybRelatedPersonDetails { + idRelatedPerson: string; + firstName?: string; + lastName?: string; + email?: string; + dateOfBirth?: string; + nationalities?: string[]; + docFront?: string | null; + docBack?: string | null; + docFrontUri?: string | null; + docBackUri?: string | null; +} + +/** Response item from `GET …/customers/{customerId}/kyb/details`; the endpoint returns an array. */ +export interface AlfredpayKybCustomerAndBusiness { + customerId: string; + country: string; + businessName: string; + taxId: string; + website?: string; + state: string; + city: string; + address: string; + zipCode: string; submissionId: string; - relatedPersons?: AlfredpayKybRelatedPersonResponse[]; + relatedPersons: AlfredpayKybRelatedPersonDetails[]; + verificationSessionId?: string | null; + articlesIncorporation?: string; + articlesIncorporationUri?: string; + proofAddress?: string; + proofAddressUri?: string; + taxIdDocument?: string; + taxIdDocumentUri?: string; } From 7a9cce2619eded041c61a15a8ab0e3bb1492eb59 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Tue, 5 May 2026 20:42:41 +0200 Subject: [PATCH 05/13] add Mexico KYB Alfredpay Related Person endpoints --- .../services/alfredpay/alfredpayApiService.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/services/alfredpay/alfredpayApiService.ts b/packages/shared/src/services/alfredpay/alfredpayApiService.ts index a5670f263..d01f0cb63 100644 --- a/packages/shared/src/services/alfredpay/alfredpayApiService.ts +++ b/packages/shared/src/services/alfredpay/alfredpayApiService.ts @@ -7,6 +7,7 @@ import { AlfredpayFiatAccountFields, AlfredpayFiatAccountType, AlfredpayFiatCurrency, + AlfredpayKybCustomerAndBusiness, AlfredpayKybFileType, AlfredpayKybRelatedPersonFileType, AlfredpayKycFileType, @@ -263,7 +264,7 @@ export class AlfredpayApiService { file: Blob ): Promise { const formData = new FormData(); - formData.append("fileBody", file); + formData.append("rawBody", file); formData.append("fileType", fileType); const url = `${ALFREDPAY_BASE_URL}/api/v1/third-party-service/penny/customers/${customerId}/kyc/${submissionId}/files`; @@ -295,6 +296,15 @@ export class AlfredpayApiService { return (await this.executeRequest(path, "POST", { kybSubmission: data })) as SubmitKybInformationResponse; } + /** + * Alfredpay: GET …/customers/{customerId}/kyb/details — returns the relate-person ids needed for KYB file uploads. + * Docs: https://alfredpay.readme.io/v2.0/reference/kybcontroller_findcustomerandbusiness-1 + */ + public async getKybBusinessDetails(customerId: string): Promise { + const path = `/api/v1/third-party-service/penny/customers/${customerId}/kyb/details`; + return (await this.executeRequest(path, "GET")) ?? []; + } + public async submitKybFiles( customerId: string, submissionId: string, @@ -321,6 +331,11 @@ export class AlfredpayApiService { } } + /** + * Penny: POST …/customers/{customerId}/kyb/{idRelatedPerson}/files/relate-person + * Path segment = Penny “Related Person ID” (from KYB submit response `relatedPersons[].id`, not the customerId). + * Docs (typo in path name): https://alfredpay.readme.io/v2.0/reference/kybcontroller_submitkybfilerelateperson-3 + */ public async submitKybRelatedPersonFiles( customerId: string, relatedPersonId: string, From 210fff8807c5c472d6927caa6b98ef889c23a9e7 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Tue, 5 May 2026 20:50:55 +0200 Subject: [PATCH 06/13] refactor Mexico KYB Flow --- .../api/controllers/alfredpay.controller.ts | 33 ++- apps/api/src/api/routes/v1/alfredpay.route.ts | 1 + .../components/Alfredpay/AlfredpayKycFlow.tsx | 2 + .../Alfredpay/KybBusinessDocsScreen.tsx | 32 ++- .../src/machines/alfredpayKyc.machine.ts | 209 ++++++++++++++---- 5 files changed, 227 insertions(+), 50 deletions(-) diff --git a/apps/api/src/api/controllers/alfredpay.controller.ts b/apps/api/src/api/controllers/alfredpay.controller.ts index 1e628b314..7def597d1 100644 --- a/apps/api/src/api/controllers/alfredpay.controller.ts +++ b/apps/api/src/api/controllers/alfredpay.controller.ts @@ -547,6 +547,30 @@ export class AlfredpayController { } } + static async findKybCustomerAndBusiness(req: Request, res: Response) { + try { + const { country } = req.query as { country: string }; + const userId = req.userId!; + + const alfredPayCustomer = await AlfredPayCustomer.findOne({ + where: { country: country as AlfredPayCountry, type: AlfredpayCustomerType.BUSINESS, userId } + }); + + if (!alfredPayCustomer) { + return res.status(404).json({ error: "Alfredpay business customer not found" }); + } + + const alfredpayService = AlfredpayApiService.getInstance(); + const details = await alfredpayService.getKybBusinessDetails(alfredPayCustomer.alfredPayId); + + res.json(details); + } catch (error) { + logger.error("Error finding Alfredpay KYB customer and business:", error); + const message = error instanceof Error ? error.message : "Internal server error"; + res.status(500).json({ error: message }); + } + } + static async submitKybFile(req: Request, res: Response) { try { const { country, submissionId, fileType } = req.body as { country: string; submissionId: string; fileType: string }; @@ -582,6 +606,7 @@ export class AlfredpayController { } static async submitKybRelatedPersonFile(req: Request, res: Response) { + let pennyCustomerId: string | undefined; try { const { country, relatedPersonId, fileType } = req.body as { country: string; @@ -602,6 +627,8 @@ export class AlfredpayController { return res.status(404).json({ error: "Alfredpay business customer not found" }); } + pennyCustomerId = alfredPayCustomer.alfredPayId; + const fileBlob = new File([new Uint8Array(req.file.buffer)], req.file.originalname, { type: req.file.mimetype }); const alfredpayService = AlfredpayApiService.getInstance(); await alfredpayService.submitKybRelatedPersonFiles( @@ -613,7 +640,11 @@ export class AlfredpayController { res.json({ success: true }); } catch (error) { - logger.error("Error submitting KYB related person file:", error); + const body = req.body as { country?: string; relatedPersonId?: string; fileType?: string }; + const errSummary = error instanceof Error ? error.message : String(error); + logger.error( + `[submitKybRelatedPersonFile] ${errSummary} | customerIdPenny=${pennyCustomerId ?? "n/a"} relatedPersonId=${body.relatedPersonId ?? "n/a"} userId=${req.userId ?? "n/a"}` + ); const message = error instanceof Error ? error.message : "Internal server error"; res.status(500).json({ error: message }); } diff --git a/apps/api/src/api/routes/v1/alfredpay.route.ts b/apps/api/src/api/routes/v1/alfredpay.route.ts index 1a464668d..e2009db8e 100644 --- a/apps/api/src/api/routes/v1/alfredpay.route.ts +++ b/apps/api/src/api/routes/v1/alfredpay.route.ts @@ -25,6 +25,7 @@ router.post("/sendKycSubmission", requireAuth, validateResultCountry, AlfredpayC // Business API-based KYB router.post("/submitKybInformation", requireAuth, validateResultCountry, AlfredpayController.submitKybInformation); router.post("/submitKybFile", requireAuth, upload.single("file"), validateResultCountry, AlfredpayController.submitKybFile); +router.get("/findKybCustomerAndBusiness", requireAuth, validateResultCountry, AlfredpayController.findKybCustomerAndBusiness); router.post( "/submitKybRelatedPersonFile", requireAuth, diff --git a/apps/frontend/src/components/Alfredpay/AlfredpayKycFlow.tsx b/apps/frontend/src/components/Alfredpay/AlfredpayKycFlow.tsx index 3b12570ce..0b97500d4 100644 --- a/apps/frontend/src/components/Alfredpay/AlfredpayKycFlow.tsx +++ b/apps/frontend/src/components/Alfredpay/AlfredpayKycFlow.tsx @@ -70,6 +70,8 @@ export const AlfredpayKycFlow = () => { stateValue === "SendingSubmission" || stateValue === "SubmittingKybInfo" || stateValue === "SubmittingKybBusinessFiles" || + stateValue === "FindingKybCustomerAndBusiness" || + stateValue === "SubmittingKybRelatedPersonBundle" || stateValue === "SubmittingKybPersonFiles" || stateValue === "SendingKybSubmission" ) { diff --git a/apps/frontend/src/components/Alfredpay/KybBusinessDocsScreen.tsx b/apps/frontend/src/components/Alfredpay/KybBusinessDocsScreen.tsx index 1b6307cc8..fe2a59975 100644 --- a/apps/frontend/src/components/Alfredpay/KybBusinessDocsScreen.tsx +++ b/apps/frontend/src/components/Alfredpay/KybBusinessDocsScreen.tsx @@ -71,15 +71,18 @@ interface KybBusinessDocsScreenProps { export function KybBusinessDocsScreen({ onBack, onSubmit }: KybBusinessDocsScreenProps) { const { t } = useTranslation(); + const [taxIdDocument, setTaxIdDocument] = useState(null); const [articlesIncorporation, setArticlesIncorporation] = useState(null); const [proofAddress, setProofAddress] = useState(null); - const [shareholderRegistry, setShareholderRegistry] = useState(null); + const [docFront, setDocFront] = useState(null); + const [docBack, setDocBack] = useState(null); - const isValid = articlesIncorporation !== null && proofAddress !== null && shareholderRegistry !== null; + const isValid = + taxIdDocument !== null && articlesIncorporation !== null && proofAddress !== null && docFront !== null && docBack !== null; const handleSubmit = () => { - if (!articlesIncorporation || !proofAddress || !shareholderRegistry) return; - onSubmit({ articlesIncorporation, proofAddress, shareholderRegistry }); + if (!taxIdDocument || !articlesIncorporation || !proofAddress || !docFront || !docBack) return; + onSubmit({ articlesIncorporation, docBack, docFront, proofAddress, taxIdDocument }); }; return ( @@ -89,6 +92,12 @@ export function KybBusinessDocsScreen({ onBack, onSubmit }: KybBusinessDocsScree

{t("components.kybBusinessDocs.subtitle")}

+ + -

{t("components.mxnDocumentUpload.fileHint")}