@solvapay/server 1.0.0-preview.1 → 1.0.0-preview.10
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 +124 -9
- package/dist/edge.d.ts +485 -55
- package/dist/edge.js +675 -107
- package/dist/index.cjs +684 -106
- package/dist/index.d.cts +485 -55
- package/dist/index.d.ts +485 -55
- package/dist/index.js +675 -107
- package/package.json +12 -3
package/dist/edge.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-R5U7XKVJ.js";
|
|
4
4
|
|
|
5
5
|
// src/edge.ts
|
|
6
|
-
import { SolvaPayError as
|
|
6
|
+
import { SolvaPayError as SolvaPayError5 } from "@solvapay/core";
|
|
7
7
|
|
|
8
8
|
// src/client.ts
|
|
9
9
|
import { SolvaPayError } from "@solvapay/core";
|
|
@@ -14,15 +14,16 @@ function createSolvaPayClient(opts) {
|
|
|
14
14
|
"Content-Type": "application/json",
|
|
15
15
|
"Authorization": `Bearer ${opts.apiKey}`
|
|
16
16
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const debug = process.env.SOLVAPAY_DEBUG === "true";
|
|
18
|
+
const log = (...args) => {
|
|
19
|
+
if (debug) {
|
|
20
|
+
console.log(...args);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
20
23
|
return {
|
|
21
24
|
// POST: /v1/sdk/limits
|
|
22
25
|
async checkLimits(params) {
|
|
23
26
|
const url = `${base}/v1/sdk/limits`;
|
|
24
|
-
console.log(`\u{1F4E1} API Request: POST ${url}`);
|
|
25
|
-
console.log(` Params:`, JSON.stringify(params, null, 2));
|
|
26
27
|
const res = await fetch(url, {
|
|
27
28
|
method: "POST",
|
|
28
29
|
headers,
|
|
@@ -30,24 +31,15 @@ function createSolvaPayClient(opts) {
|
|
|
30
31
|
});
|
|
31
32
|
if (!res.ok) {
|
|
32
33
|
const error = await res.text();
|
|
33
|
-
|
|
34
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
34
35
|
throw new SolvaPayError(`Check limits failed (${res.status}): ${error}`);
|
|
35
36
|
}
|
|
36
37
|
const result = await res.json();
|
|
37
|
-
console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
38
|
-
console.log(`\u{1F50D} DEBUG - checkLimits breakdown:`);
|
|
39
|
-
console.log(` - withinLimits: ${result.withinLimits}`);
|
|
40
|
-
console.log(` - remaining: ${result.remaining}`);
|
|
41
|
-
console.log(` - plan: ${result.plan || "N/A"}`);
|
|
42
|
-
console.log(` - checkoutUrl: ${result.checkoutUrl || "N/A"}`);
|
|
43
|
-
console.log(` - Full response keys:`, Object.keys(result));
|
|
44
38
|
return result;
|
|
45
39
|
},
|
|
46
40
|
// POST: /v1/sdk/usages
|
|
47
41
|
async trackUsage(params) {
|
|
48
42
|
const url = `${base}/v1/sdk/usages`;
|
|
49
|
-
console.log(`\u{1F4E1} API Request: POST ${url}`);
|
|
50
|
-
console.log(` Params:`, JSON.stringify(params, null, 2));
|
|
51
43
|
const res = await fetch(url, {
|
|
52
44
|
method: "POST",
|
|
53
45
|
headers,
|
|
@@ -55,16 +47,13 @@ function createSolvaPayClient(opts) {
|
|
|
55
47
|
});
|
|
56
48
|
if (!res.ok) {
|
|
57
49
|
const error = await res.text();
|
|
58
|
-
|
|
50
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
59
51
|
throw new SolvaPayError(`Track usage failed (${res.status}): ${error}`);
|
|
60
52
|
}
|
|
61
|
-
console.log(`\u2705 Usage tracked successfully`);
|
|
62
53
|
},
|
|
63
54
|
// POST: /v1/sdk/customers
|
|
64
55
|
async createCustomer(params) {
|
|
65
56
|
const url = `${base}/v1/sdk/customers`;
|
|
66
|
-
console.log(`\u{1F4E1} API Request: POST ${url}`);
|
|
67
|
-
console.log(` Params:`, JSON.stringify(params, null, 2));
|
|
68
57
|
const res = await fetch(url, {
|
|
69
58
|
method: "POST",
|
|
70
59
|
headers,
|
|
@@ -72,51 +61,69 @@ function createSolvaPayClient(opts) {
|
|
|
72
61
|
});
|
|
73
62
|
if (!res.ok) {
|
|
74
63
|
const error = await res.text();
|
|
75
|
-
|
|
64
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
76
65
|
throw new SolvaPayError(`Create customer failed (${res.status}): ${error}`);
|
|
77
66
|
}
|
|
78
67
|
const result = await res.json();
|
|
79
|
-
console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
80
|
-
console.log(`\u{1F50D} DEBUG - createCustomer response:`);
|
|
81
|
-
console.log(` - reference/customerRef: ${result.reference || result.customerRef}`);
|
|
82
|
-
console.log(` - Has plan info: ${result.plan ? "YES" : "NO"}`);
|
|
83
|
-
console.log(` - Has subscription info: ${result.subscription ? "YES" : "NO"}`);
|
|
84
|
-
console.log(` - Full response keys:`, Object.keys(result));
|
|
85
68
|
return result;
|
|
86
69
|
},
|
|
87
70
|
// GET: /v1/sdk/customers/{reference}
|
|
88
71
|
async getCustomer(params) {
|
|
89
72
|
const url = `${base}/v1/sdk/customers/${params.customerRef}`;
|
|
90
|
-
console.log(`\u{1F4E1} API Request: GET ${url}`);
|
|
91
73
|
const res = await fetch(url, {
|
|
92
74
|
method: "GET",
|
|
93
75
|
headers
|
|
94
76
|
});
|
|
95
77
|
if (!res.ok) {
|
|
96
78
|
const error = await res.text();
|
|
97
|
-
|
|
79
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
98
80
|
throw new SolvaPayError(`Get customer failed (${res.status}): ${error}`);
|
|
99
81
|
}
|
|
100
82
|
const result = await res.json();
|
|
101
|
-
|
|
102
|
-
|
|
83
|
+
return {
|
|
84
|
+
customerRef: result.reference || result.customerRef,
|
|
85
|
+
email: result.email,
|
|
86
|
+
name: result.name,
|
|
87
|
+
externalRef: result.externalRef,
|
|
88
|
+
subscriptions: result.subscriptions || []
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
// GET: /v1/sdk/customers?externalRef={externalRef}
|
|
92
|
+
async getCustomerByExternalRef(params) {
|
|
93
|
+
const url = `${base}/v1/sdk/customers?externalRef=${encodeURIComponent(params.externalRef)}`;
|
|
94
|
+
const res = await fetch(url, {
|
|
95
|
+
method: "GET",
|
|
96
|
+
headers
|
|
97
|
+
});
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
const error = await res.text();
|
|
100
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
101
|
+
throw new SolvaPayError(`Get customer by externalRef failed (${res.status}): ${error}`);
|
|
102
|
+
}
|
|
103
|
+
const result = await res.json();
|
|
104
|
+
const customer = Array.isArray(result) ? result[0] : result;
|
|
105
|
+
return {
|
|
106
|
+
customerRef: customer.reference || customer.customerRef,
|
|
107
|
+
email: customer.email,
|
|
108
|
+
name: customer.name,
|
|
109
|
+
externalRef: customer.externalRef,
|
|
110
|
+
subscriptions: customer.subscriptions || []
|
|
111
|
+
};
|
|
103
112
|
},
|
|
104
113
|
// Management methods (primarily for integration tests)
|
|
105
114
|
// GET: /v1/sdk/agents
|
|
106
115
|
async listAgents() {
|
|
107
116
|
const url = `${base}/v1/sdk/agents`;
|
|
108
|
-
console.log(`\u{1F4E1} API Request: GET ${url}`);
|
|
109
117
|
const res = await fetch(url, {
|
|
110
118
|
method: "GET",
|
|
111
119
|
headers
|
|
112
120
|
});
|
|
113
121
|
if (!res.ok) {
|
|
114
122
|
const error = await res.text();
|
|
115
|
-
|
|
123
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
116
124
|
throw new SolvaPayError(`List agents failed (${res.status}): ${error}`);
|
|
117
125
|
}
|
|
118
126
|
const result = await res.json();
|
|
119
|
-
console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
120
127
|
const agents = Array.isArray(result) ? result : result.agents || [];
|
|
121
128
|
return agents.map((agent) => ({
|
|
122
129
|
...agent,
|
|
@@ -126,8 +133,6 @@ function createSolvaPayClient(opts) {
|
|
|
126
133
|
// POST: /v1/sdk/agents
|
|
127
134
|
async createAgent(params) {
|
|
128
135
|
const url = `${base}/v1/sdk/agents`;
|
|
129
|
-
console.log(`\u{1F4E1} API Request: POST ${url}`);
|
|
130
|
-
console.log(` Params:`, JSON.stringify(params, null, 2));
|
|
131
136
|
const res = await fetch(url, {
|
|
132
137
|
method: "POST",
|
|
133
138
|
headers,
|
|
@@ -135,54 +140,54 @@ function createSolvaPayClient(opts) {
|
|
|
135
140
|
});
|
|
136
141
|
if (!res.ok) {
|
|
137
142
|
const error = await res.text();
|
|
138
|
-
|
|
143
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
139
144
|
throw new SolvaPayError(`Create agent failed (${res.status}): ${error}`);
|
|
140
145
|
}
|
|
141
146
|
const result = await res.json();
|
|
142
|
-
console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
143
147
|
return result;
|
|
144
148
|
},
|
|
145
149
|
// DELETE: /v1/sdk/agents/{agentRef}
|
|
146
150
|
async deleteAgent(agentRef) {
|
|
147
151
|
const url = `${base}/v1/sdk/agents/${agentRef}`;
|
|
148
|
-
console.log(`\u{1F4E1} API Request: DELETE ${url}`);
|
|
149
152
|
const res = await fetch(url, {
|
|
150
153
|
method: "DELETE",
|
|
151
154
|
headers
|
|
152
155
|
});
|
|
153
156
|
if (!res.ok && res.status !== 404) {
|
|
154
157
|
const error = await res.text();
|
|
155
|
-
|
|
158
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
156
159
|
throw new SolvaPayError(`Delete agent failed (${res.status}): ${error}`);
|
|
157
160
|
}
|
|
158
|
-
console.log(`\u2705 Agent deleted successfully`);
|
|
159
161
|
},
|
|
160
162
|
// GET: /v1/sdk/agents/{agentRef}/plans
|
|
161
163
|
async listPlans(agentRef) {
|
|
162
164
|
const url = `${base}/v1/sdk/agents/${agentRef}/plans`;
|
|
163
|
-
console.log(`\u{1F4E1} API Request: GET ${url}`);
|
|
164
165
|
const res = await fetch(url, {
|
|
165
166
|
method: "GET",
|
|
166
167
|
headers
|
|
167
168
|
});
|
|
168
169
|
if (!res.ok) {
|
|
169
170
|
const error = await res.text();
|
|
170
|
-
|
|
171
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
171
172
|
throw new SolvaPayError(`List plans failed (${res.status}): ${error}`);
|
|
172
173
|
}
|
|
173
174
|
const result = await res.json();
|
|
174
|
-
console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
175
175
|
const plans = Array.isArray(result) ? result : result.plans || [];
|
|
176
|
-
return plans.map((plan) =>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
return plans.map((plan) => {
|
|
177
|
+
const price = plan.price ?? plan.data?.price;
|
|
178
|
+
const unwrapped = {
|
|
179
|
+
...plan.data || {},
|
|
180
|
+
...plan,
|
|
181
|
+
// Explicitly preserve price field to ensure it's not lost
|
|
182
|
+
...price !== void 0 && { price }
|
|
183
|
+
};
|
|
184
|
+
delete unwrapped.data;
|
|
185
|
+
return unwrapped;
|
|
186
|
+
});
|
|
180
187
|
},
|
|
181
188
|
// POST: /v1/sdk/agents/{agentRef}/plans
|
|
182
189
|
async createPlan(params) {
|
|
183
190
|
const url = `${base}/v1/sdk/agents/${params.agentRef}/plans`;
|
|
184
|
-
console.log(`\u{1F4E1} API Request: POST ${url}`);
|
|
185
|
-
console.log(` Params:`, JSON.stringify(params, null, 2));
|
|
186
191
|
const res = await fetch(url, {
|
|
187
192
|
method: "POST",
|
|
188
193
|
headers,
|
|
@@ -190,37 +195,29 @@ function createSolvaPayClient(opts) {
|
|
|
190
195
|
});
|
|
191
196
|
if (!res.ok) {
|
|
192
197
|
const error = await res.text();
|
|
193
|
-
|
|
198
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
194
199
|
throw new SolvaPayError(`Create plan failed (${res.status}): ${error}`);
|
|
195
200
|
}
|
|
196
201
|
const result = await res.json();
|
|
197
|
-
console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
198
202
|
return result;
|
|
199
203
|
},
|
|
200
204
|
// DELETE: /v1/sdk/agents/{agentRef}/plans/{planRef}
|
|
201
205
|
async deletePlan(agentRef, planRef) {
|
|
202
206
|
const url = `${base}/v1/sdk/agents/${agentRef}/plans/${planRef}`;
|
|
203
|
-
console.log(`\u{1F4E1} API Request: DELETE ${url}`);
|
|
204
207
|
const res = await fetch(url, {
|
|
205
208
|
method: "DELETE",
|
|
206
209
|
headers
|
|
207
210
|
});
|
|
208
211
|
if (!res.ok && res.status !== 404) {
|
|
209
212
|
const error = await res.text();
|
|
210
|
-
|
|
213
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
211
214
|
throw new SolvaPayError(`Delete plan failed (${res.status}): ${error}`);
|
|
212
215
|
}
|
|
213
|
-
console.log(`\u2705 Plan deleted successfully`);
|
|
214
216
|
},
|
|
215
217
|
// POST: /payment-intents
|
|
216
218
|
async createPaymentIntent(params) {
|
|
217
219
|
const idempotencyKey = params.idempotencyKey || `payment-${params.planRef}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
218
220
|
const url = `${base}/v1/sdk/payment-intents`;
|
|
219
|
-
console.log(`\u{1F4E1} API Request: POST ${url}`);
|
|
220
|
-
console.log(` Agent Ref: ${params.agentRef}`);
|
|
221
|
-
console.log(` Plan Ref: ${params.planRef}`);
|
|
222
|
-
console.log(` Customer Ref: ${params.customerRef}`);
|
|
223
|
-
console.log(` Idempotency Key: ${idempotencyKey}`);
|
|
224
221
|
const res = await fetch(url, {
|
|
225
222
|
method: "POST",
|
|
226
223
|
headers: {
|
|
@@ -235,16 +232,113 @@ function createSolvaPayClient(opts) {
|
|
|
235
232
|
});
|
|
236
233
|
if (!res.ok) {
|
|
237
234
|
const error = await res.text();
|
|
238
|
-
|
|
235
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
239
236
|
throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
|
|
240
237
|
}
|
|
241
238
|
const result = await res.json();
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
239
|
+
return result;
|
|
240
|
+
},
|
|
241
|
+
// POST: /v1/sdk/payment-intents/{paymentIntentId}/process
|
|
242
|
+
async processPayment(params) {
|
|
243
|
+
const url = `${base}/v1/sdk/payment-intents/${params.paymentIntentId}/process`;
|
|
244
|
+
const res = await fetch(url, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: {
|
|
247
|
+
...headers,
|
|
248
|
+
"Content-Type": "application/json"
|
|
249
|
+
},
|
|
250
|
+
body: JSON.stringify({
|
|
251
|
+
agentRef: params.agentRef,
|
|
252
|
+
customerRef: params.customerRef,
|
|
253
|
+
planRef: params.planRef
|
|
254
|
+
})
|
|
247
255
|
});
|
|
256
|
+
if (!res.ok) {
|
|
257
|
+
const error = await res.text();
|
|
258
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
259
|
+
throw new SolvaPayError(`Process payment failed (${res.status}): ${error}`);
|
|
260
|
+
}
|
|
261
|
+
const result = await res.json();
|
|
262
|
+
return result;
|
|
263
|
+
},
|
|
264
|
+
// POST: /v1/sdk/subscriptions/{subscriptionRef}/cancel
|
|
265
|
+
async cancelSubscription(params) {
|
|
266
|
+
const url = `${base}/v1/sdk/subscriptions/${params.subscriptionRef}/cancel`;
|
|
267
|
+
const requestOptions = {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers
|
|
270
|
+
};
|
|
271
|
+
if (params.reason) {
|
|
272
|
+
requestOptions.body = JSON.stringify({ reason: params.reason });
|
|
273
|
+
}
|
|
274
|
+
const res = await fetch(url, requestOptions);
|
|
275
|
+
if (!res.ok) {
|
|
276
|
+
const error = await res.text();
|
|
277
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
278
|
+
if (res.status === 404) {
|
|
279
|
+
throw new SolvaPayError(`Subscription not found: ${error}`);
|
|
280
|
+
}
|
|
281
|
+
if (res.status === 400) {
|
|
282
|
+
throw new SolvaPayError(`Subscription cannot be cancelled or does not belong to provider: ${error}`);
|
|
283
|
+
}
|
|
284
|
+
throw new SolvaPayError(`Cancel subscription failed (${res.status}): ${error}`);
|
|
285
|
+
}
|
|
286
|
+
const responseText = await res.text();
|
|
287
|
+
let responseData;
|
|
288
|
+
try {
|
|
289
|
+
responseData = JSON.parse(responseText);
|
|
290
|
+
} catch (parseError) {
|
|
291
|
+
log(`\u274C Failed to parse response as JSON: ${parseError}`);
|
|
292
|
+
throw new SolvaPayError(`Invalid JSON response from cancel subscription endpoint: ${responseText.substring(0, 200)}`);
|
|
293
|
+
}
|
|
294
|
+
if (!responseData || typeof responseData !== "object") {
|
|
295
|
+
log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
|
|
296
|
+
throw new SolvaPayError(`Invalid response structure from cancel subscription endpoint`);
|
|
297
|
+
}
|
|
298
|
+
let result;
|
|
299
|
+
if (responseData.subscription && typeof responseData.subscription === "object") {
|
|
300
|
+
result = responseData.subscription;
|
|
301
|
+
} else if (responseData.reference) {
|
|
302
|
+
result = responseData;
|
|
303
|
+
} else {
|
|
304
|
+
result = responseData.subscription || responseData;
|
|
305
|
+
}
|
|
306
|
+
if (!result || typeof result !== "object") {
|
|
307
|
+
log(`\u274C Invalid subscription data in response. Full response:`, responseData);
|
|
308
|
+
throw new SolvaPayError(`Invalid subscription data in cancel subscription response`);
|
|
309
|
+
}
|
|
310
|
+
return result;
|
|
311
|
+
},
|
|
312
|
+
// POST: /v1/sdk/checkout-sessions
|
|
313
|
+
async createCheckoutSession(params) {
|
|
314
|
+
const url = `${base}/v1/sdk/checkout-sessions`;
|
|
315
|
+
const res = await fetch(url, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
headers,
|
|
318
|
+
body: JSON.stringify(params)
|
|
319
|
+
});
|
|
320
|
+
if (!res.ok) {
|
|
321
|
+
const error = await res.text();
|
|
322
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
323
|
+
throw new SolvaPayError(`Create checkout session failed (${res.status}): ${error}`);
|
|
324
|
+
}
|
|
325
|
+
const result = await res.json();
|
|
326
|
+
return result;
|
|
327
|
+
},
|
|
328
|
+
// POST: /v1/sdk/customers/customer-sessions
|
|
329
|
+
async createCustomerSession(params) {
|
|
330
|
+
const url = `${base}/v1/sdk/customers/customer-sessions`;
|
|
331
|
+
const res = await fetch(url, {
|
|
332
|
+
method: "POST",
|
|
333
|
+
headers,
|
|
334
|
+
body: JSON.stringify(params)
|
|
335
|
+
});
|
|
336
|
+
if (!res.ok) {
|
|
337
|
+
const error = await res.text();
|
|
338
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
339
|
+
throw new SolvaPayError(`Create customer session failed (${res.status}): ${error}`);
|
|
340
|
+
}
|
|
341
|
+
const result = await res.json();
|
|
248
342
|
return result;
|
|
249
343
|
}
|
|
250
344
|
};
|
|
@@ -296,6 +390,95 @@ function calculateDelay(initialDelay, attempt, strategy) {
|
|
|
296
390
|
function sleep(ms) {
|
|
297
391
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
298
392
|
}
|
|
393
|
+
function createRequestDeduplicator(options = {}) {
|
|
394
|
+
const {
|
|
395
|
+
cacheTTL = 2e3,
|
|
396
|
+
maxCacheSize = 1e3,
|
|
397
|
+
cacheErrors = true
|
|
398
|
+
} = options;
|
|
399
|
+
const inFlightRequests = /* @__PURE__ */ new Map();
|
|
400
|
+
const resultCache = /* @__PURE__ */ new Map();
|
|
401
|
+
let cleanupInterval = null;
|
|
402
|
+
if (cacheTTL > 0) {
|
|
403
|
+
cleanupInterval = setInterval(() => {
|
|
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((a, b) => a[1].timestamp - b[1].timestamp);
|
|
416
|
+
const toRemove = sortedEntries.slice(0, resultCache.size - maxCacheSize);
|
|
417
|
+
for (const [key] of toRemove) {
|
|
418
|
+
resultCache.delete(key);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}, Math.min(cacheTTL, 1e3));
|
|
422
|
+
}
|
|
423
|
+
const deduplicate = async (key, fn) => {
|
|
424
|
+
if (cacheTTL > 0) {
|
|
425
|
+
const cached = resultCache.get(key);
|
|
426
|
+
if (cached && Date.now() - cached.timestamp < cacheTTL) {
|
|
427
|
+
return cached.data;
|
|
428
|
+
} else if (cached) {
|
|
429
|
+
resultCache.delete(key);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
let requestPromise = inFlightRequests.get(key);
|
|
433
|
+
if (!requestPromise) {
|
|
434
|
+
requestPromise = (async () => {
|
|
435
|
+
try {
|
|
436
|
+
const result = await fn();
|
|
437
|
+
if (cacheTTL > 0) {
|
|
438
|
+
resultCache.set(key, {
|
|
439
|
+
data: result,
|
|
440
|
+
timestamp: Date.now()
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
return result;
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (cacheTTL > 0 && cacheErrors) {
|
|
446
|
+
resultCache.set(key, {
|
|
447
|
+
data: error,
|
|
448
|
+
timestamp: Date.now()
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
throw error;
|
|
452
|
+
} finally {
|
|
453
|
+
inFlightRequests.delete(key);
|
|
454
|
+
}
|
|
455
|
+
})();
|
|
456
|
+
const existingPromise = inFlightRequests.get(key);
|
|
457
|
+
if (existingPromise) {
|
|
458
|
+
requestPromise = existingPromise;
|
|
459
|
+
} else {
|
|
460
|
+
inFlightRequests.set(key, requestPromise);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return requestPromise;
|
|
464
|
+
};
|
|
465
|
+
const clearCache = (key) => {
|
|
466
|
+
resultCache.delete(key);
|
|
467
|
+
};
|
|
468
|
+
const clearAllCache = () => {
|
|
469
|
+
resultCache.clear();
|
|
470
|
+
};
|
|
471
|
+
const getStats = () => ({
|
|
472
|
+
inFlight: inFlightRequests.size,
|
|
473
|
+
cached: resultCache.size
|
|
474
|
+
});
|
|
475
|
+
return {
|
|
476
|
+
deduplicate,
|
|
477
|
+
clearCache,
|
|
478
|
+
clearAllCache,
|
|
479
|
+
getStats
|
|
480
|
+
};
|
|
481
|
+
}
|
|
299
482
|
|
|
300
483
|
// src/paywall.ts
|
|
301
484
|
var PaywallError = class extends Error {
|
|
@@ -305,10 +488,18 @@ var PaywallError = class extends Error {
|
|
|
305
488
|
this.name = "PaywallError";
|
|
306
489
|
}
|
|
307
490
|
};
|
|
491
|
+
var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
|
|
492
|
+
cacheTTL: 6e4,
|
|
493
|
+
// Cache results for 60 seconds (reduces API calls significantly)
|
|
494
|
+
maxCacheSize: 1e3,
|
|
495
|
+
// Maximum cache entries
|
|
496
|
+
cacheErrors: false
|
|
497
|
+
// Don't cache errors - retry on next request
|
|
498
|
+
});
|
|
308
499
|
var SolvaPayPaywall = class {
|
|
309
500
|
constructor(apiClient, options = {}) {
|
|
310
501
|
this.apiClient = apiClient;
|
|
311
|
-
this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG
|
|
502
|
+
this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG === "true";
|
|
312
503
|
}
|
|
313
504
|
customerCreationAttempts = /* @__PURE__ */ new Set();
|
|
314
505
|
customerRefMapping = /* @__PURE__ */ new Map();
|
|
@@ -348,15 +539,12 @@ var SolvaPayPaywall = class {
|
|
|
348
539
|
const backendCustomerRef = await this.ensureCustomer(inputCustomerRef);
|
|
349
540
|
try {
|
|
350
541
|
const planRef = metadata.plan || toolName;
|
|
351
|
-
this.log(`\u{1F50D} Checking limits for customer: ${backendCustomerRef}, agent: ${agent}, plan: ${planRef}`);
|
|
352
542
|
const limitsCheck = await this.apiClient.checkLimits({
|
|
353
543
|
customerRef: backendCustomerRef,
|
|
354
544
|
agentRef: agent
|
|
355
545
|
});
|
|
356
|
-
this.log(`\u2713 Limits check passed:`, limitsCheck);
|
|
357
546
|
if (!limitsCheck.withinLimits) {
|
|
358
547
|
const latencyMs2 = Date.now() - startTime;
|
|
359
|
-
this.log(`\u{1F6AB} Paywall triggered - tracking usage`);
|
|
360
548
|
await this.trackUsage(backendCustomerRef, agent, planRef, toolName, "paywall", requestId, latencyMs2);
|
|
361
549
|
throw new PaywallError("Payment required", {
|
|
362
550
|
kind: "payment_required",
|
|
@@ -365,16 +553,17 @@ var SolvaPayPaywall = class {
|
|
|
365
553
|
message: `Plan subscription required. Remaining: ${limitsCheck.remaining}`
|
|
366
554
|
});
|
|
367
555
|
}
|
|
368
|
-
this.log(`\u26A1 Executing handler: ${toolName}`);
|
|
369
556
|
const result = await handler(args);
|
|
370
|
-
this.log(`\u2713 Handler completed successfully`);
|
|
371
557
|
const latencyMs = Date.now() - startTime;
|
|
372
|
-
this.log(`\u{1F4CA} Tracking successful usage`);
|
|
373
558
|
await this.trackUsage(backendCustomerRef, agent, planRef, toolName, "success", requestId, latencyMs);
|
|
374
|
-
this.log(`\u2705 Request completed successfully`);
|
|
375
559
|
return result;
|
|
376
560
|
} catch (error) {
|
|
377
|
-
|
|
561
|
+
if (error instanceof Error) {
|
|
562
|
+
const errorType = error instanceof PaywallError ? "PaywallError" : "API Error";
|
|
563
|
+
this.log(`\u274C Error in paywall [${errorType}]: ${error.message}`);
|
|
564
|
+
} else {
|
|
565
|
+
this.log(`\u274C Error in paywall:`, error);
|
|
566
|
+
}
|
|
378
567
|
const latencyMs = Date.now() - startTime;
|
|
379
568
|
const outcome = error instanceof PaywallError ? "paywall" : "fail";
|
|
380
569
|
const planRef = metadata.plan || toolName;
|
|
@@ -388,41 +577,80 @@ var SolvaPayPaywall = class {
|
|
|
388
577
|
* This is a public helper for testing, pre-creating customers, and internal use.
|
|
389
578
|
* Only attempts creation once per customer (idempotent).
|
|
390
579
|
* Returns the backend customer reference to use in API calls.
|
|
580
|
+
*
|
|
581
|
+
* @param customerRef - The customer reference used as a cache key (e.g., Supabase user ID)
|
|
582
|
+
* @param externalRef - Optional external reference for backend lookup (e.g., Supabase user ID)
|
|
583
|
+
* If provided, will lookup existing customer by externalRef before creating new one.
|
|
584
|
+
* The externalRef is stored on the SolvaPay backend for customer lookup.
|
|
585
|
+
* @param options - Optional customer details (email, name) for customer creation
|
|
391
586
|
*/
|
|
392
|
-
async ensureCustomer(customerRef) {
|
|
587
|
+
async ensureCustomer(customerRef, externalRef, options) {
|
|
393
588
|
if (this.customerRefMapping.has(customerRef)) {
|
|
394
589
|
return this.customerRefMapping.get(customerRef);
|
|
395
590
|
}
|
|
396
591
|
if (customerRef === "anonymous") {
|
|
397
592
|
return customerRef;
|
|
398
593
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
console.warn(`\u26A0\uFE0F Cannot auto-create customer ${customerRef}: createCustomer method not available on API client`);
|
|
404
|
-
return customerRef;
|
|
594
|
+
const cacheKey = externalRef || customerRef;
|
|
595
|
+
if (this.customerRefMapping.has(customerRef)) {
|
|
596
|
+
const cached = this.customerRefMapping.get(customerRef);
|
|
597
|
+
return cached;
|
|
405
598
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
599
|
+
const backendRef = await sharedCustomerLookupDeduplicator.deduplicate(
|
|
600
|
+
cacheKey,
|
|
601
|
+
async () => {
|
|
602
|
+
if (externalRef && this.apiClient.getCustomerByExternalRef) {
|
|
603
|
+
try {
|
|
604
|
+
const existingCustomer = await this.apiClient.getCustomerByExternalRef({ externalRef });
|
|
605
|
+
if (existingCustomer && existingCustomer.customerRef) {
|
|
606
|
+
const ref = existingCustomer.customerRef;
|
|
607
|
+
this.customerRefMapping.set(customerRef, ref);
|
|
608
|
+
this.customerCreationAttempts.add(customerRef);
|
|
609
|
+
if (externalRef !== customerRef) {
|
|
610
|
+
this.customerCreationAttempts.add(externalRef);
|
|
611
|
+
}
|
|
612
|
+
return ref;
|
|
613
|
+
}
|
|
614
|
+
} catch (error) {
|
|
615
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
616
|
+
if (!errorMessage.includes("404") && !errorMessage.includes("not found")) {
|
|
617
|
+
this.log(`\u26A0\uFE0F Error looking up customer by externalRef: ${errorMessage}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (this.customerCreationAttempts.has(customerRef) || externalRef && this.customerCreationAttempts.has(externalRef)) {
|
|
622
|
+
const mappedRef = this.customerRefMapping.get(customerRef);
|
|
623
|
+
return mappedRef || customerRef;
|
|
624
|
+
}
|
|
625
|
+
if (!this.apiClient.createCustomer) {
|
|
626
|
+
console.warn(`\u26A0\uFE0F Cannot auto-create customer ${customerRef}: createCustomer method not available on API client`);
|
|
627
|
+
return customerRef;
|
|
628
|
+
}
|
|
629
|
+
this.customerCreationAttempts.add(customerRef);
|
|
630
|
+
try {
|
|
631
|
+
const createParams = {
|
|
632
|
+
email: options?.email || `${customerRef}@auto-created.local`
|
|
633
|
+
};
|
|
634
|
+
if (options?.name) {
|
|
635
|
+
createParams.name = options.name;
|
|
636
|
+
}
|
|
637
|
+
if (externalRef) {
|
|
638
|
+
createParams.externalRef = externalRef;
|
|
639
|
+
}
|
|
640
|
+
const result = await this.apiClient.createCustomer(createParams);
|
|
641
|
+
const ref = result.customerRef || result.reference || customerRef;
|
|
642
|
+
this.customerRefMapping.set(customerRef, ref);
|
|
643
|
+
return ref;
|
|
644
|
+
} catch (error) {
|
|
645
|
+
this.log(`\u274C Failed to auto-create customer ${customerRef}:`, error instanceof Error ? error.message : error);
|
|
646
|
+
return customerRef;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
);
|
|
650
|
+
if (backendRef !== customerRef) {
|
|
420
651
|
this.customerRefMapping.set(customerRef, backendRef);
|
|
421
|
-
return backendRef;
|
|
422
|
-
} catch (error) {
|
|
423
|
-
this.log(`\u274C Failed to auto-create customer ${customerRef}:`, error instanceof Error ? error.message : error);
|
|
424
|
-
return customerRef;
|
|
425
652
|
}
|
|
653
|
+
return backendRef;
|
|
426
654
|
}
|
|
427
655
|
async trackUsage(customerRef, agentRef, planRef, toolName, outcome, requestId, actionDuration) {
|
|
428
656
|
await withRetry(
|
|
@@ -714,11 +942,21 @@ var McpAdapter = class {
|
|
|
714
942
|
};
|
|
715
943
|
|
|
716
944
|
// src/factory.ts
|
|
717
|
-
import { SolvaPayError as SolvaPayError2 } from "@solvapay/core";
|
|
945
|
+
import { SolvaPayError as SolvaPayError2, getSolvaPayConfig } from "@solvapay/core";
|
|
718
946
|
function createSolvaPay(config) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
947
|
+
let resolvedConfig;
|
|
948
|
+
if (!config) {
|
|
949
|
+
const envConfig = getSolvaPayConfig();
|
|
950
|
+
resolvedConfig = {
|
|
951
|
+
apiKey: envConfig.apiKey,
|
|
952
|
+
apiBaseUrl: envConfig.apiBaseUrl
|
|
953
|
+
};
|
|
954
|
+
} else {
|
|
955
|
+
resolvedConfig = config;
|
|
956
|
+
}
|
|
957
|
+
const apiClient = resolvedConfig.apiClient || createSolvaPayClient({
|
|
958
|
+
apiKey: resolvedConfig.apiKey,
|
|
959
|
+
apiBaseUrl: resolvedConfig.apiBaseUrl
|
|
722
960
|
});
|
|
723
961
|
const paywall = new SolvaPayPaywall(apiClient, {
|
|
724
962
|
debug: process.env.SOLVAPAY_DEBUG !== "false"
|
|
@@ -727,8 +965,8 @@ function createSolvaPay(config) {
|
|
|
727
965
|
// Direct access to API client for advanced operations
|
|
728
966
|
apiClient,
|
|
729
967
|
// Common API methods exposed directly for convenience
|
|
730
|
-
ensureCustomer(customerRef) {
|
|
731
|
-
return paywall.ensureCustomer(customerRef);
|
|
968
|
+
ensureCustomer(customerRef, externalRef, options) {
|
|
969
|
+
return paywall.ensureCustomer(customerRef, externalRef, options);
|
|
732
970
|
},
|
|
733
971
|
createPaymentIntent(params) {
|
|
734
972
|
if (!apiClient.createPaymentIntent) {
|
|
@@ -736,6 +974,12 @@ function createSolvaPay(config) {
|
|
|
736
974
|
}
|
|
737
975
|
return apiClient.createPaymentIntent(params);
|
|
738
976
|
},
|
|
977
|
+
processPayment(params) {
|
|
978
|
+
if (!apiClient.processPayment) {
|
|
979
|
+
throw new SolvaPayError2("processPayment is not available on this API client");
|
|
980
|
+
}
|
|
981
|
+
return apiClient.processPayment(params);
|
|
982
|
+
},
|
|
739
983
|
checkLimits(params) {
|
|
740
984
|
return apiClient.checkLimits(params);
|
|
741
985
|
},
|
|
@@ -754,6 +998,12 @@ function createSolvaPay(config) {
|
|
|
754
998
|
}
|
|
755
999
|
return apiClient.getCustomer(params);
|
|
756
1000
|
},
|
|
1001
|
+
createCheckoutSession(params) {
|
|
1002
|
+
return apiClient.createCheckoutSession(params);
|
|
1003
|
+
},
|
|
1004
|
+
createCustomerSession(params) {
|
|
1005
|
+
return apiClient.createCustomerSession(params);
|
|
1006
|
+
},
|
|
757
1007
|
// Payable API for framework-specific handlers
|
|
758
1008
|
payable(options = {}) {
|
|
759
1009
|
const agent = options.agentRef || options.agent || process.env.SOLVAPAY_AGENT || getPackageJsonName() || "default-agent";
|
|
@@ -817,6 +1067,314 @@ function getPackageJsonName() {
|
|
|
817
1067
|
}
|
|
818
1068
|
}
|
|
819
1069
|
|
|
1070
|
+
// src/helpers/error.ts
|
|
1071
|
+
import { SolvaPayError as SolvaPayError3 } from "@solvapay/core";
|
|
1072
|
+
function isErrorResult(result) {
|
|
1073
|
+
return typeof result === "object" && result !== null && "error" in result && "status" in result;
|
|
1074
|
+
}
|
|
1075
|
+
function handleRouteError(error, operationName, defaultMessage) {
|
|
1076
|
+
console.error(`[${operationName}] Error:`, error);
|
|
1077
|
+
if (error instanceof SolvaPayError3) {
|
|
1078
|
+
return {
|
|
1079
|
+
error: error.message,
|
|
1080
|
+
status: 500,
|
|
1081
|
+
details: error.message
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1085
|
+
const message = defaultMessage || `${operationName} failed`;
|
|
1086
|
+
return {
|
|
1087
|
+
error: message,
|
|
1088
|
+
status: 500,
|
|
1089
|
+
details: errorMessage
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// src/helpers/auth.ts
|
|
1094
|
+
async function getAuthenticatedUserCore(request, options = {}) {
|
|
1095
|
+
try {
|
|
1096
|
+
const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
|
|
1097
|
+
const userIdOrError = requireUserId(request);
|
|
1098
|
+
if (userIdOrError instanceof Response) {
|
|
1099
|
+
const clonedResponse = userIdOrError.clone();
|
|
1100
|
+
const body = await clonedResponse.json().catch(() => ({ error: "Unauthorized" }));
|
|
1101
|
+
return {
|
|
1102
|
+
error: body.error || "Unauthorized",
|
|
1103
|
+
status: userIdOrError.status,
|
|
1104
|
+
details: body.error || "Unauthorized"
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
const userId = userIdOrError;
|
|
1108
|
+
const email = options.includeEmail !== false ? await getUserEmailFromRequest(request) : null;
|
|
1109
|
+
const name = options.includeName !== false ? await getUserNameFromRequest(request) : null;
|
|
1110
|
+
return {
|
|
1111
|
+
userId,
|
|
1112
|
+
email,
|
|
1113
|
+
name
|
|
1114
|
+
};
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
return handleRouteError(error, "Get authenticated user", "Authentication failed");
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// src/helpers/customer.ts
|
|
1121
|
+
async function syncCustomerCore(request, options = {}) {
|
|
1122
|
+
try {
|
|
1123
|
+
const userResult = await getAuthenticatedUserCore(request, {
|
|
1124
|
+
includeEmail: options.includeEmail,
|
|
1125
|
+
includeName: options.includeName
|
|
1126
|
+
});
|
|
1127
|
+
if (isErrorResult(userResult)) {
|
|
1128
|
+
return userResult;
|
|
1129
|
+
}
|
|
1130
|
+
const { userId, email, name } = userResult;
|
|
1131
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1132
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
1133
|
+
email: email || void 0,
|
|
1134
|
+
name: name || void 0
|
|
1135
|
+
});
|
|
1136
|
+
return customerRef;
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
return handleRouteError(error, "Sync customer", "Failed to sync customer");
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// src/helpers/payment.ts
|
|
1143
|
+
async function createPaymentIntentCore(request, body, options = {}) {
|
|
1144
|
+
try {
|
|
1145
|
+
if (!body.planRef || !body.agentRef) {
|
|
1146
|
+
return {
|
|
1147
|
+
error: "Missing required parameters: planRef and agentRef are required",
|
|
1148
|
+
status: 400
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1152
|
+
solvaPay: options.solvaPay,
|
|
1153
|
+
includeEmail: options.includeEmail,
|
|
1154
|
+
includeName: options.includeName
|
|
1155
|
+
});
|
|
1156
|
+
if (isErrorResult(customerResult)) {
|
|
1157
|
+
return customerResult;
|
|
1158
|
+
}
|
|
1159
|
+
const customerRef = customerResult;
|
|
1160
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1161
|
+
const paymentIntent = await solvaPay.createPaymentIntent({
|
|
1162
|
+
agentRef: body.agentRef,
|
|
1163
|
+
planRef: body.planRef,
|
|
1164
|
+
customerRef
|
|
1165
|
+
});
|
|
1166
|
+
return {
|
|
1167
|
+
id: paymentIntent.id,
|
|
1168
|
+
clientSecret: paymentIntent.clientSecret,
|
|
1169
|
+
publishableKey: paymentIntent.publishableKey,
|
|
1170
|
+
accountId: paymentIntent.accountId,
|
|
1171
|
+
customerRef
|
|
1172
|
+
// Return the backend customer reference
|
|
1173
|
+
};
|
|
1174
|
+
} catch (error) {
|
|
1175
|
+
return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
async function processPaymentCore(request, body, options = {}) {
|
|
1179
|
+
try {
|
|
1180
|
+
if (!body.paymentIntentId || !body.agentRef) {
|
|
1181
|
+
return {
|
|
1182
|
+
error: "paymentIntentId and agentRef are required",
|
|
1183
|
+
status: 400
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1187
|
+
solvaPay: options.solvaPay
|
|
1188
|
+
});
|
|
1189
|
+
if (isErrorResult(customerResult)) {
|
|
1190
|
+
return customerResult;
|
|
1191
|
+
}
|
|
1192
|
+
const customerRef = customerResult;
|
|
1193
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1194
|
+
const result = await solvaPay.processPayment({
|
|
1195
|
+
paymentIntentId: body.paymentIntentId,
|
|
1196
|
+
agentRef: body.agentRef,
|
|
1197
|
+
customerRef,
|
|
1198
|
+
planRef: body.planRef
|
|
1199
|
+
});
|
|
1200
|
+
return result;
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
return handleRouteError(error, "Process payment", "Payment processing failed");
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// src/helpers/checkout.ts
|
|
1207
|
+
async function createCheckoutSessionCore(request, body, options = {}) {
|
|
1208
|
+
try {
|
|
1209
|
+
if (!body.agentRef) {
|
|
1210
|
+
return {
|
|
1211
|
+
error: "Missing required parameter: agentRef is required",
|
|
1212
|
+
status: 400
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1216
|
+
solvaPay: options.solvaPay,
|
|
1217
|
+
includeEmail: options.includeEmail,
|
|
1218
|
+
includeName: options.includeName
|
|
1219
|
+
});
|
|
1220
|
+
if (isErrorResult(customerResult)) {
|
|
1221
|
+
return customerResult;
|
|
1222
|
+
}
|
|
1223
|
+
const customerRef = customerResult;
|
|
1224
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1225
|
+
const session = await solvaPay.createCheckoutSession({
|
|
1226
|
+
agentRef: body.agentRef,
|
|
1227
|
+
customerRef,
|
|
1228
|
+
planRef: body.planRef || void 0
|
|
1229
|
+
});
|
|
1230
|
+
return {
|
|
1231
|
+
sessionId: session.sessionId,
|
|
1232
|
+
checkoutUrl: session.checkoutUrl
|
|
1233
|
+
};
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
return handleRouteError(error, "Create checkout session", "Checkout session creation failed");
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
async function createCustomerSessionCore(request, options = {}) {
|
|
1239
|
+
try {
|
|
1240
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1241
|
+
solvaPay: options.solvaPay,
|
|
1242
|
+
includeEmail: options.includeEmail,
|
|
1243
|
+
includeName: options.includeName
|
|
1244
|
+
});
|
|
1245
|
+
if (isErrorResult(customerResult)) {
|
|
1246
|
+
return customerResult;
|
|
1247
|
+
}
|
|
1248
|
+
const customerRef = customerResult;
|
|
1249
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1250
|
+
const session = await solvaPay.createCustomerSession({
|
|
1251
|
+
customerRef
|
|
1252
|
+
});
|
|
1253
|
+
return session;
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
return handleRouteError(error, "Create customer session", "Customer session creation failed");
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/helpers/subscription.ts
|
|
1260
|
+
import { SolvaPayError as SolvaPayError4 } from "@solvapay/core";
|
|
1261
|
+
async function cancelSubscriptionCore(request, body, options = {}) {
|
|
1262
|
+
try {
|
|
1263
|
+
const userResult = await getAuthenticatedUserCore(request);
|
|
1264
|
+
if (isErrorResult(userResult)) {
|
|
1265
|
+
return userResult;
|
|
1266
|
+
}
|
|
1267
|
+
const { userId } = userResult;
|
|
1268
|
+
if (!body.subscriptionRef) {
|
|
1269
|
+
return {
|
|
1270
|
+
error: "Missing required parameter: subscriptionRef is required",
|
|
1271
|
+
status: 400
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1275
|
+
if (!solvaPay.apiClient.cancelSubscription) {
|
|
1276
|
+
return {
|
|
1277
|
+
error: "Cancel subscription method not available on SDK client",
|
|
1278
|
+
status: 500
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
let cancelledSubscription = await solvaPay.apiClient.cancelSubscription({
|
|
1282
|
+
subscriptionRef: body.subscriptionRef,
|
|
1283
|
+
reason: body.reason
|
|
1284
|
+
});
|
|
1285
|
+
if (!cancelledSubscription || typeof cancelledSubscription !== "object") {
|
|
1286
|
+
return {
|
|
1287
|
+
error: "Invalid response from cancel subscription endpoint",
|
|
1288
|
+
status: 500
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
const responseAny = cancelledSubscription;
|
|
1292
|
+
if (responseAny.subscription && typeof responseAny.subscription === "object") {
|
|
1293
|
+
cancelledSubscription = responseAny.subscription;
|
|
1294
|
+
}
|
|
1295
|
+
if (!cancelledSubscription.reference) {
|
|
1296
|
+
return {
|
|
1297
|
+
error: "Cancel subscription response missing required fields",
|
|
1298
|
+
status: 500
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
const isCancelled = cancelledSubscription.status === "cancelled" || cancelledSubscription.cancelledAt;
|
|
1302
|
+
if (!isCancelled) {
|
|
1303
|
+
return {
|
|
1304
|
+
error: `Subscription cancellation failed: backend returned status '${cancelledSubscription.status}' without cancelledAt timestamp`,
|
|
1305
|
+
status: 500
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1309
|
+
return cancelledSubscription;
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
if (error instanceof SolvaPayError4) {
|
|
1312
|
+
const errorMessage = error.message;
|
|
1313
|
+
if (errorMessage.includes("Subscription not found")) {
|
|
1314
|
+
return {
|
|
1315
|
+
error: "Subscription not found",
|
|
1316
|
+
status: 404,
|
|
1317
|
+
details: errorMessage
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
if (errorMessage.includes("cannot be cancelled") || errorMessage.includes("does not belong to provider")) {
|
|
1321
|
+
return {
|
|
1322
|
+
error: "Subscription cannot be cancelled or does not belong to provider",
|
|
1323
|
+
status: 400,
|
|
1324
|
+
details: errorMessage
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
return {
|
|
1328
|
+
error: errorMessage,
|
|
1329
|
+
status: 500,
|
|
1330
|
+
details: errorMessage
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
return handleRouteError(error, "Cancel subscription", "Failed to cancel subscription");
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// src/helpers/plans.ts
|
|
1338
|
+
import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
|
|
1339
|
+
async function listPlansCore(request) {
|
|
1340
|
+
try {
|
|
1341
|
+
const url = new URL(request.url);
|
|
1342
|
+
const agentRef = url.searchParams.get("agentRef");
|
|
1343
|
+
if (!agentRef) {
|
|
1344
|
+
return {
|
|
1345
|
+
error: "Missing required parameter: agentRef",
|
|
1346
|
+
status: 400
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
const config = getSolvaPayConfig2();
|
|
1350
|
+
const solvapaySecretKey = config.apiKey;
|
|
1351
|
+
const solvapayApiBaseUrl = config.apiBaseUrl;
|
|
1352
|
+
if (!solvapaySecretKey) {
|
|
1353
|
+
return {
|
|
1354
|
+
error: "Server configuration error: SolvaPay secret key not configured",
|
|
1355
|
+
status: 500
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
const apiClient = createSolvaPayClient({
|
|
1359
|
+
apiKey: solvapaySecretKey,
|
|
1360
|
+
apiBaseUrl: solvapayApiBaseUrl
|
|
1361
|
+
});
|
|
1362
|
+
if (!apiClient.listPlans) {
|
|
1363
|
+
return {
|
|
1364
|
+
error: "List plans method not available",
|
|
1365
|
+
status: 500
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
const plans = await apiClient.listPlans(agentRef);
|
|
1369
|
+
return {
|
|
1370
|
+
plans: plans || [],
|
|
1371
|
+
agentRef
|
|
1372
|
+
};
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
return handleRouteError(error, "List plans", "Failed to fetch plans");
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
820
1378
|
// src/edge.ts
|
|
821
1379
|
async function verifyWebhook({
|
|
822
1380
|
body,
|
|
@@ -834,14 +1392,24 @@ async function verifyWebhook({
|
|
|
834
1392
|
const sigBuf = await crypto.subtle.sign("HMAC", key, enc.encode(body));
|
|
835
1393
|
const hex = Array.from(new Uint8Array(sigBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
836
1394
|
if (hex !== signature) {
|
|
837
|
-
throw new
|
|
1395
|
+
throw new SolvaPayError5("Invalid webhook signature");
|
|
838
1396
|
}
|
|
839
1397
|
return JSON.parse(body);
|
|
840
1398
|
}
|
|
841
1399
|
export {
|
|
842
1400
|
PaywallError,
|
|
1401
|
+
cancelSubscriptionCore,
|
|
1402
|
+
createCheckoutSessionCore,
|
|
1403
|
+
createCustomerSessionCore,
|
|
1404
|
+
createPaymentIntentCore,
|
|
843
1405
|
createSolvaPay,
|
|
844
1406
|
createSolvaPayClient,
|
|
1407
|
+
getAuthenticatedUserCore,
|
|
1408
|
+
handleRouteError,
|
|
1409
|
+
isErrorResult,
|
|
1410
|
+
listPlansCore,
|
|
1411
|
+
processPaymentCore,
|
|
1412
|
+
syncCustomerCore,
|
|
845
1413
|
verifyWebhook,
|
|
846
1414
|
withRetry
|
|
847
1415
|
};
|