@solvapay/server 1.0.0-preview.2 → 1.0.0-preview.20
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/LICENSE.md +21 -0
- package/README.md +66 -27
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/edge.d.ts +1423 -261
- package/dist/edge.js +889 -227
- package/dist/{esm-5GYCIXIY.js → esm-UW7WCMEK.js} +1 -1
- package/dist/index.cjs +901 -223
- package/dist/index.d.cts +1466 -261
- package/dist/index.d.ts +1466 -261
- package/dist/index.js +894 -228
- package/package.json +13 -4
- package/dist/chunk-R5U7XKVJ.js +0 -16
package/dist/edge.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
__require
|
|
3
|
-
} from "./chunk-R5U7XKVJ.js";
|
|
1
|
+
import "./chunk-MLKGABMK.js";
|
|
4
2
|
|
|
5
3
|
// src/edge.ts
|
|
6
|
-
import { SolvaPayError as
|
|
4
|
+
import { SolvaPayError as SolvaPayError5 } from "@solvapay/core";
|
|
7
5
|
|
|
8
6
|
// src/client.ts
|
|
9
7
|
import { SolvaPayError } from "@solvapay/core";
|
|
10
8
|
function createSolvaPayClient(opts) {
|
|
11
|
-
const base = opts.apiBaseUrl ?? "https://api
|
|
9
|
+
const base = opts.apiBaseUrl ?? "https://api.solvapay.com";
|
|
12
10
|
if (!opts.apiKey) throw new SolvaPayError("Missing apiKey");
|
|
13
11
|
const headers = {
|
|
14
12
|
"Content-Type": "application/json",
|
|
15
|
-
|
|
13
|
+
Authorization: `Bearer ${opts.apiKey}`
|
|
16
14
|
};
|
|
17
15
|
const debug = process.env.SOLVAPAY_DEBUG === "true";
|
|
18
16
|
const log = (...args) => {
|
|
@@ -20,15 +18,10 @@ function createSolvaPayClient(opts) {
|
|
|
20
18
|
console.log(...args);
|
|
21
19
|
}
|
|
22
20
|
};
|
|
23
|
-
log(`\u{1F50C} SolvaPay API Client initialized`);
|
|
24
|
-
log(` Backend URL: ${base}`);
|
|
25
|
-
log(` API Key: ${opts.apiKey.substring(0, 10)}...`);
|
|
26
21
|
return {
|
|
27
22
|
// POST: /v1/sdk/limits
|
|
28
23
|
async checkLimits(params) {
|
|
29
24
|
const url = `${base}/v1/sdk/limits`;
|
|
30
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
31
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
32
25
|
const res = await fetch(url, {
|
|
33
26
|
method: "POST",
|
|
34
27
|
headers,
|
|
@@ -40,20 +33,11 @@ function createSolvaPayClient(opts) {
|
|
|
40
33
|
throw new SolvaPayError(`Check limits failed (${res.status}): ${error}`);
|
|
41
34
|
}
|
|
42
35
|
const result = await res.json();
|
|
43
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
44
|
-
log(`\u{1F50D} DEBUG - checkLimits breakdown:`);
|
|
45
|
-
log(` - withinLimits: ${result.withinLimits}`);
|
|
46
|
-
log(` - remaining: ${result.remaining}`);
|
|
47
|
-
log(` - plan: ${result.plan || "N/A"}`);
|
|
48
|
-
log(` - checkoutUrl: ${result.checkoutUrl || "N/A"}`);
|
|
49
|
-
log(` - Full response keys:`, Object.keys(result));
|
|
50
36
|
return result;
|
|
51
37
|
},
|
|
52
38
|
// POST: /v1/sdk/usages
|
|
53
39
|
async trackUsage(params) {
|
|
54
40
|
const url = `${base}/v1/sdk/usages`;
|
|
55
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
56
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
57
41
|
const res = await fetch(url, {
|
|
58
42
|
method: "POST",
|
|
59
43
|
headers,
|
|
@@ -64,13 +48,10 @@ function createSolvaPayClient(opts) {
|
|
|
64
48
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
65
49
|
throw new SolvaPayError(`Track usage failed (${res.status}): ${error}`);
|
|
66
50
|
}
|
|
67
|
-
log(`\u2705 Usage tracked successfully`);
|
|
68
51
|
},
|
|
69
52
|
// POST: /v1/sdk/customers
|
|
70
53
|
async createCustomer(params) {
|
|
71
54
|
const url = `${base}/v1/sdk/customers`;
|
|
72
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
73
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
74
55
|
const res = await fetch(url, {
|
|
75
56
|
method: "POST",
|
|
76
57
|
headers,
|
|
@@ -82,18 +63,24 @@ function createSolvaPayClient(opts) {
|
|
|
82
63
|
throw new SolvaPayError(`Create customer failed (${res.status}): ${error}`);
|
|
83
64
|
}
|
|
84
65
|
const result = await res.json();
|
|
85
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
86
|
-
log(`\u{1F50D} DEBUG - createCustomer response:`);
|
|
87
|
-
log(` - reference/customerRef: ${result.reference || result.customerRef}`);
|
|
88
|
-
log(` - Has plan info: ${result.plan ? "YES" : "NO"}`);
|
|
89
|
-
log(` - Has subscription info: ${result.subscription ? "YES" : "NO"}`);
|
|
90
|
-
log(` - Full response keys:`, Object.keys(result));
|
|
91
66
|
return result;
|
|
92
67
|
},
|
|
93
|
-
// GET: /v1/sdk/customers/{reference}
|
|
68
|
+
// GET: /v1/sdk/customers/{reference} or /v1/sdk/customers?externalRef={externalRef}|email={email}
|
|
94
69
|
async getCustomer(params) {
|
|
95
|
-
|
|
96
|
-
|
|
70
|
+
let url;
|
|
71
|
+
let isByExternalRef = false;
|
|
72
|
+
let isByEmail = false;
|
|
73
|
+
if (params.externalRef) {
|
|
74
|
+
url = `${base}/v1/sdk/customers?externalRef=${encodeURIComponent(params.externalRef)}`;
|
|
75
|
+
isByExternalRef = true;
|
|
76
|
+
} else if (params.email) {
|
|
77
|
+
url = `${base}/v1/sdk/customers?email=${encodeURIComponent(params.email)}`;
|
|
78
|
+
isByEmail = true;
|
|
79
|
+
} else if (params.customerRef) {
|
|
80
|
+
url = `${base}/v1/sdk/customers/${params.customerRef}`;
|
|
81
|
+
} else {
|
|
82
|
+
throw new SolvaPayError("One of customerRef, externalRef, or email must be provided");
|
|
83
|
+
}
|
|
97
84
|
const res = await fetch(url, {
|
|
98
85
|
method: "GET",
|
|
99
86
|
headers
|
|
@@ -104,14 +91,28 @@ function createSolvaPayClient(opts) {
|
|
|
104
91
|
throw new SolvaPayError(`Get customer failed (${res.status}): ${error}`);
|
|
105
92
|
}
|
|
106
93
|
const result = await res.json();
|
|
107
|
-
|
|
108
|
-
|
|
94
|
+
let customer = result;
|
|
95
|
+
if (isByExternalRef || isByEmail) {
|
|
96
|
+
const directCustomer = result && typeof result === "object" && (result.reference || result.customerRef || result.externalRef) ? result : void 0;
|
|
97
|
+
const wrappedCustomer = result && typeof result === "object" && result.customer ? result.customer : void 0;
|
|
98
|
+
const customers = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.customers) ? result.customers : [];
|
|
99
|
+
customer = directCustomer || wrappedCustomer || customers[0];
|
|
100
|
+
if (!customer) {
|
|
101
|
+
throw new SolvaPayError(`No customer found with externalRef: ${params.externalRef}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
customerRef: customer.reference || customer.customerRef,
|
|
106
|
+
email: customer.email,
|
|
107
|
+
name: customer.name,
|
|
108
|
+
externalRef: customer.externalRef,
|
|
109
|
+
purchases: customer.purchases || []
|
|
110
|
+
};
|
|
109
111
|
},
|
|
110
|
-
//
|
|
111
|
-
// GET: /v1/sdk/
|
|
112
|
-
async
|
|
113
|
-
const url = `${base}/v1/sdk/
|
|
114
|
-
log(`\u{1F4E1} API Request: GET ${url}`);
|
|
112
|
+
// Product management methods (primarily for integration tests)
|
|
113
|
+
// GET: /v1/sdk/products
|
|
114
|
+
async listProducts() {
|
|
115
|
+
const url = `${base}/v1/sdk/products`;
|
|
115
116
|
const res = await fetch(url, {
|
|
116
117
|
method: "GET",
|
|
117
118
|
headers
|
|
@@ -119,21 +120,18 @@ function createSolvaPayClient(opts) {
|
|
|
119
120
|
if (!res.ok) {
|
|
120
121
|
const error = await res.text();
|
|
121
122
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
122
|
-
throw new SolvaPayError(`List
|
|
123
|
+
throw new SolvaPayError(`List products failed (${res.status}): ${error}`);
|
|
123
124
|
}
|
|
124
125
|
const result = await res.json();
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
...
|
|
129
|
-
...agent.data || {}
|
|
126
|
+
const products = Array.isArray(result) ? result : result.products || [];
|
|
127
|
+
return products.map((product) => ({
|
|
128
|
+
...product,
|
|
129
|
+
...product.data || {}
|
|
130
130
|
}));
|
|
131
131
|
},
|
|
132
|
-
// POST: /v1/sdk/
|
|
133
|
-
async
|
|
134
|
-
const url = `${base}/v1/sdk/
|
|
135
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
136
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
132
|
+
// POST: /v1/sdk/products
|
|
133
|
+
async createProduct(params) {
|
|
134
|
+
const url = `${base}/v1/sdk/products`;
|
|
137
135
|
const res = await fetch(url, {
|
|
138
136
|
method: "POST",
|
|
139
137
|
headers,
|
|
@@ -142,16 +140,14 @@ function createSolvaPayClient(opts) {
|
|
|
142
140
|
if (!res.ok) {
|
|
143
141
|
const error = await res.text();
|
|
144
142
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
145
|
-
throw new SolvaPayError(`Create
|
|
143
|
+
throw new SolvaPayError(`Create product failed (${res.status}): ${error}`);
|
|
146
144
|
}
|
|
147
145
|
const result = await res.json();
|
|
148
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
149
146
|
return result;
|
|
150
147
|
},
|
|
151
|
-
// DELETE: /v1/sdk/
|
|
152
|
-
async
|
|
153
|
-
const url = `${base}/v1/sdk/
|
|
154
|
-
log(`\u{1F4E1} API Request: DELETE ${url}`);
|
|
148
|
+
// DELETE: /v1/sdk/products/{productRef}
|
|
149
|
+
async deleteProduct(productRef) {
|
|
150
|
+
const url = `${base}/v1/sdk/products/${productRef}`;
|
|
155
151
|
const res = await fetch(url, {
|
|
156
152
|
method: "DELETE",
|
|
157
153
|
headers
|
|
@@ -159,14 +155,12 @@ function createSolvaPayClient(opts) {
|
|
|
159
155
|
if (!res.ok && res.status !== 404) {
|
|
160
156
|
const error = await res.text();
|
|
161
157
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
162
|
-
throw new SolvaPayError(`Delete
|
|
158
|
+
throw new SolvaPayError(`Delete product failed (${res.status}): ${error}`);
|
|
163
159
|
}
|
|
164
|
-
log(`\u2705 Agent deleted successfully`);
|
|
165
160
|
},
|
|
166
|
-
// GET: /v1/sdk/
|
|
167
|
-
async listPlans(
|
|
168
|
-
const url = `${base}/v1/sdk/
|
|
169
|
-
log(`\u{1F4E1} API Request: GET ${url}`);
|
|
161
|
+
// GET: /v1/sdk/products/{productRef}/plans
|
|
162
|
+
async listPlans(productRef) {
|
|
163
|
+
const url = `${base}/v1/sdk/products/${productRef}/plans`;
|
|
170
164
|
const res = await fetch(url, {
|
|
171
165
|
method: "GET",
|
|
172
166
|
headers
|
|
@@ -177,18 +171,22 @@ function createSolvaPayClient(opts) {
|
|
|
177
171
|
throw new SolvaPayError(`List plans failed (${res.status}): ${error}`);
|
|
178
172
|
}
|
|
179
173
|
const result = await res.json();
|
|
180
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
181
174
|
const plans = Array.isArray(result) ? result : result.plans || [];
|
|
182
|
-
return plans.map((plan) =>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
175
|
+
return plans.map((plan) => {
|
|
176
|
+
const data = plan.data || {};
|
|
177
|
+
const price = plan.price ?? data.price;
|
|
178
|
+
const unwrapped = {
|
|
179
|
+
...data,
|
|
180
|
+
...plan,
|
|
181
|
+
...price !== void 0 && { price }
|
|
182
|
+
};
|
|
183
|
+
delete unwrapped.data;
|
|
184
|
+
return unwrapped;
|
|
185
|
+
});
|
|
186
186
|
},
|
|
187
|
-
// POST: /v1/sdk/
|
|
187
|
+
// POST: /v1/sdk/products/{productRef}/plans
|
|
188
188
|
async createPlan(params) {
|
|
189
|
-
const url = `${base}/v1/sdk/
|
|
190
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
191
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
189
|
+
const url = `${base}/v1/sdk/products/${params.productRef}/plans`;
|
|
192
190
|
const res = await fetch(url, {
|
|
193
191
|
method: "POST",
|
|
194
192
|
headers,
|
|
@@ -200,13 +198,11 @@ function createSolvaPayClient(opts) {
|
|
|
200
198
|
throw new SolvaPayError(`Create plan failed (${res.status}): ${error}`);
|
|
201
199
|
}
|
|
202
200
|
const result = await res.json();
|
|
203
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
204
201
|
return result;
|
|
205
202
|
},
|
|
206
|
-
// DELETE: /v1/sdk/
|
|
207
|
-
async deletePlan(
|
|
208
|
-
const url = `${base}/v1/sdk/
|
|
209
|
-
log(`\u{1F4E1} API Request: DELETE ${url}`);
|
|
203
|
+
// DELETE: /v1/sdk/products/{productRef}/plans/{planRef}
|
|
204
|
+
async deletePlan(productRef, planRef) {
|
|
205
|
+
const url = `${base}/v1/sdk/products/${productRef}/plans/${planRef}`;
|
|
210
206
|
const res = await fetch(url, {
|
|
211
207
|
method: "DELETE",
|
|
212
208
|
headers
|
|
@@ -216,17 +212,11 @@ function createSolvaPayClient(opts) {
|
|
|
216
212
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
217
213
|
throw new SolvaPayError(`Delete plan failed (${res.status}): ${error}`);
|
|
218
214
|
}
|
|
219
|
-
log(`\u2705 Plan deleted successfully`);
|
|
220
215
|
},
|
|
221
216
|
// POST: /payment-intents
|
|
222
217
|
async createPaymentIntent(params) {
|
|
223
218
|
const idempotencyKey = params.idempotencyKey || `payment-${params.planRef}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
224
219
|
const url = `${base}/v1/sdk/payment-intents`;
|
|
225
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
226
|
-
log(` Agent Ref: ${params.agentRef}`);
|
|
227
|
-
log(` Plan Ref: ${params.planRef}`);
|
|
228
|
-
log(` Customer Ref: ${params.customerRef}`);
|
|
229
|
-
log(` Idempotency Key: ${idempotencyKey}`);
|
|
230
220
|
const res = await fetch(url, {
|
|
231
221
|
method: "POST",
|
|
232
222
|
headers: {
|
|
@@ -234,7 +224,7 @@ function createSolvaPayClient(opts) {
|
|
|
234
224
|
"Idempotency-Key": idempotencyKey
|
|
235
225
|
},
|
|
236
226
|
body: JSON.stringify({
|
|
237
|
-
|
|
227
|
+
productRef: params.productRef,
|
|
238
228
|
planRef: params.planRef,
|
|
239
229
|
customerReference: params.customerRef
|
|
240
230
|
})
|
|
@@ -245,12 +235,113 @@ function createSolvaPayClient(opts) {
|
|
|
245
235
|
throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
|
|
246
236
|
}
|
|
247
237
|
const result = await res.json();
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
238
|
+
return result;
|
|
239
|
+
},
|
|
240
|
+
// POST: /v1/sdk/payment-intents/{paymentIntentId}/process
|
|
241
|
+
async processPaymentIntent(params) {
|
|
242
|
+
const url = `${base}/v1/sdk/payment-intents/${params.paymentIntentId}/process`;
|
|
243
|
+
const res = await fetch(url, {
|
|
244
|
+
method: "POST",
|
|
245
|
+
headers: {
|
|
246
|
+
...headers,
|
|
247
|
+
"Content-Type": "application/json"
|
|
248
|
+
},
|
|
249
|
+
body: JSON.stringify({
|
|
250
|
+
productRef: params.productRef,
|
|
251
|
+
customerRef: params.customerRef,
|
|
252
|
+
planRef: params.planRef
|
|
253
|
+
})
|
|
253
254
|
});
|
|
255
|
+
if (!res.ok) {
|
|
256
|
+
const error = await res.text();
|
|
257
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
258
|
+
throw new SolvaPayError(`Process payment failed (${res.status}): ${error}`);
|
|
259
|
+
}
|
|
260
|
+
const result = await res.json();
|
|
261
|
+
return result;
|
|
262
|
+
},
|
|
263
|
+
// POST: /v1/sdk/purchases/{purchaseRef}/cancel
|
|
264
|
+
async cancelPurchase(params) {
|
|
265
|
+
const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/cancel`;
|
|
266
|
+
const requestOptions = {
|
|
267
|
+
method: "POST",
|
|
268
|
+
headers
|
|
269
|
+
};
|
|
270
|
+
if (params.reason) {
|
|
271
|
+
requestOptions.body = JSON.stringify({ reason: params.reason });
|
|
272
|
+
}
|
|
273
|
+
const res = await fetch(url, requestOptions);
|
|
274
|
+
if (!res.ok) {
|
|
275
|
+
const error = await res.text();
|
|
276
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
277
|
+
if (res.status === 404) {
|
|
278
|
+
throw new SolvaPayError(`Purchase not found: ${error}`);
|
|
279
|
+
}
|
|
280
|
+
if (res.status === 400) {
|
|
281
|
+
throw new SolvaPayError(
|
|
282
|
+
`Purchase cannot be cancelled or does not belong to provider: ${error}`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
throw new SolvaPayError(`Cancel purchase failed (${res.status}): ${error}`);
|
|
286
|
+
}
|
|
287
|
+
const responseText = await res.text();
|
|
288
|
+
let responseData;
|
|
289
|
+
try {
|
|
290
|
+
responseData = JSON.parse(responseText);
|
|
291
|
+
} catch (parseError) {
|
|
292
|
+
log(`\u274C Failed to parse response as JSON: ${parseError}`);
|
|
293
|
+
throw new SolvaPayError(
|
|
294
|
+
`Invalid JSON response from cancel purchase endpoint: ${responseText.substring(0, 200)}`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
if (!responseData || typeof responseData !== "object") {
|
|
298
|
+
log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
|
|
299
|
+
throw new SolvaPayError(`Invalid response structure from cancel purchase endpoint`);
|
|
300
|
+
}
|
|
301
|
+
let result;
|
|
302
|
+
if (responseData.purchase && typeof responseData.purchase === "object") {
|
|
303
|
+
result = responseData.purchase;
|
|
304
|
+
} else if (responseData.reference) {
|
|
305
|
+
result = responseData;
|
|
306
|
+
} else {
|
|
307
|
+
result = responseData.purchase || responseData;
|
|
308
|
+
}
|
|
309
|
+
if (!result || typeof result !== "object") {
|
|
310
|
+
log(`\u274C Invalid purchase data in response. Full response:`, responseData);
|
|
311
|
+
throw new SolvaPayError(`Invalid purchase data in cancel purchase response`);
|
|
312
|
+
}
|
|
313
|
+
return result;
|
|
314
|
+
},
|
|
315
|
+
// POST: /v1/sdk/checkout-sessions
|
|
316
|
+
async createCheckoutSession(params) {
|
|
317
|
+
const url = `${base}/v1/sdk/checkout-sessions`;
|
|
318
|
+
const res = await fetch(url, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers,
|
|
321
|
+
body: JSON.stringify(params)
|
|
322
|
+
});
|
|
323
|
+
if (!res.ok) {
|
|
324
|
+
const error = await res.text();
|
|
325
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
326
|
+
throw new SolvaPayError(`Create checkout session failed (${res.status}): ${error}`);
|
|
327
|
+
}
|
|
328
|
+
const result = await res.json();
|
|
329
|
+
return result;
|
|
330
|
+
},
|
|
331
|
+
// POST: /v1/sdk/customers/customer-sessions
|
|
332
|
+
async createCustomerSession(params) {
|
|
333
|
+
const url = `${base}/v1/sdk/customers/customer-sessions`;
|
|
334
|
+
const res = await fetch(url, {
|
|
335
|
+
method: "POST",
|
|
336
|
+
headers,
|
|
337
|
+
body: JSON.stringify(params)
|
|
338
|
+
});
|
|
339
|
+
if (!res.ok) {
|
|
340
|
+
const error = await res.text();
|
|
341
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
342
|
+
throw new SolvaPayError(`Create customer session failed (${res.status}): ${error}`);
|
|
343
|
+
}
|
|
344
|
+
const result = await res.json();
|
|
254
345
|
return result;
|
|
255
346
|
}
|
|
256
347
|
};
|
|
@@ -302,19 +393,123 @@ function calculateDelay(initialDelay, attempt, strategy) {
|
|
|
302
393
|
function sleep(ms) {
|
|
303
394
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
304
395
|
}
|
|
396
|
+
function createRequestDeduplicator(options = {}) {
|
|
397
|
+
const { cacheTTL = 2e3, maxCacheSize = 1e3, cacheErrors = true } = options;
|
|
398
|
+
const inFlightRequests = /* @__PURE__ */ new Map();
|
|
399
|
+
const resultCache = /* @__PURE__ */ new Map();
|
|
400
|
+
let _cleanupInterval = null;
|
|
401
|
+
if (cacheTTL > 0) {
|
|
402
|
+
_cleanupInterval = setInterval(
|
|
403
|
+
() => {
|
|
404
|
+
const now = Date.now();
|
|
405
|
+
const entriesToDelete = [];
|
|
406
|
+
for (const [key, cached] of resultCache.entries()) {
|
|
407
|
+
if (now - cached.timestamp >= cacheTTL) {
|
|
408
|
+
entriesToDelete.push(key);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
for (const key of entriesToDelete) {
|
|
412
|
+
resultCache.delete(key);
|
|
413
|
+
}
|
|
414
|
+
if (resultCache.size > maxCacheSize) {
|
|
415
|
+
const sortedEntries = Array.from(resultCache.entries()).sort(
|
|
416
|
+
(a, b) => a[1].timestamp - b[1].timestamp
|
|
417
|
+
);
|
|
418
|
+
const toRemove = sortedEntries.slice(0, resultCache.size - maxCacheSize);
|
|
419
|
+
for (const [key] of toRemove) {
|
|
420
|
+
resultCache.delete(key);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
Math.min(cacheTTL, 1e3)
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
const deduplicate = async (key, fn) => {
|
|
428
|
+
if (cacheTTL > 0) {
|
|
429
|
+
const cached = resultCache.get(key);
|
|
430
|
+
if (cached && Date.now() - cached.timestamp < cacheTTL) {
|
|
431
|
+
return cached.data;
|
|
432
|
+
} else if (cached) {
|
|
433
|
+
resultCache.delete(key);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
let requestPromise = inFlightRequests.get(key);
|
|
437
|
+
if (!requestPromise) {
|
|
438
|
+
requestPromise = (async () => {
|
|
439
|
+
try {
|
|
440
|
+
const result = await fn();
|
|
441
|
+
if (cacheTTL > 0) {
|
|
442
|
+
resultCache.set(key, {
|
|
443
|
+
data: result,
|
|
444
|
+
timestamp: Date.now()
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
return result;
|
|
448
|
+
} catch (error) {
|
|
449
|
+
if (cacheTTL > 0 && cacheErrors) {
|
|
450
|
+
resultCache.set(key, {
|
|
451
|
+
data: error,
|
|
452
|
+
timestamp: Date.now()
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
throw error;
|
|
456
|
+
} finally {
|
|
457
|
+
inFlightRequests.delete(key);
|
|
458
|
+
}
|
|
459
|
+
})();
|
|
460
|
+
const existingPromise = inFlightRequests.get(key);
|
|
461
|
+
if (existingPromise) {
|
|
462
|
+
requestPromise = existingPromise;
|
|
463
|
+
} else {
|
|
464
|
+
inFlightRequests.set(key, requestPromise);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return requestPromise;
|
|
468
|
+
};
|
|
469
|
+
const clearCache = (key) => {
|
|
470
|
+
resultCache.delete(key);
|
|
471
|
+
};
|
|
472
|
+
const clearAllCache = () => {
|
|
473
|
+
resultCache.clear();
|
|
474
|
+
};
|
|
475
|
+
const getStats = () => ({
|
|
476
|
+
inFlight: inFlightRequests.size,
|
|
477
|
+
cached: resultCache.size
|
|
478
|
+
});
|
|
479
|
+
return {
|
|
480
|
+
deduplicate,
|
|
481
|
+
clearCache,
|
|
482
|
+
clearAllCache,
|
|
483
|
+
getStats
|
|
484
|
+
};
|
|
485
|
+
}
|
|
305
486
|
|
|
306
487
|
// src/paywall.ts
|
|
307
488
|
var PaywallError = class extends Error {
|
|
489
|
+
/**
|
|
490
|
+
* Creates a new PaywallError instance.
|
|
491
|
+
*
|
|
492
|
+
* @param message - Error message
|
|
493
|
+
* @param structuredContent - Structured content with checkout URLs and metadata
|
|
494
|
+
*/
|
|
308
495
|
constructor(message, structuredContent) {
|
|
309
496
|
super(message);
|
|
310
497
|
this.structuredContent = structuredContent;
|
|
311
498
|
this.name = "PaywallError";
|
|
312
499
|
}
|
|
313
500
|
};
|
|
501
|
+
var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
|
|
502
|
+
cacheTTL: 6e4,
|
|
503
|
+
// Cache results for 60 seconds (reduces API calls significantly)
|
|
504
|
+
maxCacheSize: 1e3,
|
|
505
|
+
// Maximum cache entries
|
|
506
|
+
cacheErrors: false
|
|
507
|
+
// Don't cache errors - retry on next request
|
|
508
|
+
});
|
|
314
509
|
var SolvaPayPaywall = class {
|
|
315
510
|
constructor(apiClient, options = {}) {
|
|
316
511
|
this.apiClient = apiClient;
|
|
317
|
-
this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG
|
|
512
|
+
this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG === "true";
|
|
318
513
|
}
|
|
319
514
|
customerCreationAttempts = /* @__PURE__ */ new Set();
|
|
320
515
|
customerRefMapping = /* @__PURE__ */ new Map();
|
|
@@ -325,16 +520,8 @@ var SolvaPayPaywall = class {
|
|
|
325
520
|
console.log(...args);
|
|
326
521
|
}
|
|
327
522
|
}
|
|
328
|
-
|
|
329
|
-
return metadata.
|
|
330
|
-
}
|
|
331
|
-
getPackageJsonName() {
|
|
332
|
-
try {
|
|
333
|
-
const pkg = __require(process.cwd() + "/package.json");
|
|
334
|
-
return pkg.name;
|
|
335
|
-
} catch {
|
|
336
|
-
return void 0;
|
|
337
|
-
}
|
|
523
|
+
resolveProduct(metadata) {
|
|
524
|
+
return metadata.product || process.env.SOLVAPAY_PRODUCT || "default-product";
|
|
338
525
|
}
|
|
339
526
|
generateRequestId() {
|
|
340
527
|
const timestamp = Date.now();
|
|
@@ -344,47 +531,75 @@ var SolvaPayPaywall = class {
|
|
|
344
531
|
/**
|
|
345
532
|
* Core protection method - works for both MCP and HTTP
|
|
346
533
|
*/
|
|
534
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
347
535
|
async protect(handler, metadata = {}, getCustomerRef) {
|
|
348
|
-
const
|
|
536
|
+
const product = this.resolveProduct(metadata);
|
|
349
537
|
const toolName = handler.name || "anonymous";
|
|
350
538
|
return async (args) => {
|
|
351
539
|
const startTime = Date.now();
|
|
352
540
|
const requestId = this.generateRequestId();
|
|
353
541
|
const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
|
|
354
|
-
|
|
542
|
+
let backendCustomerRef;
|
|
543
|
+
if (inputCustomerRef.startsWith("cus_")) {
|
|
544
|
+
backendCustomerRef = inputCustomerRef;
|
|
545
|
+
} else {
|
|
546
|
+
backendCustomerRef = await this.ensureCustomer(inputCustomerRef, inputCustomerRef);
|
|
547
|
+
}
|
|
355
548
|
try {
|
|
356
549
|
const planRef = metadata.plan || toolName;
|
|
357
|
-
this.log(`\u{1F50D} Checking limits for customer: ${backendCustomerRef}, agent: ${agent}, plan: ${planRef}`);
|
|
358
550
|
const limitsCheck = await this.apiClient.checkLimits({
|
|
359
551
|
customerRef: backendCustomerRef,
|
|
360
|
-
|
|
552
|
+
productRef: product
|
|
361
553
|
});
|
|
362
|
-
this.log(`\u2713 Limits check passed:`, limitsCheck);
|
|
363
554
|
if (!limitsCheck.withinLimits) {
|
|
364
555
|
const latencyMs2 = Date.now() - startTime;
|
|
365
|
-
this.
|
|
366
|
-
|
|
556
|
+
await this.trackUsage(
|
|
557
|
+
backendCustomerRef,
|
|
558
|
+
product,
|
|
559
|
+
planRef,
|
|
560
|
+
toolName,
|
|
561
|
+
"paywall",
|
|
562
|
+
requestId,
|
|
563
|
+
latencyMs2
|
|
564
|
+
);
|
|
367
565
|
throw new PaywallError("Payment required", {
|
|
368
566
|
kind: "payment_required",
|
|
369
|
-
|
|
567
|
+
product,
|
|
370
568
|
checkoutUrl: limitsCheck.checkoutUrl || "",
|
|
371
|
-
message: `Plan
|
|
569
|
+
message: `Plan purchase required. Remaining: ${limitsCheck.remaining}`
|
|
372
570
|
});
|
|
373
571
|
}
|
|
374
|
-
this.log(`\u26A1 Executing handler: ${toolName}`);
|
|
375
572
|
const result = await handler(args);
|
|
376
|
-
this.log(`\u2713 Handler completed successfully`);
|
|
377
573
|
const latencyMs = Date.now() - startTime;
|
|
378
|
-
this.
|
|
379
|
-
|
|
380
|
-
|
|
574
|
+
await this.trackUsage(
|
|
575
|
+
backendCustomerRef,
|
|
576
|
+
product,
|
|
577
|
+
planRef,
|
|
578
|
+
toolName,
|
|
579
|
+
"success",
|
|
580
|
+
requestId,
|
|
581
|
+
latencyMs
|
|
582
|
+
);
|
|
381
583
|
return result;
|
|
382
584
|
} catch (error) {
|
|
383
|
-
|
|
585
|
+
if (error instanceof Error) {
|
|
586
|
+
const errorType = error instanceof PaywallError ? "PaywallError" : "API Error";
|
|
587
|
+
this.log(`\u274C Error in paywall [${errorType}]: ${error.message}`);
|
|
588
|
+
} else {
|
|
589
|
+
this.log(`\u274C Error in paywall:`, error);
|
|
590
|
+
}
|
|
384
591
|
const latencyMs = Date.now() - startTime;
|
|
385
592
|
const outcome = error instanceof PaywallError ? "paywall" : "fail";
|
|
386
593
|
const planRef = metadata.plan || toolName;
|
|
387
|
-
await this.trackUsage(
|
|
594
|
+
await this.trackUsage(
|
|
595
|
+
backendCustomerRef,
|
|
596
|
+
product,
|
|
597
|
+
planRef,
|
|
598
|
+
toolName,
|
|
599
|
+
outcome,
|
|
600
|
+
requestId,
|
|
601
|
+
latencyMs
|
|
602
|
+
);
|
|
388
603
|
throw error;
|
|
389
604
|
}
|
|
390
605
|
};
|
|
@@ -394,47 +609,150 @@ var SolvaPayPaywall = class {
|
|
|
394
609
|
* This is a public helper for testing, pre-creating customers, and internal use.
|
|
395
610
|
* Only attempts creation once per customer (idempotent).
|
|
396
611
|
* Returns the backend customer reference to use in API calls.
|
|
612
|
+
*
|
|
613
|
+
* @param customerRef - The customer reference used as a cache key (e.g., Supabase user ID)
|
|
614
|
+
* @param externalRef - Optional external reference for backend lookup (e.g., Supabase user ID)
|
|
615
|
+
* If provided, will lookup existing customer by externalRef before creating new one.
|
|
616
|
+
* The externalRef is stored on the SolvaPay backend for customer lookup.
|
|
617
|
+
* @param options - Optional customer details (email, name) for customer creation
|
|
397
618
|
*/
|
|
398
|
-
async ensureCustomer(customerRef) {
|
|
619
|
+
async ensureCustomer(customerRef, externalRef, options) {
|
|
399
620
|
if (this.customerRefMapping.has(customerRef)) {
|
|
400
621
|
return this.customerRefMapping.get(customerRef);
|
|
401
622
|
}
|
|
402
623
|
if (customerRef === "anonymous") {
|
|
403
624
|
return customerRef;
|
|
404
625
|
}
|
|
405
|
-
if (
|
|
626
|
+
if (customerRef.startsWith("cus_")) {
|
|
406
627
|
return customerRef;
|
|
407
628
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
629
|
+
const cacheKey = externalRef || customerRef;
|
|
630
|
+
if (this.customerRefMapping.has(customerRef)) {
|
|
631
|
+
const cached = this.customerRefMapping.get(customerRef);
|
|
632
|
+
return cached;
|
|
411
633
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
634
|
+
const backendRef = await sharedCustomerLookupDeduplicator.deduplicate(cacheKey, async () => {
|
|
635
|
+
if (externalRef) {
|
|
636
|
+
try {
|
|
637
|
+
const existingCustomer = await this.apiClient.getCustomer({ externalRef });
|
|
638
|
+
if (existingCustomer && existingCustomer.customerRef) {
|
|
639
|
+
const ref = existingCustomer.customerRef;
|
|
640
|
+
this.customerRefMapping.set(customerRef, ref);
|
|
641
|
+
this.customerCreationAttempts.add(customerRef);
|
|
642
|
+
if (externalRef !== customerRef) {
|
|
643
|
+
this.customerCreationAttempts.add(externalRef);
|
|
644
|
+
}
|
|
645
|
+
return ref;
|
|
646
|
+
}
|
|
647
|
+
} catch (error) {
|
|
648
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
649
|
+
if (!errorMessage.includes("404") && !errorMessage.includes("not found")) {
|
|
650
|
+
this.log(`\u26A0\uFE0F Error looking up customer by externalRef: ${errorMessage}`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (this.customerCreationAttempts.has(customerRef) || externalRef && this.customerCreationAttempts.has(externalRef)) {
|
|
655
|
+
const mappedRef = this.customerRefMapping.get(customerRef);
|
|
656
|
+
return mappedRef || customerRef;
|
|
657
|
+
}
|
|
658
|
+
if (!this.apiClient.createCustomer) {
|
|
659
|
+
console.warn(
|
|
660
|
+
`\u26A0\uFE0F Cannot auto-create customer ${customerRef}: createCustomer method not available on API client`
|
|
661
|
+
);
|
|
662
|
+
return customerRef;
|
|
663
|
+
}
|
|
664
|
+
this.customerCreationAttempts.add(customerRef);
|
|
665
|
+
try {
|
|
666
|
+
const createParams = {
|
|
667
|
+
email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`
|
|
668
|
+
};
|
|
669
|
+
if (options?.name) {
|
|
670
|
+
createParams.name = options.name;
|
|
671
|
+
}
|
|
672
|
+
if (externalRef) {
|
|
673
|
+
createParams.externalRef = externalRef;
|
|
674
|
+
}
|
|
675
|
+
const result = await this.apiClient.createCustomer(createParams);
|
|
676
|
+
const resultObj = result;
|
|
677
|
+
const ref = resultObj.customerRef || resultObj.reference || customerRef;
|
|
678
|
+
this.customerRefMapping.set(customerRef, ref);
|
|
679
|
+
return ref;
|
|
680
|
+
} catch (error) {
|
|
681
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
682
|
+
if (errorMessage.includes("409") || errorMessage.includes("already exists")) {
|
|
683
|
+
if (externalRef) {
|
|
684
|
+
try {
|
|
685
|
+
const searchResult = await this.apiClient.getCustomer({ externalRef });
|
|
686
|
+
if (searchResult && searchResult.customerRef) {
|
|
687
|
+
this.customerRefMapping.set(customerRef, searchResult.customerRef);
|
|
688
|
+
return searchResult.customerRef;
|
|
689
|
+
}
|
|
690
|
+
} catch (lookupError) {
|
|
691
|
+
this.log(`\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`, lookupError instanceof Error ? lookupError.message : lookupError);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
|
|
695
|
+
if (externalRef && isEmailConflict && options?.email) {
|
|
696
|
+
try {
|
|
697
|
+
const byEmail = await this.apiClient.getCustomer({ email: options.email });
|
|
698
|
+
if (byEmail && byEmail.customerRef) {
|
|
699
|
+
this.customerRefMapping.set(customerRef, byEmail.customerRef);
|
|
700
|
+
this.log(
|
|
701
|
+
`\u26A0\uFE0F Resolved customer ${customerRef} by email after conflict; using existing customer ${byEmail.customerRef}`
|
|
702
|
+
);
|
|
703
|
+
return byEmail.customerRef;
|
|
704
|
+
}
|
|
705
|
+
} catch (emailLookupError) {
|
|
706
|
+
this.log(
|
|
707
|
+
`\u26A0\uFE0F Email lookup failed after customer conflict for ${customerRef}:`,
|
|
708
|
+
emailLookupError instanceof Error ? emailLookupError.message : emailLookupError
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
try {
|
|
712
|
+
const retryParams = {
|
|
713
|
+
email: `${customerRef}-${Date.now()}@auto-created.local`,
|
|
714
|
+
externalRef
|
|
715
|
+
};
|
|
716
|
+
if (options?.name) {
|
|
717
|
+
retryParams.name = options.name;
|
|
718
|
+
}
|
|
719
|
+
const retryResult = await this.apiClient.createCustomer(retryParams);
|
|
720
|
+
const retryObj = retryResult;
|
|
721
|
+
const retryRef = retryObj.customerRef || retryObj.reference || customerRef;
|
|
722
|
+
this.customerRefMapping.set(customerRef, retryRef);
|
|
723
|
+
this.log(
|
|
724
|
+
`\u26A0\uFE0F Retried customer creation for ${customerRef} with generated email after email conflict`
|
|
725
|
+
);
|
|
726
|
+
return retryRef;
|
|
727
|
+
} catch (retryError) {
|
|
728
|
+
this.log(
|
|
729
|
+
`\u26A0\uFE0F Retry create customer with generated email failed for ${customerRef}:`,
|
|
730
|
+
retryError instanceof Error ? retryError.message : retryError
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
const unresolvedMessage = errorMessage || "Customer already exists but could not be resolved";
|
|
735
|
+
throw new Error(
|
|
736
|
+
`Failed to resolve existing customer for ${customerRef} after conflict: ${unresolvedMessage}. Ensure the existing customer is linked to this externalRef.`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
this.log(
|
|
740
|
+
`\u274C Failed to auto-create customer ${customerRef}:`,
|
|
741
|
+
error instanceof Error ? error.message : error
|
|
742
|
+
);
|
|
743
|
+
throw error;
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
if (backendRef !== customerRef) {
|
|
426
747
|
this.customerRefMapping.set(customerRef, backendRef);
|
|
427
|
-
return backendRef;
|
|
428
|
-
} catch (error) {
|
|
429
|
-
this.log(`\u274C Failed to auto-create customer ${customerRef}:`, error instanceof Error ? error.message : error);
|
|
430
|
-
return customerRef;
|
|
431
748
|
}
|
|
749
|
+
return backendRef;
|
|
432
750
|
}
|
|
433
|
-
async trackUsage(customerRef,
|
|
751
|
+
async trackUsage(customerRef, productRef, planRef, toolName, outcome, requestId, actionDuration) {
|
|
434
752
|
await withRetry(
|
|
435
753
|
() => this.apiClient.trackUsage({
|
|
436
754
|
customerRef,
|
|
437
|
-
|
|
755
|
+
productRef,
|
|
438
756
|
planRef,
|
|
439
757
|
outcome,
|
|
440
758
|
action: toolName,
|
|
@@ -446,7 +764,7 @@ var SolvaPayPaywall = class {
|
|
|
446
764
|
maxRetries: 2,
|
|
447
765
|
initialDelay: 500,
|
|
448
766
|
shouldRetry: (error) => error.message.includes("Customer not found"),
|
|
449
|
-
// TODO: review if this is needed and what to check for
|
|
767
|
+
// TODO: review if this is needed and what to check for
|
|
450
768
|
onRetry: (error, attempt) => {
|
|
451
769
|
console.warn(`\u26A0\uFE0F Customer not found (attempt ${attempt + 1}/3), retrying in 500ms...`);
|
|
452
770
|
}
|
|
@@ -466,9 +784,6 @@ var AdapterUtils = class {
|
|
|
466
784
|
if (!customerRef || customerRef === "anonymous") {
|
|
467
785
|
return "anonymous";
|
|
468
786
|
}
|
|
469
|
-
if (!customerRef.startsWith("customer_") && !customerRef.startsWith("demo_")) {
|
|
470
|
-
return `customer_${customerRef.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
471
|
-
}
|
|
472
787
|
return customerRef;
|
|
473
788
|
}
|
|
474
789
|
/**
|
|
@@ -476,7 +791,7 @@ var AdapterUtils = class {
|
|
|
476
791
|
*/
|
|
477
792
|
static async extractFromJWT(token, options) {
|
|
478
793
|
try {
|
|
479
|
-
const { jwtVerify } = await import("./esm-
|
|
794
|
+
const { jwtVerify } = await import("./esm-UW7WCMEK.js");
|
|
480
795
|
const jwtSecret = new TextEncoder().encode(
|
|
481
796
|
options?.secret || process.env.OAUTH_JWKS_SECRET || "test-jwt-secret"
|
|
482
797
|
);
|
|
@@ -485,7 +800,7 @@ var AdapterUtils = class {
|
|
|
485
800
|
audience: options?.audience || process.env.OAUTH_CLIENT_ID || "test-client-id"
|
|
486
801
|
});
|
|
487
802
|
return payload.sub || null;
|
|
488
|
-
} catch
|
|
803
|
+
} catch {
|
|
489
804
|
return null;
|
|
490
805
|
}
|
|
491
806
|
}
|
|
@@ -496,7 +811,7 @@ async function createAdapterHandler(adapter, paywall, metadata, businessLogic) {
|
|
|
496
811
|
const args = await adapter.extractArgs(context);
|
|
497
812
|
const customerRef = await adapter.getCustomerRef(context);
|
|
498
813
|
args.auth = { customer_ref: customerRef };
|
|
499
|
-
const getCustomerRef = (args2) => args2.auth
|
|
814
|
+
const getCustomerRef = (args2) => args2.auth?.customer_ref || "anonymous";
|
|
500
815
|
const protectedHandler = await paywall.protect(businessLogic, metadata, getCustomerRef);
|
|
501
816
|
const result = await protectedHandler(args);
|
|
502
817
|
return adapter.formatResponse(result, context);
|
|
@@ -555,7 +870,7 @@ var HttpAdapter = class {
|
|
|
555
870
|
const errorResponse2 = {
|
|
556
871
|
success: false,
|
|
557
872
|
error: "Payment required",
|
|
558
|
-
|
|
873
|
+
product: error.structuredContent.product,
|
|
559
874
|
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
560
875
|
message: error.structuredContent.message
|
|
561
876
|
};
|
|
@@ -599,7 +914,7 @@ var NextAdapter = class {
|
|
|
599
914
|
if (request.method !== "GET" && request.headers.get("content-type")?.includes("application/json")) {
|
|
600
915
|
body = await request.json();
|
|
601
916
|
}
|
|
602
|
-
} catch
|
|
917
|
+
} catch {
|
|
603
918
|
}
|
|
604
919
|
let routeParams = {};
|
|
605
920
|
if (context?.params) {
|
|
@@ -628,6 +943,10 @@ var NextAdapter = class {
|
|
|
628
943
|
return AdapterUtils.ensureCustomerRef(jwtSub);
|
|
629
944
|
}
|
|
630
945
|
}
|
|
946
|
+
const userId = request.headers.get("x-user-id");
|
|
947
|
+
if (userId) {
|
|
948
|
+
return AdapterUtils.ensureCustomerRef(userId);
|
|
949
|
+
}
|
|
631
950
|
const headerRef = request.headers.get("x-customer-ref");
|
|
632
951
|
if (headerRef) {
|
|
633
952
|
return AdapterUtils.ensureCustomerRef(headerRef);
|
|
@@ -643,24 +962,30 @@ var NextAdapter = class {
|
|
|
643
962
|
}
|
|
644
963
|
formatError(error, _context) {
|
|
645
964
|
if (error instanceof PaywallError) {
|
|
646
|
-
return new Response(
|
|
965
|
+
return new Response(
|
|
966
|
+
JSON.stringify({
|
|
967
|
+
success: false,
|
|
968
|
+
error: "Payment required",
|
|
969
|
+
product: error.structuredContent.product,
|
|
970
|
+
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
971
|
+
message: error.structuredContent.message
|
|
972
|
+
}),
|
|
973
|
+
{
|
|
974
|
+
status: 402,
|
|
975
|
+
headers: { "Content-Type": "application/json" }
|
|
976
|
+
}
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
return new Response(
|
|
980
|
+
JSON.stringify({
|
|
647
981
|
success: false,
|
|
648
|
-
error: "
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
}), {
|
|
653
|
-
status: 402,
|
|
982
|
+
error: error instanceof Error ? error.message : "Internal server error"
|
|
983
|
+
}),
|
|
984
|
+
{
|
|
985
|
+
status: 500,
|
|
654
986
|
headers: { "Content-Type": "application/json" }
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
return new Response(JSON.stringify({
|
|
658
|
-
success: false,
|
|
659
|
-
error: error instanceof Error ? error.message : "Internal server error"
|
|
660
|
-
}), {
|
|
661
|
-
status: 500,
|
|
662
|
-
headers: { "Content-Type": "application/json" }
|
|
663
|
-
});
|
|
987
|
+
}
|
|
988
|
+
);
|
|
664
989
|
}
|
|
665
990
|
};
|
|
666
991
|
|
|
@@ -677,54 +1002,79 @@ var McpAdapter = class {
|
|
|
677
1002
|
const ref = await this.options.getCustomerRef(args);
|
|
678
1003
|
return AdapterUtils.ensureCustomerRef(ref);
|
|
679
1004
|
}
|
|
680
|
-
const
|
|
1005
|
+
const auth = args?.auth;
|
|
1006
|
+
const customerRef = auth?.customer_ref || "anonymous";
|
|
681
1007
|
return AdapterUtils.ensureCustomerRef(customerRef);
|
|
682
1008
|
}
|
|
683
1009
|
formatResponse(result, _context) {
|
|
684
1010
|
const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
|
|
685
1011
|
return {
|
|
686
|
-
content: [
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1012
|
+
content: [
|
|
1013
|
+
{
|
|
1014
|
+
type: "text",
|
|
1015
|
+
text: JSON.stringify(transformed, null, 2)
|
|
1016
|
+
}
|
|
1017
|
+
]
|
|
690
1018
|
};
|
|
691
1019
|
}
|
|
692
1020
|
formatError(error, _context) {
|
|
693
1021
|
if (error instanceof PaywallError) {
|
|
694
1022
|
return {
|
|
695
|
-
content: [
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1023
|
+
content: [
|
|
1024
|
+
{
|
|
1025
|
+
type: "text",
|
|
1026
|
+
text: JSON.stringify(
|
|
1027
|
+
{
|
|
1028
|
+
success: false,
|
|
1029
|
+
error: "Payment required",
|
|
1030
|
+
product: error.structuredContent.product,
|
|
1031
|
+
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
1032
|
+
message: error.structuredContent.message
|
|
1033
|
+
},
|
|
1034
|
+
null,
|
|
1035
|
+
2
|
|
1036
|
+
)
|
|
1037
|
+
}
|
|
1038
|
+
],
|
|
705
1039
|
isError: true,
|
|
706
1040
|
structuredContent: error.structuredContent
|
|
707
1041
|
};
|
|
708
1042
|
}
|
|
709
1043
|
return {
|
|
710
|
-
content: [
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
1044
|
+
content: [
|
|
1045
|
+
{
|
|
1046
|
+
type: "text",
|
|
1047
|
+
text: JSON.stringify(
|
|
1048
|
+
{
|
|
1049
|
+
success: false,
|
|
1050
|
+
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
1051
|
+
},
|
|
1052
|
+
null,
|
|
1053
|
+
2
|
|
1054
|
+
)
|
|
1055
|
+
}
|
|
1056
|
+
],
|
|
717
1057
|
isError: true
|
|
718
1058
|
};
|
|
719
1059
|
}
|
|
720
1060
|
};
|
|
721
1061
|
|
|
722
1062
|
// src/factory.ts
|
|
723
|
-
import { SolvaPayError as SolvaPayError2 } from "@solvapay/core";
|
|
1063
|
+
import { SolvaPayError as SolvaPayError2, getSolvaPayConfig } from "@solvapay/core";
|
|
724
1064
|
function createSolvaPay(config) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
1065
|
+
let resolvedConfig;
|
|
1066
|
+
if (!config) {
|
|
1067
|
+
const envConfig = getSolvaPayConfig();
|
|
1068
|
+
resolvedConfig = {
|
|
1069
|
+
apiKey: envConfig.apiKey,
|
|
1070
|
+
apiBaseUrl: envConfig.apiBaseUrl
|
|
1071
|
+
};
|
|
1072
|
+
} else {
|
|
1073
|
+
resolvedConfig = config;
|
|
1074
|
+
}
|
|
1075
|
+
const apiClient = resolvedConfig.apiClient || createSolvaPayClient({
|
|
1076
|
+
apiKey: resolvedConfig.apiKey,
|
|
1077
|
+
apiBaseUrl: resolvedConfig.apiBaseUrl
|
|
728
1078
|
});
|
|
729
1079
|
const paywall = new SolvaPayPaywall(apiClient, {
|
|
730
1080
|
debug: process.env.SOLVAPAY_DEBUG !== "false"
|
|
@@ -733,8 +1083,8 @@ function createSolvaPay(config) {
|
|
|
733
1083
|
// Direct access to API client for advanced operations
|
|
734
1084
|
apiClient,
|
|
735
1085
|
// Common API methods exposed directly for convenience
|
|
736
|
-
ensureCustomer(customerRef) {
|
|
737
|
-
return paywall.ensureCustomer(customerRef);
|
|
1086
|
+
ensureCustomer(customerRef, externalRef, options) {
|
|
1087
|
+
return paywall.ensureCustomer(customerRef, externalRef, options);
|
|
738
1088
|
},
|
|
739
1089
|
createPaymentIntent(params) {
|
|
740
1090
|
if (!apiClient.createPaymentIntent) {
|
|
@@ -742,6 +1092,12 @@ function createSolvaPay(config) {
|
|
|
742
1092
|
}
|
|
743
1093
|
return apiClient.createPaymentIntent(params);
|
|
744
1094
|
},
|
|
1095
|
+
processPaymentIntent(params) {
|
|
1096
|
+
if (!apiClient.processPaymentIntent) {
|
|
1097
|
+
throw new SolvaPayError2("processPaymentIntent is not available on this API client");
|
|
1098
|
+
}
|
|
1099
|
+
return apiClient.processPaymentIntent(params);
|
|
1100
|
+
},
|
|
745
1101
|
checkLimits(params) {
|
|
746
1102
|
return apiClient.checkLimits(params);
|
|
747
1103
|
},
|
|
@@ -755,57 +1111,49 @@ function createSolvaPay(config) {
|
|
|
755
1111
|
return apiClient.createCustomer(params);
|
|
756
1112
|
},
|
|
757
1113
|
getCustomer(params) {
|
|
758
|
-
if (!apiClient.getCustomer) {
|
|
759
|
-
throw new SolvaPayError2("getCustomer is not available on this API client");
|
|
760
|
-
}
|
|
761
1114
|
return apiClient.getCustomer(params);
|
|
762
1115
|
},
|
|
1116
|
+
createCheckoutSession(params) {
|
|
1117
|
+
return apiClient.createCheckoutSession({
|
|
1118
|
+
customerReference: params.customerRef,
|
|
1119
|
+
productRef: params.productRef,
|
|
1120
|
+
planRef: params.planRef
|
|
1121
|
+
});
|
|
1122
|
+
},
|
|
1123
|
+
createCustomerSession(params) {
|
|
1124
|
+
return apiClient.createCustomerSession(params);
|
|
1125
|
+
},
|
|
763
1126
|
// Payable API for framework-specific handlers
|
|
764
1127
|
payable(options = {}) {
|
|
765
|
-
const
|
|
766
|
-
const plan = options.planRef || options.plan ||
|
|
767
|
-
const metadata = {
|
|
1128
|
+
const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
|
|
1129
|
+
const plan = options.planRef || options.plan || product;
|
|
1130
|
+
const metadata = { product, plan };
|
|
768
1131
|
return {
|
|
769
|
-
//
|
|
1132
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
770
1133
|
http(businessLogic, adapterOptions) {
|
|
771
1134
|
const adapter = new HttpAdapter(adapterOptions);
|
|
772
1135
|
return async (req, reply) => {
|
|
773
|
-
const handler = await createAdapterHandler(
|
|
774
|
-
adapter,
|
|
775
|
-
paywall,
|
|
776
|
-
metadata,
|
|
777
|
-
businessLogic
|
|
778
|
-
);
|
|
1136
|
+
const handler = await createAdapterHandler(adapter, paywall, metadata, businessLogic);
|
|
779
1137
|
return handler([req, reply]);
|
|
780
1138
|
};
|
|
781
1139
|
},
|
|
782
|
-
//
|
|
1140
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
783
1141
|
next(businessLogic, adapterOptions) {
|
|
784
1142
|
const adapter = new NextAdapter(adapterOptions);
|
|
785
1143
|
return async (request, context) => {
|
|
786
|
-
const handler = await createAdapterHandler(
|
|
787
|
-
adapter,
|
|
788
|
-
paywall,
|
|
789
|
-
metadata,
|
|
790
|
-
businessLogic
|
|
791
|
-
);
|
|
1144
|
+
const handler = await createAdapterHandler(adapter, paywall, metadata, businessLogic);
|
|
792
1145
|
return handler([request, context]);
|
|
793
1146
|
};
|
|
794
1147
|
},
|
|
795
|
-
//
|
|
1148
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
796
1149
|
mcp(businessLogic, adapterOptions) {
|
|
797
1150
|
const adapter = new McpAdapter(adapterOptions);
|
|
798
1151
|
return async (args) => {
|
|
799
|
-
const handler = await createAdapterHandler(
|
|
800
|
-
adapter,
|
|
801
|
-
paywall,
|
|
802
|
-
metadata,
|
|
803
|
-
businessLogic
|
|
804
|
-
);
|
|
1152
|
+
const handler = await createAdapterHandler(adapter, paywall, metadata, businessLogic);
|
|
805
1153
|
return handler(args);
|
|
806
1154
|
};
|
|
807
1155
|
},
|
|
808
|
-
//
|
|
1156
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
809
1157
|
async function(businessLogic) {
|
|
810
1158
|
const getCustomerRef = (args) => args.auth?.customer_ref || "anonymous";
|
|
811
1159
|
return paywall.protect(businessLogic, metadata, getCustomerRef);
|
|
@@ -814,12 +1162,316 @@ function createSolvaPay(config) {
|
|
|
814
1162
|
}
|
|
815
1163
|
};
|
|
816
1164
|
}
|
|
817
|
-
|
|
1165
|
+
|
|
1166
|
+
// src/helpers/error.ts
|
|
1167
|
+
import { SolvaPayError as SolvaPayError3 } from "@solvapay/core";
|
|
1168
|
+
function isErrorResult(result) {
|
|
1169
|
+
return typeof result === "object" && result !== null && "error" in result && "status" in result;
|
|
1170
|
+
}
|
|
1171
|
+
function handleRouteError(error, operationName, defaultMessage) {
|
|
1172
|
+
console.error(`[${operationName}] Error:`, error);
|
|
1173
|
+
if (error instanceof SolvaPayError3) {
|
|
1174
|
+
const errorMessage2 = error.message;
|
|
1175
|
+
return {
|
|
1176
|
+
error: errorMessage2,
|
|
1177
|
+
status: 500,
|
|
1178
|
+
details: errorMessage2
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1182
|
+
const message = defaultMessage || `${operationName} failed`;
|
|
1183
|
+
return {
|
|
1184
|
+
error: message,
|
|
1185
|
+
status: 500,
|
|
1186
|
+
details: errorMessage
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/helpers/auth.ts
|
|
1191
|
+
async function getAuthenticatedUserCore(request, options = {}) {
|
|
1192
|
+
try {
|
|
1193
|
+
const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
|
|
1194
|
+
const userIdOrError = requireUserId(request);
|
|
1195
|
+
if (userIdOrError instanceof Response) {
|
|
1196
|
+
const clonedResponse = userIdOrError.clone();
|
|
1197
|
+
const body = await clonedResponse.json().catch(() => ({ error: "Unauthorized" }));
|
|
1198
|
+
return {
|
|
1199
|
+
error: body.error || "Unauthorized",
|
|
1200
|
+
status: userIdOrError.status,
|
|
1201
|
+
details: body.error || "Unauthorized"
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
const userId = userIdOrError;
|
|
1205
|
+
const email = options.includeEmail !== false ? await getUserEmailFromRequest(request) : null;
|
|
1206
|
+
const name = options.includeName !== false ? await getUserNameFromRequest(request) : null;
|
|
1207
|
+
return {
|
|
1208
|
+
userId,
|
|
1209
|
+
email,
|
|
1210
|
+
name
|
|
1211
|
+
};
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
return handleRouteError(error, "Get authenticated user", "Authentication failed");
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// src/helpers/customer.ts
|
|
1218
|
+
async function syncCustomerCore(request, options = {}) {
|
|
1219
|
+
try {
|
|
1220
|
+
const userResult = await getAuthenticatedUserCore(request, {
|
|
1221
|
+
includeEmail: options.includeEmail,
|
|
1222
|
+
includeName: options.includeName
|
|
1223
|
+
});
|
|
1224
|
+
if (isErrorResult(userResult)) {
|
|
1225
|
+
return userResult;
|
|
1226
|
+
}
|
|
1227
|
+
const { userId, email, name } = userResult;
|
|
1228
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1229
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
1230
|
+
email: email || void 0,
|
|
1231
|
+
name: name || void 0
|
|
1232
|
+
});
|
|
1233
|
+
return customerRef;
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
return handleRouteError(error, "Sync customer", "Failed to sync customer");
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/helpers/payment.ts
|
|
1240
|
+
async function createPaymentIntentCore(request, body, options = {}) {
|
|
1241
|
+
try {
|
|
1242
|
+
if (!body.planRef || !body.productRef) {
|
|
1243
|
+
return {
|
|
1244
|
+
error: "Missing required parameters: planRef and productRef are required",
|
|
1245
|
+
status: 400
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1249
|
+
solvaPay: options.solvaPay,
|
|
1250
|
+
includeEmail: options.includeEmail,
|
|
1251
|
+
includeName: options.includeName
|
|
1252
|
+
});
|
|
1253
|
+
if (isErrorResult(customerResult)) {
|
|
1254
|
+
return customerResult;
|
|
1255
|
+
}
|
|
1256
|
+
const customerRef = customerResult;
|
|
1257
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1258
|
+
const paymentIntent = await solvaPay.createPaymentIntent({
|
|
1259
|
+
productRef: body.productRef,
|
|
1260
|
+
planRef: body.planRef,
|
|
1261
|
+
customerRef
|
|
1262
|
+
});
|
|
1263
|
+
return {
|
|
1264
|
+
id: paymentIntent.id,
|
|
1265
|
+
clientSecret: paymentIntent.clientSecret,
|
|
1266
|
+
publishableKey: paymentIntent.publishableKey,
|
|
1267
|
+
accountId: paymentIntent.accountId,
|
|
1268
|
+
customerRef
|
|
1269
|
+
};
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
async function processPaymentIntentCore(request, body, options = {}) {
|
|
1275
|
+
try {
|
|
1276
|
+
if (!body.paymentIntentId || !body.productRef) {
|
|
1277
|
+
return {
|
|
1278
|
+
error: "paymentIntentId and productRef are required",
|
|
1279
|
+
status: 400
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1283
|
+
solvaPay: options.solvaPay
|
|
1284
|
+
});
|
|
1285
|
+
if (isErrorResult(customerResult)) {
|
|
1286
|
+
return customerResult;
|
|
1287
|
+
}
|
|
1288
|
+
const customerRef = customerResult;
|
|
1289
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1290
|
+
const result = await solvaPay.processPaymentIntent({
|
|
1291
|
+
paymentIntentId: body.paymentIntentId,
|
|
1292
|
+
productRef: body.productRef,
|
|
1293
|
+
customerRef,
|
|
1294
|
+
planRef: body.planRef
|
|
1295
|
+
});
|
|
1296
|
+
return result;
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
return handleRouteError(error, "Process payment intent", "Payment processing failed");
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// src/helpers/checkout.ts
|
|
1303
|
+
async function createCheckoutSessionCore(request, body, options = {}) {
|
|
1304
|
+
try {
|
|
1305
|
+
if (!body.productRef) {
|
|
1306
|
+
return {
|
|
1307
|
+
error: "Missing required parameter: productRef is required",
|
|
1308
|
+
status: 400
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1312
|
+
solvaPay: options.solvaPay,
|
|
1313
|
+
includeEmail: options.includeEmail,
|
|
1314
|
+
includeName: options.includeName
|
|
1315
|
+
});
|
|
1316
|
+
if (isErrorResult(customerResult)) {
|
|
1317
|
+
return customerResult;
|
|
1318
|
+
}
|
|
1319
|
+
const customerRef = customerResult;
|
|
1320
|
+
let returnUrl = body.returnUrl || options.returnUrl;
|
|
1321
|
+
if (!returnUrl) {
|
|
1322
|
+
try {
|
|
1323
|
+
const url = new URL(request.url);
|
|
1324
|
+
returnUrl = url.origin;
|
|
1325
|
+
} catch {
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1329
|
+
const session = await solvaPay.createCheckoutSession({
|
|
1330
|
+
productRef: body.productRef,
|
|
1331
|
+
customerRef,
|
|
1332
|
+
planRef: body.planRef || void 0,
|
|
1333
|
+
returnUrl
|
|
1334
|
+
});
|
|
1335
|
+
return {
|
|
1336
|
+
sessionId: session.sessionId,
|
|
1337
|
+
checkoutUrl: session.checkoutUrl
|
|
1338
|
+
};
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
return handleRouteError(error, "Create checkout session", "Checkout session creation failed");
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
async function createCustomerSessionCore(request, options = {}) {
|
|
1344
|
+
try {
|
|
1345
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1346
|
+
solvaPay: options.solvaPay,
|
|
1347
|
+
includeEmail: options.includeEmail,
|
|
1348
|
+
includeName: options.includeName
|
|
1349
|
+
});
|
|
1350
|
+
if (isErrorResult(customerResult)) {
|
|
1351
|
+
return customerResult;
|
|
1352
|
+
}
|
|
1353
|
+
const customerRef = customerResult;
|
|
1354
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1355
|
+
const session = await solvaPay.createCustomerSession({
|
|
1356
|
+
customerRef
|
|
1357
|
+
});
|
|
1358
|
+
return session;
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
return handleRouteError(error, "Create customer session", "Customer session creation failed");
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// src/helpers/renewal.ts
|
|
1365
|
+
import { SolvaPayError as SolvaPayError4 } from "@solvapay/core";
|
|
1366
|
+
async function cancelPurchaseCore(request, body, options = {}) {
|
|
1367
|
+
try {
|
|
1368
|
+
if (!body.purchaseRef) {
|
|
1369
|
+
return {
|
|
1370
|
+
error: "Missing required parameter: purchaseRef is required",
|
|
1371
|
+
status: 400
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1375
|
+
if (!solvaPay.apiClient.cancelPurchase) {
|
|
1376
|
+
return {
|
|
1377
|
+
error: "Cancel purchase method not available on SDK client",
|
|
1378
|
+
status: 500
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
let cancelledPurchase = await solvaPay.apiClient.cancelPurchase({
|
|
1382
|
+
purchaseRef: body.purchaseRef,
|
|
1383
|
+
reason: body.reason
|
|
1384
|
+
});
|
|
1385
|
+
if (!cancelledPurchase || typeof cancelledPurchase !== "object") {
|
|
1386
|
+
return {
|
|
1387
|
+
error: "Invalid response from cancel purchase endpoint",
|
|
1388
|
+
status: 500
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
const responseObj = cancelledPurchase;
|
|
1392
|
+
if (responseObj.purchase && typeof responseObj.purchase === "object") {
|
|
1393
|
+
cancelledPurchase = responseObj.purchase;
|
|
1394
|
+
}
|
|
1395
|
+
if (!cancelledPurchase.reference) {
|
|
1396
|
+
return {
|
|
1397
|
+
error: "Cancel purchase response missing required fields",
|
|
1398
|
+
status: 500
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
const isCancelled = cancelledPurchase.status === "cancelled" || cancelledPurchase.cancelledAt;
|
|
1402
|
+
if (!isCancelled) {
|
|
1403
|
+
return {
|
|
1404
|
+
error: `Purchase cancellation failed: backend returned status '${cancelledPurchase.status}' without cancelledAt timestamp`,
|
|
1405
|
+
status: 500
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1409
|
+
return cancelledPurchase;
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
if (error instanceof SolvaPayError4) {
|
|
1412
|
+
const errorMessage = error.message;
|
|
1413
|
+
if (errorMessage.includes("not found")) {
|
|
1414
|
+
return {
|
|
1415
|
+
error: "Purchase not found",
|
|
1416
|
+
status: 404,
|
|
1417
|
+
details: errorMessage
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
if (errorMessage.includes("cannot be cancelled") || errorMessage.includes("does not belong to provider")) {
|
|
1421
|
+
return {
|
|
1422
|
+
error: "Purchase cannot be cancelled or does not belong to provider",
|
|
1423
|
+
status: 400,
|
|
1424
|
+
details: errorMessage
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
return {
|
|
1428
|
+
error: errorMessage,
|
|
1429
|
+
status: 500,
|
|
1430
|
+
details: errorMessage
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// src/helpers/plans.ts
|
|
1438
|
+
import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
|
|
1439
|
+
async function listPlansCore(request) {
|
|
818
1440
|
try {
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1441
|
+
const url = new URL(request.url);
|
|
1442
|
+
const productRef = url.searchParams.get("productRef");
|
|
1443
|
+
if (!productRef) {
|
|
1444
|
+
return {
|
|
1445
|
+
error: "Missing required parameter: productRef",
|
|
1446
|
+
status: 400
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
const config = getSolvaPayConfig2();
|
|
1450
|
+
const solvapaySecretKey = config.apiKey;
|
|
1451
|
+
const solvapayApiBaseUrl = config.apiBaseUrl;
|
|
1452
|
+
if (!solvapaySecretKey) {
|
|
1453
|
+
return {
|
|
1454
|
+
error: "Server configuration error: SolvaPay secret key not configured",
|
|
1455
|
+
status: 500
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
const apiClient = createSolvaPayClient({
|
|
1459
|
+
apiKey: solvapaySecretKey,
|
|
1460
|
+
apiBaseUrl: solvapayApiBaseUrl
|
|
1461
|
+
});
|
|
1462
|
+
if (!apiClient.listPlans) {
|
|
1463
|
+
return {
|
|
1464
|
+
error: "List plans method not available",
|
|
1465
|
+
status: 500
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
const plans = await apiClient.listPlans(productRef);
|
|
1469
|
+
return {
|
|
1470
|
+
plans: plans || [],
|
|
1471
|
+
productRef
|
|
1472
|
+
};
|
|
1473
|
+
} catch (error) {
|
|
1474
|
+
return handleRouteError(error, "List plans", "Failed to fetch plans");
|
|
823
1475
|
}
|
|
824
1476
|
}
|
|
825
1477
|
|
|
@@ -840,14 +1492,24 @@ async function verifyWebhook({
|
|
|
840
1492
|
const sigBuf = await crypto.subtle.sign("HMAC", key, enc.encode(body));
|
|
841
1493
|
const hex = Array.from(new Uint8Array(sigBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
842
1494
|
if (hex !== signature) {
|
|
843
|
-
throw new
|
|
1495
|
+
throw new SolvaPayError5("Invalid webhook signature");
|
|
844
1496
|
}
|
|
845
1497
|
return JSON.parse(body);
|
|
846
1498
|
}
|
|
847
1499
|
export {
|
|
848
1500
|
PaywallError,
|
|
1501
|
+
cancelPurchaseCore,
|
|
1502
|
+
createCheckoutSessionCore,
|
|
1503
|
+
createCustomerSessionCore,
|
|
1504
|
+
createPaymentIntentCore,
|
|
849
1505
|
createSolvaPay,
|
|
850
1506
|
createSolvaPayClient,
|
|
1507
|
+
getAuthenticatedUserCore,
|
|
1508
|
+
handleRouteError,
|
|
1509
|
+
isErrorResult,
|
|
1510
|
+
listPlansCore,
|
|
1511
|
+
processPaymentIntentCore,
|
|
1512
|
+
syncCustomerCore,
|
|
851
1513
|
verifyWebhook,
|
|
852
1514
|
withRetry
|
|
853
1515
|
};
|