@pandait.tech/payment-nuvei 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -0
- package/dist/handlers/index.cjs +1671 -0
- package/dist/handlers/index.cjs.map +1 -0
- package/dist/handlers/index.d.cts +413 -0
- package/dist/handlers/index.d.ts +413 -0
- package/dist/handlers/index.js +1656 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/index.cjs +170 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +115 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.js +157 -0
- package/dist/index.js.map +1 -0
- package/dist/payment-links/index.cjs +58 -0
- package/dist/payment-links/index.cjs.map +1 -0
- package/dist/payment-links/index.d.cts +60 -0
- package/dist/payment-links/index.d.ts +60 -0
- package/dist/payment-links/index.js +49 -0
- package/dist/payment-links/index.js.map +1 -0
- package/dist/ui/index.cjs +1236 -0
- package/dist/ui/index.cjs.map +1 -0
- package/dist/ui/index.d.cts +148 -0
- package/dist/ui/index.d.ts +148 -0
- package/dist/ui/index.js +1222 -0
- package/dist/ui/index.js.map +1 -0
- package/package.json +90 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
declare function generateAuthToken(): string;
|
|
2
|
+
declare function nuveiRequest<T>(path: string, method: "GET" | "POST", body?: Record<string, unknown>): Promise<T>;
|
|
3
|
+
declare function listCards(uid: string): Promise<{
|
|
4
|
+
cards: Array<{
|
|
5
|
+
bin: string;
|
|
6
|
+
status: string;
|
|
7
|
+
token: string;
|
|
8
|
+
holder_name: string;
|
|
9
|
+
expiry_year: string;
|
|
10
|
+
expiry_month: string;
|
|
11
|
+
transaction_reference: string;
|
|
12
|
+
type: string;
|
|
13
|
+
number: string;
|
|
14
|
+
}>;
|
|
15
|
+
result_size: number;
|
|
16
|
+
}>;
|
|
17
|
+
declare function deleteCard(token: string, uid: string): Promise<{
|
|
18
|
+
message: string;
|
|
19
|
+
}>;
|
|
20
|
+
declare function refundTransaction(transactionId: string): Promise<{
|
|
21
|
+
status: string;
|
|
22
|
+
detail: string;
|
|
23
|
+
}>;
|
|
24
|
+
declare function verifyCard(params: {
|
|
25
|
+
userId: string;
|
|
26
|
+
transactionReference: string;
|
|
27
|
+
value: string;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
transaction?: {
|
|
30
|
+
status: string;
|
|
31
|
+
status_detail: number;
|
|
32
|
+
id: string;
|
|
33
|
+
message: string | null;
|
|
34
|
+
};
|
|
35
|
+
error?: {
|
|
36
|
+
type: string;
|
|
37
|
+
help: string;
|
|
38
|
+
description: string;
|
|
39
|
+
};
|
|
40
|
+
}>;
|
|
41
|
+
interface ThreeDSResponse {
|
|
42
|
+
authentication: {
|
|
43
|
+
status: string;
|
|
44
|
+
return_code: string;
|
|
45
|
+
return_message?: string;
|
|
46
|
+
cavv?: string;
|
|
47
|
+
eci?: string;
|
|
48
|
+
xid?: string;
|
|
49
|
+
reference_id?: string;
|
|
50
|
+
};
|
|
51
|
+
browser_response: {
|
|
52
|
+
hidden_iframe?: string;
|
|
53
|
+
challenge_request?: string;
|
|
54
|
+
};
|
|
55
|
+
sdk_response?: {
|
|
56
|
+
acs_trans_id?: string;
|
|
57
|
+
acs_reference_number?: string;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
interface DebitResponse {
|
|
61
|
+
transaction?: {
|
|
62
|
+
status: string;
|
|
63
|
+
current_status: string;
|
|
64
|
+
id: string;
|
|
65
|
+
message: string | null;
|
|
66
|
+
status_detail: number;
|
|
67
|
+
authorization_code: string | null;
|
|
68
|
+
};
|
|
69
|
+
card?: {
|
|
70
|
+
bin: string;
|
|
71
|
+
type: string;
|
|
72
|
+
number: string;
|
|
73
|
+
};
|
|
74
|
+
"3ds"?: ThreeDSResponse;
|
|
75
|
+
error?: {
|
|
76
|
+
type: string;
|
|
77
|
+
help: string;
|
|
78
|
+
description: string;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
declare function debitWithToken(params: {
|
|
82
|
+
userId: string;
|
|
83
|
+
userEmail: string;
|
|
84
|
+
amount: number;
|
|
85
|
+
description: string;
|
|
86
|
+
devReference: string;
|
|
87
|
+
cardToken: string;
|
|
88
|
+
cvc?: string;
|
|
89
|
+
vat?: number;
|
|
90
|
+
taxableAmount?: number;
|
|
91
|
+
installments?: number;
|
|
92
|
+
installmentsType?: number;
|
|
93
|
+
browserInfo?: {
|
|
94
|
+
accept_header: string;
|
|
95
|
+
user_agent: string;
|
|
96
|
+
language: string;
|
|
97
|
+
timezone_offset: number;
|
|
98
|
+
screen_width: number;
|
|
99
|
+
screen_height: number;
|
|
100
|
+
color_depth: number;
|
|
101
|
+
js_enabled: boolean;
|
|
102
|
+
java_enabled: boolean;
|
|
103
|
+
ip_address?: string;
|
|
104
|
+
};
|
|
105
|
+
termUrl?: string;
|
|
106
|
+
}): Promise<DebitResponse>;
|
|
107
|
+
interface VerifyThreeDSParams {
|
|
108
|
+
transactionId: string;
|
|
109
|
+
userId: string;
|
|
110
|
+
type: "AUTHENTICATION_CONTINUE" | "BY_CRES" | "BY_OTP";
|
|
111
|
+
value?: string;
|
|
112
|
+
}
|
|
113
|
+
declare function verifyThreeDS(params: VerifyThreeDSParams): Promise<DebitResponse>;
|
|
114
|
+
|
|
115
|
+
export { type DebitResponse, type ThreeDSResponse, type VerifyThreeDSParams, debitWithToken, deleteCard, generateAuthToken, listCards, nuveiRequest, refundTransaction, verifyCard, verifyThreeDS };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
declare function generateAuthToken(): string;
|
|
2
|
+
declare function nuveiRequest<T>(path: string, method: "GET" | "POST", body?: Record<string, unknown>): Promise<T>;
|
|
3
|
+
declare function listCards(uid: string): Promise<{
|
|
4
|
+
cards: Array<{
|
|
5
|
+
bin: string;
|
|
6
|
+
status: string;
|
|
7
|
+
token: string;
|
|
8
|
+
holder_name: string;
|
|
9
|
+
expiry_year: string;
|
|
10
|
+
expiry_month: string;
|
|
11
|
+
transaction_reference: string;
|
|
12
|
+
type: string;
|
|
13
|
+
number: string;
|
|
14
|
+
}>;
|
|
15
|
+
result_size: number;
|
|
16
|
+
}>;
|
|
17
|
+
declare function deleteCard(token: string, uid: string): Promise<{
|
|
18
|
+
message: string;
|
|
19
|
+
}>;
|
|
20
|
+
declare function refundTransaction(transactionId: string): Promise<{
|
|
21
|
+
status: string;
|
|
22
|
+
detail: string;
|
|
23
|
+
}>;
|
|
24
|
+
declare function verifyCard(params: {
|
|
25
|
+
userId: string;
|
|
26
|
+
transactionReference: string;
|
|
27
|
+
value: string;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
transaction?: {
|
|
30
|
+
status: string;
|
|
31
|
+
status_detail: number;
|
|
32
|
+
id: string;
|
|
33
|
+
message: string | null;
|
|
34
|
+
};
|
|
35
|
+
error?: {
|
|
36
|
+
type: string;
|
|
37
|
+
help: string;
|
|
38
|
+
description: string;
|
|
39
|
+
};
|
|
40
|
+
}>;
|
|
41
|
+
interface ThreeDSResponse {
|
|
42
|
+
authentication: {
|
|
43
|
+
status: string;
|
|
44
|
+
return_code: string;
|
|
45
|
+
return_message?: string;
|
|
46
|
+
cavv?: string;
|
|
47
|
+
eci?: string;
|
|
48
|
+
xid?: string;
|
|
49
|
+
reference_id?: string;
|
|
50
|
+
};
|
|
51
|
+
browser_response: {
|
|
52
|
+
hidden_iframe?: string;
|
|
53
|
+
challenge_request?: string;
|
|
54
|
+
};
|
|
55
|
+
sdk_response?: {
|
|
56
|
+
acs_trans_id?: string;
|
|
57
|
+
acs_reference_number?: string;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
interface DebitResponse {
|
|
61
|
+
transaction?: {
|
|
62
|
+
status: string;
|
|
63
|
+
current_status: string;
|
|
64
|
+
id: string;
|
|
65
|
+
message: string | null;
|
|
66
|
+
status_detail: number;
|
|
67
|
+
authorization_code: string | null;
|
|
68
|
+
};
|
|
69
|
+
card?: {
|
|
70
|
+
bin: string;
|
|
71
|
+
type: string;
|
|
72
|
+
number: string;
|
|
73
|
+
};
|
|
74
|
+
"3ds"?: ThreeDSResponse;
|
|
75
|
+
error?: {
|
|
76
|
+
type: string;
|
|
77
|
+
help: string;
|
|
78
|
+
description: string;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
declare function debitWithToken(params: {
|
|
82
|
+
userId: string;
|
|
83
|
+
userEmail: string;
|
|
84
|
+
amount: number;
|
|
85
|
+
description: string;
|
|
86
|
+
devReference: string;
|
|
87
|
+
cardToken: string;
|
|
88
|
+
cvc?: string;
|
|
89
|
+
vat?: number;
|
|
90
|
+
taxableAmount?: number;
|
|
91
|
+
installments?: number;
|
|
92
|
+
installmentsType?: number;
|
|
93
|
+
browserInfo?: {
|
|
94
|
+
accept_header: string;
|
|
95
|
+
user_agent: string;
|
|
96
|
+
language: string;
|
|
97
|
+
timezone_offset: number;
|
|
98
|
+
screen_width: number;
|
|
99
|
+
screen_height: number;
|
|
100
|
+
color_depth: number;
|
|
101
|
+
js_enabled: boolean;
|
|
102
|
+
java_enabled: boolean;
|
|
103
|
+
ip_address?: string;
|
|
104
|
+
};
|
|
105
|
+
termUrl?: string;
|
|
106
|
+
}): Promise<DebitResponse>;
|
|
107
|
+
interface VerifyThreeDSParams {
|
|
108
|
+
transactionId: string;
|
|
109
|
+
userId: string;
|
|
110
|
+
type: "AUTHENTICATION_CONTINUE" | "BY_CRES" | "BY_OTP";
|
|
111
|
+
value?: string;
|
|
112
|
+
}
|
|
113
|
+
declare function verifyThreeDS(params: VerifyThreeDSParams): Promise<DebitResponse>;
|
|
114
|
+
|
|
115
|
+
export { type DebitResponse, type ThreeDSResponse, type VerifyThreeDSParams, debitWithToken, deleteCard, generateAuthToken, listCards, nuveiRequest, refundTransaction, verifyCard, verifyThreeDS };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
// src/sdk.ts
|
|
4
|
+
var NUVEI_DOMAIN = "paymentez.com";
|
|
5
|
+
function getBaseUrl() {
|
|
6
|
+
const env = process.env.NUVEI_ENV === "prod" ? "prod" : "stg";
|
|
7
|
+
return env === "prod" ? `https://ccapi.${NUVEI_DOMAIN}` : `https://ccapi-stg.${NUVEI_DOMAIN}`;
|
|
8
|
+
}
|
|
9
|
+
function getServerCredentials() {
|
|
10
|
+
const appCode = process.env.NUVEI_SERVER_APP_CODE;
|
|
11
|
+
const appKey = process.env.NUVEI_SERVER_APP_KEY;
|
|
12
|
+
if (!appCode || !appKey) {
|
|
13
|
+
throw new Error("Nuvei server credentials not configured");
|
|
14
|
+
}
|
|
15
|
+
return { appCode, appKey };
|
|
16
|
+
}
|
|
17
|
+
function generateAuthToken() {
|
|
18
|
+
const { appCode, appKey } = getServerCredentials();
|
|
19
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
20
|
+
const uniqToken = crypto.createHash("sha256").update(`${appKey}${timestamp}`).digest("hex");
|
|
21
|
+
return Buffer.from(`${appCode};${timestamp};${uniqToken}`).toString(
|
|
22
|
+
"base64"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
function getProxyUrl() {
|
|
26
|
+
return process.env.NUVEI_PROXY_URL || null;
|
|
27
|
+
}
|
|
28
|
+
async function nuveiRequest(path, method, body) {
|
|
29
|
+
const authToken = generateAuthToken();
|
|
30
|
+
const proxyUrl = getProxyUrl();
|
|
31
|
+
if (proxyUrl) {
|
|
32
|
+
console.log(`[nuvei] ${method} ${path} via proxy`);
|
|
33
|
+
const proxyResponse = await fetch(proxyUrl, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
"x-nuvei-auth-token": authToken,
|
|
38
|
+
"x-nuvei-env": process.env.NUVEI_ENV || "stg"
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({ path, method, body })
|
|
41
|
+
});
|
|
42
|
+
if (!proxyResponse.ok) {
|
|
43
|
+
const errorBody = await proxyResponse.text();
|
|
44
|
+
console.error(`[nuvei] ${method} ${path} proxy failed with status ${proxyResponse.status}. Body: ${errorBody}`);
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(errorBody);
|
|
47
|
+
} catch {
|
|
48
|
+
throw new Error(`Nuvei proxy ${method} ${path} failed: ${proxyResponse.status}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return proxyResponse.json();
|
|
52
|
+
}
|
|
53
|
+
const url = `${getBaseUrl()}${path}`;
|
|
54
|
+
const options = {
|
|
55
|
+
method,
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
"Auth-Token": authToken
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
if (body && method === "POST") {
|
|
62
|
+
options.body = JSON.stringify(body);
|
|
63
|
+
}
|
|
64
|
+
const response = await fetch(url, options);
|
|
65
|
+
const responseText = await response.text();
|
|
66
|
+
console.log(`[nuvei] ${method} ${path} \u2192 ${response.status} | body: ${responseText.slice(0, 300)}`);
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
console.error(`[nuvei] ${method} ${path} failed with status ${response.status}`);
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(responseText);
|
|
71
|
+
} catch {
|
|
72
|
+
throw new Error(`Nuvei ${method} ${path} failed: ${response.status}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(responseText);
|
|
77
|
+
} catch {
|
|
78
|
+
throw new Error(`Nuvei ${method} ${path}: invalid JSON response`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function listCards(uid) {
|
|
82
|
+
return nuveiRequest(`/v2/card/list?uid=${encodeURIComponent(uid)}`, "GET");
|
|
83
|
+
}
|
|
84
|
+
async function deleteCard(token, uid) {
|
|
85
|
+
return nuveiRequest("/v2/card/delete/", "POST", {
|
|
86
|
+
card: { token },
|
|
87
|
+
user: { id: uid }
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async function refundTransaction(transactionId) {
|
|
91
|
+
return nuveiRequest("/v2/transaction/refund/", "POST", {
|
|
92
|
+
transaction: { id: transactionId }
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function verifyCard(params) {
|
|
96
|
+
return nuveiRequest("/v2/transaction/verify/", "POST", {
|
|
97
|
+
user: {
|
|
98
|
+
id: params.userId
|
|
99
|
+
},
|
|
100
|
+
transaction: {
|
|
101
|
+
id: params.transactionReference
|
|
102
|
+
},
|
|
103
|
+
type: "BY_AMOUNT",
|
|
104
|
+
value: params.value
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function debitWithToken(params) {
|
|
108
|
+
const order = {
|
|
109
|
+
amount: params.amount,
|
|
110
|
+
description: params.description,
|
|
111
|
+
dev_reference: params.devReference,
|
|
112
|
+
vat: params.vat ?? 0,
|
|
113
|
+
taxable_amount: params.taxableAmount ?? params.amount - (params.vat ?? 0),
|
|
114
|
+
tax_percentage: 15
|
|
115
|
+
};
|
|
116
|
+
if (params.installments && params.installments > 0) {
|
|
117
|
+
order.installments = params.installments;
|
|
118
|
+
order.installments_type = params.installmentsType ?? 0;
|
|
119
|
+
}
|
|
120
|
+
const body = {
|
|
121
|
+
user: {
|
|
122
|
+
id: params.userId,
|
|
123
|
+
email: params.userEmail
|
|
124
|
+
},
|
|
125
|
+
order,
|
|
126
|
+
card: {
|
|
127
|
+
token: params.cardToken,
|
|
128
|
+
...params.cvc ? { cvc: params.cvc } : {}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
if (params.browserInfo && params.termUrl) {
|
|
132
|
+
body.extra_params = {
|
|
133
|
+
threeDS2_data: {
|
|
134
|
+
term_url: params.termUrl,
|
|
135
|
+
device_type: "browser"
|
|
136
|
+
},
|
|
137
|
+
browser_info: params.browserInfo
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return nuveiRequest("/v2/transaction/debit/", "POST", body);
|
|
141
|
+
}
|
|
142
|
+
async function verifyThreeDS(params) {
|
|
143
|
+
const body = {
|
|
144
|
+
user: {
|
|
145
|
+
id: params.userId
|
|
146
|
+
},
|
|
147
|
+
transaction: { id: params.transactionId },
|
|
148
|
+
type: params.type,
|
|
149
|
+
value: params.value ?? "",
|
|
150
|
+
more_info: true
|
|
151
|
+
};
|
|
152
|
+
return nuveiRequest("/v2/transaction/verify/", "POST", body);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export { debitWithToken, deleteCard, generateAuthToken, listCards, nuveiRequest, refundTransaction, verifyCard, verifyThreeDS };
|
|
156
|
+
//# sourceMappingURL=index.js.map
|
|
157
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sdk.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Source: pauhenriques-website/src/lib/nuvei.ts\n// Phase 1 step 1.2: verbatim copy. Refactor to factory pattern (createNuveiClient) happens in step 1.3.\n\nimport crypto from \"crypto\";\n\nconst NUVEI_DOMAIN = \"paymentez.com\";\n\nfunction getBaseUrl(): string {\n const env = process.env.NUVEI_ENV === \"prod\" ? \"prod\" : \"stg\";\n return env === \"prod\"\n ? `https://ccapi.${NUVEI_DOMAIN}`\n : `https://ccapi-stg.${NUVEI_DOMAIN}`;\n}\n\n\nfunction getServerCredentials() {\n const appCode = process.env.NUVEI_SERVER_APP_CODE;\n const appKey = process.env.NUVEI_SERVER_APP_KEY;\n if (!appCode || !appKey) {\n throw new Error(\"Nuvei server credentials not configured\");\n }\n return { appCode, appKey };\n}\n\nexport function generateAuthToken(): string {\n const { appCode, appKey } = getServerCredentials();\n const timestamp = Math.floor(Date.now() / 1000);\n const uniqToken = crypto\n .createHash(\"sha256\")\n .update(`${appKey}${timestamp}`)\n .digest(\"hex\");\n return Buffer.from(`${appCode};${timestamp};${uniqToken}`).toString(\n \"base64\",\n );\n}\n\nfunction getProxyUrl(): string | null {\n return process.env.NUVEI_PROXY_URL || null;\n}\n\nexport async function nuveiRequest<T>(\n path: string,\n method: \"GET\" | \"POST\",\n body?: Record<string, unknown>,\n): Promise<T> {\n const authToken = generateAuthToken();\n const proxyUrl = getProxyUrl();\n\n // Use Cloud Function proxy if configured (workaround for Cloud Run / Nuvei incompatibility)\n if (proxyUrl) {\n console.log(`[nuvei] ${method} ${path} via proxy`);\n const proxyResponse = await fetch(proxyUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-nuvei-auth-token\": authToken,\n \"x-nuvei-env\": process.env.NUVEI_ENV || \"stg\",\n },\n body: JSON.stringify({ path, method, body }),\n });\n if (!proxyResponse.ok) {\n const errorBody = await proxyResponse.text();\n console.error(`[nuvei] ${method} ${path} proxy failed with status ${proxyResponse.status}. Body: ${errorBody}`);\n try {\n return JSON.parse(errorBody) as T;\n } catch {\n throw new Error(`Nuvei proxy ${method} ${path} failed: ${proxyResponse.status}`);\n }\n }\n return proxyResponse.json() as Promise<T>;\n }\n\n // Direct call\n const url = `${getBaseUrl()}${path}`;\n\n const options: RequestInit = {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Auth-Token\": authToken,\n },\n };\n\n if (body && method === \"POST\") {\n options.body = JSON.stringify(body);\n }\n\n const response = await fetch(url, options);\n const responseText = await response.text();\n console.log(`[nuvei] ${method} ${path} → ${response.status} | body: ${responseText.slice(0, 300)}`);\n\n if (!response.ok) {\n console.error(`[nuvei] ${method} ${path} failed with status ${response.status}`);\n try {\n return JSON.parse(responseText) as T;\n } catch {\n throw new Error(`Nuvei ${method} ${path} failed: ${response.status}`);\n }\n }\n\n try {\n return JSON.parse(responseText) as T;\n } catch {\n throw new Error(`Nuvei ${method} ${path}: invalid JSON response`);\n }\n}\n\nexport async function listCards(uid: string) {\n return nuveiRequest<{\n cards: Array<{\n bin: string;\n status: string;\n token: string;\n holder_name: string;\n expiry_year: string;\n expiry_month: string;\n transaction_reference: string;\n type: string;\n number: string;\n }>;\n result_size: number;\n }>(`/v2/card/list?uid=${encodeURIComponent(uid)}`, \"GET\");\n}\n\nexport async function deleteCard(token: string, uid: string) {\n return nuveiRequest<{ message: string }>(\"/v2/card/delete/\", \"POST\", {\n card: { token },\n user: { id: uid },\n });\n}\n\nexport async function refundTransaction(transactionId: string) {\n return nuveiRequest<{\n status: string;\n detail: string;\n }>(\"/v2/transaction/refund/\", \"POST\", {\n transaction: { id: transactionId },\n });\n}\n\nexport async function verifyCard(params: {\n userId: string;\n transactionReference: string;\n value: string;\n}) {\n return nuveiRequest<{\n transaction?: {\n status: string;\n status_detail: number;\n id: string;\n message: string | null;\n };\n error?: {\n type: string;\n help: string;\n description: string;\n };\n }>(\"/v2/transaction/verify/\", \"POST\", {\n user: {\n id: params.userId,\n },\n transaction: {\n id: params.transactionReference,\n },\n type: \"BY_AMOUNT\",\n value: params.value,\n });\n}\n\nexport interface ThreeDSResponse {\n authentication: {\n status: string;\n return_code: string;\n return_message?: string;\n cavv?: string;\n eci?: string;\n xid?: string;\n reference_id?: string;\n };\n browser_response: {\n hidden_iframe?: string;\n challenge_request?: string;\n };\n sdk_response?: {\n acs_trans_id?: string;\n acs_reference_number?: string;\n };\n}\n\nexport interface DebitResponse {\n transaction?: {\n status: string;\n current_status: string;\n id: string;\n message: string | null;\n status_detail: number;\n authorization_code: string | null;\n };\n card?: {\n bin: string;\n type: string;\n number: string;\n };\n \"3ds\"?: ThreeDSResponse;\n error?: {\n type: string;\n help: string;\n description: string;\n };\n}\n\nexport async function debitWithToken(params: {\n userId: string;\n userEmail: string;\n amount: number;\n description: string;\n devReference: string;\n cardToken: string;\n cvc?: string;\n vat?: number;\n taxableAmount?: number;\n installments?: number;\n installmentsType?: number;\n browserInfo?: {\n accept_header: string;\n user_agent: string;\n language: string;\n timezone_offset: number;\n screen_width: number;\n screen_height: number;\n color_depth: number;\n js_enabled: boolean;\n java_enabled: boolean;\n ip_address?: string;\n };\n termUrl?: string;\n}) {\n const order: Record<string, unknown> = {\n amount: params.amount,\n description: params.description,\n dev_reference: params.devReference,\n vat: params.vat ?? 0,\n taxable_amount: params.taxableAmount ?? (params.amount - (params.vat ?? 0)),\n tax_percentage: 15,\n };\n\n if (params.installments && params.installments > 0) {\n order.installments = params.installments;\n order.installments_type = params.installmentsType ?? 0;\n }\n\n const body: Record<string, unknown> = {\n user: {\n id: params.userId,\n email: params.userEmail,\n },\n order,\n card: {\n token: params.cardToken,\n ...(params.cvc ? { cvc: params.cvc } : {}),\n },\n };\n\n if (params.browserInfo && params.termUrl) {\n body.extra_params = {\n threeDS2_data: {\n term_url: params.termUrl,\n device_type: \"browser\",\n },\n browser_info: params.browserInfo,\n };\n }\n\n return nuveiRequest<DebitResponse>(\"/v2/transaction/debit/\", \"POST\", body);\n}\n\n// --- 3DS Verify API ---\n\nexport interface VerifyThreeDSParams {\n transactionId: string;\n userId: string;\n type: \"AUTHENTICATION_CONTINUE\" | \"BY_CRES\" | \"BY_OTP\";\n value?: string;\n}\n\nexport async function verifyThreeDS(params: VerifyThreeDSParams): Promise<DebitResponse> {\n const body: Record<string, unknown> = {\n user: {\n id: params.userId,\n },\n transaction: { id: params.transactionId },\n type: params.type,\n value: params.value ?? \"\",\n more_info: true,\n };\n return nuveiRequest<DebitResponse>(\"/v2/transaction/verify/\", \"POST\", body);\n}\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
// src/payment-links/paymentLink.ts
|
|
6
|
+
var PAYMENT_LINK_PRICE_MIN = 1;
|
|
7
|
+
var PAYMENT_LINK_PRICE_MAX = 2e3;
|
|
8
|
+
var PAYMENT_LINK_PRICE_CONFIRM_THRESHOLD = 200;
|
|
9
|
+
var PAYMENT_LINK_DEFAULT_EXPIRY_DAYS = 30;
|
|
10
|
+
function generatePaymentLinkToken() {
|
|
11
|
+
return crypto.randomBytes(12).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
12
|
+
}
|
|
13
|
+
function defaultExpiry() {
|
|
14
|
+
const d = /* @__PURE__ */ new Date();
|
|
15
|
+
d.setDate(d.getDate() + PAYMENT_LINK_DEFAULT_EXPIRY_DAYS);
|
|
16
|
+
return d;
|
|
17
|
+
}
|
|
18
|
+
function isExpired(expiresAt) {
|
|
19
|
+
const iso = typeof expiresAt === "string" ? expiresAt : expiresAt.toISOString();
|
|
20
|
+
return new Date(iso).getTime() < Date.now();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/payment-links/anonymousAuth.ts
|
|
24
|
+
function createAnonymousAuthHelper(deps) {
|
|
25
|
+
const sessionEndpoint = deps.sessionEndpoint ?? "/api/auth/session";
|
|
26
|
+
return async function ensureAnonymousSession(existingUser) {
|
|
27
|
+
let user = existingUser ?? deps.auth.currentUser;
|
|
28
|
+
if (!user) {
|
|
29
|
+
const { signInAnonymously } = await import('firebase/auth');
|
|
30
|
+
const cred = await signInAnonymously(deps.auth);
|
|
31
|
+
user = cred.user;
|
|
32
|
+
}
|
|
33
|
+
const idToken = await user.getIdToken(
|
|
34
|
+
/* forceRefresh */
|
|
35
|
+
true
|
|
36
|
+
);
|
|
37
|
+
const res = await fetch(sessionEndpoint, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "Content-Type": "application/json" },
|
|
40
|
+
body: JSON.stringify({ idToken })
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
throw new Error(`No se pudo establecer la sesi\xF3n (${res.status})`);
|
|
44
|
+
}
|
|
45
|
+
return user;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
exports.PAYMENT_LINK_DEFAULT_EXPIRY_DAYS = PAYMENT_LINK_DEFAULT_EXPIRY_DAYS;
|
|
50
|
+
exports.PAYMENT_LINK_PRICE_CONFIRM_THRESHOLD = PAYMENT_LINK_PRICE_CONFIRM_THRESHOLD;
|
|
51
|
+
exports.PAYMENT_LINK_PRICE_MAX = PAYMENT_LINK_PRICE_MAX;
|
|
52
|
+
exports.PAYMENT_LINK_PRICE_MIN = PAYMENT_LINK_PRICE_MIN;
|
|
53
|
+
exports.createAnonymousAuthHelper = createAnonymousAuthHelper;
|
|
54
|
+
exports.defaultExpiry = defaultExpiry;
|
|
55
|
+
exports.generatePaymentLinkToken = generatePaymentLinkToken;
|
|
56
|
+
exports.isExpired = isExpired;
|
|
57
|
+
//# sourceMappingURL=index.cjs.map
|
|
58
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/payment-links/paymentLink.ts","../../src/payment-links/anonymousAuth.ts"],"names":["randomBytes"],"mappings":";;;;;AAkCO,IAAM,sBAAA,GAAyB;AAC/B,IAAM,sBAAA,GAAyB;AAC/B,IAAM,oCAAA,GAAuC;AAC7C,IAAM,gCAAA,GAAmC;AAEzC,SAAS,wBAAA,GAAmC;AAGjD,EAAA,OAAOA,mBAAY,EAAE,CAAA,CAClB,QAAA,CAAS,QAAQ,EACjB,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAClB,QAAQ,KAAA,EAAO,GAAG,CAAA,CAClB,OAAA,CAAQ,QAAQ,EAAE,CAAA;AACvB;AAEO,SAAS,aAAA,GAAsB;AACpC,EAAA,MAAM,CAAA,uBAAQ,IAAA,EAAK;AACnB,EAAA,CAAA,CAAE,OAAA,CAAQ,CAAA,CAAE,OAAA,EAAQ,GAAI,gCAAgC,CAAA;AACxD,EAAA,OAAO,CAAA;AACT;AAEO,SAAS,UAAU,SAAA,EAAmC;AAC3D,EAAA,MAAM,MAAM,OAAO,SAAA,KAAc,QAAA,GAAW,SAAA,GAAY,UAAU,WAAA,EAAY;AAC9E,EAAA,OAAO,IAAI,IAAA,CAAK,GAAG,EAAE,OAAA,EAAQ,GAAI,KAAK,GAAA,EAAI;AAC5C;;;ACrBO,SAAS,0BAA0B,IAAA,EAA+B;AACvE,EAAA,MAAM,eAAA,GAAkB,KAAK,eAAA,IAAmB,mBAAA;AAEhD,EAAA,OAAO,eAAe,uBACpB,YAAA,EACe;AACf,IAAA,IAAI,IAAA,GAAO,YAAA,IAAgB,IAAA,CAAK,IAAA,CAAK,WAAA;AAErC,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA,MAAM,EAAE,iBAAA,EAAkB,GAAI,MAAM,OAAO,eAAe,CAAA;AAC1D,MAAA,MAAM,IAAA,GAAO,MAAM,iBAAA,CAAkB,IAAA,CAAK,IAAI,CAAA;AAC9C,MAAA,IAAA,GAAO,IAAA,CAAK,IAAA;AAAA,IACd;AAEA,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA;AAAA;AAAA,MAA8B;AAAA,KAAI;AAC7D,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,eAAA,EAAiB;AAAA,MACvC,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,SAAS;AAAA,KACjC,CAAA;AAED,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oCAAA,EAAoC,GAAA,CAAI,MAAM,CAAA,CAAA,CAAG,CAAA;AAAA,IACnE;AAEA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AACF","file":"index.cjs","sourcesContent":["import { randomBytes } from \"crypto\";\n\nexport interface PaymentLinkInput {\n price: number;\n label?: string;\n publicLabel?: string;\n expiresAt: Date;\n tallerId: string;\n notes?: string;\n}\n\nexport interface PaymentLink {\n id: string;\n token: string;\n tallerId: string;\n price: number;\n // Nombre interno (solo admin) — referencia para identificar el link en la\n // tabla y en el dashboard. NUNCA se muestra al cliente.\n label: string | null;\n // Etiqueta promocional visible al cliente en /pago/t/[token] junto al\n // emoji 🔥 (ej. \"Promo amigas\", \"Última semana\"). Opcional.\n publicLabel: string | null;\n notes: string | null;\n active: boolean;\n expiresAt: string;\n createdByUid: string;\n createdByEmail: string | null;\n createdByName: string | null;\n timesPaid: number;\n lastPaidAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport const PAYMENT_LINK_PRICE_MIN = 1;\nexport const PAYMENT_LINK_PRICE_MAX = 2000;\nexport const PAYMENT_LINK_PRICE_CONFIRM_THRESHOLD = 200;\nexport const PAYMENT_LINK_DEFAULT_EXPIRY_DAYS = 30;\n\nexport function generatePaymentLinkToken(): string {\n // 12 bytes → 16 chars base64url, suficientemente largo para evitar adivinanza\n // (2^96 combinaciones) y lo bastante corto para un URL amigable.\n return randomBytes(12)\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/g, \"\");\n}\n\nexport function defaultExpiry(): Date {\n const d = new Date();\n d.setDate(d.getDate() + PAYMENT_LINK_DEFAULT_EXPIRY_DAYS);\n return d;\n}\n\nexport function isExpired(expiresAt: Date | string): boolean {\n const iso = typeof expiresAt === \"string\" ? expiresAt : expiresAt.toISOString();\n return new Date(iso).getTime() < Date.now();\n}\n","// Source: pauhenriques-website/src/lib/pago-link/anonymousAuth.ts\n// Phase 1.4: refactored to factory pattern.\n//\n// Client-only helper used by guest checkout flows (e.g. payment links) that\n// need a Firebase __session cookie to call the package's POST handlers\n// (charge, 3ds-complete, refund, etc.) — those handlers verify a session\n// cookie, so guests must first sign in anonymously.\n//\n// The two consumer touchpoints (Firebase client Auth instance and session\n// endpoint path) are injected as deps rather than imported from a fixed\n// module path, so the helper works in any Next.js app regardless of how\n// the consumer initializes Firebase.\n\nimport type { Auth, User } from \"firebase/auth\";\n\n/** Dependencies for the anonymous-auth helper factory. */\nexport interface AnonymousAuthHelperDeps {\n /** Firebase client Auth instance. Must be the same instance used by the consumer's app for sign-in state to stay consistent across calls. */\n auth: Auth;\n /** URL where the helper POSTs `{ idToken }` to exchange the Firebase ID token for a server-side session cookie. The endpoint must respond 2xx on success. Default: \"/api/auth/session\". */\n sessionEndpoint?: string;\n}\n\n/**\n * Creates an `ensureAnonymousSession(existingUser)` function that signs in\n * anonymously (if not already signed in) and establishes a server-side\n * session cookie. Idempotent: if a user is already signed in, refreshes\n * the cookie with the current user.\n *\n * Usage:\n *\n * const ensureAnonymousSession = createAnonymousAuthHelper({\n * auth: clientAuth,\n * sessionEndpoint: \"/api/auth/session\",\n * });\n * const user = await ensureAnonymousSession(currentUser);\n */\nexport function createAnonymousAuthHelper(deps: AnonymousAuthHelperDeps) {\n const sessionEndpoint = deps.sessionEndpoint ?? \"/api/auth/session\";\n\n return async function ensureAnonymousSession(\n existingUser: User | null,\n ): Promise<User> {\n let user = existingUser ?? deps.auth.currentUser;\n\n if (!user) {\n const { signInAnonymously } = await import(\"firebase/auth\");\n const cred = await signInAnonymously(deps.auth);\n user = cred.user;\n }\n\n const idToken = await user.getIdToken(/* forceRefresh */ true);\n const res = await fetch(sessionEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ idToken }),\n });\n\n if (!res.ok) {\n throw new Error(`No se pudo establecer la sesión (${res.status})`);\n }\n\n return user;\n };\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Auth, User } from 'firebase/auth';
|
|
2
|
+
|
|
3
|
+
interface PaymentLinkInput {
|
|
4
|
+
price: number;
|
|
5
|
+
label?: string;
|
|
6
|
+
publicLabel?: string;
|
|
7
|
+
expiresAt: Date;
|
|
8
|
+
tallerId: string;
|
|
9
|
+
notes?: string;
|
|
10
|
+
}
|
|
11
|
+
interface PaymentLink {
|
|
12
|
+
id: string;
|
|
13
|
+
token: string;
|
|
14
|
+
tallerId: string;
|
|
15
|
+
price: number;
|
|
16
|
+
label: string | null;
|
|
17
|
+
publicLabel: string | null;
|
|
18
|
+
notes: string | null;
|
|
19
|
+
active: boolean;
|
|
20
|
+
expiresAt: string;
|
|
21
|
+
createdByUid: string;
|
|
22
|
+
createdByEmail: string | null;
|
|
23
|
+
createdByName: string | null;
|
|
24
|
+
timesPaid: number;
|
|
25
|
+
lastPaidAt: string | null;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
updatedAt: string;
|
|
28
|
+
}
|
|
29
|
+
declare const PAYMENT_LINK_PRICE_MIN = 1;
|
|
30
|
+
declare const PAYMENT_LINK_PRICE_MAX = 2000;
|
|
31
|
+
declare const PAYMENT_LINK_PRICE_CONFIRM_THRESHOLD = 200;
|
|
32
|
+
declare const PAYMENT_LINK_DEFAULT_EXPIRY_DAYS = 30;
|
|
33
|
+
declare function generatePaymentLinkToken(): string;
|
|
34
|
+
declare function defaultExpiry(): Date;
|
|
35
|
+
declare function isExpired(expiresAt: Date | string): boolean;
|
|
36
|
+
|
|
37
|
+
/** Dependencies for the anonymous-auth helper factory. */
|
|
38
|
+
interface AnonymousAuthHelperDeps {
|
|
39
|
+
/** Firebase client Auth instance. Must be the same instance used by the consumer's app for sign-in state to stay consistent across calls. */
|
|
40
|
+
auth: Auth;
|
|
41
|
+
/** URL where the helper POSTs `{ idToken }` to exchange the Firebase ID token for a server-side session cookie. The endpoint must respond 2xx on success. Default: "/api/auth/session". */
|
|
42
|
+
sessionEndpoint?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates an `ensureAnonymousSession(existingUser)` function that signs in
|
|
46
|
+
* anonymously (if not already signed in) and establishes a server-side
|
|
47
|
+
* session cookie. Idempotent: if a user is already signed in, refreshes
|
|
48
|
+
* the cookie with the current user.
|
|
49
|
+
*
|
|
50
|
+
* Usage:
|
|
51
|
+
*
|
|
52
|
+
* const ensureAnonymousSession = createAnonymousAuthHelper({
|
|
53
|
+
* auth: clientAuth,
|
|
54
|
+
* sessionEndpoint: "/api/auth/session",
|
|
55
|
+
* });
|
|
56
|
+
* const user = await ensureAnonymousSession(currentUser);
|
|
57
|
+
*/
|
|
58
|
+
declare function createAnonymousAuthHelper(deps: AnonymousAuthHelperDeps): (existingUser: User | null) => Promise<User>;
|
|
59
|
+
|
|
60
|
+
export { type AnonymousAuthHelperDeps, PAYMENT_LINK_DEFAULT_EXPIRY_DAYS, PAYMENT_LINK_PRICE_CONFIRM_THRESHOLD, PAYMENT_LINK_PRICE_MAX, PAYMENT_LINK_PRICE_MIN, type PaymentLink, type PaymentLinkInput, createAnonymousAuthHelper, defaultExpiry, generatePaymentLinkToken, isExpired };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Auth, User } from 'firebase/auth';
|
|
2
|
+
|
|
3
|
+
interface PaymentLinkInput {
|
|
4
|
+
price: number;
|
|
5
|
+
label?: string;
|
|
6
|
+
publicLabel?: string;
|
|
7
|
+
expiresAt: Date;
|
|
8
|
+
tallerId: string;
|
|
9
|
+
notes?: string;
|
|
10
|
+
}
|
|
11
|
+
interface PaymentLink {
|
|
12
|
+
id: string;
|
|
13
|
+
token: string;
|
|
14
|
+
tallerId: string;
|
|
15
|
+
price: number;
|
|
16
|
+
label: string | null;
|
|
17
|
+
publicLabel: string | null;
|
|
18
|
+
notes: string | null;
|
|
19
|
+
active: boolean;
|
|
20
|
+
expiresAt: string;
|
|
21
|
+
createdByUid: string;
|
|
22
|
+
createdByEmail: string | null;
|
|
23
|
+
createdByName: string | null;
|
|
24
|
+
timesPaid: number;
|
|
25
|
+
lastPaidAt: string | null;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
updatedAt: string;
|
|
28
|
+
}
|
|
29
|
+
declare const PAYMENT_LINK_PRICE_MIN = 1;
|
|
30
|
+
declare const PAYMENT_LINK_PRICE_MAX = 2000;
|
|
31
|
+
declare const PAYMENT_LINK_PRICE_CONFIRM_THRESHOLD = 200;
|
|
32
|
+
declare const PAYMENT_LINK_DEFAULT_EXPIRY_DAYS = 30;
|
|
33
|
+
declare function generatePaymentLinkToken(): string;
|
|
34
|
+
declare function defaultExpiry(): Date;
|
|
35
|
+
declare function isExpired(expiresAt: Date | string): boolean;
|
|
36
|
+
|
|
37
|
+
/** Dependencies for the anonymous-auth helper factory. */
|
|
38
|
+
interface AnonymousAuthHelperDeps {
|
|
39
|
+
/** Firebase client Auth instance. Must be the same instance used by the consumer's app for sign-in state to stay consistent across calls. */
|
|
40
|
+
auth: Auth;
|
|
41
|
+
/** URL where the helper POSTs `{ idToken }` to exchange the Firebase ID token for a server-side session cookie. The endpoint must respond 2xx on success. Default: "/api/auth/session". */
|
|
42
|
+
sessionEndpoint?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates an `ensureAnonymousSession(existingUser)` function that signs in
|
|
46
|
+
* anonymously (if not already signed in) and establishes a server-side
|
|
47
|
+
* session cookie. Idempotent: if a user is already signed in, refreshes
|
|
48
|
+
* the cookie with the current user.
|
|
49
|
+
*
|
|
50
|
+
* Usage:
|
|
51
|
+
*
|
|
52
|
+
* const ensureAnonymousSession = createAnonymousAuthHelper({
|
|
53
|
+
* auth: clientAuth,
|
|
54
|
+
* sessionEndpoint: "/api/auth/session",
|
|
55
|
+
* });
|
|
56
|
+
* const user = await ensureAnonymousSession(currentUser);
|
|
57
|
+
*/
|
|
58
|
+
declare function createAnonymousAuthHelper(deps: AnonymousAuthHelperDeps): (existingUser: User | null) => Promise<User>;
|
|
59
|
+
|
|
60
|
+
export { type AnonymousAuthHelperDeps, PAYMENT_LINK_DEFAULT_EXPIRY_DAYS, PAYMENT_LINK_PRICE_CONFIRM_THRESHOLD, PAYMENT_LINK_PRICE_MAX, PAYMENT_LINK_PRICE_MIN, type PaymentLink, type PaymentLinkInput, createAnonymousAuthHelper, defaultExpiry, generatePaymentLinkToken, isExpired };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
// src/payment-links/paymentLink.ts
|
|
4
|
+
var PAYMENT_LINK_PRICE_MIN = 1;
|
|
5
|
+
var PAYMENT_LINK_PRICE_MAX = 2e3;
|
|
6
|
+
var PAYMENT_LINK_PRICE_CONFIRM_THRESHOLD = 200;
|
|
7
|
+
var PAYMENT_LINK_DEFAULT_EXPIRY_DAYS = 30;
|
|
8
|
+
function generatePaymentLinkToken() {
|
|
9
|
+
return randomBytes(12).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
10
|
+
}
|
|
11
|
+
function defaultExpiry() {
|
|
12
|
+
const d = /* @__PURE__ */ new Date();
|
|
13
|
+
d.setDate(d.getDate() + PAYMENT_LINK_DEFAULT_EXPIRY_DAYS);
|
|
14
|
+
return d;
|
|
15
|
+
}
|
|
16
|
+
function isExpired(expiresAt) {
|
|
17
|
+
const iso = typeof expiresAt === "string" ? expiresAt : expiresAt.toISOString();
|
|
18
|
+
return new Date(iso).getTime() < Date.now();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/payment-links/anonymousAuth.ts
|
|
22
|
+
function createAnonymousAuthHelper(deps) {
|
|
23
|
+
const sessionEndpoint = deps.sessionEndpoint ?? "/api/auth/session";
|
|
24
|
+
return async function ensureAnonymousSession(existingUser) {
|
|
25
|
+
let user = existingUser ?? deps.auth.currentUser;
|
|
26
|
+
if (!user) {
|
|
27
|
+
const { signInAnonymously } = await import('firebase/auth');
|
|
28
|
+
const cred = await signInAnonymously(deps.auth);
|
|
29
|
+
user = cred.user;
|
|
30
|
+
}
|
|
31
|
+
const idToken = await user.getIdToken(
|
|
32
|
+
/* forceRefresh */
|
|
33
|
+
true
|
|
34
|
+
);
|
|
35
|
+
const res = await fetch(sessionEndpoint, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify({ idToken })
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
throw new Error(`No se pudo establecer la sesi\xF3n (${res.status})`);
|
|
42
|
+
}
|
|
43
|
+
return user;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { PAYMENT_LINK_DEFAULT_EXPIRY_DAYS, PAYMENT_LINK_PRICE_CONFIRM_THRESHOLD, PAYMENT_LINK_PRICE_MAX, PAYMENT_LINK_PRICE_MIN, createAnonymousAuthHelper, defaultExpiry, generatePaymentLinkToken, isExpired };
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/payment-links/paymentLink.ts","../../src/payment-links/anonymousAuth.ts"],"names":[],"mappings":";;;AAkCO,IAAM,sBAAA,GAAyB;AAC/B,IAAM,sBAAA,GAAyB;AAC/B,IAAM,oCAAA,GAAuC;AAC7C,IAAM,gCAAA,GAAmC;AAEzC,SAAS,wBAAA,GAAmC;AAGjD,EAAA,OAAO,YAAY,EAAE,CAAA,CAClB,QAAA,CAAS,QAAQ,EACjB,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAClB,QAAQ,KAAA,EAAO,GAAG,CAAA,CAClB,OAAA,CAAQ,QAAQ,EAAE,CAAA;AACvB;AAEO,SAAS,aAAA,GAAsB;AACpC,EAAA,MAAM,CAAA,uBAAQ,IAAA,EAAK;AACnB,EAAA,CAAA,CAAE,OAAA,CAAQ,CAAA,CAAE,OAAA,EAAQ,GAAI,gCAAgC,CAAA;AACxD,EAAA,OAAO,CAAA;AACT;AAEO,SAAS,UAAU,SAAA,EAAmC;AAC3D,EAAA,MAAM,MAAM,OAAO,SAAA,KAAc,QAAA,GAAW,SAAA,GAAY,UAAU,WAAA,EAAY;AAC9E,EAAA,OAAO,IAAI,IAAA,CAAK,GAAG,EAAE,OAAA,EAAQ,GAAI,KAAK,GAAA,EAAI;AAC5C;;;ACrBO,SAAS,0BAA0B,IAAA,EAA+B;AACvE,EAAA,MAAM,eAAA,GAAkB,KAAK,eAAA,IAAmB,mBAAA;AAEhD,EAAA,OAAO,eAAe,uBACpB,YAAA,EACe;AACf,IAAA,IAAI,IAAA,GAAO,YAAA,IAAgB,IAAA,CAAK,IAAA,CAAK,WAAA;AAErC,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA,MAAM,EAAE,iBAAA,EAAkB,GAAI,MAAM,OAAO,eAAe,CAAA;AAC1D,MAAA,MAAM,IAAA,GAAO,MAAM,iBAAA,CAAkB,IAAA,CAAK,IAAI,CAAA;AAC9C,MAAA,IAAA,GAAO,IAAA,CAAK,IAAA;AAAA,IACd;AAEA,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA;AAAA;AAAA,MAA8B;AAAA,KAAI;AAC7D,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,eAAA,EAAiB;AAAA,MACvC,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,SAAS;AAAA,KACjC,CAAA;AAED,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oCAAA,EAAoC,GAAA,CAAI,MAAM,CAAA,CAAA,CAAG,CAAA;AAAA,IACnE;AAEA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AACF","file":"index.js","sourcesContent":["import { randomBytes } from \"crypto\";\n\nexport interface PaymentLinkInput {\n price: number;\n label?: string;\n publicLabel?: string;\n expiresAt: Date;\n tallerId: string;\n notes?: string;\n}\n\nexport interface PaymentLink {\n id: string;\n token: string;\n tallerId: string;\n price: number;\n // Nombre interno (solo admin) — referencia para identificar el link en la\n // tabla y en el dashboard. NUNCA se muestra al cliente.\n label: string | null;\n // Etiqueta promocional visible al cliente en /pago/t/[token] junto al\n // emoji 🔥 (ej. \"Promo amigas\", \"Última semana\"). Opcional.\n publicLabel: string | null;\n notes: string | null;\n active: boolean;\n expiresAt: string;\n createdByUid: string;\n createdByEmail: string | null;\n createdByName: string | null;\n timesPaid: number;\n lastPaidAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport const PAYMENT_LINK_PRICE_MIN = 1;\nexport const PAYMENT_LINK_PRICE_MAX = 2000;\nexport const PAYMENT_LINK_PRICE_CONFIRM_THRESHOLD = 200;\nexport const PAYMENT_LINK_DEFAULT_EXPIRY_DAYS = 30;\n\nexport function generatePaymentLinkToken(): string {\n // 12 bytes → 16 chars base64url, suficientemente largo para evitar adivinanza\n // (2^96 combinaciones) y lo bastante corto para un URL amigable.\n return randomBytes(12)\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/g, \"\");\n}\n\nexport function defaultExpiry(): Date {\n const d = new Date();\n d.setDate(d.getDate() + PAYMENT_LINK_DEFAULT_EXPIRY_DAYS);\n return d;\n}\n\nexport function isExpired(expiresAt: Date | string): boolean {\n const iso = typeof expiresAt === \"string\" ? expiresAt : expiresAt.toISOString();\n return new Date(iso).getTime() < Date.now();\n}\n","// Source: pauhenriques-website/src/lib/pago-link/anonymousAuth.ts\n// Phase 1.4: refactored to factory pattern.\n//\n// Client-only helper used by guest checkout flows (e.g. payment links) that\n// need a Firebase __session cookie to call the package's POST handlers\n// (charge, 3ds-complete, refund, etc.) — those handlers verify a session\n// cookie, so guests must first sign in anonymously.\n//\n// The two consumer touchpoints (Firebase client Auth instance and session\n// endpoint path) are injected as deps rather than imported from a fixed\n// module path, so the helper works in any Next.js app regardless of how\n// the consumer initializes Firebase.\n\nimport type { Auth, User } from \"firebase/auth\";\n\n/** Dependencies for the anonymous-auth helper factory. */\nexport interface AnonymousAuthHelperDeps {\n /** Firebase client Auth instance. Must be the same instance used by the consumer's app for sign-in state to stay consistent across calls. */\n auth: Auth;\n /** URL where the helper POSTs `{ idToken }` to exchange the Firebase ID token for a server-side session cookie. The endpoint must respond 2xx on success. Default: \"/api/auth/session\". */\n sessionEndpoint?: string;\n}\n\n/**\n * Creates an `ensureAnonymousSession(existingUser)` function that signs in\n * anonymously (if not already signed in) and establishes a server-side\n * session cookie. Idempotent: if a user is already signed in, refreshes\n * the cookie with the current user.\n *\n * Usage:\n *\n * const ensureAnonymousSession = createAnonymousAuthHelper({\n * auth: clientAuth,\n * sessionEndpoint: \"/api/auth/session\",\n * });\n * const user = await ensureAnonymousSession(currentUser);\n */\nexport function createAnonymousAuthHelper(deps: AnonymousAuthHelperDeps) {\n const sessionEndpoint = deps.sessionEndpoint ?? \"/api/auth/session\";\n\n return async function ensureAnonymousSession(\n existingUser: User | null,\n ): Promise<User> {\n let user = existingUser ?? deps.auth.currentUser;\n\n if (!user) {\n const { signInAnonymously } = await import(\"firebase/auth\");\n const cred = await signInAnonymously(deps.auth);\n user = cred.user;\n }\n\n const idToken = await user.getIdToken(/* forceRefresh */ true);\n const res = await fetch(sessionEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ idToken }),\n });\n\n if (!res.ok) {\n throw new Error(`No se pudo establecer la sesión (${res.status})`);\n }\n\n return user;\n };\n}\n"]}
|