@solvapay/server 1.0.0-preview.2 → 1.0.0-preview.21
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 +97 -32
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/edge.d.ts +2723 -287
- package/dist/edge.js +1192 -251
- package/dist/{esm-5GYCIXIY.js → esm-UW7WCMEK.js} +1 -1
- package/dist/index.cjs +1269 -246
- package/dist/index.d.cts +2793 -287
- package/dist/index.d.ts +2793 -287
- package/dist/index.js +1255 -251
- package/package.json +16 -6
- package/dist/chunk-R5U7XKVJ.js +0 -16
package/dist/index.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
__require
|
|
3
|
-
} from "./chunk-R5U7XKVJ.js";
|
|
1
|
+
import "./chunk-MLKGABMK.js";
|
|
4
2
|
|
|
5
3
|
// src/index.ts
|
|
6
4
|
import crypto from "crypto";
|
|
7
|
-
import { SolvaPayError as
|
|
5
|
+
import { SolvaPayError as SolvaPayError5 } from "@solvapay/core";
|
|
8
6
|
|
|
9
7
|
// src/client.ts
|
|
10
8
|
import { SolvaPayError } from "@solvapay/core";
|
|
11
9
|
function createSolvaPayClient(opts) {
|
|
12
|
-
const base = opts.apiBaseUrl ?? "https://api
|
|
10
|
+
const base = opts.apiBaseUrl ?? "https://api.solvapay.com";
|
|
13
11
|
if (!opts.apiKey) throw new SolvaPayError("Missing apiKey");
|
|
14
12
|
const headers = {
|
|
15
13
|
"Content-Type": "application/json",
|
|
16
|
-
|
|
14
|
+
Authorization: `Bearer ${opts.apiKey}`
|
|
17
15
|
};
|
|
18
16
|
const debug = process.env.SOLVAPAY_DEBUG === "true";
|
|
19
17
|
const log = (...args) => {
|
|
@@ -21,15 +19,10 @@ function createSolvaPayClient(opts) {
|
|
|
21
19
|
console.log(...args);
|
|
22
20
|
}
|
|
23
21
|
};
|
|
24
|
-
log(`\u{1F50C} SolvaPay API Client initialized`);
|
|
25
|
-
log(` Backend URL: ${base}`);
|
|
26
|
-
log(` API Key: ${opts.apiKey.substring(0, 10)}...`);
|
|
27
22
|
return {
|
|
28
23
|
// POST: /v1/sdk/limits
|
|
29
24
|
async checkLimits(params) {
|
|
30
25
|
const url = `${base}/v1/sdk/limits`;
|
|
31
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
32
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
33
26
|
const res = await fetch(url, {
|
|
34
27
|
method: "POST",
|
|
35
28
|
headers,
|
|
@@ -41,37 +34,27 @@ function createSolvaPayClient(opts) {
|
|
|
41
34
|
throw new SolvaPayError(`Check limits failed (${res.status}): ${error}`);
|
|
42
35
|
}
|
|
43
36
|
const result = await res.json();
|
|
44
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
45
|
-
log(`\u{1F50D} DEBUG - checkLimits breakdown:`);
|
|
46
|
-
log(` - withinLimits: ${result.withinLimits}`);
|
|
47
|
-
log(` - remaining: ${result.remaining}`);
|
|
48
|
-
log(` - plan: ${result.plan || "N/A"}`);
|
|
49
|
-
log(` - checkoutUrl: ${result.checkoutUrl || "N/A"}`);
|
|
50
|
-
log(` - Full response keys:`, Object.keys(result));
|
|
51
37
|
return result;
|
|
52
38
|
},
|
|
53
39
|
// POST: /v1/sdk/usages
|
|
54
40
|
async trackUsage(params) {
|
|
55
41
|
const url = `${base}/v1/sdk/usages`;
|
|
56
|
-
|
|
57
|
-
|
|
42
|
+
const { customerRef, ...rest } = params;
|
|
43
|
+
const body = { ...rest, customerId: customerRef };
|
|
58
44
|
const res = await fetch(url, {
|
|
59
45
|
method: "POST",
|
|
60
46
|
headers,
|
|
61
|
-
body: JSON.stringify(
|
|
47
|
+
body: JSON.stringify(body)
|
|
62
48
|
});
|
|
63
49
|
if (!res.ok) {
|
|
64
50
|
const error = await res.text();
|
|
65
51
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
66
52
|
throw new SolvaPayError(`Track usage failed (${res.status}): ${error}`);
|
|
67
53
|
}
|
|
68
|
-
log(`\u2705 Usage tracked successfully`);
|
|
69
54
|
},
|
|
70
55
|
// POST: /v1/sdk/customers
|
|
71
56
|
async createCustomer(params) {
|
|
72
57
|
const url = `${base}/v1/sdk/customers`;
|
|
73
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
74
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
75
58
|
const res = await fetch(url, {
|
|
76
59
|
method: "POST",
|
|
77
60
|
headers,
|
|
@@ -83,18 +66,24 @@ function createSolvaPayClient(opts) {
|
|
|
83
66
|
throw new SolvaPayError(`Create customer failed (${res.status}): ${error}`);
|
|
84
67
|
}
|
|
85
68
|
const result = await res.json();
|
|
86
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
87
|
-
log(`\u{1F50D} DEBUG - createCustomer response:`);
|
|
88
|
-
log(` - reference/customerRef: ${result.reference || result.customerRef}`);
|
|
89
|
-
log(` - Has plan info: ${result.plan ? "YES" : "NO"}`);
|
|
90
|
-
log(` - Has subscription info: ${result.subscription ? "YES" : "NO"}`);
|
|
91
|
-
log(` - Full response keys:`, Object.keys(result));
|
|
92
69
|
return result;
|
|
93
70
|
},
|
|
94
|
-
// GET: /v1/sdk/customers/{reference}
|
|
71
|
+
// GET: /v1/sdk/customers/{reference} or /v1/sdk/customers?externalRef={externalRef}|email={email}
|
|
95
72
|
async getCustomer(params) {
|
|
96
|
-
|
|
97
|
-
|
|
73
|
+
let url;
|
|
74
|
+
let isByExternalRef = false;
|
|
75
|
+
let isByEmail = false;
|
|
76
|
+
if (params.externalRef) {
|
|
77
|
+
url = `${base}/v1/sdk/customers?externalRef=${encodeURIComponent(params.externalRef)}`;
|
|
78
|
+
isByExternalRef = true;
|
|
79
|
+
} else if (params.email) {
|
|
80
|
+
url = `${base}/v1/sdk/customers?email=${encodeURIComponent(params.email)}`;
|
|
81
|
+
isByEmail = true;
|
|
82
|
+
} else if (params.customerRef) {
|
|
83
|
+
url = `${base}/v1/sdk/customers/${params.customerRef}`;
|
|
84
|
+
} else {
|
|
85
|
+
throw new SolvaPayError("One of customerRef, externalRef, or email must be provided");
|
|
86
|
+
}
|
|
98
87
|
const res = await fetch(url, {
|
|
99
88
|
method: "GET",
|
|
100
89
|
headers
|
|
@@ -105,14 +94,28 @@ function createSolvaPayClient(opts) {
|
|
|
105
94
|
throw new SolvaPayError(`Get customer failed (${res.status}): ${error}`);
|
|
106
95
|
}
|
|
107
96
|
const result = await res.json();
|
|
108
|
-
|
|
109
|
-
|
|
97
|
+
let customer = result;
|
|
98
|
+
if (isByExternalRef || isByEmail) {
|
|
99
|
+
const directCustomer = result && typeof result === "object" && (result.reference || result.customerRef || result.externalRef) ? result : void 0;
|
|
100
|
+
const wrappedCustomer = result && typeof result === "object" && result.customer ? result.customer : void 0;
|
|
101
|
+
const customers = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.customers) ? result.customers : [];
|
|
102
|
+
customer = directCustomer || wrappedCustomer || customers[0];
|
|
103
|
+
if (!customer) {
|
|
104
|
+
throw new SolvaPayError(`No customer found with externalRef: ${params.externalRef}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
customerRef: customer.reference || customer.customerRef,
|
|
109
|
+
email: customer.email,
|
|
110
|
+
name: customer.name,
|
|
111
|
+
externalRef: customer.externalRef,
|
|
112
|
+
purchases: customer.purchases || []
|
|
113
|
+
};
|
|
110
114
|
},
|
|
111
|
-
//
|
|
112
|
-
// GET: /v1/sdk/
|
|
113
|
-
async
|
|
114
|
-
const url = `${base}/v1/sdk/
|
|
115
|
-
log(`\u{1F4E1} API Request: GET ${url}`);
|
|
115
|
+
// Product management methods (primarily for integration tests)
|
|
116
|
+
// GET: /v1/sdk/products
|
|
117
|
+
async listProducts() {
|
|
118
|
+
const url = `${base}/v1/sdk/products`;
|
|
116
119
|
const res = await fetch(url, {
|
|
117
120
|
method: "GET",
|
|
118
121
|
headers
|
|
@@ -120,21 +123,18 @@ function createSolvaPayClient(opts) {
|
|
|
120
123
|
if (!res.ok) {
|
|
121
124
|
const error = await res.text();
|
|
122
125
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
123
|
-
throw new SolvaPayError(`List
|
|
126
|
+
throw new SolvaPayError(`List products failed (${res.status}): ${error}`);
|
|
124
127
|
}
|
|
125
128
|
const result = await res.json();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
...
|
|
130
|
-
...agent.data || {}
|
|
129
|
+
const products = Array.isArray(result) ? result : result.products || [];
|
|
130
|
+
return products.map((product) => ({
|
|
131
|
+
...product,
|
|
132
|
+
...product.data || {}
|
|
131
133
|
}));
|
|
132
134
|
},
|
|
133
|
-
// POST: /v1/sdk/
|
|
134
|
-
async
|
|
135
|
-
const url = `${base}/v1/sdk/
|
|
136
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
137
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
135
|
+
// POST: /v1/sdk/products
|
|
136
|
+
async createProduct(params) {
|
|
137
|
+
const url = `${base}/v1/sdk/products`;
|
|
138
138
|
const res = await fetch(url, {
|
|
139
139
|
method: "POST",
|
|
140
140
|
headers,
|
|
@@ -143,16 +143,29 @@ function createSolvaPayClient(opts) {
|
|
|
143
143
|
if (!res.ok) {
|
|
144
144
|
const error = await res.text();
|
|
145
145
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
146
|
-
throw new SolvaPayError(`Create
|
|
146
|
+
throw new SolvaPayError(`Create product failed (${res.status}): ${error}`);
|
|
147
147
|
}
|
|
148
148
|
const result = await res.json();
|
|
149
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
150
149
|
return result;
|
|
151
150
|
},
|
|
152
|
-
//
|
|
153
|
-
async
|
|
154
|
-
const url = `${base}/v1/sdk/
|
|
155
|
-
|
|
151
|
+
// POST: /v1/sdk/products/mcp/bootstrap
|
|
152
|
+
async bootstrapMcpProduct(params) {
|
|
153
|
+
const url = `${base}/v1/sdk/products/mcp/bootstrap`;
|
|
154
|
+
const res = await fetch(url, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers,
|
|
157
|
+
body: JSON.stringify(params)
|
|
158
|
+
});
|
|
159
|
+
if (!res.ok) {
|
|
160
|
+
const error = await res.text();
|
|
161
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
162
|
+
throw new SolvaPayError(`Bootstrap MCP product failed (${res.status}): ${error}`);
|
|
163
|
+
}
|
|
164
|
+
return await res.json();
|
|
165
|
+
},
|
|
166
|
+
// DELETE: /v1/sdk/products/{productRef}
|
|
167
|
+
async deleteProduct(productRef) {
|
|
168
|
+
const url = `${base}/v1/sdk/products/${productRef}`;
|
|
156
169
|
const res = await fetch(url, {
|
|
157
170
|
method: "DELETE",
|
|
158
171
|
headers
|
|
@@ -160,14 +173,27 @@ function createSolvaPayClient(opts) {
|
|
|
160
173
|
if (!res.ok && res.status !== 404) {
|
|
161
174
|
const error = await res.text();
|
|
162
175
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
163
|
-
throw new SolvaPayError(`Delete
|
|
176
|
+
throw new SolvaPayError(`Delete product failed (${res.status}): ${error}`);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
// POST: /v1/sdk/products/{productRef}/clone
|
|
180
|
+
async cloneProduct(productRef, overrides) {
|
|
181
|
+
const url = `${base}/v1/sdk/products/${productRef}/clone`;
|
|
182
|
+
const res = await fetch(url, {
|
|
183
|
+
method: "POST",
|
|
184
|
+
headers,
|
|
185
|
+
body: JSON.stringify(overrides || {})
|
|
186
|
+
});
|
|
187
|
+
if (!res.ok) {
|
|
188
|
+
const error = await res.text();
|
|
189
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
190
|
+
throw new SolvaPayError(`Clone product failed (${res.status}): ${error}`);
|
|
164
191
|
}
|
|
165
|
-
|
|
192
|
+
return await res.json();
|
|
166
193
|
},
|
|
167
|
-
// GET: /v1/sdk/
|
|
168
|
-
async listPlans(
|
|
169
|
-
const url = `${base}/v1/sdk/
|
|
170
|
-
log(`\u{1F4E1} API Request: GET ${url}`);
|
|
194
|
+
// GET: /v1/sdk/products/{productRef}/plans
|
|
195
|
+
async listPlans(productRef) {
|
|
196
|
+
const url = `${base}/v1/sdk/products/${productRef}/plans`;
|
|
171
197
|
const res = await fetch(url, {
|
|
172
198
|
method: "GET",
|
|
173
199
|
headers
|
|
@@ -178,18 +204,22 @@ function createSolvaPayClient(opts) {
|
|
|
178
204
|
throw new SolvaPayError(`List plans failed (${res.status}): ${error}`);
|
|
179
205
|
}
|
|
180
206
|
const result = await res.json();
|
|
181
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
182
207
|
const plans = Array.isArray(result) ? result : result.plans || [];
|
|
183
|
-
return plans.map((plan) =>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
208
|
+
return plans.map((plan) => {
|
|
209
|
+
const data = plan.data || {};
|
|
210
|
+
const price = plan.price ?? data.price;
|
|
211
|
+
const unwrapped = {
|
|
212
|
+
...data,
|
|
213
|
+
...plan,
|
|
214
|
+
...price !== void 0 && { price }
|
|
215
|
+
};
|
|
216
|
+
delete unwrapped.data;
|
|
217
|
+
return unwrapped;
|
|
218
|
+
});
|
|
187
219
|
},
|
|
188
|
-
// POST: /v1/sdk/
|
|
220
|
+
// POST: /v1/sdk/products/{productRef}/plans
|
|
189
221
|
async createPlan(params) {
|
|
190
|
-
const url = `${base}/v1/sdk/
|
|
191
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
192
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
222
|
+
const url = `${base}/v1/sdk/products/${params.productRef}/plans`;
|
|
193
223
|
const res = await fetch(url, {
|
|
194
224
|
method: "POST",
|
|
195
225
|
headers,
|
|
@@ -201,13 +231,26 @@ function createSolvaPayClient(opts) {
|
|
|
201
231
|
throw new SolvaPayError(`Create plan failed (${res.status}): ${error}`);
|
|
202
232
|
}
|
|
203
233
|
const result = await res.json();
|
|
204
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
205
234
|
return result;
|
|
206
235
|
},
|
|
207
|
-
//
|
|
208
|
-
async
|
|
209
|
-
const url = `${base}/v1/sdk/
|
|
210
|
-
|
|
236
|
+
// PUT: /v1/sdk/products/{productRef}/plans/{planRef}
|
|
237
|
+
async updatePlan(productRef, planRef, params) {
|
|
238
|
+
const url = `${base}/v1/sdk/products/${productRef}/plans/${planRef}`;
|
|
239
|
+
const res = await fetch(url, {
|
|
240
|
+
method: "PUT",
|
|
241
|
+
headers,
|
|
242
|
+
body: JSON.stringify(params)
|
|
243
|
+
});
|
|
244
|
+
if (!res.ok) {
|
|
245
|
+
const error = await res.text();
|
|
246
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
247
|
+
throw new SolvaPayError(`Update plan failed (${res.status}): ${error}`);
|
|
248
|
+
}
|
|
249
|
+
return await res.json();
|
|
250
|
+
},
|
|
251
|
+
// DELETE: /v1/sdk/products/{productRef}/plans/{planRef}
|
|
252
|
+
async deletePlan(productRef, planRef) {
|
|
253
|
+
const url = `${base}/v1/sdk/products/${productRef}/plans/${planRef}`;
|
|
211
254
|
const res = await fetch(url, {
|
|
212
255
|
method: "DELETE",
|
|
213
256
|
headers
|
|
@@ -217,17 +260,11 @@ function createSolvaPayClient(opts) {
|
|
|
217
260
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
218
261
|
throw new SolvaPayError(`Delete plan failed (${res.status}): ${error}`);
|
|
219
262
|
}
|
|
220
|
-
log(`\u2705 Plan deleted successfully`);
|
|
221
263
|
},
|
|
222
264
|
// POST: /payment-intents
|
|
223
265
|
async createPaymentIntent(params) {
|
|
224
266
|
const idempotencyKey = params.idempotencyKey || `payment-${params.planRef}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
225
267
|
const url = `${base}/v1/sdk/payment-intents`;
|
|
226
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
227
|
-
log(` Agent Ref: ${params.agentRef}`);
|
|
228
|
-
log(` Plan Ref: ${params.planRef}`);
|
|
229
|
-
log(` Customer Ref: ${params.customerRef}`);
|
|
230
|
-
log(` Idempotency Key: ${idempotencyKey}`);
|
|
231
268
|
const res = await fetch(url, {
|
|
232
269
|
method: "POST",
|
|
233
270
|
headers: {
|
|
@@ -235,7 +272,7 @@ function createSolvaPayClient(opts) {
|
|
|
235
272
|
"Idempotency-Key": idempotencyKey
|
|
236
273
|
},
|
|
237
274
|
body: JSON.stringify({
|
|
238
|
-
|
|
275
|
+
productRef: params.productRef,
|
|
239
276
|
planRef: params.planRef,
|
|
240
277
|
customerReference: params.customerRef
|
|
241
278
|
})
|
|
@@ -246,12 +283,128 @@ function createSolvaPayClient(opts) {
|
|
|
246
283
|
throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
|
|
247
284
|
}
|
|
248
285
|
const result = await res.json();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
286
|
+
return result;
|
|
287
|
+
},
|
|
288
|
+
// POST: /v1/sdk/payment-intents/{paymentIntentId}/process
|
|
289
|
+
async processPaymentIntent(params) {
|
|
290
|
+
const url = `${base}/v1/sdk/payment-intents/${params.paymentIntentId}/process`;
|
|
291
|
+
const res = await fetch(url, {
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers: {
|
|
294
|
+
...headers,
|
|
295
|
+
"Content-Type": "application/json"
|
|
296
|
+
},
|
|
297
|
+
body: JSON.stringify({
|
|
298
|
+
productRef: params.productRef,
|
|
299
|
+
customerRef: params.customerRef,
|
|
300
|
+
planRef: params.planRef
|
|
301
|
+
})
|
|
302
|
+
});
|
|
303
|
+
if (!res.ok) {
|
|
304
|
+
const error = await res.text();
|
|
305
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
306
|
+
throw new SolvaPayError(`Process payment failed (${res.status}): ${error}`);
|
|
307
|
+
}
|
|
308
|
+
const result = await res.json();
|
|
309
|
+
return result;
|
|
310
|
+
},
|
|
311
|
+
// POST: /v1/sdk/purchases/{purchaseRef}/cancel
|
|
312
|
+
async cancelPurchase(params) {
|
|
313
|
+
const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/cancel`;
|
|
314
|
+
const requestOptions = {
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers
|
|
317
|
+
};
|
|
318
|
+
if (params.reason) {
|
|
319
|
+
requestOptions.body = JSON.stringify({ reason: params.reason });
|
|
320
|
+
}
|
|
321
|
+
const res = await fetch(url, requestOptions);
|
|
322
|
+
if (!res.ok) {
|
|
323
|
+
const error = await res.text();
|
|
324
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
325
|
+
if (res.status === 404) {
|
|
326
|
+
throw new SolvaPayError(`Purchase not found: ${error}`);
|
|
327
|
+
}
|
|
328
|
+
if (res.status === 400) {
|
|
329
|
+
throw new SolvaPayError(
|
|
330
|
+
`Purchase cannot be cancelled or does not belong to provider: ${error}`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
throw new SolvaPayError(`Cancel purchase failed (${res.status}): ${error}`);
|
|
334
|
+
}
|
|
335
|
+
const responseText = await res.text();
|
|
336
|
+
let responseData;
|
|
337
|
+
try {
|
|
338
|
+
responseData = JSON.parse(responseText);
|
|
339
|
+
} catch (parseError) {
|
|
340
|
+
log(`\u274C Failed to parse response as JSON: ${parseError}`);
|
|
341
|
+
throw new SolvaPayError(
|
|
342
|
+
`Invalid JSON response from cancel purchase endpoint: ${responseText.substring(0, 200)}`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
if (!responseData || typeof responseData !== "object") {
|
|
346
|
+
log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
|
|
347
|
+
throw new SolvaPayError(`Invalid response structure from cancel purchase endpoint`);
|
|
348
|
+
}
|
|
349
|
+
let result;
|
|
350
|
+
if (responseData.purchase && typeof responseData.purchase === "object") {
|
|
351
|
+
result = responseData.purchase;
|
|
352
|
+
} else if (responseData.reference) {
|
|
353
|
+
result = responseData;
|
|
354
|
+
} else {
|
|
355
|
+
result = responseData.purchase || responseData;
|
|
356
|
+
}
|
|
357
|
+
if (!result || typeof result !== "object") {
|
|
358
|
+
log(`\u274C Invalid purchase data in response. Full response:`, responseData);
|
|
359
|
+
throw new SolvaPayError(`Invalid purchase data in cancel purchase response`);
|
|
360
|
+
}
|
|
361
|
+
return result;
|
|
362
|
+
},
|
|
363
|
+
// POST: /v1/sdk/user-info
|
|
364
|
+
async getUserInfo(params) {
|
|
365
|
+
const url = `${base}/v1/sdk/user-info`;
|
|
366
|
+
const res = await fetch(url, {
|
|
367
|
+
method: "POST",
|
|
368
|
+
headers,
|
|
369
|
+
body: JSON.stringify(params)
|
|
370
|
+
});
|
|
371
|
+
if (!res.ok) {
|
|
372
|
+
const error = await res.text();
|
|
373
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
374
|
+
throw new SolvaPayError(`Get user info failed (${res.status}): ${error}`);
|
|
375
|
+
}
|
|
376
|
+
return await res.json();
|
|
377
|
+
},
|
|
378
|
+
// POST: /v1/sdk/checkout-sessions
|
|
379
|
+
async createCheckoutSession(params) {
|
|
380
|
+
const url = `${base}/v1/sdk/checkout-sessions`;
|
|
381
|
+
const res = await fetch(url, {
|
|
382
|
+
method: "POST",
|
|
383
|
+
headers,
|
|
384
|
+
body: JSON.stringify(params)
|
|
385
|
+
});
|
|
386
|
+
if (!res.ok) {
|
|
387
|
+
const error = await res.text();
|
|
388
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
389
|
+
throw new SolvaPayError(`Create checkout session failed (${res.status}): ${error}`);
|
|
390
|
+
}
|
|
391
|
+
const result = await res.json();
|
|
392
|
+
return result;
|
|
393
|
+
},
|
|
394
|
+
// POST: /v1/sdk/customers/customer-sessions
|
|
395
|
+
async createCustomerSession(params) {
|
|
396
|
+
const url = `${base}/v1/sdk/customers/customer-sessions`;
|
|
397
|
+
const res = await fetch(url, {
|
|
398
|
+
method: "POST",
|
|
399
|
+
headers,
|
|
400
|
+
body: JSON.stringify(params)
|
|
254
401
|
});
|
|
402
|
+
if (!res.ok) {
|
|
403
|
+
const error = await res.text();
|
|
404
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
405
|
+
throw new SolvaPayError(`Create customer session failed (${res.status}): ${error}`);
|
|
406
|
+
}
|
|
407
|
+
const result = await res.json();
|
|
255
408
|
return result;
|
|
256
409
|
}
|
|
257
410
|
};
|
|
@@ -303,39 +456,137 @@ function calculateDelay(initialDelay, attempt, strategy) {
|
|
|
303
456
|
function sleep(ms) {
|
|
304
457
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
305
458
|
}
|
|
459
|
+
function createRequestDeduplicator(options = {}) {
|
|
460
|
+
const { cacheTTL = 2e3, maxCacheSize = 1e3, cacheErrors = true } = options;
|
|
461
|
+
const inFlightRequests = /* @__PURE__ */ new Map();
|
|
462
|
+
const resultCache = /* @__PURE__ */ new Map();
|
|
463
|
+
let _cleanupInterval = null;
|
|
464
|
+
if (cacheTTL > 0) {
|
|
465
|
+
_cleanupInterval = setInterval(
|
|
466
|
+
() => {
|
|
467
|
+
const now = Date.now();
|
|
468
|
+
const entriesToDelete = [];
|
|
469
|
+
for (const [key, cached] of resultCache.entries()) {
|
|
470
|
+
if (now - cached.timestamp >= cacheTTL) {
|
|
471
|
+
entriesToDelete.push(key);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
for (const key of entriesToDelete) {
|
|
475
|
+
resultCache.delete(key);
|
|
476
|
+
}
|
|
477
|
+
if (resultCache.size > maxCacheSize) {
|
|
478
|
+
const sortedEntries = Array.from(resultCache.entries()).sort(
|
|
479
|
+
(a, b) => a[1].timestamp - b[1].timestamp
|
|
480
|
+
);
|
|
481
|
+
const toRemove = sortedEntries.slice(0, resultCache.size - maxCacheSize);
|
|
482
|
+
for (const [key] of toRemove) {
|
|
483
|
+
resultCache.delete(key);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
Math.min(cacheTTL, 1e3)
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
const deduplicate = async (key, fn) => {
|
|
491
|
+
if (cacheTTL > 0) {
|
|
492
|
+
const cached = resultCache.get(key);
|
|
493
|
+
if (cached && Date.now() - cached.timestamp < cacheTTL) {
|
|
494
|
+
return cached.data;
|
|
495
|
+
} else if (cached) {
|
|
496
|
+
resultCache.delete(key);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
let requestPromise = inFlightRequests.get(key);
|
|
500
|
+
if (!requestPromise) {
|
|
501
|
+
requestPromise = (async () => {
|
|
502
|
+
try {
|
|
503
|
+
const result = await fn();
|
|
504
|
+
if (cacheTTL > 0) {
|
|
505
|
+
resultCache.set(key, {
|
|
506
|
+
data: result,
|
|
507
|
+
timestamp: Date.now()
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return result;
|
|
511
|
+
} catch (error) {
|
|
512
|
+
if (cacheTTL > 0 && cacheErrors) {
|
|
513
|
+
resultCache.set(key, {
|
|
514
|
+
data: error,
|
|
515
|
+
timestamp: Date.now()
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
throw error;
|
|
519
|
+
} finally {
|
|
520
|
+
inFlightRequests.delete(key);
|
|
521
|
+
}
|
|
522
|
+
})();
|
|
523
|
+
const existingPromise = inFlightRequests.get(key);
|
|
524
|
+
if (existingPromise) {
|
|
525
|
+
requestPromise = existingPromise;
|
|
526
|
+
} else {
|
|
527
|
+
inFlightRequests.set(key, requestPromise);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return requestPromise;
|
|
531
|
+
};
|
|
532
|
+
const clearCache = (key) => {
|
|
533
|
+
resultCache.delete(key);
|
|
534
|
+
};
|
|
535
|
+
const clearAllCache = () => {
|
|
536
|
+
resultCache.clear();
|
|
537
|
+
};
|
|
538
|
+
const getStats = () => ({
|
|
539
|
+
inFlight: inFlightRequests.size,
|
|
540
|
+
cached: resultCache.size
|
|
541
|
+
});
|
|
542
|
+
return {
|
|
543
|
+
deduplicate,
|
|
544
|
+
clearCache,
|
|
545
|
+
clearAllCache,
|
|
546
|
+
getStats
|
|
547
|
+
};
|
|
548
|
+
}
|
|
306
549
|
|
|
307
550
|
// src/paywall.ts
|
|
308
551
|
var PaywallError = class extends Error {
|
|
552
|
+
/**
|
|
553
|
+
* Creates a new PaywallError instance.
|
|
554
|
+
*
|
|
555
|
+
* @param message - Error message
|
|
556
|
+
* @param structuredContent - Structured content with checkout URLs and metadata
|
|
557
|
+
*/
|
|
309
558
|
constructor(message, structuredContent) {
|
|
310
559
|
super(message);
|
|
311
560
|
this.structuredContent = structuredContent;
|
|
312
561
|
this.name = "PaywallError";
|
|
313
562
|
}
|
|
314
563
|
};
|
|
564
|
+
var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
|
|
565
|
+
cacheTTL: 6e4,
|
|
566
|
+
// Cache results for 60 seconds (reduces API calls significantly)
|
|
567
|
+
maxCacheSize: 1e3,
|
|
568
|
+
// Maximum cache entries
|
|
569
|
+
cacheErrors: false
|
|
570
|
+
// Don't cache errors - retry on next request
|
|
571
|
+
});
|
|
315
572
|
var SolvaPayPaywall = class {
|
|
316
573
|
constructor(apiClient, options = {}) {
|
|
317
574
|
this.apiClient = apiClient;
|
|
318
|
-
this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG
|
|
575
|
+
this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG === "true";
|
|
576
|
+
this.limitsCacheTTL = options.limitsCacheTTL ?? 1e4;
|
|
319
577
|
}
|
|
320
578
|
customerCreationAttempts = /* @__PURE__ */ new Set();
|
|
321
579
|
customerRefMapping = /* @__PURE__ */ new Map();
|
|
322
|
-
// input ref -> backend ref
|
|
323
580
|
debug;
|
|
581
|
+
limitsCache = /* @__PURE__ */ new Map();
|
|
582
|
+
limitsCacheTTL;
|
|
324
583
|
log(...args) {
|
|
325
584
|
if (this.debug) {
|
|
326
585
|
console.log(...args);
|
|
327
586
|
}
|
|
328
587
|
}
|
|
329
|
-
|
|
330
|
-
return metadata.
|
|
331
|
-
}
|
|
332
|
-
getPackageJsonName() {
|
|
333
|
-
try {
|
|
334
|
-
const pkg = __require(process.cwd() + "/package.json");
|
|
335
|
-
return pkg.name;
|
|
336
|
-
} catch {
|
|
337
|
-
return void 0;
|
|
338
|
-
}
|
|
588
|
+
resolveProduct(metadata) {
|
|
589
|
+
return metadata.product || process.env.SOLVAPAY_PRODUCT || "default-product";
|
|
339
590
|
}
|
|
340
591
|
generateRequestId() {
|
|
341
592
|
const timestamp = Date.now();
|
|
@@ -345,47 +596,122 @@ var SolvaPayPaywall = class {
|
|
|
345
596
|
/**
|
|
346
597
|
* Core protection method - works for both MCP and HTTP
|
|
347
598
|
*/
|
|
599
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
348
600
|
async protect(handler, metadata = {}, getCustomerRef) {
|
|
349
|
-
const
|
|
350
|
-
const
|
|
601
|
+
const product = this.resolveProduct(metadata);
|
|
602
|
+
const configuredPlanRef = metadata.plan?.trim();
|
|
603
|
+
const usagePlanRef = configuredPlanRef || "unspecified";
|
|
604
|
+
const usageType = metadata.usageType || "requests";
|
|
351
605
|
return async (args) => {
|
|
352
606
|
const startTime = Date.now();
|
|
353
607
|
const requestId = this.generateRequestId();
|
|
354
608
|
const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
|
|
355
|
-
|
|
609
|
+
let backendCustomerRef;
|
|
610
|
+
if (inputCustomerRef.startsWith("cus_")) {
|
|
611
|
+
backendCustomerRef = inputCustomerRef;
|
|
612
|
+
} else {
|
|
613
|
+
backendCustomerRef = await this.ensureCustomer(inputCustomerRef, inputCustomerRef);
|
|
614
|
+
}
|
|
615
|
+
let resolvedMeterName;
|
|
356
616
|
try {
|
|
357
|
-
const
|
|
358
|
-
this.
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (
|
|
617
|
+
const limitsCacheKey = `${backendCustomerRef}:${product}:${configuredPlanRef || ""}:${usageType}`;
|
|
618
|
+
const cachedLimits = this.limitsCache.get(limitsCacheKey);
|
|
619
|
+
const now = Date.now();
|
|
620
|
+
let withinLimits;
|
|
621
|
+
let remaining;
|
|
622
|
+
let checkoutUrl;
|
|
623
|
+
const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
|
|
624
|
+
if (hasFreshCachedLimits) {
|
|
625
|
+
checkoutUrl = cachedLimits.checkoutUrl;
|
|
626
|
+
resolvedMeterName = cachedLimits.meterName;
|
|
627
|
+
if (cachedLimits.remaining > 0) {
|
|
628
|
+
cachedLimits.remaining--;
|
|
629
|
+
if (cachedLimits.remaining <= 0) {
|
|
630
|
+
this.limitsCache.delete(limitsCacheKey);
|
|
631
|
+
}
|
|
632
|
+
withinLimits = true;
|
|
633
|
+
remaining = cachedLimits.remaining;
|
|
634
|
+
} else {
|
|
635
|
+
withinLimits = false;
|
|
636
|
+
remaining = 0;
|
|
637
|
+
this.limitsCache.delete(limitsCacheKey);
|
|
638
|
+
}
|
|
639
|
+
} else {
|
|
640
|
+
if (cachedLimits) {
|
|
641
|
+
this.limitsCache.delete(limitsCacheKey);
|
|
642
|
+
}
|
|
643
|
+
const limitsCheck = await this.apiClient.checkLimits({
|
|
644
|
+
customerRef: backendCustomerRef,
|
|
645
|
+
productRef: product,
|
|
646
|
+
...configuredPlanRef ? { planRef: configuredPlanRef } : {},
|
|
647
|
+
meterName: usageType
|
|
648
|
+
});
|
|
649
|
+
withinLimits = limitsCheck.withinLimits;
|
|
650
|
+
remaining = limitsCheck.remaining;
|
|
651
|
+
checkoutUrl = limitsCheck.checkoutUrl;
|
|
652
|
+
resolvedMeterName = limitsCheck.meterName;
|
|
653
|
+
const consumedAllowance = withinLimits && remaining > 0;
|
|
654
|
+
if (consumedAllowance) {
|
|
655
|
+
remaining = Math.max(0, remaining - 1);
|
|
656
|
+
}
|
|
657
|
+
if (consumedAllowance) {
|
|
658
|
+
this.limitsCache.set(limitsCacheKey, {
|
|
659
|
+
remaining,
|
|
660
|
+
checkoutUrl,
|
|
661
|
+
meterName: resolvedMeterName,
|
|
662
|
+
timestamp: now
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (!withinLimits) {
|
|
365
667
|
const latencyMs2 = Date.now() - startTime;
|
|
366
|
-
this.
|
|
367
|
-
|
|
668
|
+
this.trackUsage(
|
|
669
|
+
backendCustomerRef,
|
|
670
|
+
product,
|
|
671
|
+
usagePlanRef,
|
|
672
|
+
resolvedMeterName || usageType,
|
|
673
|
+
"paywall",
|
|
674
|
+
requestId,
|
|
675
|
+
latencyMs2
|
|
676
|
+
);
|
|
368
677
|
throw new PaywallError("Payment required", {
|
|
369
678
|
kind: "payment_required",
|
|
370
|
-
|
|
371
|
-
checkoutUrl:
|
|
372
|
-
message: `
|
|
679
|
+
product,
|
|
680
|
+
checkoutUrl: checkoutUrl || "",
|
|
681
|
+
message: `Purchase required. Remaining: ${remaining}`
|
|
373
682
|
});
|
|
374
683
|
}
|
|
375
|
-
this.log(`\u26A1 Executing handler: ${toolName}`);
|
|
376
684
|
const result = await handler(args);
|
|
377
|
-
this.log(`\u2713 Handler completed successfully`);
|
|
378
685
|
const latencyMs = Date.now() - startTime;
|
|
379
|
-
this.
|
|
380
|
-
|
|
381
|
-
|
|
686
|
+
this.trackUsage(
|
|
687
|
+
backendCustomerRef,
|
|
688
|
+
product,
|
|
689
|
+
usagePlanRef,
|
|
690
|
+
resolvedMeterName || usageType,
|
|
691
|
+
"success",
|
|
692
|
+
requestId,
|
|
693
|
+
latencyMs
|
|
694
|
+
);
|
|
382
695
|
return result;
|
|
383
696
|
} catch (error) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
697
|
+
if (error instanceof Error) {
|
|
698
|
+
const errorType = error instanceof PaywallError ? "PaywallError" : "API Error";
|
|
699
|
+
this.log(`\u274C Error in paywall [${errorType}]: ${error.message}`);
|
|
700
|
+
} else {
|
|
701
|
+
this.log(`\u274C Error in paywall:`, error);
|
|
702
|
+
}
|
|
703
|
+
if (!(error instanceof PaywallError)) {
|
|
704
|
+
const latencyMs = Date.now() - startTime;
|
|
705
|
+
this.trackUsage(
|
|
706
|
+
backendCustomerRef,
|
|
707
|
+
product,
|
|
708
|
+
usagePlanRef,
|
|
709
|
+
resolvedMeterName || usageType,
|
|
710
|
+
"fail",
|
|
711
|
+
requestId,
|
|
712
|
+
latencyMs
|
|
713
|
+
);
|
|
714
|
+
}
|
|
389
715
|
throw error;
|
|
390
716
|
}
|
|
391
717
|
};
|
|
@@ -395,60 +721,162 @@ var SolvaPayPaywall = class {
|
|
|
395
721
|
* This is a public helper for testing, pre-creating customers, and internal use.
|
|
396
722
|
* Only attempts creation once per customer (idempotent).
|
|
397
723
|
* Returns the backend customer reference to use in API calls.
|
|
724
|
+
*
|
|
725
|
+
* @param customerRef - The customer reference used as a cache key (e.g., Supabase user ID)
|
|
726
|
+
* @param externalRef - Optional external reference for backend lookup (e.g., Supabase user ID)
|
|
727
|
+
* If provided, will lookup existing customer by externalRef before creating new one.
|
|
728
|
+
* The externalRef is stored on the SolvaPay backend for customer lookup.
|
|
729
|
+
* @param options - Optional customer details (email, name) for customer creation
|
|
398
730
|
*/
|
|
399
|
-
async ensureCustomer(customerRef) {
|
|
731
|
+
async ensureCustomer(customerRef, externalRef, options) {
|
|
400
732
|
if (this.customerRefMapping.has(customerRef)) {
|
|
401
733
|
return this.customerRefMapping.get(customerRef);
|
|
402
734
|
}
|
|
403
735
|
if (customerRef === "anonymous") {
|
|
404
736
|
return customerRef;
|
|
405
737
|
}
|
|
406
|
-
if (
|
|
738
|
+
if (customerRef.startsWith("cus_")) {
|
|
407
739
|
return customerRef;
|
|
408
740
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
741
|
+
const cacheKey = externalRef || customerRef;
|
|
742
|
+
if (this.customerRefMapping.has(customerRef)) {
|
|
743
|
+
const cached = this.customerRefMapping.get(customerRef);
|
|
744
|
+
return cached;
|
|
412
745
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
746
|
+
const backendRef = await sharedCustomerLookupDeduplicator.deduplicate(cacheKey, async () => {
|
|
747
|
+
if (externalRef) {
|
|
748
|
+
try {
|
|
749
|
+
const existingCustomer = await this.apiClient.getCustomer({ externalRef });
|
|
750
|
+
if (existingCustomer && existingCustomer.customerRef) {
|
|
751
|
+
const ref = existingCustomer.customerRef;
|
|
752
|
+
this.customerRefMapping.set(customerRef, ref);
|
|
753
|
+
this.customerCreationAttempts.add(customerRef);
|
|
754
|
+
if (externalRef !== customerRef) {
|
|
755
|
+
this.customerCreationAttempts.add(externalRef);
|
|
756
|
+
}
|
|
757
|
+
return ref;
|
|
758
|
+
}
|
|
759
|
+
} catch (error) {
|
|
760
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
761
|
+
if (!errorMessage.includes("404") && !errorMessage.includes("not found")) {
|
|
762
|
+
this.log(`\u26A0\uFE0F Error looking up customer by externalRef: ${errorMessage}`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (this.customerCreationAttempts.has(customerRef) || externalRef && this.customerCreationAttempts.has(externalRef)) {
|
|
767
|
+
const mappedRef = this.customerRefMapping.get(customerRef);
|
|
768
|
+
return mappedRef || customerRef;
|
|
769
|
+
}
|
|
770
|
+
if (!this.apiClient.createCustomer) {
|
|
771
|
+
console.warn(
|
|
772
|
+
`\u26A0\uFE0F Cannot auto-create customer ${customerRef}: createCustomer method not available on API client`
|
|
773
|
+
);
|
|
774
|
+
return customerRef;
|
|
775
|
+
}
|
|
776
|
+
this.customerCreationAttempts.add(customerRef);
|
|
777
|
+
try {
|
|
778
|
+
const createParams = {
|
|
779
|
+
email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`
|
|
780
|
+
};
|
|
781
|
+
if (options?.name) {
|
|
782
|
+
createParams.name = options.name;
|
|
783
|
+
}
|
|
784
|
+
if (externalRef) {
|
|
785
|
+
createParams.externalRef = externalRef;
|
|
786
|
+
}
|
|
787
|
+
const result = await this.apiClient.createCustomer(createParams);
|
|
788
|
+
const resultObj = result;
|
|
789
|
+
const ref = resultObj.customerRef || resultObj.reference || customerRef;
|
|
790
|
+
this.customerRefMapping.set(customerRef, ref);
|
|
791
|
+
return ref;
|
|
792
|
+
} catch (error) {
|
|
793
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
794
|
+
if (errorMessage.includes("409") || errorMessage.includes("already exists")) {
|
|
795
|
+
if (externalRef) {
|
|
796
|
+
try {
|
|
797
|
+
const searchResult = await this.apiClient.getCustomer({ externalRef });
|
|
798
|
+
if (searchResult && searchResult.customerRef) {
|
|
799
|
+
this.customerRefMapping.set(customerRef, searchResult.customerRef);
|
|
800
|
+
return searchResult.customerRef;
|
|
801
|
+
}
|
|
802
|
+
} catch (lookupError) {
|
|
803
|
+
this.log(`\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`, lookupError instanceof Error ? lookupError.message : lookupError);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
|
|
807
|
+
if (externalRef && isEmailConflict && options?.email) {
|
|
808
|
+
try {
|
|
809
|
+
const byEmail = await this.apiClient.getCustomer({ email: options.email });
|
|
810
|
+
if (byEmail && byEmail.customerRef) {
|
|
811
|
+
this.customerRefMapping.set(customerRef, byEmail.customerRef);
|
|
812
|
+
this.log(
|
|
813
|
+
`\u26A0\uFE0F Resolved customer ${customerRef} by email after conflict; using existing customer ${byEmail.customerRef}`
|
|
814
|
+
);
|
|
815
|
+
return byEmail.customerRef;
|
|
816
|
+
}
|
|
817
|
+
} catch (emailLookupError) {
|
|
818
|
+
this.log(
|
|
819
|
+
`\u26A0\uFE0F Email lookup failed after customer conflict for ${customerRef}:`,
|
|
820
|
+
emailLookupError instanceof Error ? emailLookupError.message : emailLookupError
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
try {
|
|
824
|
+
const retryParams = {
|
|
825
|
+
email: `${customerRef}-${Date.now()}@auto-created.local`,
|
|
826
|
+
externalRef
|
|
827
|
+
};
|
|
828
|
+
if (options?.name) {
|
|
829
|
+
retryParams.name = options.name;
|
|
830
|
+
}
|
|
831
|
+
const retryResult = await this.apiClient.createCustomer(retryParams);
|
|
832
|
+
const retryObj = retryResult;
|
|
833
|
+
const retryRef = retryObj.customerRef || retryObj.reference || customerRef;
|
|
834
|
+
this.customerRefMapping.set(customerRef, retryRef);
|
|
835
|
+
this.log(
|
|
836
|
+
`\u26A0\uFE0F Retried customer creation for ${customerRef} with generated email after email conflict`
|
|
837
|
+
);
|
|
838
|
+
return retryRef;
|
|
839
|
+
} catch (retryError) {
|
|
840
|
+
this.log(
|
|
841
|
+
`\u26A0\uFE0F Retry create customer with generated email failed for ${customerRef}:`,
|
|
842
|
+
retryError instanceof Error ? retryError.message : retryError
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
const unresolvedMessage = errorMessage || "Customer already exists but could not be resolved";
|
|
847
|
+
throw new Error(
|
|
848
|
+
`Failed to resolve existing customer for ${customerRef} after conflict: ${unresolvedMessage}. Ensure the existing customer is linked to this externalRef.`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
this.log(
|
|
852
|
+
`\u274C Failed to auto-create customer ${customerRef}:`,
|
|
853
|
+
error instanceof Error ? error.message : error
|
|
854
|
+
);
|
|
855
|
+
throw error;
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
if (backendRef !== customerRef) {
|
|
427
859
|
this.customerRefMapping.set(customerRef, backendRef);
|
|
428
|
-
return backendRef;
|
|
429
|
-
} catch (error) {
|
|
430
|
-
this.log(`\u274C Failed to auto-create customer ${customerRef}:`, error instanceof Error ? error.message : error);
|
|
431
|
-
return customerRef;
|
|
432
860
|
}
|
|
861
|
+
return backendRef;
|
|
433
862
|
}
|
|
434
|
-
async trackUsage(customerRef,
|
|
863
|
+
async trackUsage(customerRef, _productRef, _planRef, action, outcome, requestId, actionDuration) {
|
|
435
864
|
await withRetry(
|
|
436
865
|
() => this.apiClient.trackUsage({
|
|
437
866
|
customerRef,
|
|
438
|
-
|
|
439
|
-
|
|
867
|
+
actionType: "api_call",
|
|
868
|
+
units: 1,
|
|
440
869
|
outcome,
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
870
|
+
productReference: _productRef,
|
|
871
|
+
duration: actionDuration,
|
|
872
|
+
metadata: { action: action || "api_requests", requestId },
|
|
444
873
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
445
874
|
}),
|
|
446
875
|
{
|
|
447
876
|
maxRetries: 2,
|
|
448
877
|
initialDelay: 500,
|
|
449
878
|
shouldRetry: (error) => error.message.includes("Customer not found"),
|
|
450
|
-
|
|
451
|
-
onRetry: (error, attempt) => {
|
|
879
|
+
onRetry: (_error, attempt) => {
|
|
452
880
|
console.warn(`\u26A0\uFE0F Customer not found (attempt ${attempt + 1}/3), retrying in 500ms...`);
|
|
453
881
|
}
|
|
454
882
|
}
|
|
@@ -467,9 +895,6 @@ var AdapterUtils = class {
|
|
|
467
895
|
if (!customerRef || customerRef === "anonymous") {
|
|
468
896
|
return "anonymous";
|
|
469
897
|
}
|
|
470
|
-
if (!customerRef.startsWith("customer_") && !customerRef.startsWith("demo_")) {
|
|
471
|
-
return `customer_${customerRef.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
472
|
-
}
|
|
473
898
|
return customerRef;
|
|
474
899
|
}
|
|
475
900
|
/**
|
|
@@ -477,7 +902,7 @@ var AdapterUtils = class {
|
|
|
477
902
|
*/
|
|
478
903
|
static async extractFromJWT(token, options) {
|
|
479
904
|
try {
|
|
480
|
-
const { jwtVerify } = await import("./esm-
|
|
905
|
+
const { jwtVerify } = await import("./esm-UW7WCMEK.js");
|
|
481
906
|
const jwtSecret = new TextEncoder().encode(
|
|
482
907
|
options?.secret || process.env.OAUTH_JWKS_SECRET || "test-jwt-secret"
|
|
483
908
|
);
|
|
@@ -486,19 +911,25 @@ var AdapterUtils = class {
|
|
|
486
911
|
audience: options?.audience || process.env.OAUTH_CLIENT_ID || "test-client-id"
|
|
487
912
|
});
|
|
488
913
|
return payload.sub || null;
|
|
489
|
-
} catch
|
|
914
|
+
} catch {
|
|
490
915
|
return null;
|
|
491
916
|
}
|
|
492
917
|
}
|
|
493
918
|
};
|
|
494
919
|
async function createAdapterHandler(adapter, paywall, metadata, businessLogic) {
|
|
920
|
+
const backendRefCache = /* @__PURE__ */ new Map();
|
|
921
|
+
const getCustomerRef = (args) => args.auth?.customer_ref || "anonymous";
|
|
922
|
+
const protectedHandler = await paywall.protect(businessLogic, metadata, getCustomerRef);
|
|
495
923
|
return async (context) => {
|
|
496
924
|
try {
|
|
497
925
|
const args = await adapter.extractArgs(context);
|
|
498
926
|
const customerRef = await adapter.getCustomerRef(context);
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
927
|
+
let backendRef = backendRefCache.get(customerRef);
|
|
928
|
+
if (!backendRef) {
|
|
929
|
+
backendRef = await paywall.ensureCustomer(customerRef, customerRef);
|
|
930
|
+
backendRefCache.set(customerRef, backendRef);
|
|
931
|
+
}
|
|
932
|
+
args.auth = { customer_ref: backendRef };
|
|
502
933
|
const result = await protectedHandler(args);
|
|
503
934
|
return adapter.formatResponse(result, context);
|
|
504
935
|
} catch (error) {
|
|
@@ -556,7 +987,7 @@ var HttpAdapter = class {
|
|
|
556
987
|
const errorResponse2 = {
|
|
557
988
|
success: false,
|
|
558
989
|
error: "Payment required",
|
|
559
|
-
|
|
990
|
+
product: error.structuredContent.product,
|
|
560
991
|
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
561
992
|
message: error.structuredContent.message
|
|
562
993
|
};
|
|
@@ -600,7 +1031,7 @@ var NextAdapter = class {
|
|
|
600
1031
|
if (request.method !== "GET" && request.headers.get("content-type")?.includes("application/json")) {
|
|
601
1032
|
body = await request.json();
|
|
602
1033
|
}
|
|
603
|
-
} catch
|
|
1034
|
+
} catch {
|
|
604
1035
|
}
|
|
605
1036
|
let routeParams = {};
|
|
606
1037
|
if (context?.params) {
|
|
@@ -629,6 +1060,10 @@ var NextAdapter = class {
|
|
|
629
1060
|
return AdapterUtils.ensureCustomerRef(jwtSub);
|
|
630
1061
|
}
|
|
631
1062
|
}
|
|
1063
|
+
const userId = request.headers.get("x-user-id");
|
|
1064
|
+
if (userId) {
|
|
1065
|
+
return AdapterUtils.ensureCustomerRef(userId);
|
|
1066
|
+
}
|
|
632
1067
|
const headerRef = request.headers.get("x-customer-ref");
|
|
633
1068
|
if (headerRef) {
|
|
634
1069
|
return AdapterUtils.ensureCustomerRef(headerRef);
|
|
@@ -644,24 +1079,30 @@ var NextAdapter = class {
|
|
|
644
1079
|
}
|
|
645
1080
|
formatError(error, _context) {
|
|
646
1081
|
if (error instanceof PaywallError) {
|
|
647
|
-
return new Response(
|
|
1082
|
+
return new Response(
|
|
1083
|
+
JSON.stringify({
|
|
1084
|
+
success: false,
|
|
1085
|
+
error: "Payment required",
|
|
1086
|
+
product: error.structuredContent.product,
|
|
1087
|
+
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
1088
|
+
message: error.structuredContent.message
|
|
1089
|
+
}),
|
|
1090
|
+
{
|
|
1091
|
+
status: 402,
|
|
1092
|
+
headers: { "Content-Type": "application/json" }
|
|
1093
|
+
}
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
return new Response(
|
|
1097
|
+
JSON.stringify({
|
|
648
1098
|
success: false,
|
|
649
|
-
error: "
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}), {
|
|
654
|
-
status: 402,
|
|
1099
|
+
error: error instanceof Error ? error.message : "Internal server error"
|
|
1100
|
+
}),
|
|
1101
|
+
{
|
|
1102
|
+
status: 500,
|
|
655
1103
|
headers: { "Content-Type": "application/json" }
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
return new Response(JSON.stringify({
|
|
659
|
-
success: false,
|
|
660
|
-
error: error instanceof Error ? error.message : "Internal server error"
|
|
661
|
-
}), {
|
|
662
|
-
status: 500,
|
|
663
|
-
headers: { "Content-Type": "application/json" }
|
|
664
|
-
});
|
|
1104
|
+
}
|
|
1105
|
+
);
|
|
665
1106
|
}
|
|
666
1107
|
};
|
|
667
1108
|
|
|
@@ -678,64 +1119,225 @@ var McpAdapter = class {
|
|
|
678
1119
|
const ref = await this.options.getCustomerRef(args);
|
|
679
1120
|
return AdapterUtils.ensureCustomerRef(ref);
|
|
680
1121
|
}
|
|
681
|
-
const
|
|
1122
|
+
const auth = args?.auth;
|
|
1123
|
+
const customerRef = auth?.customer_ref || "anonymous";
|
|
682
1124
|
return AdapterUtils.ensureCustomerRef(customerRef);
|
|
683
1125
|
}
|
|
684
1126
|
formatResponse(result, _context) {
|
|
685
1127
|
const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
|
|
686
1128
|
return {
|
|
687
|
-
content: [
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
1129
|
+
content: [
|
|
1130
|
+
{
|
|
1131
|
+
type: "text",
|
|
1132
|
+
text: JSON.stringify(transformed, null, 2)
|
|
1133
|
+
}
|
|
1134
|
+
]
|
|
691
1135
|
};
|
|
692
1136
|
}
|
|
693
1137
|
formatError(error, _context) {
|
|
694
1138
|
if (error instanceof PaywallError) {
|
|
695
1139
|
return {
|
|
696
|
-
content: [
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1140
|
+
content: [
|
|
1141
|
+
{
|
|
1142
|
+
type: "text",
|
|
1143
|
+
text: JSON.stringify(
|
|
1144
|
+
{
|
|
1145
|
+
success: false,
|
|
1146
|
+
error: "Payment required",
|
|
1147
|
+
product: error.structuredContent.product,
|
|
1148
|
+
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
1149
|
+
message: error.structuredContent.message
|
|
1150
|
+
},
|
|
1151
|
+
null,
|
|
1152
|
+
2
|
|
1153
|
+
)
|
|
1154
|
+
}
|
|
1155
|
+
],
|
|
706
1156
|
isError: true,
|
|
707
1157
|
structuredContent: error.structuredContent
|
|
708
1158
|
};
|
|
709
1159
|
}
|
|
710
1160
|
return {
|
|
711
|
-
content: [
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1161
|
+
content: [
|
|
1162
|
+
{
|
|
1163
|
+
type: "text",
|
|
1164
|
+
text: JSON.stringify(
|
|
1165
|
+
{
|
|
1166
|
+
success: false,
|
|
1167
|
+
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
1168
|
+
},
|
|
1169
|
+
null,
|
|
1170
|
+
2
|
|
1171
|
+
)
|
|
1172
|
+
}
|
|
1173
|
+
],
|
|
718
1174
|
isError: true
|
|
719
1175
|
};
|
|
720
1176
|
}
|
|
721
1177
|
};
|
|
722
1178
|
|
|
723
1179
|
// src/factory.ts
|
|
724
|
-
import { SolvaPayError as SolvaPayError2 } from "@solvapay/core";
|
|
1180
|
+
import { SolvaPayError as SolvaPayError2, getSolvaPayConfig } from "@solvapay/core";
|
|
1181
|
+
|
|
1182
|
+
// src/virtual-tools.ts
|
|
1183
|
+
var TOOL_GET_USER_INFO = {
|
|
1184
|
+
name: "get_user_info",
|
|
1185
|
+
description: "Get information about the current user and their purchase status for this MCP server. Returns user profile (reference, name, email) and active purchase details including product name, type, dates, and usage limit if applicable.",
|
|
1186
|
+
inputSchema: {
|
|
1187
|
+
type: "object",
|
|
1188
|
+
properties: {},
|
|
1189
|
+
required: []
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
var TOOL_UPGRADE = {
|
|
1193
|
+
name: "upgrade",
|
|
1194
|
+
description: "Get available pricing options and checkout URLs for upgrading. Returns a list of available pricing options with their details (price, features) and checkout URLs. Users can click on a checkout URL to purchase. If a specific planRef is provided, returns only the checkout URL for that pricing option.",
|
|
1195
|
+
inputSchema: {
|
|
1196
|
+
type: "object",
|
|
1197
|
+
properties: {
|
|
1198
|
+
planRef: {
|
|
1199
|
+
type: "string",
|
|
1200
|
+
description: 'Optional pricing reference (e.g., "pln_abc123") to get a checkout URL for a specific option. If not provided, returns all available pricing options with their checkout URLs.'
|
|
1201
|
+
}
|
|
1202
|
+
},
|
|
1203
|
+
required: []
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
var TOOL_MANAGE_ACCOUNT = {
|
|
1207
|
+
name: "manage_account",
|
|
1208
|
+
description: "Get a URL to the customer portal where users can view and manage their account. The portal shows current account status, billing history, and allows subscription changes. Returns a secure, time-limited URL that the user can click to access their account management page.",
|
|
1209
|
+
inputSchema: {
|
|
1210
|
+
type: "object",
|
|
1211
|
+
properties: {},
|
|
1212
|
+
required: []
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
var VIRTUAL_TOOL_DEFINITIONS = [TOOL_GET_USER_INFO, TOOL_UPGRADE, TOOL_MANAGE_ACCOUNT];
|
|
1216
|
+
function mcpTextResult(text) {
|
|
1217
|
+
return { content: [{ type: "text", text }] };
|
|
1218
|
+
}
|
|
1219
|
+
function mcpErrorResult(message) {
|
|
1220
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
1221
|
+
}
|
|
1222
|
+
function createGetUserInfoHandler(apiClient, productRef, getCustomerRef) {
|
|
1223
|
+
return async (args) => {
|
|
1224
|
+
const customerRef = getCustomerRef(args);
|
|
1225
|
+
try {
|
|
1226
|
+
if (!apiClient.getUserInfo) {
|
|
1227
|
+
return mcpErrorResult("getUserInfo is not available on this API client");
|
|
1228
|
+
}
|
|
1229
|
+
const userInfo = await apiClient.getUserInfo({ customerRef, productRef });
|
|
1230
|
+
return mcpTextResult(JSON.stringify(userInfo, null, 2));
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
return mcpErrorResult(
|
|
1233
|
+
`Failed to retrieve user information: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
function createUpgradeHandler(apiClient, productRef, getCustomerRef) {
|
|
1239
|
+
return async (args) => {
|
|
1240
|
+
const customerRef = getCustomerRef(args);
|
|
1241
|
+
const planRef = typeof args.planRef === "string" ? args.planRef : void 0;
|
|
1242
|
+
try {
|
|
1243
|
+
const result = await apiClient.createCheckoutSession({
|
|
1244
|
+
customerReference: customerRef,
|
|
1245
|
+
productRef,
|
|
1246
|
+
...planRef && { planRef }
|
|
1247
|
+
});
|
|
1248
|
+
const checkoutUrl = result.checkoutUrl;
|
|
1249
|
+
if (planRef) {
|
|
1250
|
+
const responseText2 = `## Upgrade
|
|
1251
|
+
|
|
1252
|
+
**[Click here to upgrade \u2192](${checkoutUrl})**
|
|
1253
|
+
|
|
1254
|
+
After completing the checkout, your purchase will be activated immediately.`;
|
|
1255
|
+
return mcpTextResult(responseText2);
|
|
1256
|
+
}
|
|
1257
|
+
const responseText = `## Upgrade Your Subscription
|
|
1258
|
+
|
|
1259
|
+
**[Click here to view pricing options and upgrade \u2192](${checkoutUrl})**
|
|
1260
|
+
|
|
1261
|
+
You'll be able to compare options and select the one that's right for you.`;
|
|
1262
|
+
return mcpTextResult(responseText);
|
|
1263
|
+
} catch (error) {
|
|
1264
|
+
return mcpErrorResult(
|
|
1265
|
+
`Failed to create checkout session: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
function createManageAccountHandler(apiClient, productRef, getCustomerRef) {
|
|
1271
|
+
return async (args) => {
|
|
1272
|
+
const customerRef = getCustomerRef(args);
|
|
1273
|
+
try {
|
|
1274
|
+
const session = await apiClient.createCustomerSession({ customerRef, productRef });
|
|
1275
|
+
const portalUrl = session.customerUrl;
|
|
1276
|
+
const responseText = `## Manage Your Account
|
|
1277
|
+
|
|
1278
|
+
Access your account management portal to:
|
|
1279
|
+
- View your current account status
|
|
1280
|
+
- See billing history and invoices
|
|
1281
|
+
- Update payment methods
|
|
1282
|
+
- Cancel or modify your subscription
|
|
1283
|
+
|
|
1284
|
+
**[Open Account Portal \u2192](${portalUrl})**
|
|
1285
|
+
|
|
1286
|
+
This link is secure and will expire after a short period.`;
|
|
1287
|
+
return mcpTextResult(responseText);
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
return mcpErrorResult(
|
|
1290
|
+
`Failed to create customer portal session: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
function createVirtualTools(apiClient, options) {
|
|
1296
|
+
const { product, getCustomerRef, exclude = [] } = options;
|
|
1297
|
+
const excludeSet = new Set(exclude);
|
|
1298
|
+
const allTools = [
|
|
1299
|
+
{
|
|
1300
|
+
...TOOL_GET_USER_INFO,
|
|
1301
|
+
handler: createGetUserInfoHandler(apiClient, product, getCustomerRef)
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
...TOOL_UPGRADE,
|
|
1305
|
+
handler: createUpgradeHandler(apiClient, product, getCustomerRef)
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
...TOOL_MANAGE_ACCOUNT,
|
|
1309
|
+
handler: createManageAccountHandler(apiClient, product, getCustomerRef)
|
|
1310
|
+
}
|
|
1311
|
+
];
|
|
1312
|
+
return allTools.filter((t) => !excludeSet.has(t.name));
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// src/factory.ts
|
|
725
1316
|
function createSolvaPay(config) {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1317
|
+
let resolvedConfig;
|
|
1318
|
+
if (!config) {
|
|
1319
|
+
const envConfig = getSolvaPayConfig();
|
|
1320
|
+
resolvedConfig = {
|
|
1321
|
+
apiKey: envConfig.apiKey,
|
|
1322
|
+
apiBaseUrl: envConfig.apiBaseUrl
|
|
1323
|
+
};
|
|
1324
|
+
} else {
|
|
1325
|
+
resolvedConfig = config;
|
|
1326
|
+
}
|
|
1327
|
+
const apiClient = resolvedConfig.apiClient || createSolvaPayClient({
|
|
1328
|
+
apiKey: resolvedConfig.apiKey,
|
|
1329
|
+
apiBaseUrl: resolvedConfig.apiBaseUrl
|
|
729
1330
|
});
|
|
730
1331
|
const paywall = new SolvaPayPaywall(apiClient, {
|
|
731
|
-
debug: process.env.SOLVAPAY_DEBUG !== "false"
|
|
1332
|
+
debug: process.env.SOLVAPAY_DEBUG !== "false",
|
|
1333
|
+
limitsCacheTTL: resolvedConfig.limitsCacheTTL
|
|
732
1334
|
});
|
|
733
1335
|
return {
|
|
734
1336
|
// Direct access to API client for advanced operations
|
|
735
1337
|
apiClient,
|
|
736
1338
|
// Common API methods exposed directly for convenience
|
|
737
|
-
ensureCustomer(customerRef) {
|
|
738
|
-
return paywall.ensureCustomer(customerRef);
|
|
1339
|
+
ensureCustomer(customerRef, externalRef, options) {
|
|
1340
|
+
return paywall.ensureCustomer(customerRef, externalRef, options);
|
|
739
1341
|
},
|
|
740
1342
|
createPaymentIntent(params) {
|
|
741
1343
|
if (!apiClient.createPaymentIntent) {
|
|
@@ -743,6 +1345,12 @@ function createSolvaPay(config) {
|
|
|
743
1345
|
}
|
|
744
1346
|
return apiClient.createPaymentIntent(params);
|
|
745
1347
|
},
|
|
1348
|
+
processPaymentIntent(params) {
|
|
1349
|
+
if (!apiClient.processPaymentIntent) {
|
|
1350
|
+
throw new SolvaPayError2("processPaymentIntent is not available on this API client");
|
|
1351
|
+
}
|
|
1352
|
+
return apiClient.processPaymentIntent(params);
|
|
1353
|
+
},
|
|
746
1354
|
checkLimits(params) {
|
|
747
1355
|
return apiClient.checkLimits(params);
|
|
748
1356
|
},
|
|
@@ -756,85 +1364,481 @@ function createSolvaPay(config) {
|
|
|
756
1364
|
return apiClient.createCustomer(params);
|
|
757
1365
|
},
|
|
758
1366
|
getCustomer(params) {
|
|
759
|
-
if (!apiClient.getCustomer) {
|
|
760
|
-
throw new SolvaPayError2("getCustomer is not available on this API client");
|
|
761
|
-
}
|
|
762
1367
|
return apiClient.getCustomer(params);
|
|
763
1368
|
},
|
|
1369
|
+
createCheckoutSession(params) {
|
|
1370
|
+
return apiClient.createCheckoutSession({
|
|
1371
|
+
customerReference: params.customerRef,
|
|
1372
|
+
productRef: params.productRef,
|
|
1373
|
+
planRef: params.planRef
|
|
1374
|
+
});
|
|
1375
|
+
},
|
|
1376
|
+
createCustomerSession(params) {
|
|
1377
|
+
return apiClient.createCustomerSession(params);
|
|
1378
|
+
},
|
|
1379
|
+
bootstrapMcpProduct(params) {
|
|
1380
|
+
if (!apiClient.bootstrapMcpProduct) {
|
|
1381
|
+
throw new SolvaPayError2("bootstrapMcpProduct is not available on this API client");
|
|
1382
|
+
}
|
|
1383
|
+
return apiClient.bootstrapMcpProduct(params);
|
|
1384
|
+
},
|
|
1385
|
+
getVirtualTools(options) {
|
|
1386
|
+
return createVirtualTools(apiClient, options);
|
|
1387
|
+
},
|
|
764
1388
|
// Payable API for framework-specific handlers
|
|
765
1389
|
payable(options = {}) {
|
|
766
|
-
const
|
|
767
|
-
const plan = options.planRef || options.plan
|
|
768
|
-
const
|
|
1390
|
+
const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
|
|
1391
|
+
const plan = options.planRef || options.plan;
|
|
1392
|
+
const usageType = options.usageType || "requests";
|
|
1393
|
+
const metadata = { product, plan, usageType };
|
|
769
1394
|
return {
|
|
770
|
-
//
|
|
1395
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
771
1396
|
http(businessLogic, adapterOptions) {
|
|
772
|
-
const adapter = new HttpAdapter(
|
|
1397
|
+
const adapter = new HttpAdapter({
|
|
1398
|
+
...adapterOptions,
|
|
1399
|
+
getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
|
|
1400
|
+
});
|
|
1401
|
+
const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
|
|
773
1402
|
return async (req, reply) => {
|
|
774
|
-
const handler = await
|
|
775
|
-
adapter,
|
|
776
|
-
paywall,
|
|
777
|
-
metadata,
|
|
778
|
-
businessLogic
|
|
779
|
-
);
|
|
1403
|
+
const handler = await handlerPromise;
|
|
780
1404
|
return handler([req, reply]);
|
|
781
1405
|
};
|
|
782
1406
|
},
|
|
783
|
-
//
|
|
1407
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
784
1408
|
next(businessLogic, adapterOptions) {
|
|
785
|
-
const adapter = new NextAdapter(
|
|
1409
|
+
const adapter = new NextAdapter({
|
|
1410
|
+
...adapterOptions,
|
|
1411
|
+
getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
|
|
1412
|
+
});
|
|
1413
|
+
const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
|
|
786
1414
|
return async (request, context) => {
|
|
787
|
-
const handler = await
|
|
788
|
-
adapter,
|
|
789
|
-
paywall,
|
|
790
|
-
metadata,
|
|
791
|
-
businessLogic
|
|
792
|
-
);
|
|
1415
|
+
const handler = await handlerPromise;
|
|
793
1416
|
return handler([request, context]);
|
|
794
1417
|
};
|
|
795
1418
|
},
|
|
796
|
-
//
|
|
1419
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
797
1420
|
mcp(businessLogic, adapterOptions) {
|
|
798
|
-
const adapter = new McpAdapter(
|
|
1421
|
+
const adapter = new McpAdapter({
|
|
1422
|
+
...adapterOptions,
|
|
1423
|
+
getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
|
|
1424
|
+
});
|
|
1425
|
+
const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
|
|
799
1426
|
return async (args) => {
|
|
800
|
-
const handler = await
|
|
801
|
-
adapter,
|
|
802
|
-
paywall,
|
|
803
|
-
metadata,
|
|
804
|
-
businessLogic
|
|
805
|
-
);
|
|
1427
|
+
const handler = await handlerPromise;
|
|
806
1428
|
return handler(args);
|
|
807
1429
|
};
|
|
808
1430
|
},
|
|
809
|
-
//
|
|
1431
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
810
1432
|
async function(businessLogic) {
|
|
811
|
-
const getCustomerRef = (args) =>
|
|
1433
|
+
const getCustomerRef = (args) => {
|
|
1434
|
+
const configuredRef = options.getCustomerRef?.(args);
|
|
1435
|
+
if (typeof configuredRef === "string") {
|
|
1436
|
+
return configuredRef;
|
|
1437
|
+
}
|
|
1438
|
+
return args.auth?.customer_ref || "anonymous";
|
|
1439
|
+
};
|
|
812
1440
|
return paywall.protect(businessLogic, metadata, getCustomerRef);
|
|
813
1441
|
}
|
|
814
1442
|
};
|
|
815
1443
|
}
|
|
816
1444
|
};
|
|
817
1445
|
}
|
|
818
|
-
|
|
1446
|
+
|
|
1447
|
+
// src/mcp-auth.ts
|
|
1448
|
+
var McpBearerAuthError = class extends Error {
|
|
1449
|
+
constructor(message) {
|
|
1450
|
+
super(message);
|
|
1451
|
+
this.name = "McpBearerAuthError";
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
function base64UrlDecode(input) {
|
|
1455
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
1456
|
+
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
1457
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
1458
|
+
}
|
|
1459
|
+
function extractBearerToken(authorization) {
|
|
1460
|
+
if (!authorization) return null;
|
|
1461
|
+
if (!authorization.startsWith("Bearer ")) return null;
|
|
1462
|
+
return authorization.slice(7).trim() || null;
|
|
1463
|
+
}
|
|
1464
|
+
function decodeJwtPayload(token) {
|
|
1465
|
+
const parts = token.split(".");
|
|
1466
|
+
if (parts.length < 2) {
|
|
1467
|
+
throw new McpBearerAuthError("Invalid JWT format");
|
|
1468
|
+
}
|
|
819
1469
|
try {
|
|
820
|
-
const
|
|
821
|
-
|
|
1470
|
+
const payloadText = base64UrlDecode(parts[1]);
|
|
1471
|
+
const payload = JSON.parse(payloadText);
|
|
1472
|
+
return payload;
|
|
822
1473
|
} catch {
|
|
823
|
-
|
|
1474
|
+
throw new McpBearerAuthError("Invalid JWT payload");
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function getCustomerRefFromJwtPayload(payload, options = {}) {
|
|
1478
|
+
const claimPriority = options.claimPriority || ["customerRef", "customer_ref", "sub"];
|
|
1479
|
+
for (const claim of claimPriority) {
|
|
1480
|
+
const value = payload[claim];
|
|
1481
|
+
if (typeof value === "string" && value.trim()) {
|
|
1482
|
+
return value.trim();
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
throw new McpBearerAuthError(
|
|
1486
|
+
`No customer reference claim found (checked: ${claimPriority.join(", ")})`
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
function getCustomerRefFromBearerAuthHeader(authorization, options = {}) {
|
|
1490
|
+
const token = extractBearerToken(authorization);
|
|
1491
|
+
if (!token) {
|
|
1492
|
+
throw new McpBearerAuthError("Missing bearer token");
|
|
1493
|
+
}
|
|
1494
|
+
const payload = decodeJwtPayload(token);
|
|
1495
|
+
return getCustomerRefFromJwtPayload(payload, options);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// src/helpers/error.ts
|
|
1499
|
+
import { SolvaPayError as SolvaPayError3 } from "@solvapay/core";
|
|
1500
|
+
function isErrorResult(result) {
|
|
1501
|
+
return typeof result === "object" && result !== null && "error" in result && "status" in result;
|
|
1502
|
+
}
|
|
1503
|
+
function handleRouteError(error, operationName, defaultMessage) {
|
|
1504
|
+
console.error(`[${operationName}] Error:`, error);
|
|
1505
|
+
if (error instanceof SolvaPayError3) {
|
|
1506
|
+
const errorMessage2 = error.message;
|
|
1507
|
+
return {
|
|
1508
|
+
error: errorMessage2,
|
|
1509
|
+
status: 500,
|
|
1510
|
+
details: errorMessage2
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1514
|
+
const message = defaultMessage || `${operationName} failed`;
|
|
1515
|
+
return {
|
|
1516
|
+
error: message,
|
|
1517
|
+
status: 500,
|
|
1518
|
+
details: errorMessage
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// src/helpers/auth.ts
|
|
1523
|
+
async function getAuthenticatedUserCore(request, options = {}) {
|
|
1524
|
+
try {
|
|
1525
|
+
const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
|
|
1526
|
+
const userIdOrError = requireUserId(request);
|
|
1527
|
+
if (userIdOrError instanceof Response) {
|
|
1528
|
+
const clonedResponse = userIdOrError.clone();
|
|
1529
|
+
const body = await clonedResponse.json().catch(() => ({ error: "Unauthorized" }));
|
|
1530
|
+
return {
|
|
1531
|
+
error: body.error || "Unauthorized",
|
|
1532
|
+
status: userIdOrError.status,
|
|
1533
|
+
details: body.error || "Unauthorized"
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
const userId = userIdOrError;
|
|
1537
|
+
const email = options.includeEmail !== false ? await getUserEmailFromRequest(request) : null;
|
|
1538
|
+
const name = options.includeName !== false ? await getUserNameFromRequest(request) : null;
|
|
1539
|
+
return {
|
|
1540
|
+
userId,
|
|
1541
|
+
email,
|
|
1542
|
+
name
|
|
1543
|
+
};
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
return handleRouteError(error, "Get authenticated user", "Authentication failed");
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// src/helpers/customer.ts
|
|
1550
|
+
async function syncCustomerCore(request, options = {}) {
|
|
1551
|
+
try {
|
|
1552
|
+
const userResult = await getAuthenticatedUserCore(request, {
|
|
1553
|
+
includeEmail: options.includeEmail,
|
|
1554
|
+
includeName: options.includeName
|
|
1555
|
+
});
|
|
1556
|
+
if (isErrorResult(userResult)) {
|
|
1557
|
+
return userResult;
|
|
1558
|
+
}
|
|
1559
|
+
const { userId, email, name } = userResult;
|
|
1560
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1561
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
1562
|
+
email: email || void 0,
|
|
1563
|
+
name: name || void 0
|
|
1564
|
+
});
|
|
1565
|
+
return customerRef;
|
|
1566
|
+
} catch (error) {
|
|
1567
|
+
return handleRouteError(error, "Sync customer", "Failed to sync customer");
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// src/helpers/payment.ts
|
|
1572
|
+
async function createPaymentIntentCore(request, body, options = {}) {
|
|
1573
|
+
try {
|
|
1574
|
+
if (!body.planRef || !body.productRef) {
|
|
1575
|
+
return {
|
|
1576
|
+
error: "Missing required parameters: planRef and productRef are required",
|
|
1577
|
+
status: 400
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1581
|
+
solvaPay: options.solvaPay,
|
|
1582
|
+
includeEmail: options.includeEmail,
|
|
1583
|
+
includeName: options.includeName
|
|
1584
|
+
});
|
|
1585
|
+
if (isErrorResult(customerResult)) {
|
|
1586
|
+
return customerResult;
|
|
1587
|
+
}
|
|
1588
|
+
const customerRef = customerResult;
|
|
1589
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1590
|
+
const paymentIntent = await solvaPay.createPaymentIntent({
|
|
1591
|
+
productRef: body.productRef,
|
|
1592
|
+
planRef: body.planRef,
|
|
1593
|
+
customerRef
|
|
1594
|
+
});
|
|
1595
|
+
return {
|
|
1596
|
+
id: paymentIntent.id,
|
|
1597
|
+
clientSecret: paymentIntent.clientSecret,
|
|
1598
|
+
publishableKey: paymentIntent.publishableKey,
|
|
1599
|
+
accountId: paymentIntent.accountId,
|
|
1600
|
+
customerRef
|
|
1601
|
+
};
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
async function processPaymentIntentCore(request, body, options = {}) {
|
|
1607
|
+
try {
|
|
1608
|
+
if (!body.paymentIntentId || !body.productRef) {
|
|
1609
|
+
return {
|
|
1610
|
+
error: "paymentIntentId and productRef are required",
|
|
1611
|
+
status: 400
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1615
|
+
solvaPay: options.solvaPay
|
|
1616
|
+
});
|
|
1617
|
+
if (isErrorResult(customerResult)) {
|
|
1618
|
+
return customerResult;
|
|
1619
|
+
}
|
|
1620
|
+
const customerRef = customerResult;
|
|
1621
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1622
|
+
const result = await solvaPay.processPaymentIntent({
|
|
1623
|
+
paymentIntentId: body.paymentIntentId,
|
|
1624
|
+
productRef: body.productRef,
|
|
1625
|
+
customerRef,
|
|
1626
|
+
planRef: body.planRef
|
|
1627
|
+
});
|
|
1628
|
+
return result;
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
return handleRouteError(error, "Process payment intent", "Payment processing failed");
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
// src/helpers/checkout.ts
|
|
1635
|
+
async function createCheckoutSessionCore(request, body, options = {}) {
|
|
1636
|
+
try {
|
|
1637
|
+
if (!body.productRef) {
|
|
1638
|
+
return {
|
|
1639
|
+
error: "Missing required parameter: productRef is required",
|
|
1640
|
+
status: 400
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1644
|
+
solvaPay: options.solvaPay,
|
|
1645
|
+
includeEmail: options.includeEmail,
|
|
1646
|
+
includeName: options.includeName
|
|
1647
|
+
});
|
|
1648
|
+
if (isErrorResult(customerResult)) {
|
|
1649
|
+
return customerResult;
|
|
1650
|
+
}
|
|
1651
|
+
const customerRef = customerResult;
|
|
1652
|
+
let returnUrl = body.returnUrl || options.returnUrl;
|
|
1653
|
+
if (!returnUrl) {
|
|
1654
|
+
try {
|
|
1655
|
+
const url = new URL(request.url);
|
|
1656
|
+
returnUrl = url.origin;
|
|
1657
|
+
} catch {
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1661
|
+
const session = await solvaPay.createCheckoutSession({
|
|
1662
|
+
productRef: body.productRef,
|
|
1663
|
+
customerRef,
|
|
1664
|
+
planRef: body.planRef || void 0,
|
|
1665
|
+
returnUrl
|
|
1666
|
+
});
|
|
1667
|
+
return {
|
|
1668
|
+
sessionId: session.sessionId,
|
|
1669
|
+
checkoutUrl: session.checkoutUrl
|
|
1670
|
+
};
|
|
1671
|
+
} catch (error) {
|
|
1672
|
+
return handleRouteError(error, "Create checkout session", "Checkout session creation failed");
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
async function createCustomerSessionCore(request, options = {}) {
|
|
1676
|
+
try {
|
|
1677
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1678
|
+
solvaPay: options.solvaPay,
|
|
1679
|
+
includeEmail: options.includeEmail,
|
|
1680
|
+
includeName: options.includeName
|
|
1681
|
+
});
|
|
1682
|
+
if (isErrorResult(customerResult)) {
|
|
1683
|
+
return customerResult;
|
|
1684
|
+
}
|
|
1685
|
+
const customerRef = customerResult;
|
|
1686
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1687
|
+
const session = await solvaPay.createCustomerSession({
|
|
1688
|
+
customerRef
|
|
1689
|
+
});
|
|
1690
|
+
return session;
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
return handleRouteError(error, "Create customer session", "Customer session creation failed");
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/helpers/renewal.ts
|
|
1697
|
+
import { SolvaPayError as SolvaPayError4 } from "@solvapay/core";
|
|
1698
|
+
async function cancelPurchaseCore(request, body, options = {}) {
|
|
1699
|
+
try {
|
|
1700
|
+
if (!body.purchaseRef) {
|
|
1701
|
+
return {
|
|
1702
|
+
error: "Missing required parameter: purchaseRef is required",
|
|
1703
|
+
status: 400
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1707
|
+
if (!solvaPay.apiClient.cancelPurchase) {
|
|
1708
|
+
return {
|
|
1709
|
+
error: "Cancel purchase method not available on SDK client",
|
|
1710
|
+
status: 500
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
let cancelledPurchase = await solvaPay.apiClient.cancelPurchase({
|
|
1714
|
+
purchaseRef: body.purchaseRef,
|
|
1715
|
+
reason: body.reason
|
|
1716
|
+
});
|
|
1717
|
+
if (!cancelledPurchase || typeof cancelledPurchase !== "object") {
|
|
1718
|
+
return {
|
|
1719
|
+
error: "Invalid response from cancel purchase endpoint",
|
|
1720
|
+
status: 500
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
const responseObj = cancelledPurchase;
|
|
1724
|
+
if (responseObj.purchase && typeof responseObj.purchase === "object") {
|
|
1725
|
+
cancelledPurchase = responseObj.purchase;
|
|
1726
|
+
}
|
|
1727
|
+
if (!cancelledPurchase.reference) {
|
|
1728
|
+
return {
|
|
1729
|
+
error: "Cancel purchase response missing required fields",
|
|
1730
|
+
status: 500
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
const isCancelled = cancelledPurchase.status === "cancelled" || cancelledPurchase.cancelledAt;
|
|
1734
|
+
if (!isCancelled) {
|
|
1735
|
+
return {
|
|
1736
|
+
error: `Purchase cancellation failed: backend returned status '${cancelledPurchase.status}' without cancelledAt timestamp`,
|
|
1737
|
+
status: 500
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1741
|
+
return cancelledPurchase;
|
|
1742
|
+
} catch (error) {
|
|
1743
|
+
if (error instanceof SolvaPayError4) {
|
|
1744
|
+
const errorMessage = error.message;
|
|
1745
|
+
if (errorMessage.includes("not found")) {
|
|
1746
|
+
return {
|
|
1747
|
+
error: "Purchase not found",
|
|
1748
|
+
status: 404,
|
|
1749
|
+
details: errorMessage
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
if (errorMessage.includes("cannot be cancelled") || errorMessage.includes("does not belong to provider")) {
|
|
1753
|
+
return {
|
|
1754
|
+
error: "Purchase cannot be cancelled or does not belong to provider",
|
|
1755
|
+
status: 400,
|
|
1756
|
+
details: errorMessage
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
return {
|
|
1760
|
+
error: errorMessage,
|
|
1761
|
+
status: 500,
|
|
1762
|
+
details: errorMessage
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/helpers/plans.ts
|
|
1770
|
+
import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
|
|
1771
|
+
async function listPlansCore(request) {
|
|
1772
|
+
try {
|
|
1773
|
+
const url = new URL(request.url);
|
|
1774
|
+
const productRef = url.searchParams.get("productRef");
|
|
1775
|
+
if (!productRef) {
|
|
1776
|
+
return {
|
|
1777
|
+
error: "Missing required parameter: productRef",
|
|
1778
|
+
status: 400
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
const config = getSolvaPayConfig2();
|
|
1782
|
+
const solvapaySecretKey = config.apiKey;
|
|
1783
|
+
const solvapayApiBaseUrl = config.apiBaseUrl;
|
|
1784
|
+
if (!solvapaySecretKey) {
|
|
1785
|
+
return {
|
|
1786
|
+
error: "Server configuration error: SolvaPay secret key not configured",
|
|
1787
|
+
status: 500
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
const apiClient = createSolvaPayClient({
|
|
1791
|
+
apiKey: solvapaySecretKey,
|
|
1792
|
+
apiBaseUrl: solvapayApiBaseUrl
|
|
1793
|
+
});
|
|
1794
|
+
if (!apiClient.listPlans) {
|
|
1795
|
+
return {
|
|
1796
|
+
error: "List plans method not available",
|
|
1797
|
+
status: 500
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
const plans = await apiClient.listPlans(productRef);
|
|
1801
|
+
return {
|
|
1802
|
+
plans: plans || [],
|
|
1803
|
+
productRef
|
|
1804
|
+
};
|
|
1805
|
+
} catch (error) {
|
|
1806
|
+
return handleRouteError(error, "List plans", "Failed to fetch plans");
|
|
824
1807
|
}
|
|
825
1808
|
}
|
|
826
1809
|
|
|
827
1810
|
// src/index.ts
|
|
828
|
-
function verifyWebhook({
|
|
1811
|
+
function verifyWebhook({
|
|
1812
|
+
body,
|
|
1813
|
+
signature,
|
|
1814
|
+
secret
|
|
1815
|
+
}) {
|
|
829
1816
|
const hmac = crypto.createHmac("sha256", secret).update(body).digest("hex");
|
|
830
1817
|
const ok = crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(signature));
|
|
831
|
-
if (!ok) throw new
|
|
1818
|
+
if (!ok) throw new SolvaPayError5("Invalid webhook signature");
|
|
832
1819
|
return JSON.parse(body);
|
|
833
1820
|
}
|
|
834
1821
|
export {
|
|
1822
|
+
McpBearerAuthError,
|
|
835
1823
|
PaywallError,
|
|
1824
|
+
VIRTUAL_TOOL_DEFINITIONS,
|
|
1825
|
+
cancelPurchaseCore,
|
|
1826
|
+
createCheckoutSessionCore,
|
|
1827
|
+
createCustomerSessionCore,
|
|
1828
|
+
createPaymentIntentCore,
|
|
836
1829
|
createSolvaPay,
|
|
837
1830
|
createSolvaPayClient,
|
|
1831
|
+
createVirtualTools,
|
|
1832
|
+
decodeJwtPayload,
|
|
1833
|
+
extractBearerToken,
|
|
1834
|
+
getAuthenticatedUserCore,
|
|
1835
|
+
getCustomerRefFromBearerAuthHeader,
|
|
1836
|
+
getCustomerRefFromJwtPayload,
|
|
1837
|
+
handleRouteError,
|
|
1838
|
+
isErrorResult,
|
|
1839
|
+
listPlansCore,
|
|
1840
|
+
processPaymentIntentCore,
|
|
1841
|
+
syncCustomerCore,
|
|
838
1842
|
verifyWebhook,
|
|
839
1843
|
withRetry
|
|
840
1844
|
};
|