-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcreate-virtual-account.ts
More file actions
149 lines (128 loc) · 4.15 KB
/
Copy pathcreate-virtual-account.ts
File metadata and controls
149 lines (128 loc) · 4.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/// <reference types="@netlify/edge-functions" />
import type { Context, Config } from "@netlify/edge-functions";
declare global {
const Netlify: {
env: {
get(key: string): string;
};
};
}
// Flutterwave API configuration
const FLUTTERWAVE_API_URL = 'https://api.flutterwave.com/v3';
// Generate a unique tx_ref
function generateTxRef(prefix = 'VA') {
const timestamp = new Date().toISOString().replace(/[-:.TZ]/g, '');
const random = Math.random().toString(36).slice(2, 6);
return `${prefix}_${timestamp}${random}`;
}
export default async (request: Request, context: Context) => {
// Set security headers
const securityHeaders = {
'Content-Type': 'application/json',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Cache-Control': 'no-store, no-cache, must-revalidate'
};
// Only allow POST requests
if (request.method !== 'POST') {
return new Response(JSON.stringify({
status: 'error',
message: 'Method not allowed'
}), {
status: 405,
headers: securityHeaders
});
}
try {
// Get the request body
const body = await request.json();
// Validate required fields
const requiredFields = ['email', 'firstname', 'lastname', 'phonenumber'];
const missingFields = requiredFields.filter(field => !body[field]);
if (missingFields.length > 0) {
return new Response(JSON.stringify({
status: 'error',
message: `Missing required fields: ${missingFields.join(', ')}`
}), {
status: 400,
headers: securityHeaders
});
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(body.email)) {
return new Response(JSON.stringify({
status: 'error',
message: 'Invalid email format'
}), {
status: 400,
headers: securityHeaders
});
}
// Validate phone number (basic validation)
const phoneRegex = /^\d{10,15}$/;
if (!phoneRegex.test(body.phonenumber)) {
return new Response(JSON.stringify({
status: 'error',
message: 'Invalid phone number format'
}), {
status: 400,
headers: securityHeaders
});
}
// Prepare the payload
const payload = {
email: body.email,
is_permanent: body.is_permanent ?? true,
bvn: body.bvn,
tx_ref: generateTxRef(),
phonenumber: body.phonenumber,
firstname: body.firstname,
lastname: body.lastname,
narration: `${body.firstname} ${body.lastname} VA`
};
// Make request to Flutterwave API with timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
const response = await fetch(`${FLUTTERWAVE_API_URL}/virtual-account-numbers`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${Netlify.env.get("FLUTTERWAVE_SECRET_KEY")}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload),
signal: controller.signal
});
clearTimeout(timeoutId);
const data = await response.json();
// Return the response
return new Response(JSON.stringify(data), {
status: response.status,
headers: {
...securityHeaders,
'Cache-Control': 'no-store, private'
}
});
} catch (error) {
// Log error for monitoring but don't expose details
console.error('Virtual Account Creation Error:', error);
const errorMessage = error.name === 'AbortError'
? 'Request timeout'
: 'Internal server error';
return new Response(JSON.stringify({
status: 'error',
message: errorMessage
}), {
status: error.name === 'AbortError' ? 408 : 500,
headers: securityHeaders
});
}
};
// Updated config for 2025
export const config: Config = {
path: "/api/virtual-account",
cache: "manual",
onError: "/error", // Custom error page
};