@solvapay/server 1.0.9-preview.1 → 1.0.9

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.
@@ -0,0 +1,3019 @@
1
+ // src/helpers/error.ts
2
+ import { SolvaPayError } from "@solvapay/core";
3
+ function isErrorResult(result) {
4
+ return typeof result === "object" && result !== null && "error" in result && "status" in result;
5
+ }
6
+ function handleRouteError(error, operationName, defaultMessage) {
7
+ console.error(`[${operationName}] Error:`, error);
8
+ if (error instanceof SolvaPayError) {
9
+ const errorMessage2 = error.message;
10
+ return {
11
+ error: errorMessage2,
12
+ status: 500,
13
+ details: errorMessage2
14
+ };
15
+ }
16
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
17
+ const message = defaultMessage || `${operationName} failed`;
18
+ return {
19
+ error: message,
20
+ status: 500,
21
+ details: errorMessage
22
+ };
23
+ }
24
+
25
+ // src/helpers/auth.ts
26
+ function base64UrlDecode(input) {
27
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
28
+ const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
29
+ const base64 = padded + padding;
30
+ if (typeof atob === "function") {
31
+ const binary = atob(base64);
32
+ let result = "";
33
+ for (let i = 0; i < binary.length; i++) {
34
+ result += String.fromCharCode(binary.charCodeAt(i));
35
+ }
36
+ try {
37
+ return decodeURIComponent(escape(result));
38
+ } catch {
39
+ return result;
40
+ }
41
+ }
42
+ const BufferCtor = globalThis.Buffer;
43
+ if (BufferCtor) {
44
+ return BufferCtor.from(base64, "base64").toString("utf-8");
45
+ }
46
+ throw new Error("No base64 decoder available in this runtime");
47
+ }
48
+ function decodeJwtUnverified(token) {
49
+ const parts = token.split(".");
50
+ if (parts.length !== 3) return null;
51
+ try {
52
+ const json = base64UrlDecode(parts[1]);
53
+ const payload = JSON.parse(json);
54
+ if (typeof payload !== "object" || payload === null) return null;
55
+ return payload;
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+ function extractBearerToken(request) {
61
+ const authHeader = request.headers.get("authorization");
62
+ if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
63
+ const token = authHeader.slice(7).trim();
64
+ return token.length > 0 ? token : null;
65
+ }
66
+ function readEnv(name) {
67
+ const proc = globalThis.process;
68
+ return proc?.env?.[name];
69
+ }
70
+ function isStrictMode() {
71
+ return readEnv("SOLVAPAY_AUTH_STRICT") === "true";
72
+ }
73
+ function getConfiguredSecret() {
74
+ return readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
75
+ }
76
+ async function verifyHs256(token, secret) {
77
+ try {
78
+ const { jwtVerify } = await import("jose");
79
+ const key = new TextEncoder().encode(secret);
80
+ const { payload } = await jwtVerify(token, key, { algorithms: ["HS256"] });
81
+ return payload;
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+ function pickName(payload) {
87
+ const metadataFullName = typeof payload.user_metadata?.full_name === "string" ? payload.user_metadata.full_name : null;
88
+ const metadataName = typeof payload.user_metadata?.name === "string" ? payload.user_metadata.name : null;
89
+ const claimName = typeof payload.name === "string" ? payload.name : null;
90
+ return metadataFullName || metadataName || claimName || null;
91
+ }
92
+ function pickEmail(payload) {
93
+ return typeof payload.email === "string" ? payload.email : null;
94
+ }
95
+ function unauthorized(details) {
96
+ return { error: "Unauthorized", status: 401, details };
97
+ }
98
+ async function getAuthenticatedUserCore(request, options = {}) {
99
+ try {
100
+ const includeEmail = options.includeEmail !== false;
101
+ const includeName = options.includeName !== false;
102
+ const headerUserId = request.headers.get("x-user-id");
103
+ if (headerUserId) {
104
+ let email = null;
105
+ let name = null;
106
+ if (includeEmail || includeName) {
107
+ const token2 = extractBearerToken(request);
108
+ if (token2) {
109
+ const secret2 = getConfiguredSecret();
110
+ const payload2 = secret2 ? await verifyHs256(token2, secret2) : isStrictMode() ? null : decodeJwtUnverified(token2);
111
+ if (payload2) {
112
+ if (includeEmail) email = pickEmail(payload2);
113
+ if (includeName) name = pickName(payload2);
114
+ }
115
+ }
116
+ }
117
+ return { userId: headerUserId, email, name };
118
+ }
119
+ const token = extractBearerToken(request);
120
+ if (!token) {
121
+ return unauthorized("User ID not found. Ensure middleware is configured.");
122
+ }
123
+ const secret = getConfiguredSecret();
124
+ let payload = null;
125
+ if (secret) {
126
+ payload = await verifyHs256(token, secret);
127
+ if (!payload) {
128
+ return unauthorized("Invalid or expired authentication token");
129
+ }
130
+ } else if (isStrictMode()) {
131
+ return unauthorized(
132
+ "Strict auth mode is enabled but no JWT secret is configured. Set SOLVAPAY_JWT_SECRET or SUPABASE_JWT_SECRET."
133
+ );
134
+ } else {
135
+ payload = decodeJwtUnverified(token);
136
+ if (!payload) {
137
+ return unauthorized("Malformed authentication token");
138
+ }
139
+ }
140
+ const userId = typeof payload.sub === "string" ? payload.sub : null;
141
+ if (!userId) {
142
+ return unauthorized("Authentication token missing subject (sub) claim");
143
+ }
144
+ return {
145
+ userId,
146
+ email: includeEmail ? pickEmail(payload) : null,
147
+ name: includeName ? pickName(payload) : null
148
+ };
149
+ } catch (error) {
150
+ return handleRouteError(error, "Get authenticated user", "Authentication failed");
151
+ }
152
+ }
153
+
154
+ // src/client.ts
155
+ import { SolvaPayError as SolvaPayError2 } from "@solvapay/core";
156
+ function createSolvaPayClient(opts) {
157
+ const base = opts.apiBaseUrl ?? "https://api.solvapay.com";
158
+ if (!opts.apiKey) throw new SolvaPayError2("Missing apiKey");
159
+ const headers = {
160
+ "Content-Type": "application/json",
161
+ Authorization: `Bearer ${opts.apiKey}`
162
+ };
163
+ const debug = process.env.SOLVAPAY_DEBUG === "true";
164
+ const log = (...args) => {
165
+ if (debug) {
166
+ console.log(...args);
167
+ }
168
+ };
169
+ return {
170
+ // POST: /v1/sdk/limits
171
+ async checkLimits(params) {
172
+ const url = `${base}/v1/sdk/limits`;
173
+ const res = await fetch(url, {
174
+ method: "POST",
175
+ headers,
176
+ body: JSON.stringify(params)
177
+ });
178
+ if (!res.ok) {
179
+ const error = await res.text();
180
+ log(`\u274C API Error: ${res.status} - ${error}`);
181
+ throw new SolvaPayError2(`Check limits failed (${res.status}): ${error}`);
182
+ }
183
+ const result = await res.json();
184
+ return result;
185
+ },
186
+ // POST: /v1/sdk/usages
187
+ async trackUsage(params) {
188
+ const url = `${base}/v1/sdk/usages`;
189
+ const res = await fetch(url, {
190
+ method: "POST",
191
+ headers,
192
+ body: JSON.stringify(params)
193
+ });
194
+ if (!res.ok) {
195
+ const error = await res.text();
196
+ log(`\u274C API Error: ${res.status} - ${error}`);
197
+ throw new SolvaPayError2(`Track usage failed (${res.status}): ${error}`);
198
+ }
199
+ },
200
+ // POST: /v1/sdk/customers
201
+ async createCustomer(params) {
202
+ const url = `${base}/v1/sdk/customers`;
203
+ const res = await fetch(url, {
204
+ method: "POST",
205
+ headers,
206
+ body: JSON.stringify(params)
207
+ });
208
+ if (!res.ok) {
209
+ const error = await res.text();
210
+ log(`\u274C API Error: ${res.status} - ${error}`);
211
+ throw new SolvaPayError2(`Create customer failed (${res.status}): ${error}`);
212
+ }
213
+ const result = await res.json();
214
+ return {
215
+ customerRef: result.reference || result.customerRef
216
+ };
217
+ },
218
+ // PATCH: /v1/sdk/customers/{customerRef}
219
+ async updateCustomer(customerRef, params) {
220
+ const url = `${base}/v1/sdk/customers/${encodeURIComponent(customerRef)}`;
221
+ const res = await fetch(url, {
222
+ method: "PATCH",
223
+ headers,
224
+ body: JSON.stringify(params)
225
+ });
226
+ if (!res.ok) {
227
+ const error = await res.text();
228
+ log(`\u274C API Error: ${res.status} - ${error}`);
229
+ throw new SolvaPayError2(`Update customer failed (${res.status}): ${error}`);
230
+ }
231
+ const result = await res.json();
232
+ return {
233
+ customerRef: result.reference || result.customerRef || customerRef
234
+ };
235
+ },
236
+ // GET: /v1/sdk/customers/{reference} or /v1/sdk/customers?externalRef={externalRef}|email={email}
237
+ async getCustomer(params) {
238
+ let url;
239
+ let isByExternalRef = false;
240
+ let isByEmail = false;
241
+ if (params.externalRef) {
242
+ url = `${base}/v1/sdk/customers?externalRef=${encodeURIComponent(params.externalRef)}`;
243
+ isByExternalRef = true;
244
+ } else if (params.email) {
245
+ url = `${base}/v1/sdk/customers?email=${encodeURIComponent(params.email)}`;
246
+ isByEmail = true;
247
+ } else if (params.customerRef) {
248
+ url = `${base}/v1/sdk/customers/${params.customerRef}`;
249
+ } else {
250
+ throw new SolvaPayError2("One of customerRef, externalRef, or email must be provided");
251
+ }
252
+ const res = await fetch(url, {
253
+ method: "GET",
254
+ headers
255
+ });
256
+ if (!res.ok) {
257
+ const error = await res.text();
258
+ log(`\u274C API Error: ${res.status} - ${error}`);
259
+ throw new SolvaPayError2(`Get customer failed (${res.status}): ${error}`);
260
+ }
261
+ const result = await res.json();
262
+ let customer = result;
263
+ if (isByExternalRef || isByEmail) {
264
+ const directCustomer = result && typeof result === "object" && (result.reference || result.customerRef || result.externalRef) ? result : void 0;
265
+ const wrappedCustomer = result && typeof result === "object" && result.customer ? result.customer : void 0;
266
+ const customers = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.customers) ? result.customers : [];
267
+ customer = directCustomer || wrappedCustomer || customers[0];
268
+ if (!customer) {
269
+ throw new SolvaPayError2(`No customer found with externalRef: ${params.externalRef}`);
270
+ }
271
+ }
272
+ return {
273
+ customerRef: customer.reference || customer.customerRef,
274
+ email: customer.email,
275
+ name: customer.name,
276
+ externalRef: customer.externalRef,
277
+ purchases: customer.purchases || []
278
+ };
279
+ },
280
+ // GET: /v1/sdk/merchant
281
+ async getMerchant() {
282
+ const url = `${base}/v1/sdk/merchant`;
283
+ const res = await fetch(url, {
284
+ method: "GET",
285
+ headers
286
+ });
287
+ if (!res.ok) {
288
+ const error = await res.text();
289
+ log(`\u274C API Error: ${res.status} - ${error}`);
290
+ throw new SolvaPayError2(`Get merchant failed (${res.status}): ${error}`);
291
+ }
292
+ return res.json();
293
+ },
294
+ // GET: /v1/sdk/platform-config
295
+ async getPlatformConfig() {
296
+ const url = `${base}/v1/sdk/platform-config`;
297
+ const res = await fetch(url, {
298
+ method: "GET",
299
+ headers
300
+ });
301
+ if (!res.ok) {
302
+ const error = await res.text();
303
+ log(`\u274C API Error: ${res.status} - ${error}`);
304
+ throw new SolvaPayError2(`Get platform config failed (${res.status}): ${error}`);
305
+ }
306
+ return res.json();
307
+ },
308
+ // GET: /v1/sdk/products/{productRef}
309
+ async getProduct(productRef) {
310
+ const url = `${base}/v1/sdk/products/${encodeURIComponent(productRef)}`;
311
+ const res = await fetch(url, {
312
+ method: "GET",
313
+ headers
314
+ });
315
+ if (!res.ok) {
316
+ const error = await res.text();
317
+ log(`\u274C API Error: ${res.status} - ${error}`);
318
+ throw new SolvaPayError2(`Get product failed (${res.status}): ${error}`);
319
+ }
320
+ const result = await res.json();
321
+ const data = result.data || {};
322
+ return { ...data, ...result };
323
+ },
324
+ // Product management methods (primarily for integration tests)
325
+ // GET: /v1/sdk/products
326
+ async listProducts() {
327
+ const url = `${base}/v1/sdk/products`;
328
+ const res = await fetch(url, {
329
+ method: "GET",
330
+ headers
331
+ });
332
+ if (!res.ok) {
333
+ const error = await res.text();
334
+ log(`\u274C API Error: ${res.status} - ${error}`);
335
+ throw new SolvaPayError2(`List products failed (${res.status}): ${error}`);
336
+ }
337
+ const result = await res.json();
338
+ const products = Array.isArray(result) ? result : result.products || [];
339
+ return products.map((product) => ({
340
+ ...product,
341
+ ...product.data || {}
342
+ }));
343
+ },
344
+ // POST: /v1/sdk/products
345
+ async createProduct(params) {
346
+ const url = `${base}/v1/sdk/products`;
347
+ const res = await fetch(url, {
348
+ method: "POST",
349
+ headers,
350
+ body: JSON.stringify(params)
351
+ });
352
+ if (!res.ok) {
353
+ const error = await res.text();
354
+ log(`\u274C API Error: ${res.status} - ${error}`);
355
+ throw new SolvaPayError2(`Create product failed (${res.status}): ${error}`);
356
+ }
357
+ const result = await res.json();
358
+ return result;
359
+ },
360
+ // POST: /v1/sdk/products/mcp/bootstrap
361
+ async bootstrapMcpProduct(params) {
362
+ const url = `${base}/v1/sdk/products/mcp/bootstrap`;
363
+ const res = await fetch(url, {
364
+ method: "POST",
365
+ headers,
366
+ body: JSON.stringify(params)
367
+ });
368
+ if (!res.ok) {
369
+ const error = await res.text();
370
+ log(`\u274C API Error: ${res.status} - ${error}`);
371
+ throw new SolvaPayError2(`Bootstrap MCP product failed (${res.status}): ${error}`);
372
+ }
373
+ return await res.json();
374
+ },
375
+ // PUT: /v1/sdk/products/{productRef}/mcp/plans
376
+ async configureMcpPlans(productRef, params) {
377
+ const url = `${base}/v1/sdk/products/${productRef}/mcp/plans`;
378
+ const res = await fetch(url, {
379
+ method: "PUT",
380
+ headers,
381
+ body: JSON.stringify(params)
382
+ });
383
+ if (!res.ok) {
384
+ const error = await res.text();
385
+ log(`\u274C API Error: ${res.status} - ${error}`);
386
+ throw new SolvaPayError2(`Configure MCP plans failed (${res.status}): ${error}`);
387
+ }
388
+ return await res.json();
389
+ },
390
+ // DELETE: /v1/sdk/products/{productRef}
391
+ async deleteProduct(productRef) {
392
+ const url = `${base}/v1/sdk/products/${productRef}`;
393
+ const res = await fetch(url, {
394
+ method: "DELETE",
395
+ headers
396
+ });
397
+ if (!res.ok && res.status !== 404) {
398
+ const error = await res.text();
399
+ log(`\u274C API Error: ${res.status} - ${error}`);
400
+ throw new SolvaPayError2(`Delete product failed (${res.status}): ${error}`);
401
+ }
402
+ },
403
+ // POST: /v1/sdk/products/{productRef}/clone
404
+ async cloneProduct(productRef, overrides) {
405
+ const url = `${base}/v1/sdk/products/${productRef}/clone`;
406
+ const res = await fetch(url, {
407
+ method: "POST",
408
+ headers,
409
+ body: JSON.stringify(overrides || {})
410
+ });
411
+ if (!res.ok) {
412
+ const error = await res.text();
413
+ log(`\u274C API Error: ${res.status} - ${error}`);
414
+ throw new SolvaPayError2(`Clone product failed (${res.status}): ${error}`);
415
+ }
416
+ return await res.json();
417
+ },
418
+ // GET: /v1/sdk/products/{productRef}/plans
419
+ async listPlans(productRef) {
420
+ const url = `${base}/v1/sdk/products/${productRef}/plans`;
421
+ const res = await fetch(url, {
422
+ method: "GET",
423
+ headers
424
+ });
425
+ if (!res.ok) {
426
+ const error = await res.text();
427
+ log(`\u274C API Error: ${res.status} - ${error}`);
428
+ throw new SolvaPayError2(`List plans failed (${res.status}): ${error}`);
429
+ }
430
+ const result = await res.json();
431
+ const plans = Array.isArray(result) ? result : result.plans || [];
432
+ return plans.map((plan) => {
433
+ const data = plan.data || {};
434
+ const price = plan.price ?? data.price;
435
+ const unwrapped = {
436
+ ...data,
437
+ ...plan,
438
+ ...price !== void 0 && { price }
439
+ };
440
+ delete unwrapped.data;
441
+ return unwrapped;
442
+ });
443
+ },
444
+ // POST: /v1/sdk/products/{productRef}/plans
445
+ async createPlan(params) {
446
+ const url = `${base}/v1/sdk/products/${params.productRef}/plans`;
447
+ const res = await fetch(url, {
448
+ method: "POST",
449
+ headers,
450
+ body: JSON.stringify(params)
451
+ });
452
+ if (!res.ok) {
453
+ const error = await res.text();
454
+ log(`\u274C API Error: ${res.status} - ${error}`);
455
+ throw new SolvaPayError2(`Create plan failed (${res.status}): ${error}`);
456
+ }
457
+ const result = await res.json();
458
+ return result;
459
+ },
460
+ // PUT: /v1/sdk/products/{productRef}/plans/{planRef}
461
+ async updatePlan(productRef, planRef, params) {
462
+ const url = `${base}/v1/sdk/products/${productRef}/plans/${planRef}`;
463
+ const res = await fetch(url, {
464
+ method: "PUT",
465
+ headers,
466
+ body: JSON.stringify(params)
467
+ });
468
+ if (!res.ok) {
469
+ const error = await res.text();
470
+ log(`\u274C API Error: ${res.status} - ${error}`);
471
+ throw new SolvaPayError2(`Update plan failed (${res.status}): ${error}`);
472
+ }
473
+ return await res.json();
474
+ },
475
+ // DELETE: /v1/sdk/products/{productRef}/plans/{planRef}
476
+ async deletePlan(productRef, planRef) {
477
+ const url = `${base}/v1/sdk/products/${productRef}/plans/${planRef}`;
478
+ const res = await fetch(url, {
479
+ method: "DELETE",
480
+ headers
481
+ });
482
+ if (!res.ok && res.status !== 404) {
483
+ const error = await res.text();
484
+ log(`\u274C API Error: ${res.status} - ${error}`);
485
+ throw new SolvaPayError2(`Delete plan failed (${res.status}): ${error}`);
486
+ }
487
+ },
488
+ // POST: /payment-intents
489
+ async createPaymentIntent(params) {
490
+ const idempotencyKey = params.idempotencyKey || `payment-${params.planRef}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
491
+ const url = `${base}/v1/sdk/payment-intents`;
492
+ const res = await fetch(url, {
493
+ method: "POST",
494
+ headers: {
495
+ ...headers,
496
+ "Idempotency-Key": idempotencyKey
497
+ },
498
+ body: JSON.stringify({
499
+ productRef: params.productRef,
500
+ planRef: params.planRef,
501
+ customerRef: params.customerRef
502
+ })
503
+ });
504
+ if (!res.ok) {
505
+ const error = await res.text();
506
+ log(`\u274C API Error: ${res.status} - ${error}`);
507
+ throw new SolvaPayError2(`Create payment intent failed (${res.status}): ${error}`);
508
+ }
509
+ return await res.json();
510
+ },
511
+ // POST: /v1/sdk/payment-intents (purpose: credit_topup)
512
+ async createTopupPaymentIntent(params) {
513
+ const idempotencyKey = params.idempotencyKey || `topup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
514
+ const url = `${base}/v1/sdk/payment-intents`;
515
+ const res = await fetch(url, {
516
+ method: "POST",
517
+ headers: {
518
+ ...headers,
519
+ "Idempotency-Key": idempotencyKey
520
+ },
521
+ body: JSON.stringify({
522
+ customerRef: params.customerRef,
523
+ purpose: "credit_topup",
524
+ amount: params.amount,
525
+ currency: params.currency,
526
+ description: params.description
527
+ })
528
+ });
529
+ if (!res.ok) {
530
+ const error = await res.text();
531
+ log(`\u274C API Error: ${res.status} - ${error}`);
532
+ throw new SolvaPayError2(`Create topup payment intent failed (${res.status}): ${error}`);
533
+ }
534
+ return await res.json();
535
+ },
536
+ // POST: /v1/sdk/payment-intents/{paymentIntentId}/process
537
+ async processPaymentIntent(params) {
538
+ const url = `${base}/v1/sdk/payment-intents/${params.paymentIntentId}/process`;
539
+ const res = await fetch(url, {
540
+ method: "POST",
541
+ headers: {
542
+ ...headers,
543
+ "Content-Type": "application/json"
544
+ },
545
+ body: JSON.stringify({
546
+ productRef: params.productRef,
547
+ customerRef: params.customerRef,
548
+ planRef: params.planRef
549
+ })
550
+ });
551
+ if (!res.ok) {
552
+ const error = await res.text();
553
+ log(`\u274C API Error: ${res.status} - ${error}`);
554
+ throw new SolvaPayError2(`Process payment failed (${res.status}): ${error}`);
555
+ }
556
+ const result = await res.json();
557
+ return result;
558
+ },
559
+ // POST: /v1/sdk/purchases/{purchaseRef}/cancel
560
+ async cancelPurchase(params) {
561
+ const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/cancel`;
562
+ const requestOptions = {
563
+ method: "POST",
564
+ headers
565
+ };
566
+ if (params.reason) {
567
+ requestOptions.body = JSON.stringify({ reason: params.reason });
568
+ }
569
+ const res = await fetch(url, requestOptions);
570
+ if (!res.ok) {
571
+ const error = await res.text();
572
+ log(`\u274C API Error: ${res.status} - ${error}`);
573
+ if (res.status === 404) {
574
+ throw new SolvaPayError2(`Purchase not found: ${error}`);
575
+ }
576
+ if (res.status === 400) {
577
+ throw new SolvaPayError2(
578
+ `Purchase cannot be cancelled or does not belong to provider: ${error}`
579
+ );
580
+ }
581
+ throw new SolvaPayError2(`Cancel purchase failed (${res.status}): ${error}`);
582
+ }
583
+ const responseText = await res.text();
584
+ let responseData;
585
+ try {
586
+ responseData = JSON.parse(responseText);
587
+ } catch (parseError) {
588
+ log(`\u274C Failed to parse response as JSON: ${parseError}`);
589
+ throw new SolvaPayError2(
590
+ `Invalid JSON response from cancel purchase endpoint: ${responseText.substring(0, 200)}`
591
+ );
592
+ }
593
+ if (!responseData || typeof responseData !== "object") {
594
+ log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
595
+ throw new SolvaPayError2(`Invalid response structure from cancel purchase endpoint`);
596
+ }
597
+ let result;
598
+ if (responseData.purchase && typeof responseData.purchase === "object") {
599
+ result = responseData.purchase;
600
+ } else if (responseData.reference) {
601
+ result = responseData;
602
+ } else {
603
+ result = responseData.purchase || responseData;
604
+ }
605
+ if (!result || typeof result !== "object") {
606
+ log(`\u274C Invalid purchase data in response. Full response:`, responseData);
607
+ throw new SolvaPayError2(`Invalid purchase data in cancel purchase response`);
608
+ }
609
+ return result;
610
+ },
611
+ // POST: /v1/sdk/purchases/{purchaseRef}/reactivate
612
+ async reactivatePurchase(params) {
613
+ const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/reactivate`;
614
+ const res = await fetch(url, {
615
+ method: "POST",
616
+ headers
617
+ });
618
+ if (!res.ok) {
619
+ const error = await res.text();
620
+ log(`\u274C API Error: ${res.status} - ${error}`);
621
+ if (res.status === 404) {
622
+ throw new SolvaPayError2(`Purchase not found: ${error}`);
623
+ }
624
+ if (res.status === 400) {
625
+ throw new SolvaPayError2(
626
+ `Purchase cannot be reactivated: ${error}`
627
+ );
628
+ }
629
+ throw new SolvaPayError2(`Reactivate purchase failed (${res.status}): ${error}`);
630
+ }
631
+ const responseText = await res.text();
632
+ let responseData;
633
+ try {
634
+ responseData = JSON.parse(responseText);
635
+ } catch (parseError) {
636
+ log(`\u274C Failed to parse response as JSON: ${parseError}`);
637
+ throw new SolvaPayError2(
638
+ `Invalid JSON response from reactivate purchase endpoint: ${responseText.substring(0, 200)}`
639
+ );
640
+ }
641
+ if (!responseData || typeof responseData !== "object") {
642
+ log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
643
+ throw new SolvaPayError2(`Invalid response structure from reactivate purchase endpoint`);
644
+ }
645
+ let result;
646
+ if (responseData.purchase && typeof responseData.purchase === "object") {
647
+ result = responseData.purchase;
648
+ } else if (responseData.reference) {
649
+ result = responseData;
650
+ } else {
651
+ result = responseData.purchase || responseData;
652
+ }
653
+ if (!result || typeof result !== "object") {
654
+ log(`\u274C Invalid purchase data in response. Full response:`, responseData);
655
+ throw new SolvaPayError2(`Invalid purchase data in reactivate purchase response`);
656
+ }
657
+ return result;
658
+ },
659
+ // POST: /v1/sdk/user-info
660
+ async getUserInfo(params) {
661
+ const url = `${base}/v1/sdk/user-info`;
662
+ const res = await fetch(url, {
663
+ method: "POST",
664
+ headers,
665
+ body: JSON.stringify(params)
666
+ });
667
+ if (!res.ok) {
668
+ const error = await res.text();
669
+ log(`\u274C API Error: ${res.status} - ${error}`);
670
+ throw new SolvaPayError2(`Get user info failed (${res.status}): ${error}`);
671
+ }
672
+ return await res.json();
673
+ },
674
+ // GET: /v1/sdk/customers/:customerRef/balance
675
+ async getCustomerBalance(params) {
676
+ const url = `${base}/v1/sdk/customers/${params.customerRef}/balance`;
677
+ const res = await fetch(url, {
678
+ method: "GET",
679
+ headers
680
+ });
681
+ if (!res.ok) {
682
+ const error = await res.text();
683
+ log(`\u274C API Error: ${res.status} - ${error}`);
684
+ throw new SolvaPayError2(`Get customer balance failed (${res.status}): ${error}`);
685
+ }
686
+ return await res.json();
687
+ },
688
+ // POST: /v1/sdk/checkout-sessions
689
+ async createCheckoutSession(params) {
690
+ const url = `${base}/v1/sdk/checkout-sessions`;
691
+ const res = await fetch(url, {
692
+ method: "POST",
693
+ headers,
694
+ body: JSON.stringify(params)
695
+ });
696
+ if (!res.ok) {
697
+ const error = await res.text();
698
+ log(`\u274C API Error: ${res.status} - ${error}`);
699
+ throw new SolvaPayError2(`Create checkout session failed (${res.status}): ${error}`);
700
+ }
701
+ const result = await res.json();
702
+ return result;
703
+ },
704
+ // POST: /v1/sdk/customers/customer-sessions
705
+ async createCustomerSession(params) {
706
+ const url = `${base}/v1/sdk/customers/customer-sessions`;
707
+ const res = await fetch(url, {
708
+ method: "POST",
709
+ headers,
710
+ body: JSON.stringify(params)
711
+ });
712
+ if (!res.ok) {
713
+ const error = await res.text();
714
+ log(`\u274C API Error: ${res.status} - ${error}`);
715
+ throw new SolvaPayError2(`Create customer session failed (${res.status}): ${error}`);
716
+ }
717
+ const result = await res.json();
718
+ return result;
719
+ },
720
+ // POST: /v1/sdk/activate
721
+ async activatePlan(params) {
722
+ const url = `${base}/v1/sdk/activate`;
723
+ const res = await fetch(url, {
724
+ method: "POST",
725
+ headers,
726
+ body: JSON.stringify(params)
727
+ });
728
+ if (!res.ok) {
729
+ const error = await res.text();
730
+ log(`\u274C API Error: ${res.status} - ${error}`);
731
+ throw new SolvaPayError2(`Activate plan failed (${res.status}): ${error}`);
732
+ }
733
+ return await res.json();
734
+ },
735
+ async getPaymentMethod(params) {
736
+ const url = new URL(`${base}/v1/sdk/payment-method`);
737
+ url.searchParams.set("customerRef", params.customerRef);
738
+ const res = await fetch(url.toString(), { method: "GET", headers });
739
+ if (!res.ok) {
740
+ const error = await res.text();
741
+ log(`\u274C API Error: ${res.status} - ${error}`);
742
+ throw new SolvaPayError2(`Get payment method failed (${res.status}): ${error}`);
743
+ }
744
+ return await res.json();
745
+ }
746
+ };
747
+ }
748
+
749
+ // src/paywall-state.ts
750
+ function classifyPaywallState(limits) {
751
+ if (!limits) return { kind: "upgrade_required" };
752
+ if (limits.activationRequired === true) {
753
+ return { kind: "activation_required" };
754
+ }
755
+ const activePlan = limits.plans?.find((p) => p.reference === limits.plan);
756
+ const isUsageBased = activePlan?.type === "usage-based" || limits.balance !== void 0;
757
+ const creditBalance = limits.balance?.creditBalance ?? limits.creditBalance;
758
+ if (isUsageBased) {
759
+ if (creditBalance === 0) return { kind: "topup_required" };
760
+ if (creditBalance === void 0 && limits.remaining === 0) {
761
+ return { kind: "topup_required" };
762
+ }
763
+ }
764
+ return { kind: "upgrade_required" };
765
+ }
766
+ function buildGateMessage(state, gate) {
767
+ const url = gate.checkoutUrl && gate.checkoutUrl.length > 0 ? gate.checkoutUrl : null;
768
+ const openClause = url ? `, or open ${url} in a browser` : "";
769
+ switch (state.kind) {
770
+ case "activation_required":
771
+ return `Your plan needs activation before you can use this tool. Call the \`activate_plan\` tool to activate it${openClause}.`;
772
+ case "topup_required":
773
+ return `You're out of credits. Call the \`topup\` tool to add more${openClause}.`;
774
+ case "upgrade_required":
775
+ return `You don't have an active plan for this tool. Call the \`upgrade\` tool to pick a plan${openClause}.`;
776
+ case "reactivation_required":
777
+ return `Your previous plan is no longer active. Call the \`manage_account\` tool to reactivate it, or the \`upgrade\` tool to pick a new plan.`;
778
+ }
779
+ }
780
+
781
+ // src/utils.ts
782
+ async function withRetry(fn, options = {}) {
783
+ const {
784
+ maxRetries = 2,
785
+ initialDelay = 500,
786
+ backoffStrategy = "fixed",
787
+ shouldRetry,
788
+ onRetry
789
+ } = options;
790
+ let lastError;
791
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
792
+ try {
793
+ return await fn();
794
+ } catch (error) {
795
+ lastError = error instanceof Error ? error : new Error(String(error));
796
+ const isLastAttempt = attempt === maxRetries;
797
+ if (isLastAttempt) {
798
+ throw lastError;
799
+ }
800
+ if (shouldRetry && !shouldRetry(lastError, attempt)) {
801
+ throw lastError;
802
+ }
803
+ const delay = calculateDelay(initialDelay, attempt, backoffStrategy);
804
+ if (onRetry) {
805
+ onRetry(lastError, attempt);
806
+ }
807
+ await sleep(delay);
808
+ }
809
+ }
810
+ throw lastError;
811
+ }
812
+ function calculateDelay(initialDelay, attempt, strategy) {
813
+ switch (strategy) {
814
+ case "fixed":
815
+ return initialDelay;
816
+ case "linear":
817
+ return initialDelay * (attempt + 1);
818
+ case "exponential":
819
+ return initialDelay * Math.pow(2, attempt);
820
+ default:
821
+ return initialDelay;
822
+ }
823
+ }
824
+ function sleep(ms) {
825
+ return new Promise((resolve) => setTimeout(resolve, ms));
826
+ }
827
+ function createRequestDeduplicator(options = {}) {
828
+ const { cacheTTL = 2e3, maxCacheSize = 1e3, cacheErrors = true } = options;
829
+ const inFlightRequests = /* @__PURE__ */ new Map();
830
+ const resultCache = /* @__PURE__ */ new Map();
831
+ let _cleanupInterval = null;
832
+ function ensureCleanupInterval() {
833
+ if (_cleanupInterval || cacheTTL <= 0) return;
834
+ _cleanupInterval = setInterval(
835
+ () => {
836
+ const now = Date.now();
837
+ const entriesToDelete = [];
838
+ for (const [key, cached] of resultCache.entries()) {
839
+ if (now - cached.timestamp >= cacheTTL) {
840
+ entriesToDelete.push(key);
841
+ }
842
+ }
843
+ for (const key of entriesToDelete) {
844
+ resultCache.delete(key);
845
+ }
846
+ if (resultCache.size > maxCacheSize) {
847
+ const sortedEntries = Array.from(resultCache.entries()).sort(
848
+ (a, b) => a[1].timestamp - b[1].timestamp
849
+ );
850
+ const toRemove = sortedEntries.slice(0, resultCache.size - maxCacheSize);
851
+ for (const [key] of toRemove) {
852
+ resultCache.delete(key);
853
+ }
854
+ }
855
+ },
856
+ Math.min(cacheTTL, 1e3)
857
+ );
858
+ }
859
+ const deduplicate = async (key, fn) => {
860
+ ensureCleanupInterval();
861
+ if (cacheTTL > 0) {
862
+ const cached = resultCache.get(key);
863
+ if (cached && Date.now() - cached.timestamp < cacheTTL) {
864
+ return cached.data;
865
+ } else if (cached) {
866
+ resultCache.delete(key);
867
+ }
868
+ }
869
+ let requestPromise = inFlightRequests.get(key);
870
+ if (!requestPromise) {
871
+ requestPromise = (async () => {
872
+ try {
873
+ const result = await fn();
874
+ if (cacheTTL > 0) {
875
+ resultCache.set(key, {
876
+ data: result,
877
+ timestamp: Date.now()
878
+ });
879
+ }
880
+ return result;
881
+ } catch (error) {
882
+ if (cacheTTL > 0 && cacheErrors) {
883
+ resultCache.set(key, {
884
+ data: error,
885
+ timestamp: Date.now()
886
+ });
887
+ }
888
+ throw error;
889
+ } finally {
890
+ inFlightRequests.delete(key);
891
+ }
892
+ })();
893
+ const existingPromise = inFlightRequests.get(key);
894
+ if (existingPromise) {
895
+ requestPromise = existingPromise;
896
+ } else {
897
+ inFlightRequests.set(key, requestPromise);
898
+ }
899
+ }
900
+ return requestPromise;
901
+ };
902
+ const clearCache = (key) => {
903
+ resultCache.delete(key);
904
+ };
905
+ const clearAllCache = () => {
906
+ resultCache.clear();
907
+ };
908
+ const getStats = () => ({
909
+ inFlight: inFlightRequests.size,
910
+ cached: resultCache.size
911
+ });
912
+ return {
913
+ deduplicate,
914
+ clearCache,
915
+ clearAllCache,
916
+ getStats
917
+ };
918
+ }
919
+
920
+ // src/paywall.ts
921
+ var PaywallError = class extends Error {
922
+ /**
923
+ * Creates a new PaywallError instance.
924
+ *
925
+ * @param message - Error message
926
+ * @param structuredContent - Structured content with checkout URLs and metadata
927
+ */
928
+ constructor(message, structuredContent) {
929
+ super(message);
930
+ this.structuredContent = structuredContent;
931
+ this.name = "PaywallError";
932
+ }
933
+ };
934
+ function paywallErrorToClientPayload(error) {
935
+ const sc = error.structuredContent;
936
+ const base = {
937
+ success: false,
938
+ error: sc.kind === "activation_required" ? "Activation required" : "Payment required",
939
+ product: sc.product,
940
+ checkoutUrl: sc.checkoutUrl,
941
+ message: sc.message
942
+ };
943
+ if (sc.kind === "activation_required") {
944
+ base.kind = "activation_required";
945
+ if (sc.plans !== void 0) base.plans = sc.plans;
946
+ if (sc.balance !== void 0) base.balance = sc.balance;
947
+ if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
948
+ if (sc.confirmationUrl !== void 0) base.confirmationUrl = sc.confirmationUrl;
949
+ } else {
950
+ base.kind = "payment_required";
951
+ if (sc.balance !== void 0) base.balance = sc.balance;
952
+ if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
953
+ }
954
+ return base;
955
+ }
956
+ var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
957
+ cacheTTL: 6e4,
958
+ // Cache results for 60 seconds (reduces API calls significantly)
959
+ maxCacheSize: 1e3,
960
+ // Maximum cache entries
961
+ cacheErrors: false
962
+ // Don't cache errors - retry on next request
963
+ });
964
+ var EXTRA_FORWARD_KEY = "__solvapayExtra";
965
+ var SolvaPayPaywall = class {
966
+ constructor(apiClient, options = {}) {
967
+ this.apiClient = apiClient;
968
+ this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG === "true";
969
+ this.limitsCacheTTL = options.limitsCacheTTL ?? 1e4;
970
+ }
971
+ customerCreationAttempts = /* @__PURE__ */ new Set();
972
+ customerRefMapping = /* @__PURE__ */ new Map();
973
+ debug;
974
+ limitsCache = /* @__PURE__ */ new Map();
975
+ limitsCacheTTL;
976
+ log(...args) {
977
+ if (this.debug) {
978
+ console.log(...args);
979
+ }
980
+ }
981
+ resolveProduct(metadata) {
982
+ return metadata.product || process.env.SOLVAPAY_PRODUCT || "default-product";
983
+ }
984
+ generateRequestId() {
985
+ const timestamp = Date.now();
986
+ const random = Math.random().toString(36).substring(2, 11);
987
+ return `solvapay_${timestamp}_${random}`;
988
+ }
989
+ /**
990
+ * Pure decision routine — performs customer resolution, limits cache
991
+ * lookup / fresh `checkLimits` fetch, and returns a `PaywallDecision`
992
+ * describing whether the handler should run.
993
+ *
994
+ * Side effects kept in lockstep with the legacy `protect()` path:
995
+ * - creates the backend customer on first use (`ensureCustomer`),
996
+ * - updates the limits cache (consume-one-unit bookkeeping), and
997
+ * - emits a `paywall` usage event on gate outcomes.
998
+ *
999
+ * `trackUsage` for the `success` / `fail` outcome is emitted by the
1000
+ * caller (adapter or `protect()`) once it has actually invoked the
1001
+ * handler — `decide()` never counts handler execution as usage.
1002
+ *
1003
+ * @since 1.1.0
1004
+ */
1005
+ async decide(args, metadata = {}, getCustomerRef) {
1006
+ const product = this.resolveProduct(metadata);
1007
+ const usageType = metadata.usageType || "requests";
1008
+ const requestId = this.generateRequestId();
1009
+ const startTime = Date.now();
1010
+ const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
1011
+ let backendCustomerRef;
1012
+ if (inputCustomerRef.startsWith("cus_")) {
1013
+ backendCustomerRef = inputCustomerRef;
1014
+ } else {
1015
+ backendCustomerRef = await this.ensureCustomer(inputCustomerRef, inputCustomerRef);
1016
+ }
1017
+ const limitsCacheKey = `${backendCustomerRef}:${product}:${usageType}`;
1018
+ const cachedLimits = this.limitsCache.get(limitsCacheKey);
1019
+ const now = Date.now();
1020
+ let withinLimits;
1021
+ let remaining;
1022
+ let checkoutUrl;
1023
+ let resolvedMeterName;
1024
+ let lastLimitsCheck;
1025
+ const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
1026
+ if (hasFreshCachedLimits) {
1027
+ checkoutUrl = cachedLimits.checkoutUrl;
1028
+ resolvedMeterName = cachedLimits.meterName;
1029
+ lastLimitsCheck = cachedLimits.limits;
1030
+ if (cachedLimits.remaining > 0) {
1031
+ cachedLimits.remaining--;
1032
+ if (cachedLimits.remaining <= 0) {
1033
+ this.limitsCache.delete(limitsCacheKey);
1034
+ }
1035
+ withinLimits = true;
1036
+ remaining = cachedLimits.remaining;
1037
+ } else {
1038
+ withinLimits = false;
1039
+ remaining = 0;
1040
+ this.limitsCache.delete(limitsCacheKey);
1041
+ }
1042
+ } else {
1043
+ if (cachedLimits) {
1044
+ this.limitsCache.delete(limitsCacheKey);
1045
+ }
1046
+ const limitsCheck = await this.apiClient.checkLimits({
1047
+ customerRef: backendCustomerRef,
1048
+ productRef: product,
1049
+ meterName: usageType
1050
+ });
1051
+ lastLimitsCheck = limitsCheck;
1052
+ withinLimits = limitsCheck.withinLimits;
1053
+ remaining = limitsCheck.remaining;
1054
+ checkoutUrl = limitsCheck.checkoutUrl;
1055
+ resolvedMeterName = limitsCheck.meterName;
1056
+ const consumedAllowance = withinLimits && remaining > 0;
1057
+ if (consumedAllowance) {
1058
+ remaining = Math.max(0, remaining - 1);
1059
+ }
1060
+ if (consumedAllowance) {
1061
+ this.limitsCache.set(limitsCacheKey, {
1062
+ remaining,
1063
+ checkoutUrl,
1064
+ meterName: resolvedMeterName,
1065
+ timestamp: now,
1066
+ limits: limitsCheck
1067
+ });
1068
+ }
1069
+ }
1070
+ if (!withinLimits) {
1071
+ const latencyMs = Date.now() - startTime;
1072
+ this.trackUsage(
1073
+ backendCustomerRef,
1074
+ product,
1075
+ resolvedMeterName || usageType,
1076
+ "paywall",
1077
+ requestId,
1078
+ latencyMs
1079
+ );
1080
+ const state = classifyPaywallState(lastLimitsCheck ?? null);
1081
+ const preMessageGate = lastLimitsCheck?.activationRequired ? {
1082
+ kind: "activation_required",
1083
+ product,
1084
+ message: "",
1085
+ checkoutUrl: lastLimitsCheck.confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "",
1086
+ ...lastLimitsCheck.confirmationUrl !== void 0 ? { confirmationUrl: lastLimitsCheck.confirmationUrl } : {},
1087
+ ...lastLimitsCheck.plans !== void 0 ? { plans: lastLimitsCheck.plans } : {},
1088
+ ...lastLimitsCheck.balance !== void 0 ? { balance: lastLimitsCheck.balance } : {},
1089
+ ...lastLimitsCheck.product !== void 0 ? { productDetails: lastLimitsCheck.product } : {}
1090
+ } : {
1091
+ kind: "payment_required",
1092
+ product,
1093
+ checkoutUrl: checkoutUrl || "",
1094
+ message: "",
1095
+ ...lastLimitsCheck?.balance !== void 0 ? { balance: lastLimitsCheck.balance } : {},
1096
+ ...lastLimitsCheck?.product !== void 0 ? { productDetails: lastLimitsCheck.product } : {}
1097
+ };
1098
+ const gate = {
1099
+ ...preMessageGate,
1100
+ message: buildGateMessage(state, preMessageGate)
1101
+ };
1102
+ return {
1103
+ outcome: "gate",
1104
+ gate,
1105
+ limits: lastLimitsCheck ?? null,
1106
+ customerRef: backendCustomerRef
1107
+ };
1108
+ }
1109
+ return {
1110
+ outcome: "allow",
1111
+ args,
1112
+ limits: lastLimitsCheck,
1113
+ customerRef: backendCustomerRef
1114
+ };
1115
+ }
1116
+ /**
1117
+ * Execute the handler for an already-obtained `allow` decision and
1118
+ * emit the post-handler `trackUsage('success' | 'fail', ...)` event.
1119
+ *
1120
+ * Exposed for adapter integration — the adapter layer drives the
1121
+ * paywall through `decide()` + `runAllow()` so `formatGate` can own
1122
+ * gate outcomes without routing through `PaywallError`. `protect()`
1123
+ * continues to offer the self-contained throw-based surface for
1124
+ * legacy consumers.
1125
+ *
1126
+ * `runAllow` intentionally does NOT re-throw `PaywallError` — if a
1127
+ * handler calls `ctx.gate(reason)` and throws from deep code, the
1128
+ * adapter catches that at the `formatGate` boundary instead.
1129
+ *
1130
+ * @since 1.1.0
1131
+ */
1132
+ async runAllow(decision, handler, metadata, args) {
1133
+ const product = this.resolveProduct(metadata);
1134
+ const usageType = metadata.usageType || "requests";
1135
+ const requestId = this.generateRequestId();
1136
+ const startTime = Date.now();
1137
+ const forwardedExtra = args[EXTRA_FORWARD_KEY];
1138
+ const handlerContext = {
1139
+ customerRef: decision.customerRef,
1140
+ limits: decision.limits,
1141
+ ...forwardedExtra !== void 0 ? { extra: forwardedExtra } : {}
1142
+ };
1143
+ try {
1144
+ const result = await handler(args, handlerContext);
1145
+ const latencyMs = Date.now() - startTime;
1146
+ this.trackUsage(
1147
+ decision.customerRef,
1148
+ product,
1149
+ decision.limits.meterName || usageType,
1150
+ "success",
1151
+ requestId,
1152
+ latencyMs
1153
+ );
1154
+ return result;
1155
+ } catch (error) {
1156
+ if (error instanceof Error) {
1157
+ const errorType = error instanceof PaywallError ? "PaywallError" : "API Error";
1158
+ this.log(`\u274C Error in paywall [${errorType}]: ${error.message}`);
1159
+ } else {
1160
+ this.log(`\u274C Error in paywall:`, error);
1161
+ }
1162
+ if (!(error instanceof PaywallError)) {
1163
+ const latencyMs = Date.now() - startTime;
1164
+ this.trackUsage(
1165
+ decision.customerRef,
1166
+ product,
1167
+ decision.limits.meterName || usageType,
1168
+ "fail",
1169
+ requestId,
1170
+ latencyMs
1171
+ );
1172
+ }
1173
+ throw error;
1174
+ }
1175
+ }
1176
+ /**
1177
+ * Core protection method - works for both MCP and HTTP
1178
+ *
1179
+ * The `handler` may optionally declare a second positional argument
1180
+ * of type `ProtectHandlerContext` to receive the resolved customer
1181
+ * ref, the pre-check `LimitResponseWithPlan`, and an opaque `extra`
1182
+ * bag threaded through from the adapter layer. One-arg handlers
1183
+ * ignore the second argument and continue to work unchanged.
1184
+ *
1185
+ * Implemented on top of `decide()`: pre-check runs through the same
1186
+ * decision routine, and gate outcomes are raised as a `PaywallError`
1187
+ * to preserve the legacy throw-based signal for consumers that
1188
+ * haven't migrated to adapter-level `formatGate` routing.
1189
+ */
1190
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1191
+ async protect(handler, metadata = {}, getCustomerRef) {
1192
+ return async (args) => {
1193
+ const decision = await this.decide(args, metadata, getCustomerRef);
1194
+ if (decision.outcome === "gate") {
1195
+ const message = decision.gate.kind === "activation_required" ? "Activation required" : "Payment required";
1196
+ this.log(`\u274C Error in paywall [PaywallError]: ${message}`);
1197
+ throw new PaywallError(message, decision.gate);
1198
+ }
1199
+ return this.runAllow(decision, handler, metadata, args);
1200
+ };
1201
+ }
1202
+ /**
1203
+ * Ensures a customer exists in the backend, creating them if necessary.
1204
+ * This is a public helper for testing, pre-creating customers, and internal use.
1205
+ * Only attempts creation once per customer (idempotent).
1206
+ * Returns the backend customer reference to use in API calls.
1207
+ *
1208
+ * @param customerRef - The customer reference used as a cache key (e.g., Supabase user ID)
1209
+ * @param externalRef - Optional external reference for backend lookup (e.g., Supabase user ID)
1210
+ * If provided, will lookup existing customer by externalRef before creating new one.
1211
+ * The externalRef is stored on the SolvaPay backend for customer lookup.
1212
+ * @param options - Optional customer details (email, name) for customer creation
1213
+ */
1214
+ async ensureCustomer(customerRef, externalRef, options) {
1215
+ if (this.customerRefMapping.has(customerRef)) {
1216
+ return this.customerRefMapping.get(customerRef);
1217
+ }
1218
+ if (customerRef === "anonymous") {
1219
+ return customerRef;
1220
+ }
1221
+ if (customerRef.startsWith("cus_")) {
1222
+ return customerRef;
1223
+ }
1224
+ const cacheKey = externalRef || customerRef;
1225
+ if (this.customerRefMapping.has(customerRef)) {
1226
+ const cached = this.customerRefMapping.get(customerRef);
1227
+ return cached;
1228
+ }
1229
+ const backendRef = await sharedCustomerLookupDeduplicator.deduplicate(cacheKey, async () => {
1230
+ if (externalRef) {
1231
+ try {
1232
+ const existingCustomer = await this.apiClient.getCustomer({ externalRef });
1233
+ if (existingCustomer && existingCustomer.customerRef) {
1234
+ const ref = existingCustomer.customerRef;
1235
+ this.customerRefMapping.set(customerRef, ref);
1236
+ this.customerCreationAttempts.add(customerRef);
1237
+ if (externalRef !== customerRef) {
1238
+ this.customerCreationAttempts.add(externalRef);
1239
+ }
1240
+ return ref;
1241
+ }
1242
+ } catch (error) {
1243
+ const errorMessage = error instanceof Error ? error.message : String(error);
1244
+ if (!errorMessage.includes("404") && !errorMessage.includes("not found")) {
1245
+ this.log(`\u26A0\uFE0F Error looking up customer by externalRef: ${errorMessage}`);
1246
+ }
1247
+ }
1248
+ }
1249
+ if (this.customerCreationAttempts.has(customerRef) || externalRef && this.customerCreationAttempts.has(externalRef)) {
1250
+ const mappedRef = this.customerRefMapping.get(customerRef);
1251
+ return mappedRef || customerRef;
1252
+ }
1253
+ if (!this.apiClient.createCustomer) {
1254
+ console.warn(
1255
+ `\u26A0\uFE0F Cannot auto-create customer ${customerRef}: createCustomer method not available on API client`
1256
+ );
1257
+ return customerRef;
1258
+ }
1259
+ this.customerCreationAttempts.add(customerRef);
1260
+ try {
1261
+ const createParams = {
1262
+ email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`,
1263
+ metadata: {}
1264
+ };
1265
+ if (options?.name) {
1266
+ createParams.name = options.name;
1267
+ }
1268
+ if (externalRef) {
1269
+ createParams.externalRef = externalRef;
1270
+ }
1271
+ const result = await this.apiClient.createCustomer(createParams);
1272
+ const resultObj = result;
1273
+ const ref = resultObj.customerRef || resultObj.reference || customerRef;
1274
+ this.customerRefMapping.set(customerRef, ref);
1275
+ return ref;
1276
+ } catch (error) {
1277
+ const errorMessage = error instanceof Error ? error.message : String(error);
1278
+ if (errorMessage.includes("409") || errorMessage.includes("already exists")) {
1279
+ if (externalRef) {
1280
+ try {
1281
+ const searchResult = await this.apiClient.getCustomer({ externalRef });
1282
+ if (searchResult && searchResult.customerRef) {
1283
+ this.customerRefMapping.set(customerRef, searchResult.customerRef);
1284
+ return searchResult.customerRef;
1285
+ }
1286
+ } catch (lookupError) {
1287
+ this.log(
1288
+ `\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`,
1289
+ lookupError instanceof Error ? lookupError.message : lookupError
1290
+ );
1291
+ }
1292
+ }
1293
+ const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
1294
+ if (externalRef && isEmailConflict && options?.email) {
1295
+ try {
1296
+ const byEmail = await this.apiClient.getCustomer({ email: options.email });
1297
+ if (byEmail && byEmail.customerRef) {
1298
+ this.customerRefMapping.set(customerRef, byEmail.customerRef);
1299
+ this.log(
1300
+ `\u26A0\uFE0F Resolved customer ${customerRef} by email after conflict; using existing customer ${byEmail.customerRef}`
1301
+ );
1302
+ if (!byEmail.externalRef && this.apiClient.updateCustomer) {
1303
+ try {
1304
+ await this.apiClient.updateCustomer(byEmail.customerRef, { externalRef });
1305
+ } catch (backfillError) {
1306
+ this.log(
1307
+ `\u26A0\uFE0F Failed to backfill externalRef on ${byEmail.customerRef}:`,
1308
+ backfillError instanceof Error ? backfillError.message : backfillError
1309
+ );
1310
+ }
1311
+ }
1312
+ return byEmail.customerRef;
1313
+ }
1314
+ } catch (emailLookupError) {
1315
+ this.log(
1316
+ `\u26A0\uFE0F Email lookup failed after customer conflict for ${customerRef}:`,
1317
+ emailLookupError instanceof Error ? emailLookupError.message : emailLookupError
1318
+ );
1319
+ }
1320
+ try {
1321
+ const retryParams = {
1322
+ email: `${customerRef}-${Date.now()}@auto-created.local`,
1323
+ externalRef,
1324
+ metadata: {}
1325
+ };
1326
+ if (options?.name) {
1327
+ retryParams.name = options.name;
1328
+ }
1329
+ const retryResult = await this.apiClient.createCustomer(retryParams);
1330
+ const retryObj = retryResult;
1331
+ const retryRef = retryObj.customerRef || retryObj.reference || customerRef;
1332
+ this.customerRefMapping.set(customerRef, retryRef);
1333
+ this.log(
1334
+ `\u26A0\uFE0F Retried customer creation for ${customerRef} with generated email after email conflict`
1335
+ );
1336
+ return retryRef;
1337
+ } catch (retryError) {
1338
+ this.log(
1339
+ `\u26A0\uFE0F Retry create customer with generated email failed for ${customerRef}:`,
1340
+ retryError instanceof Error ? retryError.message : retryError
1341
+ );
1342
+ }
1343
+ }
1344
+ const unresolvedMessage = errorMessage || "Customer already exists but could not be resolved";
1345
+ throw new Error(
1346
+ `Failed to resolve existing customer for ${customerRef} after conflict: ${unresolvedMessage}. Ensure the existing customer is linked to this externalRef.`
1347
+ );
1348
+ }
1349
+ this.log(
1350
+ `\u274C Failed to auto-create customer ${customerRef}:`,
1351
+ error instanceof Error ? error.message : error
1352
+ );
1353
+ throw error;
1354
+ }
1355
+ });
1356
+ if (backendRef !== customerRef) {
1357
+ this.customerRefMapping.set(customerRef, backendRef);
1358
+ }
1359
+ return backendRef;
1360
+ }
1361
+ async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration) {
1362
+ await withRetry(
1363
+ () => this.apiClient.trackUsage({
1364
+ customerRef,
1365
+ actionType: "api_call",
1366
+ units: 1,
1367
+ outcome,
1368
+ productRef,
1369
+ duration: actionDuration,
1370
+ metadata: { action: action || "api_requests", requestId },
1371
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1372
+ }),
1373
+ {
1374
+ maxRetries: 2,
1375
+ initialDelay: 500,
1376
+ shouldRetry: (error) => error.message.includes("Customer not found"),
1377
+ onRetry: (_error, attempt) => {
1378
+ console.warn(`\u26A0\uFE0F Customer not found (attempt ${attempt + 1}/3), retrying in 500ms...`);
1379
+ }
1380
+ }
1381
+ ).catch((error) => {
1382
+ console.error("Usage tracking failed:", error);
1383
+ });
1384
+ }
1385
+ };
1386
+
1387
+ // src/adapters/base.ts
1388
+ var EXTRA_FORWARD_KEY2 = "__solvapayExtra";
1389
+ var AdapterUtils = class {
1390
+ /**
1391
+ * Ensure customer reference is properly formatted
1392
+ */
1393
+ static ensureCustomerRef(customerRef) {
1394
+ if (!customerRef || customerRef === "anonymous") {
1395
+ return "anonymous";
1396
+ }
1397
+ return customerRef;
1398
+ }
1399
+ /**
1400
+ * Extract customer ref from JWT token
1401
+ */
1402
+ static async extractFromJWT(token, options) {
1403
+ try {
1404
+ const { jwtVerify } = await import("jose");
1405
+ const jwtSecret = new TextEncoder().encode(
1406
+ options?.secret || process.env.OAUTH_JWKS_SECRET || "test-jwt-secret"
1407
+ );
1408
+ const { payload } = await jwtVerify(token, jwtSecret, {
1409
+ issuer: options?.issuer || process.env.OAUTH_ISSUER || "http://localhost:3000",
1410
+ audience: options?.audience || process.env.OAUTH_CLIENT_ID || "test-client-id"
1411
+ });
1412
+ return payload.sub || null;
1413
+ } catch {
1414
+ return null;
1415
+ }
1416
+ }
1417
+ };
1418
+ async function createAdapterHandler(adapter, paywall, metadata, businessLogic) {
1419
+ const backendRefCache = /* @__PURE__ */ new Map();
1420
+ return async (context, extra) => {
1421
+ let args;
1422
+ let customerRef;
1423
+ try {
1424
+ args = await adapter.extractArgs(context);
1425
+ customerRef = await adapter.getCustomerRef(context, extra);
1426
+ let backendRef = backendRefCache.get(customerRef);
1427
+ if (!backendRef) {
1428
+ backendRef = await paywall.ensureCustomer(customerRef, customerRef);
1429
+ backendRefCache.set(customerRef, backendRef);
1430
+ }
1431
+ args.auth = { customer_ref: backendRef };
1432
+ } catch (error) {
1433
+ return adapter.formatError(error, context);
1434
+ }
1435
+ const decideGetCustomerRef = (args2) => args2.auth?.customer_ref || "anonymous";
1436
+ try {
1437
+ const decision = await paywall.decide(args, metadata, decideGetCustomerRef);
1438
+ if (decision.outcome === "gate") {
1439
+ return adapter.formatGate(decision.gate, context);
1440
+ }
1441
+ if (extra !== void 0) {
1442
+ ;
1443
+ args[EXTRA_FORWARD_KEY2] = extra;
1444
+ }
1445
+ try {
1446
+ const result = await paywall.runAllow(decision, businessLogic, metadata, args);
1447
+ return adapter.formatResponse(result, context);
1448
+ } finally {
1449
+ if (EXTRA_FORWARD_KEY2 in args) {
1450
+ delete args[EXTRA_FORWARD_KEY2];
1451
+ }
1452
+ }
1453
+ } catch (error) {
1454
+ if (error instanceof PaywallError) {
1455
+ return adapter.formatGate(error.structuredContent, context);
1456
+ }
1457
+ return adapter.formatError(error, context);
1458
+ }
1459
+ };
1460
+ }
1461
+
1462
+ // src/adapters/http.ts
1463
+ var HttpAdapter = class {
1464
+ constructor(options = {}) {
1465
+ this.options = options;
1466
+ }
1467
+ extractArgs([req, _reply]) {
1468
+ if (this.options.extractArgs) {
1469
+ return this.options.extractArgs(req);
1470
+ }
1471
+ return {
1472
+ ...req.body || {},
1473
+ ...req.params || {},
1474
+ ...req.query || {}
1475
+ };
1476
+ }
1477
+ async getCustomerRef([req, _reply]) {
1478
+ if (this.options.getCustomerRef) {
1479
+ const ref = await this.options.getCustomerRef(req);
1480
+ return AdapterUtils.ensureCustomerRef(ref);
1481
+ }
1482
+ const headerRef = req.headers?.["x-customer-ref"];
1483
+ if (headerRef) {
1484
+ return AdapterUtils.ensureCustomerRef(headerRef);
1485
+ }
1486
+ const authHeader = req.headers?.["authorization"];
1487
+ if (authHeader && authHeader.startsWith("Bearer ")) {
1488
+ const token = authHeader.substring(7);
1489
+ const jwtSub = await AdapterUtils.extractFromJWT(token);
1490
+ if (jwtSub) {
1491
+ return AdapterUtils.ensureCustomerRef(jwtSub);
1492
+ }
1493
+ }
1494
+ return "anonymous";
1495
+ }
1496
+ formatResponse(result, [_req, reply]) {
1497
+ if (this.options.transformResponse) {
1498
+ return this.options.transformResponse(result, reply);
1499
+ }
1500
+ if (reply && reply.status && typeof reply.json === "function") {
1501
+ reply.json(result);
1502
+ return;
1503
+ }
1504
+ return result;
1505
+ }
1506
+ /**
1507
+ * Emit a 402 Payment Required response with the same JSON body shape
1508
+ * REST consumers have always received (`{success:false, error, product,
1509
+ * checkoutUrl, message, ...}`). The shape is reused via
1510
+ * `paywallErrorToClientPayload` so HTTP / Next / hosted-proxy clients
1511
+ * don't have to branch on an SDK version.
1512
+ */
1513
+ formatGate(gate, [_req, reply]) {
1514
+ const errorResponse2 = paywallErrorToClientPayload(new PaywallError(gate.message, gate));
1515
+ if (reply && reply.status && typeof reply.json === "function") {
1516
+ reply.status(402).json(errorResponse2);
1517
+ return;
1518
+ }
1519
+ if (reply && reply.code) {
1520
+ reply.code(402);
1521
+ }
1522
+ return errorResponse2;
1523
+ }
1524
+ formatError(error, [_req, reply]) {
1525
+ const errorResponse2 = {
1526
+ success: false,
1527
+ error: error instanceof Error ? error.message : "Internal server error"
1528
+ };
1529
+ if (reply && reply.status && typeof reply.json === "function") {
1530
+ reply.status(500).json(errorResponse2);
1531
+ return;
1532
+ }
1533
+ if (reply && reply.code) {
1534
+ reply.code(500);
1535
+ }
1536
+ return errorResponse2;
1537
+ }
1538
+ };
1539
+
1540
+ // src/adapters/next.ts
1541
+ var NextAdapter = class {
1542
+ constructor(options = {}) {
1543
+ this.options = options;
1544
+ }
1545
+ async extractArgs([request, context]) {
1546
+ if (this.options.extractArgs) {
1547
+ return await this.options.extractArgs(request, context);
1548
+ }
1549
+ const url = new URL(request.url);
1550
+ const query = Object.fromEntries(url.searchParams.entries());
1551
+ let body = {};
1552
+ try {
1553
+ if (request.method !== "GET" && request.headers.get("content-type")?.includes("application/json")) {
1554
+ body = await request.json();
1555
+ }
1556
+ } catch {
1557
+ }
1558
+ let routeParams = {};
1559
+ if (context?.params) {
1560
+ if (typeof context.params === "object" && "then" in context.params) {
1561
+ routeParams = await context.params;
1562
+ } else {
1563
+ routeParams = context.params;
1564
+ }
1565
+ }
1566
+ return {
1567
+ ...body,
1568
+ ...query,
1569
+ ...routeParams
1570
+ };
1571
+ }
1572
+ async getCustomerRef([request]) {
1573
+ if (this.options.getCustomerRef) {
1574
+ const ref = await this.options.getCustomerRef(request);
1575
+ return AdapterUtils.ensureCustomerRef(ref);
1576
+ }
1577
+ const authHeader = request.headers.get("authorization");
1578
+ if (authHeader && authHeader.startsWith("Bearer ")) {
1579
+ const token = authHeader.substring(7);
1580
+ const jwtSub = await AdapterUtils.extractFromJWT(token);
1581
+ if (jwtSub) {
1582
+ return AdapterUtils.ensureCustomerRef(jwtSub);
1583
+ }
1584
+ }
1585
+ const userId = request.headers.get("x-user-id");
1586
+ if (userId) {
1587
+ return AdapterUtils.ensureCustomerRef(userId);
1588
+ }
1589
+ const headerRef = request.headers.get("x-customer-ref");
1590
+ if (headerRef) {
1591
+ return AdapterUtils.ensureCustomerRef(headerRef);
1592
+ }
1593
+ return "demo_user";
1594
+ }
1595
+ formatResponse(result, _context) {
1596
+ const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
1597
+ return new Response(JSON.stringify(transformed), {
1598
+ status: 200,
1599
+ headers: { "Content-Type": "application/json" }
1600
+ });
1601
+ }
1602
+ /**
1603
+ * Emit a 402 Payment Required `Response` with the same JSON body
1604
+ * REST consumers have always received. Reuses
1605
+ * `paywallErrorToClientPayload` so HTTP / Next / hosted-proxy
1606
+ * clients don't have to branch on an SDK version.
1607
+ */
1608
+ formatGate(gate, _context) {
1609
+ return new Response(
1610
+ JSON.stringify(paywallErrorToClientPayload(new PaywallError(gate.message, gate))),
1611
+ {
1612
+ status: 402,
1613
+ headers: { "Content-Type": "application/json" }
1614
+ }
1615
+ );
1616
+ }
1617
+ formatError(error, _context) {
1618
+ return new Response(
1619
+ JSON.stringify({
1620
+ success: false,
1621
+ error: error instanceof Error ? error.message : "Internal server error"
1622
+ }),
1623
+ {
1624
+ status: 500,
1625
+ headers: { "Content-Type": "application/json" }
1626
+ }
1627
+ );
1628
+ }
1629
+ };
1630
+
1631
+ // src/adapters/mcp.ts
1632
+ var McpAdapter = class {
1633
+ constructor(options = {}) {
1634
+ this.options = options;
1635
+ }
1636
+ extractArgs(args) {
1637
+ return args;
1638
+ }
1639
+ async getCustomerRef(args, extra) {
1640
+ if (this.options.getCustomerRef) {
1641
+ const ref = await this.options.getCustomerRef(args, extra);
1642
+ return AdapterUtils.ensureCustomerRef(ref);
1643
+ }
1644
+ const customerRefFromExtra = typeof extra?.authInfo?.extra?.customer_ref === "string" ? String(extra.authInfo.extra.customer_ref) : void 0;
1645
+ const customerRefFromArgs = typeof args.auth === "object" && args.auth !== null && typeof args.auth.customer_ref === "string" ? String(args.auth.customer_ref) : void 0;
1646
+ const directCustomerRef = typeof args.customer_ref === "string" ? args.customer_ref : void 0;
1647
+ const customerRef = (customerRefFromExtra || customerRefFromArgs || directCustomerRef || "anonymous").trim();
1648
+ return AdapterUtils.ensureCustomerRef(customerRef);
1649
+ }
1650
+ formatResponse(result, _context) {
1651
+ const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
1652
+ const response = {
1653
+ content: [
1654
+ {
1655
+ type: "text",
1656
+ text: JSON.stringify(transformed, null, 2)
1657
+ }
1658
+ ]
1659
+ };
1660
+ if (transformed && typeof transformed === "object" && !Array.isArray(transformed)) {
1661
+ response.structuredContent = transformed;
1662
+ }
1663
+ return response;
1664
+ }
1665
+ /**
1666
+ * Emit a plain-narration paywall response — `content[0].text` carries
1667
+ * the gate's human message (LLM-actionable), `structuredContent`
1668
+ * carries the machine-readable gate payload, and `isError` stays
1669
+ * `false` per the MCP spec's own `isError` definition (paywall is
1670
+ * not a self-correctable tool execution error; it is a user-facing
1671
+ * control transfer to the UI).
1672
+ *
1673
+ * Hosts that read widget metadata from `tools/list` or tool-result
1674
+ * `_meta.ui` open the paywall iframe on top of this response;
1675
+ * `buildPayableHandler` stamps the `_meta.ui.resourceUri` envelope
1676
+ * before returning.
1677
+ */
1678
+ formatGate(gate, _context) {
1679
+ return {
1680
+ content: [{ type: "text", text: gate.message }],
1681
+ isError: false,
1682
+ structuredContent: gate
1683
+ };
1684
+ }
1685
+ formatError(error, _context) {
1686
+ return {
1687
+ content: [
1688
+ {
1689
+ type: "text",
1690
+ text: JSON.stringify(
1691
+ {
1692
+ success: false,
1693
+ error: error instanceof Error ? error.message : "Unknown error occurred"
1694
+ },
1695
+ null,
1696
+ 2
1697
+ )
1698
+ }
1699
+ ],
1700
+ isError: true
1701
+ };
1702
+ }
1703
+ };
1704
+
1705
+ // src/factory.ts
1706
+ import { SolvaPayError as SolvaPayError3, getSolvaPayConfig } from "@solvapay/core";
1707
+
1708
+ // src/virtual-tools.ts
1709
+ var TOOL_GET_USER_INFO = {
1710
+ name: "get_user_info",
1711
+ 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.",
1712
+ inputSchema: {
1713
+ type: "object",
1714
+ properties: {},
1715
+ required: []
1716
+ }
1717
+ };
1718
+ var TOOL_UPGRADE = {
1719
+ name: "upgrade",
1720
+ 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.",
1721
+ inputSchema: {
1722
+ type: "object",
1723
+ properties: {
1724
+ planRef: {
1725
+ type: "string",
1726
+ 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.'
1727
+ }
1728
+ },
1729
+ required: []
1730
+ }
1731
+ };
1732
+ var TOOL_MANAGE_ACCOUNT = {
1733
+ name: "manage_account",
1734
+ 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.",
1735
+ inputSchema: {
1736
+ type: "object",
1737
+ properties: {},
1738
+ required: []
1739
+ }
1740
+ };
1741
+ function mcpTextResult(text) {
1742
+ return { content: [{ type: "text", text }] };
1743
+ }
1744
+ function mcpErrorResult(message) {
1745
+ return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
1746
+ }
1747
+ function createGetUserInfoHandler(apiClient, productRef, getCustomerRef) {
1748
+ return async (args, extra) => {
1749
+ const customerRef = getCustomerRef(args, extra);
1750
+ try {
1751
+ if (!apiClient.getUserInfo) {
1752
+ return mcpErrorResult("getUserInfo is not available on this API client");
1753
+ }
1754
+ const userInfo = await apiClient.getUserInfo({ customerRef, productRef });
1755
+ return mcpTextResult(JSON.stringify(userInfo, null, 2));
1756
+ } catch (error) {
1757
+ return mcpErrorResult(
1758
+ `Failed to retrieve user information: ${error instanceof Error ? error.message : "Unknown error"}`
1759
+ );
1760
+ }
1761
+ };
1762
+ }
1763
+ function createUpgradeHandler(apiClient, productRef, getCustomerRef) {
1764
+ return async (args, extra) => {
1765
+ const customerRef = getCustomerRef(args, extra);
1766
+ const planRef = typeof args.planRef === "string" ? args.planRef : void 0;
1767
+ try {
1768
+ const result = await apiClient.createCheckoutSession({
1769
+ customerRef,
1770
+ productRef,
1771
+ ...planRef && { planRef }
1772
+ });
1773
+ const checkoutUrl = result.checkoutUrl;
1774
+ if (planRef) {
1775
+ const responseText2 = `## Upgrade
1776
+
1777
+ **[Click here to upgrade \u2192](${checkoutUrl})**
1778
+
1779
+ After completing the checkout, your purchase will be activated immediately.`;
1780
+ return mcpTextResult(responseText2);
1781
+ }
1782
+ const responseText = `## Upgrade Your Subscription
1783
+
1784
+ **[Click here to view pricing options and upgrade \u2192](${checkoutUrl})**
1785
+
1786
+ You'll be able to compare options and select the one that's right for you.`;
1787
+ return mcpTextResult(responseText);
1788
+ } catch (error) {
1789
+ return mcpErrorResult(
1790
+ `Failed to create checkout session: ${error instanceof Error ? error.message : "Unknown error"}`
1791
+ );
1792
+ }
1793
+ };
1794
+ }
1795
+ function createManageAccountHandler(apiClient, productRef, getCustomerRef) {
1796
+ return async (args, extra) => {
1797
+ const customerRef = getCustomerRef(args, extra);
1798
+ try {
1799
+ const session = await apiClient.createCustomerSession({ customerRef, productRef });
1800
+ const portalUrl = session.customerUrl;
1801
+ const responseText = `## Manage Your Account
1802
+
1803
+ Access your account management portal to:
1804
+ - View your current account status
1805
+ - See billing history and invoices
1806
+ - Update payment methods
1807
+ - Cancel or modify your subscription
1808
+
1809
+ **[Open Account Portal \u2192](${portalUrl})**
1810
+
1811
+ This link is secure and will expire after a short period.`;
1812
+ return mcpTextResult(responseText);
1813
+ } catch (error) {
1814
+ return mcpErrorResult(
1815
+ `Failed to create customer portal session: ${error instanceof Error ? error.message : "Unknown error"}`
1816
+ );
1817
+ }
1818
+ };
1819
+ }
1820
+ function createVirtualTools(apiClient, options) {
1821
+ const { product, getCustomerRef, exclude = [] } = options;
1822
+ const excludeSet = new Set(exclude);
1823
+ const allTools = [
1824
+ {
1825
+ ...TOOL_GET_USER_INFO,
1826
+ handler: createGetUserInfoHandler(apiClient, product, getCustomerRef)
1827
+ },
1828
+ {
1829
+ ...TOOL_UPGRADE,
1830
+ handler: createUpgradeHandler(apiClient, product, getCustomerRef)
1831
+ },
1832
+ {
1833
+ ...TOOL_MANAGE_ACCOUNT,
1834
+ handler: createManageAccountHandler(apiClient, product, getCustomerRef)
1835
+ }
1836
+ ];
1837
+ return allTools.filter((t) => !excludeSet.has(t.name));
1838
+ }
1839
+
1840
+ // src/register-virtual-tools-mcp.ts
1841
+ import { createRequire } from "module";
1842
+ function getNodeRequire() {
1843
+ try {
1844
+ return (0, eval)("require");
1845
+ } catch {
1846
+ return createRequire(`${process.cwd()}/package.json`);
1847
+ }
1848
+ }
1849
+ var nodeRequire = getNodeRequire();
1850
+ function getZod() {
1851
+ try {
1852
+ const zodModule = nodeRequire("zod");
1853
+ return zodModule.z ?? zodModule;
1854
+ } catch {
1855
+ throw new Error(
1856
+ "zod is required to use registerVirtualToolsMcp(). Install it as a dependency in your MCP server project."
1857
+ );
1858
+ }
1859
+ }
1860
+ function isJsonSchemaObject(value) {
1861
+ return Boolean(value && typeof value === "object");
1862
+ }
1863
+ function toZodSchema(property) {
1864
+ const z = getZod();
1865
+ if (Array.isArray(property.enum) && property.enum.length > 0) {
1866
+ if (property.enum.every((value) => typeof value === "string")) {
1867
+ const [first, ...rest] = property.enum;
1868
+ return z.enum([first, ...rest]);
1869
+ }
1870
+ if (property.enum.length === 1) {
1871
+ return z.literal(property.enum[0]);
1872
+ }
1873
+ return z.union(property.enum.map((value) => z.literal(value)));
1874
+ }
1875
+ switch (property.type) {
1876
+ case "string":
1877
+ return z.string();
1878
+ case "number":
1879
+ return z.number();
1880
+ case "integer":
1881
+ return z.number().int();
1882
+ case "boolean":
1883
+ return z.boolean();
1884
+ case "array":
1885
+ return z.array(
1886
+ isJsonSchemaObject(property.items) ? toZodSchema(property.items) : z.any()
1887
+ );
1888
+ case "object":
1889
+ return z.object(jsonSchemaToZodRawShape(property.properties || {}));
1890
+ default:
1891
+ return z.any();
1892
+ }
1893
+ }
1894
+ function jsonSchemaToZodRawShape(properties, required = []) {
1895
+ const requiredSet = new Set(required);
1896
+ return Object.fromEntries(
1897
+ Object.entries(properties).map(([name, property]) => {
1898
+ const schema = toZodSchema(property);
1899
+ const finalSchema = requiredSet.has(name) ? schema : schema.optional();
1900
+ return [name, finalSchema];
1901
+ })
1902
+ );
1903
+ }
1904
+ function defaultGetCustomerRef(_args, extra) {
1905
+ const fromExtra = extra?.authInfo?.extra?.customer_ref;
1906
+ return typeof fromExtra === "string" && fromExtra.trim() ? fromExtra.trim() : "anonymous";
1907
+ }
1908
+ function registerVirtualToolsMcpImpl(server, apiClient, options) {
1909
+ const { filter, mapDefinition, wrapHandler, getCustomerRef, ...virtualToolsOptions } = options;
1910
+ const virtualTools = createVirtualTools(apiClient, {
1911
+ ...virtualToolsOptions,
1912
+ getCustomerRef: getCustomerRef || defaultGetCustomerRef
1913
+ });
1914
+ for (const toolDefinition of virtualTools) {
1915
+ if (filter && !filter(toolDefinition)) continue;
1916
+ const mappedDefinition = mapDefinition ? mapDefinition(toolDefinition) : toolDefinition;
1917
+ const wrappedHandler = wrapHandler ? wrapHandler(mappedDefinition.handler, mappedDefinition) : mappedDefinition.handler;
1918
+ server.registerTool(
1919
+ mappedDefinition.name,
1920
+ {
1921
+ description: mappedDefinition.description,
1922
+ inputSchema: jsonSchemaToZodRawShape(
1923
+ mappedDefinition.inputSchema.properties,
1924
+ mappedDefinition.inputSchema.required || []
1925
+ )
1926
+ },
1927
+ wrappedHandler
1928
+ );
1929
+ }
1930
+ }
1931
+
1932
+ // src/factory.ts
1933
+ function createSolvaPay(config) {
1934
+ let resolvedConfig;
1935
+ if (!config) {
1936
+ const envConfig = getSolvaPayConfig();
1937
+ resolvedConfig = {
1938
+ apiKey: envConfig.apiKey,
1939
+ apiBaseUrl: envConfig.apiBaseUrl
1940
+ };
1941
+ } else {
1942
+ resolvedConfig = config;
1943
+ }
1944
+ const apiClient = resolvedConfig.apiClient || createSolvaPayClient({
1945
+ apiKey: resolvedConfig.apiKey,
1946
+ apiBaseUrl: resolvedConfig.apiBaseUrl
1947
+ });
1948
+ const paywall = new SolvaPayPaywall(apiClient, {
1949
+ debug: process.env.SOLVAPAY_DEBUG !== "false",
1950
+ limitsCacheTTL: resolvedConfig.limitsCacheTTL
1951
+ });
1952
+ return {
1953
+ // Direct access to API client for advanced operations
1954
+ apiClient,
1955
+ // Common API methods exposed directly for convenience
1956
+ ensureCustomer(customerRef, externalRef, options) {
1957
+ return paywall.ensureCustomer(customerRef, externalRef, options);
1958
+ },
1959
+ createPaymentIntent(params) {
1960
+ if (!apiClient.createPaymentIntent) {
1961
+ throw new SolvaPayError3("createPaymentIntent is not available on this API client");
1962
+ }
1963
+ return apiClient.createPaymentIntent(params);
1964
+ },
1965
+ createTopupPaymentIntent(params) {
1966
+ if (!apiClient.createTopupPaymentIntent) {
1967
+ throw new SolvaPayError3("createTopupPaymentIntent is not available on this API client");
1968
+ }
1969
+ return apiClient.createTopupPaymentIntent(params);
1970
+ },
1971
+ processPaymentIntent(params) {
1972
+ if (!apiClient.processPaymentIntent) {
1973
+ throw new SolvaPayError3("processPaymentIntent is not available on this API client");
1974
+ }
1975
+ return apiClient.processPaymentIntent(params);
1976
+ },
1977
+ checkLimits(params) {
1978
+ return apiClient.checkLimits(params);
1979
+ },
1980
+ trackUsage(params) {
1981
+ return apiClient.trackUsage(params);
1982
+ },
1983
+ createCustomer(params) {
1984
+ if (!apiClient.createCustomer) {
1985
+ throw new SolvaPayError3("createCustomer is not available on this API client");
1986
+ }
1987
+ return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
1988
+ },
1989
+ getCustomer(params) {
1990
+ return apiClient.getCustomer(params);
1991
+ },
1992
+ getCustomerBalance(params) {
1993
+ if (!apiClient.getCustomerBalance) {
1994
+ throw new SolvaPayError3("getCustomerBalance is not available on this API client");
1995
+ }
1996
+ return apiClient.getCustomerBalance(params);
1997
+ },
1998
+ createCheckoutSession(params) {
1999
+ return apiClient.createCheckoutSession({
2000
+ customerRef: params.customerRef,
2001
+ productRef: params.productRef,
2002
+ planRef: params.planRef
2003
+ });
2004
+ },
2005
+ createCustomerSession(params) {
2006
+ return apiClient.createCustomerSession(params);
2007
+ },
2008
+ activatePlan(params) {
2009
+ if (!apiClient.activatePlan) {
2010
+ throw new SolvaPayError3("activatePlan is not available on this API client");
2011
+ }
2012
+ return apiClient.activatePlan(params);
2013
+ },
2014
+ bootstrapMcpProduct(params) {
2015
+ if (!apiClient.bootstrapMcpProduct) {
2016
+ throw new SolvaPayError3("bootstrapMcpProduct is not available on this API client");
2017
+ }
2018
+ return apiClient.bootstrapMcpProduct(params);
2019
+ },
2020
+ configureMcpPlans(productRef, params) {
2021
+ if (!apiClient.configureMcpPlans) {
2022
+ throw new SolvaPayError3("configureMcpPlans is not available on this API client");
2023
+ }
2024
+ return apiClient.configureMcpPlans(productRef, params);
2025
+ },
2026
+ getVirtualTools(options) {
2027
+ return createVirtualTools(apiClient, options);
2028
+ },
2029
+ async registerVirtualToolsMcp(server, options) {
2030
+ await registerVirtualToolsMcpImpl(server, apiClient, options);
2031
+ },
2032
+ // Payable API for framework-specific handlers
2033
+ payable(options = {}) {
2034
+ const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
2035
+ const usageType = options.usageType || "requests";
2036
+ const metadata = { product, usageType };
2037
+ return {
2038
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2039
+ http(businessLogic, adapterOptions) {
2040
+ const adapter = new HttpAdapter({
2041
+ ...adapterOptions,
2042
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
2043
+ });
2044
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
2045
+ return async (req, reply) => {
2046
+ const handler = await handlerPromise;
2047
+ return handler([req, reply]);
2048
+ };
2049
+ },
2050
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2051
+ next(businessLogic, adapterOptions) {
2052
+ const adapter = new NextAdapter({
2053
+ ...adapterOptions,
2054
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
2055
+ });
2056
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
2057
+ return async (request, context) => {
2058
+ const handler = await handlerPromise;
2059
+ return handler([request, context]);
2060
+ };
2061
+ },
2062
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2063
+ mcp(businessLogic, adapterOptions) {
2064
+ const adapter = new McpAdapter({
2065
+ ...adapterOptions,
2066
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
2067
+ });
2068
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
2069
+ return async (args, extra) => {
2070
+ const handler = await handlerPromise;
2071
+ return handler(args, extra);
2072
+ };
2073
+ },
2074
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2075
+ async function(businessLogic) {
2076
+ const getCustomerRef = (args) => {
2077
+ const configuredRef = options.getCustomerRef?.(args);
2078
+ if (typeof configuredRef === "string") {
2079
+ return configuredRef;
2080
+ }
2081
+ return args.auth?.customer_ref || "anonymous";
2082
+ };
2083
+ return paywall.protect(businessLogic, metadata, getCustomerRef);
2084
+ }
2085
+ };
2086
+ }
2087
+ };
2088
+ }
2089
+
2090
+ // src/helpers/customer.ts
2091
+ async function syncCustomerCore(request, options = {}) {
2092
+ try {
2093
+ const userResult = await getAuthenticatedUserCore(request, {
2094
+ includeEmail: options.includeEmail,
2095
+ includeName: options.includeName
2096
+ });
2097
+ if (isErrorResult(userResult)) {
2098
+ return userResult;
2099
+ }
2100
+ const { userId, email, name } = userResult;
2101
+ const solvaPay = options.solvaPay || createSolvaPay();
2102
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2103
+ email: email || void 0,
2104
+ name: name || void 0
2105
+ });
2106
+ return customerRef;
2107
+ } catch (error) {
2108
+ return handleRouteError(error, "Sync customer", "Failed to sync customer");
2109
+ }
2110
+ }
2111
+ async function getCustomerBalanceCore(request, options = {}) {
2112
+ try {
2113
+ const userResult = await getAuthenticatedUserCore(request);
2114
+ if (isErrorResult(userResult)) {
2115
+ return userResult;
2116
+ }
2117
+ const { userId, email, name } = userResult;
2118
+ const solvaPay = options.solvaPay || createSolvaPay();
2119
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2120
+ email: email || void 0,
2121
+ name: name || void 0
2122
+ });
2123
+ const result = await solvaPay.getCustomerBalance({ customerRef });
2124
+ return result;
2125
+ } catch (error) {
2126
+ return handleRouteError(error, "Get customer credits", "Failed to get customer credits");
2127
+ }
2128
+ }
2129
+
2130
+ // src/helpers/payment.ts
2131
+ async function createPaymentIntentCore(request, body, options = {}) {
2132
+ try {
2133
+ if (!body.planRef || !body.productRef) {
2134
+ return {
2135
+ error: "Missing required parameters: planRef and productRef are required",
2136
+ status: 400
2137
+ };
2138
+ }
2139
+ const customerResult = await syncCustomerCore(request, {
2140
+ solvaPay: options.solvaPay,
2141
+ includeEmail: options.includeEmail,
2142
+ includeName: options.includeName
2143
+ });
2144
+ if (isErrorResult(customerResult)) {
2145
+ return customerResult;
2146
+ }
2147
+ const customerRef = customerResult;
2148
+ const solvaPay = options.solvaPay || createSolvaPay();
2149
+ const paymentIntent = await solvaPay.createPaymentIntent({
2150
+ productRef: body.productRef,
2151
+ planRef: body.planRef,
2152
+ customerRef
2153
+ });
2154
+ return {
2155
+ processorPaymentId: paymentIntent.processorPaymentId,
2156
+ clientSecret: paymentIntent.clientSecret,
2157
+ publishableKey: paymentIntent.publishableKey,
2158
+ accountId: paymentIntent.accountId,
2159
+ customerRef
2160
+ };
2161
+ } catch (error) {
2162
+ return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
2163
+ }
2164
+ }
2165
+ async function createTopupPaymentIntentCore(request, body, options = {}) {
2166
+ try {
2167
+ if (!body.amount || body.amount <= 0) {
2168
+ return {
2169
+ error: "Missing or invalid amount: must be a positive number",
2170
+ status: 400
2171
+ };
2172
+ }
2173
+ if (!body.currency) {
2174
+ return {
2175
+ error: "Missing required parameter: currency",
2176
+ status: 400
2177
+ };
2178
+ }
2179
+ if (body.currency !== body.currency.toUpperCase()) {
2180
+ return {
2181
+ error: `Invalid currency "${body.currency}": must be an uppercase ISO 4217 code (e.g. "USD", "EUR")`,
2182
+ status: 400
2183
+ };
2184
+ }
2185
+ const customerResult = await syncCustomerCore(request, {
2186
+ solvaPay: options.solvaPay,
2187
+ includeEmail: options.includeEmail,
2188
+ includeName: options.includeName
2189
+ });
2190
+ if (isErrorResult(customerResult)) {
2191
+ return customerResult;
2192
+ }
2193
+ const customerRef = customerResult;
2194
+ const solvaPay = options.solvaPay || createSolvaPay();
2195
+ const paymentIntent = await solvaPay.createTopupPaymentIntent({
2196
+ customerRef,
2197
+ amount: body.amount,
2198
+ currency: body.currency,
2199
+ description: body.description
2200
+ });
2201
+ return {
2202
+ processorPaymentId: paymentIntent.processorPaymentId,
2203
+ clientSecret: paymentIntent.clientSecret,
2204
+ publishableKey: paymentIntent.publishableKey,
2205
+ accountId: paymentIntent.accountId,
2206
+ customerRef
2207
+ };
2208
+ } catch (error) {
2209
+ return handleRouteError(
2210
+ error,
2211
+ "Create topup payment intent",
2212
+ "Topup payment intent creation failed"
2213
+ );
2214
+ }
2215
+ }
2216
+ async function processPaymentIntentCore(request, body, options = {}) {
2217
+ try {
2218
+ if (!body.paymentIntentId || !body.productRef) {
2219
+ return {
2220
+ error: "paymentIntentId and productRef are required",
2221
+ status: 400
2222
+ };
2223
+ }
2224
+ const customerResult = await syncCustomerCore(request, {
2225
+ solvaPay: options.solvaPay
2226
+ });
2227
+ if (isErrorResult(customerResult)) {
2228
+ return customerResult;
2229
+ }
2230
+ const customerRef = customerResult;
2231
+ const solvaPay = options.solvaPay || createSolvaPay();
2232
+ const result = await solvaPay.processPaymentIntent({
2233
+ paymentIntentId: body.paymentIntentId,
2234
+ productRef: body.productRef,
2235
+ customerRef,
2236
+ planRef: body.planRef
2237
+ });
2238
+ return result;
2239
+ } catch (error) {
2240
+ return handleRouteError(error, "Process payment intent", "Payment processing failed");
2241
+ }
2242
+ }
2243
+
2244
+ // src/helpers/checkout.ts
2245
+ async function createCheckoutSessionCore(request, body, options = {}) {
2246
+ try {
2247
+ if (!body.productRef) {
2248
+ return {
2249
+ error: "Missing required parameter: productRef is required",
2250
+ status: 400
2251
+ };
2252
+ }
2253
+ const customerResult = await syncCustomerCore(request, {
2254
+ solvaPay: options.solvaPay,
2255
+ includeEmail: options.includeEmail,
2256
+ includeName: options.includeName
2257
+ });
2258
+ if (isErrorResult(customerResult)) {
2259
+ return customerResult;
2260
+ }
2261
+ const customerRef = customerResult;
2262
+ let returnUrl = body.returnUrl || options.returnUrl;
2263
+ if (!returnUrl) {
2264
+ try {
2265
+ const url = new URL(request.url);
2266
+ returnUrl = url.origin;
2267
+ } catch {
2268
+ }
2269
+ }
2270
+ const solvaPay = options.solvaPay || createSolvaPay();
2271
+ const session = await solvaPay.createCheckoutSession({
2272
+ productRef: body.productRef,
2273
+ customerRef,
2274
+ planRef: body.planRef || void 0,
2275
+ returnUrl
2276
+ });
2277
+ return {
2278
+ sessionId: session.sessionId,
2279
+ checkoutUrl: session.checkoutUrl
2280
+ };
2281
+ } catch (error) {
2282
+ return handleRouteError(error, "Create checkout session", "Checkout session creation failed");
2283
+ }
2284
+ }
2285
+ async function createCustomerSessionCore(request, options = {}) {
2286
+ try {
2287
+ const customerResult = await syncCustomerCore(request, {
2288
+ solvaPay: options.solvaPay,
2289
+ includeEmail: options.includeEmail,
2290
+ includeName: options.includeName
2291
+ });
2292
+ if (isErrorResult(customerResult)) {
2293
+ return customerResult;
2294
+ }
2295
+ const customerRef = customerResult;
2296
+ const solvaPay = options.solvaPay || createSolvaPay();
2297
+ const session = await solvaPay.createCustomerSession({
2298
+ customerRef
2299
+ });
2300
+ return session;
2301
+ } catch (error) {
2302
+ return handleRouteError(error, "Create customer session", "Customer session creation failed");
2303
+ }
2304
+ }
2305
+
2306
+ // src/helpers/renewal.ts
2307
+ import { SolvaPayError as SolvaPayError4 } from "@solvapay/core";
2308
+ async function cancelPurchaseCore(request, body, options = {}) {
2309
+ try {
2310
+ if (!body.purchaseRef) {
2311
+ return {
2312
+ error: "Missing required parameter: purchaseRef is required",
2313
+ status: 400
2314
+ };
2315
+ }
2316
+ const solvaPay = options.solvaPay || createSolvaPay();
2317
+ if (!solvaPay.apiClient.cancelPurchase) {
2318
+ return {
2319
+ error: "Cancel purchase method not available on SDK client",
2320
+ status: 500
2321
+ };
2322
+ }
2323
+ let cancelledPurchase = await solvaPay.apiClient.cancelPurchase({
2324
+ purchaseRef: body.purchaseRef,
2325
+ reason: body.reason
2326
+ });
2327
+ if (!cancelledPurchase || typeof cancelledPurchase !== "object") {
2328
+ return {
2329
+ error: "Invalid response from cancel purchase endpoint",
2330
+ status: 500
2331
+ };
2332
+ }
2333
+ const responseObj = cancelledPurchase;
2334
+ if (responseObj.purchase && typeof responseObj.purchase === "object") {
2335
+ cancelledPurchase = responseObj.purchase;
2336
+ }
2337
+ if (!cancelledPurchase.reference) {
2338
+ return {
2339
+ error: "Cancel purchase response missing required fields",
2340
+ status: 500
2341
+ };
2342
+ }
2343
+ const isCancelled = cancelledPurchase.status === "cancelled" || cancelledPurchase.cancelledAt;
2344
+ if (!isCancelled) {
2345
+ return {
2346
+ error: `Purchase cancellation failed: backend returned status '${cancelledPurchase.status}' without cancelledAt timestamp`,
2347
+ status: 500
2348
+ };
2349
+ }
2350
+ await new Promise((resolve) => setTimeout(resolve, 500));
2351
+ return cancelledPurchase;
2352
+ } catch (error) {
2353
+ if (error instanceof SolvaPayError4) {
2354
+ const errorMessage = error.message;
2355
+ if (errorMessage.includes("not found")) {
2356
+ return {
2357
+ error: "Purchase not found",
2358
+ status: 404,
2359
+ details: errorMessage
2360
+ };
2361
+ }
2362
+ if (errorMessage.includes("cannot be cancelled") || errorMessage.includes("does not belong to provider")) {
2363
+ return {
2364
+ error: "Purchase cannot be cancelled or does not belong to provider",
2365
+ status: 400,
2366
+ details: errorMessage
2367
+ };
2368
+ }
2369
+ return {
2370
+ error: errorMessage,
2371
+ status: 500,
2372
+ details: errorMessage
2373
+ };
2374
+ }
2375
+ return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
2376
+ }
2377
+ }
2378
+ async function reactivatePurchaseCore(request, body, options = {}) {
2379
+ try {
2380
+ if (!body.purchaseRef) {
2381
+ return {
2382
+ error: "Missing required parameter: purchaseRef is required",
2383
+ status: 400
2384
+ };
2385
+ }
2386
+ const solvaPay = options.solvaPay || createSolvaPay();
2387
+ if (!solvaPay.apiClient.reactivatePurchase) {
2388
+ return {
2389
+ error: "Reactivate purchase method not available on SDK client",
2390
+ status: 500
2391
+ };
2392
+ }
2393
+ let reactivatedPurchase = await solvaPay.apiClient.reactivatePurchase({
2394
+ purchaseRef: body.purchaseRef
2395
+ });
2396
+ if (!reactivatedPurchase || typeof reactivatedPurchase !== "object") {
2397
+ return {
2398
+ error: "Invalid response from reactivate purchase endpoint",
2399
+ status: 500
2400
+ };
2401
+ }
2402
+ const responseObj = reactivatedPurchase;
2403
+ if (responseObj.purchase && typeof responseObj.purchase === "object") {
2404
+ reactivatedPurchase = responseObj.purchase;
2405
+ }
2406
+ if (!reactivatedPurchase.reference) {
2407
+ return {
2408
+ error: "Reactivate purchase response missing required fields",
2409
+ status: 500
2410
+ };
2411
+ }
2412
+ if (reactivatedPurchase.cancelledAt) {
2413
+ return {
2414
+ error: `Purchase reactivation failed: cancelledAt is still set`,
2415
+ status: 500
2416
+ };
2417
+ }
2418
+ await new Promise((resolve) => setTimeout(resolve, 500));
2419
+ return reactivatedPurchase;
2420
+ } catch (error) {
2421
+ if (error instanceof SolvaPayError4) {
2422
+ const errorMessage = error.message;
2423
+ if (errorMessage.includes("not found")) {
2424
+ return {
2425
+ error: "Purchase not found",
2426
+ status: 404,
2427
+ details: errorMessage
2428
+ };
2429
+ }
2430
+ if (errorMessage.includes("cannot be reactivated") || errorMessage.includes("not pending cancellation") || errorMessage.includes("already been fully cancelled") || errorMessage.includes("already ended")) {
2431
+ return {
2432
+ error: "Purchase cannot be reactivated",
2433
+ status: 400,
2434
+ details: errorMessage
2435
+ };
2436
+ }
2437
+ return {
2438
+ error: errorMessage,
2439
+ status: 500,
2440
+ details: errorMessage
2441
+ };
2442
+ }
2443
+ return handleRouteError(error, "Reactivate purchase", "Failed to reactivate purchase");
2444
+ }
2445
+ }
2446
+
2447
+ // src/helpers/activation.ts
2448
+ async function activatePlanCore(request, body, options = {}) {
2449
+ try {
2450
+ if (!body.productRef || !body.planRef) {
2451
+ return {
2452
+ error: "Missing required parameters: productRef and planRef are required",
2453
+ status: 400
2454
+ };
2455
+ }
2456
+ const customerResult = await syncCustomerCore(request, {
2457
+ solvaPay: options.solvaPay,
2458
+ includeEmail: options.includeEmail,
2459
+ includeName: options.includeName
2460
+ });
2461
+ if (isErrorResult(customerResult)) {
2462
+ return customerResult;
2463
+ }
2464
+ const customerRef = customerResult;
2465
+ const solvaPay = options.solvaPay || createSolvaPay();
2466
+ return await solvaPay.activatePlan({
2467
+ customerRef,
2468
+ productRef: body.productRef,
2469
+ planRef: body.planRef
2470
+ });
2471
+ } catch (error) {
2472
+ return handleRouteError(error, "Activate plan", "Plan activation failed");
2473
+ }
2474
+ }
2475
+
2476
+ // src/helpers/payment-method.ts
2477
+ async function getPaymentMethodCore(request, options = {}) {
2478
+ try {
2479
+ const customerResult = await syncCustomerCore(request, {
2480
+ solvaPay: options.solvaPay,
2481
+ includeEmail: options.includeEmail,
2482
+ includeName: options.includeName
2483
+ });
2484
+ if (isErrorResult(customerResult)) {
2485
+ return customerResult;
2486
+ }
2487
+ const customerRef = customerResult;
2488
+ const solvaPay = options.solvaPay || createSolvaPay();
2489
+ if (!solvaPay.apiClient.getPaymentMethod) {
2490
+ return {
2491
+ error: "getPaymentMethod is not implemented on this API client",
2492
+ status: 500
2493
+ };
2494
+ }
2495
+ return await solvaPay.apiClient.getPaymentMethod({ customerRef });
2496
+ } catch (error) {
2497
+ return handleRouteError(error, "Get payment method", "Failed to load payment method");
2498
+ }
2499
+ }
2500
+
2501
+ // src/helpers/plans.ts
2502
+ import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
2503
+ async function listPlansCore(request, options = {}) {
2504
+ try {
2505
+ const url = new URL(request.url);
2506
+ const productRef = url.searchParams.get("productRef");
2507
+ if (!productRef) {
2508
+ return {
2509
+ error: "Missing required parameter: productRef",
2510
+ status: 400
2511
+ };
2512
+ }
2513
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2514
+ const config = getSolvaPayConfig2();
2515
+ if (!config.apiKey) return null;
2516
+ return createSolvaPayClient({
2517
+ apiKey: config.apiKey,
2518
+ apiBaseUrl: config.apiBaseUrl
2519
+ });
2520
+ })();
2521
+ if (!apiClient) {
2522
+ return {
2523
+ error: "Server configuration error: SolvaPay secret key not configured",
2524
+ status: 500
2525
+ };
2526
+ }
2527
+ if (!apiClient.listPlans) {
2528
+ return {
2529
+ error: "List plans method not available",
2530
+ status: 500
2531
+ };
2532
+ }
2533
+ const plans = await apiClient.listPlans(productRef);
2534
+ return {
2535
+ plans: plans || [],
2536
+ productRef
2537
+ };
2538
+ } catch (error) {
2539
+ return handleRouteError(error, "List plans", "Failed to fetch plans");
2540
+ }
2541
+ }
2542
+
2543
+ // src/helpers/merchant.ts
2544
+ import { getSolvaPayConfig as getSolvaPayConfig3 } from "@solvapay/core";
2545
+ async function getMerchantCore(_request, options = {}) {
2546
+ try {
2547
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2548
+ const config = getSolvaPayConfig3();
2549
+ if (!config.apiKey) return null;
2550
+ return createSolvaPayClient({
2551
+ apiKey: config.apiKey,
2552
+ apiBaseUrl: config.apiBaseUrl
2553
+ });
2554
+ })();
2555
+ if (!apiClient) {
2556
+ return {
2557
+ error: "Server configuration error: SolvaPay secret key not configured",
2558
+ status: 500
2559
+ };
2560
+ }
2561
+ if (!apiClient.getMerchant) {
2562
+ return {
2563
+ error: "Get merchant method not available",
2564
+ status: 500
2565
+ };
2566
+ }
2567
+ const merchant = await apiClient.getMerchant();
2568
+ return merchant;
2569
+ } catch (error) {
2570
+ return handleRouteError(error, "Get merchant", "Failed to fetch merchant");
2571
+ }
2572
+ }
2573
+
2574
+ // src/helpers/product.ts
2575
+ import { getSolvaPayConfig as getSolvaPayConfig4 } from "@solvapay/core";
2576
+ async function getProductCore(request, options = {}) {
2577
+ try {
2578
+ const url = new URL(request.url);
2579
+ const productRef = url.searchParams.get("productRef");
2580
+ if (!productRef) {
2581
+ return {
2582
+ error: "Missing required parameter: productRef",
2583
+ status: 400
2584
+ };
2585
+ }
2586
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2587
+ const config = getSolvaPayConfig4();
2588
+ if (!config.apiKey) return null;
2589
+ return createSolvaPayClient({
2590
+ apiKey: config.apiKey,
2591
+ apiBaseUrl: config.apiBaseUrl
2592
+ });
2593
+ })();
2594
+ if (!apiClient) {
2595
+ return {
2596
+ error: "Server configuration error: SolvaPay secret key not configured",
2597
+ status: 500
2598
+ };
2599
+ }
2600
+ if (!apiClient.getProduct) {
2601
+ return {
2602
+ error: "Get product method not available",
2603
+ status: 500
2604
+ };
2605
+ }
2606
+ const product = await apiClient.getProduct(productRef);
2607
+ return product;
2608
+ } catch (error) {
2609
+ return handleRouteError(error, "Get product", "Failed to fetch product");
2610
+ }
2611
+ }
2612
+
2613
+ // src/helpers/purchase.ts
2614
+ async function checkPurchaseCore(request, options = {}) {
2615
+ try {
2616
+ const userResult = await getAuthenticatedUserCore(request, {
2617
+ includeEmail: options.includeEmail,
2618
+ includeName: options.includeName
2619
+ });
2620
+ if (isErrorResult(userResult)) {
2621
+ return userResult;
2622
+ }
2623
+ const { userId, email, name } = userResult;
2624
+ const solvaPay = options.solvaPay || createSolvaPay();
2625
+ const cachedCustomerRef = request.headers.get("x-solvapay-customer-ref");
2626
+ if (cachedCustomerRef) {
2627
+ try {
2628
+ const customer = await solvaPay.getCustomer({ customerRef: cachedCustomerRef });
2629
+ if (customer && customer.customerRef) {
2630
+ if (customer.externalRef && customer.externalRef === userId) {
2631
+ const filteredPurchases = (customer.purchases || []).filter(
2632
+ (p) => p.status === "active"
2633
+ );
2634
+ return {
2635
+ customerRef: customer.customerRef,
2636
+ email: customer.email,
2637
+ name: customer.name,
2638
+ purchases: filteredPurchases
2639
+ };
2640
+ }
2641
+ }
2642
+ } catch {
2643
+ }
2644
+ }
2645
+ try {
2646
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2647
+ email: email || void 0,
2648
+ name: name || void 0
2649
+ });
2650
+ const customer = await solvaPay.getCustomer({ customerRef });
2651
+ const filteredPurchases = (customer.purchases || []).filter((p) => p.status === "active");
2652
+ return {
2653
+ customerRef: customer.customerRef || userId,
2654
+ email: customer.email,
2655
+ name: customer.name,
2656
+ purchases: filteredPurchases
2657
+ };
2658
+ } catch {
2659
+ return {
2660
+ customerRef: userId,
2661
+ purchases: []
2662
+ };
2663
+ }
2664
+ } catch (error) {
2665
+ return handleRouteError(error, "Check purchase", "Failed to check purchase");
2666
+ }
2667
+ }
2668
+
2669
+ // src/helpers/usage.ts
2670
+ async function trackUsageCore(request, body, options = {}) {
2671
+ try {
2672
+ const userResult = await getAuthenticatedUserCore(request);
2673
+ if (isErrorResult(userResult)) {
2674
+ return userResult;
2675
+ }
2676
+ const { userId, email, name } = userResult;
2677
+ const solvaPay = options.solvaPay || createSolvaPay();
2678
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2679
+ email: email || void 0,
2680
+ name: name || void 0
2681
+ });
2682
+ await solvaPay.trackUsage({
2683
+ customerRef,
2684
+ actionType: body.actionType,
2685
+ units: body.units,
2686
+ productRef: body.productRef,
2687
+ description: body.description,
2688
+ metadata: body.metadata
2689
+ });
2690
+ return { success: true };
2691
+ } catch (error) {
2692
+ return handleRouteError(error, "Track usage", "Track usage failed");
2693
+ }
2694
+ }
2695
+
2696
+ // src/edge.ts
2697
+ import { SolvaPayError as SolvaPayError5 } from "@solvapay/core";
2698
+ function timingSafeEqual(a, b) {
2699
+ if (a.length !== b.length) return false;
2700
+ let mismatch = 0;
2701
+ for (let i = 0; i < a.length; i++) {
2702
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
2703
+ }
2704
+ return mismatch === 0;
2705
+ }
2706
+ async function verifyWebhook({
2707
+ body,
2708
+ signature,
2709
+ secret
2710
+ }) {
2711
+ const toleranceSec = 300;
2712
+ if (!signature) throw new SolvaPayError5("Missing webhook signature");
2713
+ const parts = signature.split(",");
2714
+ const tPart = parts.find((p) => p.startsWith("t="));
2715
+ const v1Part = parts.find((p) => p.startsWith("v1="));
2716
+ if (!tPart || !v1Part) {
2717
+ throw new SolvaPayError5("Malformed webhook signature");
2718
+ }
2719
+ const timestamp = parseInt(tPart.slice(2), 10);
2720
+ const receivedHmac = v1Part.slice(3);
2721
+ if (Number.isNaN(timestamp) || !receivedHmac) {
2722
+ throw new SolvaPayError5("Malformed webhook signature");
2723
+ }
2724
+ if (toleranceSec > 0) {
2725
+ const age = Math.abs(Math.floor(Date.now() / 1e3) - timestamp);
2726
+ if (age > toleranceSec) {
2727
+ throw new SolvaPayError5("Webhook signature timestamp too old");
2728
+ }
2729
+ }
2730
+ const enc = new TextEncoder();
2731
+ const key = await crypto.subtle.importKey(
2732
+ "raw",
2733
+ enc.encode(secret),
2734
+ { name: "HMAC", hash: "SHA-256" },
2735
+ false,
2736
+ ["sign"]
2737
+ );
2738
+ const sigBuf = await crypto.subtle.sign("HMAC", key, enc.encode(`${timestamp}.${body}`));
2739
+ const expectedHmac = Array.from(new Uint8Array(sigBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
2740
+ if (!timingSafeEqual(expectedHmac, receivedHmac)) {
2741
+ throw new SolvaPayError5("Invalid webhook signature");
2742
+ }
2743
+ try {
2744
+ return JSON.parse(body);
2745
+ } catch {
2746
+ throw new SolvaPayError5("Invalid webhook payload: body is not valid JSON");
2747
+ }
2748
+ }
2749
+
2750
+ // src/fetch/cors.ts
2751
+ var corsConfig = { origins: ["*"] };
2752
+ function configureCors(config) {
2753
+ corsConfig = config;
2754
+ }
2755
+ function getCorsHeaders(req) {
2756
+ const origin = req.headers.get("Origin");
2757
+ const headers = {
2758
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
2759
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, x-solvapay-customer-ref",
2760
+ "Access-Control-Max-Age": "86400"
2761
+ };
2762
+ if (corsConfig.origins.includes("*")) {
2763
+ headers["Access-Control-Allow-Origin"] = "*";
2764
+ } else if (origin && corsConfig.origins.includes(origin)) {
2765
+ headers["Access-Control-Allow-Origin"] = origin;
2766
+ headers["Vary"] = "Origin";
2767
+ }
2768
+ return headers;
2769
+ }
2770
+ function handleCors(req) {
2771
+ if (req.method !== "OPTIONS") {
2772
+ return null;
2773
+ }
2774
+ const headers = getCorsHeaders(req);
2775
+ return new Response(null, {
2776
+ status: 204,
2777
+ headers
2778
+ });
2779
+ }
2780
+
2781
+ // src/fetch/utils.ts
2782
+ function errorResponse(result, req) {
2783
+ const corsHeaders = req ? getCorsHeaders(req) : {};
2784
+ return new Response(
2785
+ JSON.stringify({ error: result.error, ...result.details ? { details: result.details } : {} }),
2786
+ {
2787
+ status: result.status,
2788
+ headers: {
2789
+ "Content-Type": "application/json",
2790
+ ...corsHeaders
2791
+ }
2792
+ }
2793
+ );
2794
+ }
2795
+ function jsonResponseWithCors(data, req, status = 200) {
2796
+ const corsHeaders = getCorsHeaders(req);
2797
+ return new Response(JSON.stringify(data), {
2798
+ status,
2799
+ headers: {
2800
+ "Content-Type": "application/json",
2801
+ ...corsHeaders
2802
+ }
2803
+ });
2804
+ }
2805
+
2806
+ // src/fetch/handlers.ts
2807
+ async function parseJsonBody(req) {
2808
+ try {
2809
+ return await req.json();
2810
+ } catch {
2811
+ return {};
2812
+ }
2813
+ }
2814
+ async function checkPurchase(req) {
2815
+ const corsResponse = handleCors(req);
2816
+ if (corsResponse) return corsResponse;
2817
+ const result = await checkPurchaseCore(req);
2818
+ if (isErrorResult(result)) {
2819
+ return errorResponse(result, req);
2820
+ }
2821
+ return jsonResponseWithCors(result, req);
2822
+ }
2823
+ async function trackUsage(req) {
2824
+ const corsResponse = handleCors(req);
2825
+ if (corsResponse) return corsResponse;
2826
+ const body = await parseJsonBody(req);
2827
+ const result = await trackUsageCore(req, body, {});
2828
+ if (isErrorResult(result)) {
2829
+ return errorResponse(result, req);
2830
+ }
2831
+ return jsonResponseWithCors(result, req);
2832
+ }
2833
+ async function createPaymentIntent(req) {
2834
+ const corsResponse = handleCors(req);
2835
+ if (corsResponse) return corsResponse;
2836
+ const body = await parseJsonBody(req);
2837
+ const result = await createPaymentIntentCore(req, body);
2838
+ if (isErrorResult(result)) {
2839
+ return errorResponse(result, req);
2840
+ }
2841
+ return jsonResponseWithCors(result, req);
2842
+ }
2843
+ async function processPayment(req) {
2844
+ const corsResponse = handleCors(req);
2845
+ if (corsResponse) return corsResponse;
2846
+ const body = await parseJsonBody(req);
2847
+ const result = await processPaymentIntentCore(req, body);
2848
+ if (isErrorResult(result)) {
2849
+ return errorResponse(result, req);
2850
+ }
2851
+ return jsonResponseWithCors(result, req);
2852
+ }
2853
+ async function createTopupPaymentIntent(req) {
2854
+ const corsResponse = handleCors(req);
2855
+ if (corsResponse) return corsResponse;
2856
+ const body = await parseJsonBody(req);
2857
+ const result = await createTopupPaymentIntentCore(req, body);
2858
+ if (isErrorResult(result)) {
2859
+ return errorResponse(result, req);
2860
+ }
2861
+ return jsonResponseWithCors(result, req);
2862
+ }
2863
+ async function customerBalance(req) {
2864
+ const corsResponse = handleCors(req);
2865
+ if (corsResponse) return corsResponse;
2866
+ const result = await getCustomerBalanceCore(req);
2867
+ if (isErrorResult(result)) {
2868
+ return errorResponse(result, req);
2869
+ }
2870
+ return jsonResponseWithCors(result, req);
2871
+ }
2872
+ async function cancelRenewal(req) {
2873
+ const corsResponse = handleCors(req);
2874
+ if (corsResponse) return corsResponse;
2875
+ const body = await parseJsonBody(req);
2876
+ const result = await cancelPurchaseCore(req, body);
2877
+ if (isErrorResult(result)) {
2878
+ return errorResponse(result, req);
2879
+ }
2880
+ return jsonResponseWithCors(result, req);
2881
+ }
2882
+ async function reactivateRenewal(req) {
2883
+ const corsResponse = handleCors(req);
2884
+ if (corsResponse) return corsResponse;
2885
+ const body = await parseJsonBody(req);
2886
+ const result = await reactivatePurchaseCore(req, body);
2887
+ if (isErrorResult(result)) {
2888
+ return errorResponse(result, req);
2889
+ }
2890
+ return jsonResponseWithCors(result, req);
2891
+ }
2892
+ async function activatePlan(req) {
2893
+ const corsResponse = handleCors(req);
2894
+ if (corsResponse) return corsResponse;
2895
+ const body = await parseJsonBody(req);
2896
+ const result = await activatePlanCore(req, body);
2897
+ if (isErrorResult(result)) {
2898
+ return errorResponse(result, req);
2899
+ }
2900
+ return jsonResponseWithCors(result, req);
2901
+ }
2902
+ async function getPaymentMethod(req) {
2903
+ const corsResponse = handleCors(req);
2904
+ if (corsResponse) return corsResponse;
2905
+ const result = await getPaymentMethodCore(req);
2906
+ if (isErrorResult(result)) {
2907
+ return errorResponse(result, req);
2908
+ }
2909
+ return jsonResponseWithCors(result, req);
2910
+ }
2911
+ async function listPlans(req) {
2912
+ const corsResponse = handleCors(req);
2913
+ if (corsResponse) return corsResponse;
2914
+ const result = await listPlansCore(req);
2915
+ if (isErrorResult(result)) {
2916
+ return errorResponse(result, req);
2917
+ }
2918
+ return jsonResponseWithCors(result, req);
2919
+ }
2920
+ async function syncCustomer(req) {
2921
+ const corsResponse = handleCors(req);
2922
+ if (corsResponse) return corsResponse;
2923
+ const result = await syncCustomerCore(req);
2924
+ if (isErrorResult(result)) {
2925
+ return errorResponse(result, req);
2926
+ }
2927
+ return jsonResponseWithCors({ customerRef: result }, req);
2928
+ }
2929
+ async function createCheckoutSession(req) {
2930
+ const corsResponse = handleCors(req);
2931
+ if (corsResponse) return corsResponse;
2932
+ const body = await parseJsonBody(req);
2933
+ const result = await createCheckoutSessionCore(req, body);
2934
+ if (isErrorResult(result)) {
2935
+ return errorResponse(result, req);
2936
+ }
2937
+ return jsonResponseWithCors(result, req);
2938
+ }
2939
+ async function createCustomerSession(req) {
2940
+ const corsResponse = handleCors(req);
2941
+ if (corsResponse) return corsResponse;
2942
+ const result = await createCustomerSessionCore(req);
2943
+ if (isErrorResult(result)) {
2944
+ return errorResponse(result, req);
2945
+ }
2946
+ return jsonResponseWithCors(result, req);
2947
+ }
2948
+ async function getMerchant(req) {
2949
+ const corsResponse = handleCors(req);
2950
+ if (corsResponse) return corsResponse;
2951
+ const result = await getMerchantCore(req);
2952
+ if (isErrorResult(result)) {
2953
+ return errorResponse(result, req);
2954
+ }
2955
+ return jsonResponseWithCors(result, req);
2956
+ }
2957
+ async function getProduct(req) {
2958
+ const corsResponse = handleCors(req);
2959
+ if (corsResponse) return corsResponse;
2960
+ const result = await getProductCore(req);
2961
+ if (isErrorResult(result)) {
2962
+ return errorResponse(result, req);
2963
+ }
2964
+ return jsonResponseWithCors(result, req);
2965
+ }
2966
+ function solvapayWebhook(options) {
2967
+ return async (req) => {
2968
+ const secret = options.secret || (typeof process !== "undefined" ? process.env.SOLVAPAY_WEBHOOK_SECRET : void 0);
2969
+ if (!secret) {
2970
+ return new Response(JSON.stringify({ error: "Webhook secret not configured" }), {
2971
+ status: 500,
2972
+ headers: { "Content-Type": "application/json" }
2973
+ });
2974
+ }
2975
+ const body = await req.text();
2976
+ const signature = req.headers.get("sv-signature") ?? "";
2977
+ let event;
2978
+ try {
2979
+ event = await verifyWebhook({ body, signature, secret });
2980
+ } catch {
2981
+ return new Response(JSON.stringify({ error: "Invalid webhook signature" }), {
2982
+ status: 401,
2983
+ headers: { "Content-Type": "application/json" }
2984
+ });
2985
+ }
2986
+ try {
2987
+ await options.onEvent(event);
2988
+ } catch {
2989
+ return new Response(JSON.stringify({ error: "Webhook handler failed" }), {
2990
+ status: 500,
2991
+ headers: { "Content-Type": "application/json" }
2992
+ });
2993
+ }
2994
+ return new Response(JSON.stringify({ received: true }), {
2995
+ status: 200,
2996
+ headers: { "Content-Type": "application/json" }
2997
+ });
2998
+ };
2999
+ }
3000
+ export {
3001
+ activatePlan,
3002
+ cancelRenewal,
3003
+ checkPurchase,
3004
+ configureCors,
3005
+ createCheckoutSession,
3006
+ createCustomerSession,
3007
+ createPaymentIntent,
3008
+ createTopupPaymentIntent,
3009
+ customerBalance,
3010
+ getMerchant,
3011
+ getPaymentMethod,
3012
+ getProduct,
3013
+ listPlans,
3014
+ processPayment,
3015
+ reactivateRenewal,
3016
+ solvapayWebhook,
3017
+ syncCustomer,
3018
+ trackUsage
3019
+ };