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