Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion apps/api/src/api/controllers/alfredpay.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,34 @@ 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);

const minimized = details.map(business => ({
relatedPersons: (business.relatedPersons ?? []).map(person => ({ idRelatedPerson: person.idRelatedPerson }))
}));

res.json(minimized);
} catch (error) {
Comment on lines +563 to +571
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 };
Expand Down Expand Up @@ -582,6 +610,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;
Expand All @@ -602,6 +631,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(
Expand All @@ -613,7 +644,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 });
}
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/api/routes/v1/alfredpay.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
23 changes: 19 additions & 4 deletions apps/frontend/_redirects
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
/api/production/* https://api.vortexfinance.co/:splat 200
/api/staging/* https://api-staging.vortexfinance.co/:splat 200
/api/sandbox/* https://api-sandbox.vortexfinance.co/:splat 200
/* /index.html 200
https://vortexfinance.co/* https://www.vortexfinance.co/:splat 301!
/api/staging/* https://api-staging.vortexfinance.co/:splat 200
/api/sandbox/* https://api-sandbox.vortexfinance.co/:splat 200

# Known app routes — serve SPA shell with real 200
/ /index.html 200
/business /index.html 200
/contact /index.html 200
/privacy-policy /index.html 200
/terms-and-conditions /index.html 200
/terms-and-conditions-full /index.html 200
/widget /index.html 200
/es/* /index.html 200
/pt/* /index.html 200
/fr/* /index.html 200

# Everything else is a real 404
/* /404.html 404

https://vortexfinance.co/* https://www.vortexfinance.co/:splat 301!
146 changes: 146 additions & 0 deletions apps/frontend/public/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<!--
Static 404 page — intentionally not a React route.
Netlify's _redirects serves this file with a real HTTP 404 status code so
crawlers don't index unknown URLs as soft 200s. The SPA catch-all can't
return a 404 status from a static host, and a React 404 wouldn't render
until the JS bundle boots — bots and JS-disabled users would see an empty
shell. Plain HTML/CSS gives correct status, instant render, and zero bundle cost.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex" />
<title>Page not found — Vortex</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link
href="https://fonts.googleapis.com/css2?family=Red+Hat+Display:wght@400;700&display=swap"
rel="stylesheet"
/>
<style>
:root {
--color-primary: oklch(0.45 0.2 260);
--color-primary-hover: oklch(0.37 0.2 260);
--color-primary-content: oklch(1 0 0);
--color-blue-100: #dbeafe;
--radius-field: 9px;
}
html, body {
margin: 0;
padding: 0;
height: 100%;
background: #fff;
font-family: "Red Hat Display", system-ui, -apple-system, sans-serif;
color: #0f172a;
}
body {
position: relative;
overflow: hidden;
}
.bg {
position: absolute;
inset: 0;
z-index: 0;
overflow: hidden;
}
.bg::before,
.bg::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
width: 50%;
background: linear-gradient(to bottom, #fff, var(--color-blue-100), #fff);
}
.bg::before {
left: 0;
animation: float 5s ease-in-out infinite;
}
.bg::after {
right: 0;
transform: rotate(180deg);
animation: float-delayed 6s ease-in-out infinite;
}
@keyframes float {
from { transform: translateY(0) scale(1); }
50% { transform: translateY(-25px) scale(1.02); }
to { transform: translateY(0) scale(1); }
}
@keyframes float-delayed {
from { transform: translateY(0) scale(1) rotate(180deg); }
50% { transform: translateY(35px) scale(1.02) rotate(180deg); }
to { transform: translateY(0) scale(1) rotate(180deg); }
}
@media (prefers-reduced-motion: reduce) {
.bg::before,
.bg::after {
animation: none;
}
}
main {
position: relative;
z-index: 1;
min-height: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 24px;
}
.wrap {
max-width: 480px;
}
h1 {
font-size: clamp(48px, 10vw, 96px);
font-weight: 700;
margin: 0 0 8px;
line-height: 1;
color: var(--color-primary);
}
h2 {
font-size: clamp(20px, 4vw, 28px);
font-weight: 700;
margin: 0 0 16px;
}
p {
font-size: 16px;
line-height: 1.5;
margin: 0 0 24px;
color: #475569;
}
.btn-vortex-primary {
display: inline-block;
background-color: var(--color-primary);
color: var(--color-primary-content);
text-decoration: none;
padding: 12px 24px;
border-radius: var(--radius-field);
border: 1px solid var(--color-primary);
font-weight: 600;
cursor: pointer;
transition: scale 0.1s ease-in-out, background-color 0.15s ease, border-color 0.15s ease;
}
.btn-vortex-primary:hover {
background-color: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
.btn-vortex-primary:active {
scale: 0.98;
}
</style>
</head>
<body>
<div class="bg" aria-hidden="true"></div>
<main>
<div class="wrap">
<h1>404</h1>
<h2>Page not found</h2>
<p>The page you're looking for doesn't exist or has moved.</p>
<a class="btn-vortex-primary" href="/">Back to Vortex</a>
</div>
</main>
</body>
</html>
9 changes: 9 additions & 0 deletions apps/frontend/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
User-agent: *
Allow: /
Disallow: /api/
Disallow: /widget
Disallow: /es/widget
Disallow: /pt/widget
Disallow: /fr/widget

Sitemap: https://www.vortexfinance.co/sitemap.xml
33 changes: 33 additions & 0 deletions apps/frontend/public/sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://www.vortexfinance.co/</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://www.vortexfinance.co/business</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.vortexfinance.co/contact</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://www.vortexfinance.co/privacy-policy</loc>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://www.vortexfinance.co/terms-and-conditions</loc>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://www.vortexfinance.co/terms-and-conditions-full</loc>
<changefreq>yearly</changefreq>
<priority>0.2</priority>
</url>
</urlset>
4 changes: 3 additions & 1 deletion apps/frontend/src/components/Alfredpay/AlfredpayKycFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export const AlfredpayKycFlow = () => {
stateValue === "SendingSubmission" ||
stateValue === "SubmittingKybInfo" ||
stateValue === "SubmittingKybBusinessFiles" ||
stateValue === "FindingKybCustomerAndBusiness" ||
stateValue === "SubmittingKybRelatedPersonBundle" ||
stateValue === "SubmittingKybPersonFiles" ||
stateValue === "SendingKybSubmission"
) {
Expand All @@ -89,7 +91,7 @@ export const AlfredpayKycFlow = () => {
}

if (stateValue === "FillingKybForm") {
return <KybFormScreen onSubmit={submitKybForm} />;
return <KybFormScreen country={context.country} onSubmit={submitKybForm} />;
}

if (stateValue === "UploadingKybBusinessDocs") {
Expand Down
Loading
Loading