@solvapay/server 1.0.8-preview.9 → 1.0.8

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,3071 @@
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
+ if (cacheTTL > 0) {
886
+ _cleanupInterval = setInterval(
887
+ () => {
888
+ const now = Date.now();
889
+ const entriesToDelete = [];
890
+ for (const [key, cached] of resultCache.entries()) {
891
+ if (now - cached.timestamp >= cacheTTL) {
892
+ entriesToDelete.push(key);
893
+ }
894
+ }
895
+ for (const key of entriesToDelete) {
896
+ resultCache.delete(key);
897
+ }
898
+ if (resultCache.size > maxCacheSize) {
899
+ const sortedEntries = Array.from(resultCache.entries()).sort(
900
+ (a, b) => a[1].timestamp - b[1].timestamp
901
+ );
902
+ const toRemove = sortedEntries.slice(0, resultCache.size - maxCacheSize);
903
+ for (const [key] of toRemove) {
904
+ resultCache.delete(key);
905
+ }
906
+ }
907
+ },
908
+ Math.min(cacheTTL, 1e3)
909
+ );
910
+ }
911
+ const deduplicate = async (key, fn) => {
912
+ if (cacheTTL > 0) {
913
+ const cached = resultCache.get(key);
914
+ if (cached && Date.now() - cached.timestamp < cacheTTL) {
915
+ return cached.data;
916
+ } else if (cached) {
917
+ resultCache.delete(key);
918
+ }
919
+ }
920
+ let requestPromise = inFlightRequests.get(key);
921
+ if (!requestPromise) {
922
+ requestPromise = (async () => {
923
+ try {
924
+ const result = await fn();
925
+ if (cacheTTL > 0) {
926
+ resultCache.set(key, {
927
+ data: result,
928
+ timestamp: Date.now()
929
+ });
930
+ }
931
+ return result;
932
+ } catch (error) {
933
+ if (cacheTTL > 0 && cacheErrors) {
934
+ resultCache.set(key, {
935
+ data: error,
936
+ timestamp: Date.now()
937
+ });
938
+ }
939
+ throw error;
940
+ } finally {
941
+ inFlightRequests.delete(key);
942
+ }
943
+ })();
944
+ const existingPromise = inFlightRequests.get(key);
945
+ if (existingPromise) {
946
+ requestPromise = existingPromise;
947
+ } else {
948
+ inFlightRequests.set(key, requestPromise);
949
+ }
950
+ }
951
+ return requestPromise;
952
+ };
953
+ const clearCache = (key) => {
954
+ resultCache.delete(key);
955
+ };
956
+ const clearAllCache = () => {
957
+ resultCache.clear();
958
+ };
959
+ const getStats = () => ({
960
+ inFlight: inFlightRequests.size,
961
+ cached: resultCache.size
962
+ });
963
+ return {
964
+ deduplicate,
965
+ clearCache,
966
+ clearAllCache,
967
+ getStats
968
+ };
969
+ }
970
+
971
+ // src/paywall.ts
972
+ var PaywallError = class extends Error {
973
+ /**
974
+ * Creates a new PaywallError instance.
975
+ *
976
+ * @param message - Error message
977
+ * @param structuredContent - Structured content with checkout URLs and metadata
978
+ */
979
+ constructor(message, structuredContent) {
980
+ super(message);
981
+ this.structuredContent = structuredContent;
982
+ this.name = "PaywallError";
983
+ }
984
+ };
985
+ function paywallErrorToClientPayload(error) {
986
+ const sc = error.structuredContent;
987
+ const base = {
988
+ success: false,
989
+ error: sc.kind === "activation_required" ? "Activation required" : "Payment required",
990
+ product: sc.product,
991
+ checkoutUrl: sc.checkoutUrl,
992
+ message: sc.message
993
+ };
994
+ if (sc.kind === "activation_required") {
995
+ base.kind = "activation_required";
996
+ if (sc.plans !== void 0) base.plans = sc.plans;
997
+ if (sc.balance !== void 0) base.balance = sc.balance;
998
+ if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
999
+ if (sc.confirmationUrl !== void 0) base.confirmationUrl = sc.confirmationUrl;
1000
+ } else {
1001
+ base.kind = "payment_required";
1002
+ if (sc.balance !== void 0) base.balance = sc.balance;
1003
+ if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
1004
+ }
1005
+ return base;
1006
+ }
1007
+ var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
1008
+ cacheTTL: 6e4,
1009
+ // Cache results for 60 seconds (reduces API calls significantly)
1010
+ maxCacheSize: 1e3,
1011
+ // Maximum cache entries
1012
+ cacheErrors: false
1013
+ // Don't cache errors - retry on next request
1014
+ });
1015
+ var EXTRA_FORWARD_KEY = "__solvapayExtra";
1016
+ var SolvaPayPaywall = class {
1017
+ constructor(apiClient, options = {}) {
1018
+ this.apiClient = apiClient;
1019
+ this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG === "true";
1020
+ this.limitsCacheTTL = options.limitsCacheTTL ?? 1e4;
1021
+ }
1022
+ customerCreationAttempts = /* @__PURE__ */ new Set();
1023
+ customerRefMapping = /* @__PURE__ */ new Map();
1024
+ debug;
1025
+ limitsCache = /* @__PURE__ */ new Map();
1026
+ limitsCacheTTL;
1027
+ log(...args) {
1028
+ if (this.debug) {
1029
+ console.log(...args);
1030
+ }
1031
+ }
1032
+ resolveProduct(metadata) {
1033
+ return metadata.product || process.env.SOLVAPAY_PRODUCT || "default-product";
1034
+ }
1035
+ generateRequestId() {
1036
+ const timestamp = Date.now();
1037
+ const random = Math.random().toString(36).substring(2, 11);
1038
+ return `solvapay_${timestamp}_${random}`;
1039
+ }
1040
+ /**
1041
+ * Pure decision routine — performs customer resolution, limits cache
1042
+ * lookup / fresh `checkLimits` fetch, and returns a `PaywallDecision`
1043
+ * describing whether the handler should run.
1044
+ *
1045
+ * Side effects kept in lockstep with the legacy `protect()` path:
1046
+ * - creates the backend customer on first use (`ensureCustomer`),
1047
+ * - updates the limits cache (consume-one-unit bookkeeping), and
1048
+ * - emits a `paywall` usage event on gate outcomes.
1049
+ *
1050
+ * `trackUsage` for the `success` / `fail` outcome is emitted by the
1051
+ * caller (adapter or `protect()`) once it has actually invoked the
1052
+ * handler — `decide()` never counts handler execution as usage.
1053
+ *
1054
+ * @since 1.1.0
1055
+ */
1056
+ async decide(args, metadata = {}, getCustomerRef) {
1057
+ const product = this.resolveProduct(metadata);
1058
+ const usageType = metadata.usageType || "requests";
1059
+ const requestId = this.generateRequestId();
1060
+ const startTime = Date.now();
1061
+ const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
1062
+ let backendCustomerRef;
1063
+ if (inputCustomerRef.startsWith("cus_")) {
1064
+ backendCustomerRef = inputCustomerRef;
1065
+ } else {
1066
+ backendCustomerRef = await this.ensureCustomer(inputCustomerRef, inputCustomerRef);
1067
+ }
1068
+ const limitsCacheKey = `${backendCustomerRef}:${product}:${usageType}`;
1069
+ const cachedLimits = this.limitsCache.get(limitsCacheKey);
1070
+ const now = Date.now();
1071
+ let withinLimits;
1072
+ let remaining;
1073
+ let checkoutUrl;
1074
+ let resolvedMeterName;
1075
+ let lastLimitsCheck;
1076
+ const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
1077
+ if (hasFreshCachedLimits) {
1078
+ checkoutUrl = cachedLimits.checkoutUrl;
1079
+ resolvedMeterName = cachedLimits.meterName;
1080
+ lastLimitsCheck = cachedLimits.limits;
1081
+ if (cachedLimits.remaining > 0) {
1082
+ cachedLimits.remaining--;
1083
+ if (cachedLimits.remaining <= 0) {
1084
+ this.limitsCache.delete(limitsCacheKey);
1085
+ }
1086
+ withinLimits = true;
1087
+ remaining = cachedLimits.remaining;
1088
+ } else {
1089
+ withinLimits = false;
1090
+ remaining = 0;
1091
+ this.limitsCache.delete(limitsCacheKey);
1092
+ }
1093
+ } else {
1094
+ if (cachedLimits) {
1095
+ this.limitsCache.delete(limitsCacheKey);
1096
+ }
1097
+ const limitsCheck = await this.apiClient.checkLimits({
1098
+ customerRef: backendCustomerRef,
1099
+ productRef: product,
1100
+ meterName: usageType
1101
+ });
1102
+ lastLimitsCheck = limitsCheck;
1103
+ withinLimits = limitsCheck.withinLimits;
1104
+ remaining = limitsCheck.remaining;
1105
+ checkoutUrl = limitsCheck.checkoutUrl;
1106
+ resolvedMeterName = limitsCheck.meterName;
1107
+ const consumedAllowance = withinLimits && remaining > 0;
1108
+ if (consumedAllowance) {
1109
+ remaining = Math.max(0, remaining - 1);
1110
+ }
1111
+ if (consumedAllowance) {
1112
+ this.limitsCache.set(limitsCacheKey, {
1113
+ remaining,
1114
+ checkoutUrl,
1115
+ meterName: resolvedMeterName,
1116
+ timestamp: now,
1117
+ limits: limitsCheck
1118
+ });
1119
+ }
1120
+ }
1121
+ if (!withinLimits) {
1122
+ const latencyMs = Date.now() - startTime;
1123
+ this.trackUsage(
1124
+ backendCustomerRef,
1125
+ product,
1126
+ resolvedMeterName || usageType,
1127
+ "paywall",
1128
+ requestId,
1129
+ latencyMs
1130
+ );
1131
+ const state = classifyPaywallState(lastLimitsCheck ?? null);
1132
+ const preMessageGate = lastLimitsCheck?.activationRequired ? {
1133
+ kind: "activation_required",
1134
+ product,
1135
+ message: "",
1136
+ checkoutUrl: lastLimitsCheck.confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "",
1137
+ ...lastLimitsCheck.confirmationUrl !== void 0 ? { confirmationUrl: lastLimitsCheck.confirmationUrl } : {},
1138
+ ...lastLimitsCheck.plans !== void 0 ? { plans: lastLimitsCheck.plans } : {},
1139
+ ...lastLimitsCheck.balance !== void 0 ? { balance: lastLimitsCheck.balance } : {},
1140
+ ...lastLimitsCheck.product !== void 0 ? { productDetails: lastLimitsCheck.product } : {}
1141
+ } : {
1142
+ kind: "payment_required",
1143
+ product,
1144
+ checkoutUrl: checkoutUrl || "",
1145
+ message: "",
1146
+ ...lastLimitsCheck?.balance !== void 0 ? { balance: lastLimitsCheck.balance } : {},
1147
+ ...lastLimitsCheck?.product !== void 0 ? { productDetails: lastLimitsCheck.product } : {}
1148
+ };
1149
+ const gate = {
1150
+ ...preMessageGate,
1151
+ message: buildGateMessage(state, preMessageGate)
1152
+ };
1153
+ return {
1154
+ outcome: "gate",
1155
+ gate,
1156
+ limits: lastLimitsCheck ?? null,
1157
+ customerRef: backendCustomerRef
1158
+ };
1159
+ }
1160
+ return {
1161
+ outcome: "allow",
1162
+ args,
1163
+ limits: lastLimitsCheck,
1164
+ customerRef: backendCustomerRef
1165
+ };
1166
+ }
1167
+ /**
1168
+ * Execute the handler for an already-obtained `allow` decision and
1169
+ * emit the post-handler `trackUsage('success' | 'fail', ...)` event.
1170
+ *
1171
+ * Exposed for adapter integration — the adapter layer drives the
1172
+ * paywall through `decide()` + `runAllow()` so `formatGate` can own
1173
+ * gate outcomes without routing through `PaywallError`. `protect()`
1174
+ * continues to offer the self-contained throw-based surface for
1175
+ * legacy consumers.
1176
+ *
1177
+ * `runAllow` intentionally does NOT re-throw `PaywallError` — if a
1178
+ * handler calls `ctx.gate(reason)` and throws from deep code, the
1179
+ * adapter catches that at the `formatGate` boundary instead.
1180
+ *
1181
+ * @since 1.1.0
1182
+ */
1183
+ async runAllow(decision, handler, metadata, args) {
1184
+ const product = this.resolveProduct(metadata);
1185
+ const usageType = metadata.usageType || "requests";
1186
+ const requestId = this.generateRequestId();
1187
+ const startTime = Date.now();
1188
+ const forwardedExtra = args[EXTRA_FORWARD_KEY];
1189
+ const handlerContext = {
1190
+ customerRef: decision.customerRef,
1191
+ limits: decision.limits,
1192
+ ...forwardedExtra !== void 0 ? { extra: forwardedExtra } : {}
1193
+ };
1194
+ try {
1195
+ const result = await handler(args, handlerContext);
1196
+ const latencyMs = Date.now() - startTime;
1197
+ this.trackUsage(
1198
+ decision.customerRef,
1199
+ product,
1200
+ decision.limits.meterName || usageType,
1201
+ "success",
1202
+ requestId,
1203
+ latencyMs
1204
+ );
1205
+ return result;
1206
+ } catch (error) {
1207
+ if (error instanceof Error) {
1208
+ const errorType = error instanceof PaywallError ? "PaywallError" : "API Error";
1209
+ this.log(`\u274C Error in paywall [${errorType}]: ${error.message}`);
1210
+ } else {
1211
+ this.log(`\u274C Error in paywall:`, error);
1212
+ }
1213
+ if (!(error instanceof PaywallError)) {
1214
+ const latencyMs = Date.now() - startTime;
1215
+ this.trackUsage(
1216
+ decision.customerRef,
1217
+ product,
1218
+ decision.limits.meterName || usageType,
1219
+ "fail",
1220
+ requestId,
1221
+ latencyMs
1222
+ );
1223
+ }
1224
+ throw error;
1225
+ }
1226
+ }
1227
+ /**
1228
+ * Core protection method - works for both MCP and HTTP
1229
+ *
1230
+ * The `handler` may optionally declare a second positional argument
1231
+ * of type `ProtectHandlerContext` to receive the resolved customer
1232
+ * ref, the pre-check `LimitResponseWithPlan`, and an opaque `extra`
1233
+ * bag threaded through from the adapter layer. One-arg handlers
1234
+ * ignore the second argument and continue to work unchanged.
1235
+ *
1236
+ * Implemented on top of `decide()`: pre-check runs through the same
1237
+ * decision routine, and gate outcomes are raised as a `PaywallError`
1238
+ * to preserve the legacy throw-based signal for consumers that
1239
+ * haven't migrated to adapter-level `formatGate` routing.
1240
+ */
1241
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1242
+ async protect(handler, metadata = {}, getCustomerRef) {
1243
+ return async (args) => {
1244
+ const decision = await this.decide(args, metadata, getCustomerRef);
1245
+ if (decision.outcome === "gate") {
1246
+ const message = decision.gate.kind === "activation_required" ? "Activation required" : "Payment required";
1247
+ this.log(`\u274C Error in paywall [PaywallError]: ${message}`);
1248
+ throw new PaywallError(message, decision.gate);
1249
+ }
1250
+ return this.runAllow(decision, handler, metadata, args);
1251
+ };
1252
+ }
1253
+ /**
1254
+ * Ensures a customer exists in the backend, creating them if necessary.
1255
+ * This is a public helper for testing, pre-creating customers, and internal use.
1256
+ * Only attempts creation once per customer (idempotent).
1257
+ * Returns the backend customer reference to use in API calls.
1258
+ *
1259
+ * @param customerRef - The customer reference used as a cache key (e.g., Supabase user ID)
1260
+ * @param externalRef - Optional external reference for backend lookup (e.g., Supabase user ID)
1261
+ * If provided, will lookup existing customer by externalRef before creating new one.
1262
+ * The externalRef is stored on the SolvaPay backend for customer lookup.
1263
+ * @param options - Optional customer details (email, name) for customer creation
1264
+ */
1265
+ async ensureCustomer(customerRef, externalRef, options) {
1266
+ if (this.customerRefMapping.has(customerRef)) {
1267
+ return this.customerRefMapping.get(customerRef);
1268
+ }
1269
+ if (customerRef === "anonymous") {
1270
+ return customerRef;
1271
+ }
1272
+ if (customerRef.startsWith("cus_")) {
1273
+ return customerRef;
1274
+ }
1275
+ const cacheKey = externalRef || customerRef;
1276
+ if (this.customerRefMapping.has(customerRef)) {
1277
+ const cached = this.customerRefMapping.get(customerRef);
1278
+ return cached;
1279
+ }
1280
+ const backendRef = await sharedCustomerLookupDeduplicator.deduplicate(cacheKey, async () => {
1281
+ if (externalRef) {
1282
+ try {
1283
+ const existingCustomer = await this.apiClient.getCustomer({ externalRef });
1284
+ if (existingCustomer && existingCustomer.customerRef) {
1285
+ const ref = existingCustomer.customerRef;
1286
+ this.customerRefMapping.set(customerRef, ref);
1287
+ this.customerCreationAttempts.add(customerRef);
1288
+ if (externalRef !== customerRef) {
1289
+ this.customerCreationAttempts.add(externalRef);
1290
+ }
1291
+ return ref;
1292
+ }
1293
+ } catch (error) {
1294
+ const errorMessage = error instanceof Error ? error.message : String(error);
1295
+ if (!errorMessage.includes("404") && !errorMessage.includes("not found")) {
1296
+ this.log(`\u26A0\uFE0F Error looking up customer by externalRef: ${errorMessage}`);
1297
+ }
1298
+ }
1299
+ }
1300
+ if (this.customerCreationAttempts.has(customerRef) || externalRef && this.customerCreationAttempts.has(externalRef)) {
1301
+ const mappedRef = this.customerRefMapping.get(customerRef);
1302
+ return mappedRef || customerRef;
1303
+ }
1304
+ if (!this.apiClient.createCustomer) {
1305
+ console.warn(
1306
+ `\u26A0\uFE0F Cannot auto-create customer ${customerRef}: createCustomer method not available on API client`
1307
+ );
1308
+ return customerRef;
1309
+ }
1310
+ this.customerCreationAttempts.add(customerRef);
1311
+ try {
1312
+ const createParams = {
1313
+ email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`,
1314
+ metadata: {}
1315
+ };
1316
+ if (options?.name) {
1317
+ createParams.name = options.name;
1318
+ }
1319
+ if (externalRef) {
1320
+ createParams.externalRef = externalRef;
1321
+ }
1322
+ const result = await this.apiClient.createCustomer(createParams);
1323
+ const resultObj = result;
1324
+ const ref = resultObj.customerRef || resultObj.reference || customerRef;
1325
+ this.customerRefMapping.set(customerRef, ref);
1326
+ return ref;
1327
+ } catch (error) {
1328
+ const errorMessage = error instanceof Error ? error.message : String(error);
1329
+ if (errorMessage.includes("409") || errorMessage.includes("already exists")) {
1330
+ if (externalRef) {
1331
+ try {
1332
+ const searchResult = await this.apiClient.getCustomer({ externalRef });
1333
+ if (searchResult && searchResult.customerRef) {
1334
+ this.customerRefMapping.set(customerRef, searchResult.customerRef);
1335
+ return searchResult.customerRef;
1336
+ }
1337
+ } catch (lookupError) {
1338
+ this.log(
1339
+ `\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`,
1340
+ lookupError instanceof Error ? lookupError.message : lookupError
1341
+ );
1342
+ }
1343
+ }
1344
+ const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
1345
+ if (externalRef && isEmailConflict && options?.email) {
1346
+ try {
1347
+ const byEmail = await this.apiClient.getCustomer({ email: options.email });
1348
+ if (byEmail && byEmail.customerRef) {
1349
+ this.customerRefMapping.set(customerRef, byEmail.customerRef);
1350
+ this.log(
1351
+ `\u26A0\uFE0F Resolved customer ${customerRef} by email after conflict; using existing customer ${byEmail.customerRef}`
1352
+ );
1353
+ if (!byEmail.externalRef && this.apiClient.updateCustomer) {
1354
+ try {
1355
+ await this.apiClient.updateCustomer(byEmail.customerRef, { externalRef });
1356
+ } catch (backfillError) {
1357
+ this.log(
1358
+ `\u26A0\uFE0F Failed to backfill externalRef on ${byEmail.customerRef}:`,
1359
+ backfillError instanceof Error ? backfillError.message : backfillError
1360
+ );
1361
+ }
1362
+ }
1363
+ return byEmail.customerRef;
1364
+ }
1365
+ } catch (emailLookupError) {
1366
+ this.log(
1367
+ `\u26A0\uFE0F Email lookup failed after customer conflict for ${customerRef}:`,
1368
+ emailLookupError instanceof Error ? emailLookupError.message : emailLookupError
1369
+ );
1370
+ }
1371
+ try {
1372
+ const retryParams = {
1373
+ email: `${customerRef}-${Date.now()}@auto-created.local`,
1374
+ externalRef,
1375
+ metadata: {}
1376
+ };
1377
+ if (options?.name) {
1378
+ retryParams.name = options.name;
1379
+ }
1380
+ const retryResult = await this.apiClient.createCustomer(retryParams);
1381
+ const retryObj = retryResult;
1382
+ const retryRef = retryObj.customerRef || retryObj.reference || customerRef;
1383
+ this.customerRefMapping.set(customerRef, retryRef);
1384
+ this.log(
1385
+ `\u26A0\uFE0F Retried customer creation for ${customerRef} with generated email after email conflict`
1386
+ );
1387
+ return retryRef;
1388
+ } catch (retryError) {
1389
+ this.log(
1390
+ `\u26A0\uFE0F Retry create customer with generated email failed for ${customerRef}:`,
1391
+ retryError instanceof Error ? retryError.message : retryError
1392
+ );
1393
+ }
1394
+ }
1395
+ const unresolvedMessage = errorMessage || "Customer already exists but could not be resolved";
1396
+ throw new Error(
1397
+ `Failed to resolve existing customer for ${customerRef} after conflict: ${unresolvedMessage}. Ensure the existing customer is linked to this externalRef.`
1398
+ );
1399
+ }
1400
+ this.log(
1401
+ `\u274C Failed to auto-create customer ${customerRef}:`,
1402
+ error instanceof Error ? error.message : error
1403
+ );
1404
+ throw error;
1405
+ }
1406
+ });
1407
+ if (backendRef !== customerRef) {
1408
+ this.customerRefMapping.set(customerRef, backendRef);
1409
+ }
1410
+ return backendRef;
1411
+ }
1412
+ async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration) {
1413
+ await withRetry(
1414
+ () => this.apiClient.trackUsage({
1415
+ customerRef,
1416
+ actionType: "api_call",
1417
+ units: 1,
1418
+ outcome,
1419
+ productRef,
1420
+ duration: actionDuration,
1421
+ metadata: { action: action || "api_requests", requestId },
1422
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1423
+ }),
1424
+ {
1425
+ maxRetries: 2,
1426
+ initialDelay: 500,
1427
+ shouldRetry: (error) => error.message.includes("Customer not found"),
1428
+ onRetry: (_error, attempt) => {
1429
+ console.warn(`\u26A0\uFE0F Customer not found (attempt ${attempt + 1}/3), retrying in 500ms...`);
1430
+ }
1431
+ }
1432
+ ).catch((error) => {
1433
+ console.error("Usage tracking failed:", error);
1434
+ });
1435
+ }
1436
+ };
1437
+
1438
+ // src/adapters/base.ts
1439
+ var EXTRA_FORWARD_KEY2 = "__solvapayExtra";
1440
+ var AdapterUtils = class {
1441
+ /**
1442
+ * Ensure customer reference is properly formatted
1443
+ */
1444
+ static ensureCustomerRef(customerRef) {
1445
+ if (!customerRef || customerRef === "anonymous") {
1446
+ return "anonymous";
1447
+ }
1448
+ return customerRef;
1449
+ }
1450
+ /**
1451
+ * Extract customer ref from JWT token
1452
+ */
1453
+ static async extractFromJWT(token, options) {
1454
+ try {
1455
+ const { jwtVerify } = await import("jose");
1456
+ const jwtSecret = new TextEncoder().encode(
1457
+ options?.secret || process.env.OAUTH_JWKS_SECRET || "test-jwt-secret"
1458
+ );
1459
+ const { payload } = await jwtVerify(token, jwtSecret, {
1460
+ issuer: options?.issuer || process.env.OAUTH_ISSUER || "http://localhost:3000",
1461
+ audience: options?.audience || process.env.OAUTH_CLIENT_ID || "test-client-id"
1462
+ });
1463
+ return payload.sub || null;
1464
+ } catch {
1465
+ return null;
1466
+ }
1467
+ }
1468
+ };
1469
+ async function createAdapterHandler(adapter, paywall, metadata, businessLogic) {
1470
+ const backendRefCache = /* @__PURE__ */ new Map();
1471
+ return async (context, extra) => {
1472
+ let args;
1473
+ let customerRef;
1474
+ try {
1475
+ args = await adapter.extractArgs(context);
1476
+ customerRef = await adapter.getCustomerRef(context, extra);
1477
+ let backendRef = backendRefCache.get(customerRef);
1478
+ if (!backendRef) {
1479
+ backendRef = await paywall.ensureCustomer(customerRef, customerRef);
1480
+ backendRefCache.set(customerRef, backendRef);
1481
+ }
1482
+ args.auth = { customer_ref: backendRef };
1483
+ } catch (error) {
1484
+ return adapter.formatError(error, context);
1485
+ }
1486
+ const decideGetCustomerRef = (args2) => args2.auth?.customer_ref || "anonymous";
1487
+ try {
1488
+ const decision = await paywall.decide(args, metadata, decideGetCustomerRef);
1489
+ if (decision.outcome === "gate") {
1490
+ return adapter.formatGate(decision.gate, context);
1491
+ }
1492
+ if (extra !== void 0) {
1493
+ ;
1494
+ args[EXTRA_FORWARD_KEY2] = extra;
1495
+ }
1496
+ try {
1497
+ const result = await paywall.runAllow(decision, businessLogic, metadata, args);
1498
+ return adapter.formatResponse(result, context);
1499
+ } finally {
1500
+ if (EXTRA_FORWARD_KEY2 in args) {
1501
+ delete args[EXTRA_FORWARD_KEY2];
1502
+ }
1503
+ }
1504
+ } catch (error) {
1505
+ if (error instanceof PaywallError) {
1506
+ return adapter.formatGate(error.structuredContent, context);
1507
+ }
1508
+ return adapter.formatError(error, context);
1509
+ }
1510
+ };
1511
+ }
1512
+
1513
+ // src/adapters/http.ts
1514
+ var HttpAdapter = class {
1515
+ constructor(options = {}) {
1516
+ this.options = options;
1517
+ }
1518
+ extractArgs([req, _reply]) {
1519
+ if (this.options.extractArgs) {
1520
+ return this.options.extractArgs(req);
1521
+ }
1522
+ return {
1523
+ ...req.body || {},
1524
+ ...req.params || {},
1525
+ ...req.query || {}
1526
+ };
1527
+ }
1528
+ async getCustomerRef([req, _reply]) {
1529
+ if (this.options.getCustomerRef) {
1530
+ const ref = await this.options.getCustomerRef(req);
1531
+ return AdapterUtils.ensureCustomerRef(ref);
1532
+ }
1533
+ const headerRef = req.headers?.["x-customer-ref"];
1534
+ if (headerRef) {
1535
+ return AdapterUtils.ensureCustomerRef(headerRef);
1536
+ }
1537
+ const authHeader = req.headers?.["authorization"];
1538
+ if (authHeader && authHeader.startsWith("Bearer ")) {
1539
+ const token = authHeader.substring(7);
1540
+ const jwtSub = await AdapterUtils.extractFromJWT(token);
1541
+ if (jwtSub) {
1542
+ return AdapterUtils.ensureCustomerRef(jwtSub);
1543
+ }
1544
+ }
1545
+ return "anonymous";
1546
+ }
1547
+ formatResponse(result, [_req, reply]) {
1548
+ if (this.options.transformResponse) {
1549
+ return this.options.transformResponse(result, reply);
1550
+ }
1551
+ if (reply && reply.status && typeof reply.json === "function") {
1552
+ reply.json(result);
1553
+ return;
1554
+ }
1555
+ return result;
1556
+ }
1557
+ /**
1558
+ * Emit a 402 Payment Required response with the same JSON body shape
1559
+ * REST consumers have always received (`{success:false, error, product,
1560
+ * checkoutUrl, message, ...}`). The shape is reused via
1561
+ * `paywallErrorToClientPayload` so HTTP / Next / hosted-proxy clients
1562
+ * don't have to branch on an SDK version.
1563
+ */
1564
+ formatGate(gate, [_req, reply]) {
1565
+ const errorResponse2 = paywallErrorToClientPayload(new PaywallError(gate.message, gate));
1566
+ if (reply && reply.status && typeof reply.json === "function") {
1567
+ reply.status(402).json(errorResponse2);
1568
+ return;
1569
+ }
1570
+ if (reply && reply.code) {
1571
+ reply.code(402);
1572
+ }
1573
+ return errorResponse2;
1574
+ }
1575
+ formatError(error, [_req, reply]) {
1576
+ const errorResponse2 = {
1577
+ success: false,
1578
+ error: error instanceof Error ? error.message : "Internal server error"
1579
+ };
1580
+ if (reply && reply.status && typeof reply.json === "function") {
1581
+ reply.status(500).json(errorResponse2);
1582
+ return;
1583
+ }
1584
+ if (reply && reply.code) {
1585
+ reply.code(500);
1586
+ }
1587
+ return errorResponse2;
1588
+ }
1589
+ };
1590
+
1591
+ // src/adapters/next.ts
1592
+ var NextAdapter = class {
1593
+ constructor(options = {}) {
1594
+ this.options = options;
1595
+ }
1596
+ async extractArgs([request, context]) {
1597
+ if (this.options.extractArgs) {
1598
+ return await this.options.extractArgs(request, context);
1599
+ }
1600
+ const url = new URL(request.url);
1601
+ const query = Object.fromEntries(url.searchParams.entries());
1602
+ let body = {};
1603
+ try {
1604
+ if (request.method !== "GET" && request.headers.get("content-type")?.includes("application/json")) {
1605
+ body = await request.json();
1606
+ }
1607
+ } catch {
1608
+ }
1609
+ let routeParams = {};
1610
+ if (context?.params) {
1611
+ if (typeof context.params === "object" && "then" in context.params) {
1612
+ routeParams = await context.params;
1613
+ } else {
1614
+ routeParams = context.params;
1615
+ }
1616
+ }
1617
+ return {
1618
+ ...body,
1619
+ ...query,
1620
+ ...routeParams
1621
+ };
1622
+ }
1623
+ async getCustomerRef([request]) {
1624
+ if (this.options.getCustomerRef) {
1625
+ const ref = await this.options.getCustomerRef(request);
1626
+ return AdapterUtils.ensureCustomerRef(ref);
1627
+ }
1628
+ const authHeader = request.headers.get("authorization");
1629
+ if (authHeader && authHeader.startsWith("Bearer ")) {
1630
+ const token = authHeader.substring(7);
1631
+ const jwtSub = await AdapterUtils.extractFromJWT(token);
1632
+ if (jwtSub) {
1633
+ return AdapterUtils.ensureCustomerRef(jwtSub);
1634
+ }
1635
+ }
1636
+ const userId = request.headers.get("x-user-id");
1637
+ if (userId) {
1638
+ return AdapterUtils.ensureCustomerRef(userId);
1639
+ }
1640
+ const headerRef = request.headers.get("x-customer-ref");
1641
+ if (headerRef) {
1642
+ return AdapterUtils.ensureCustomerRef(headerRef);
1643
+ }
1644
+ return "demo_user";
1645
+ }
1646
+ formatResponse(result, _context) {
1647
+ const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
1648
+ return new Response(JSON.stringify(transformed), {
1649
+ status: 200,
1650
+ headers: { "Content-Type": "application/json" }
1651
+ });
1652
+ }
1653
+ /**
1654
+ * Emit a 402 Payment Required `Response` with the same JSON body
1655
+ * REST consumers have always received. Reuses
1656
+ * `paywallErrorToClientPayload` so HTTP / Next / hosted-proxy
1657
+ * clients don't have to branch on an SDK version.
1658
+ */
1659
+ formatGate(gate, _context) {
1660
+ return new Response(
1661
+ JSON.stringify(paywallErrorToClientPayload(new PaywallError(gate.message, gate))),
1662
+ {
1663
+ status: 402,
1664
+ headers: { "Content-Type": "application/json" }
1665
+ }
1666
+ );
1667
+ }
1668
+ formatError(error, _context) {
1669
+ return new Response(
1670
+ JSON.stringify({
1671
+ success: false,
1672
+ error: error instanceof Error ? error.message : "Internal server error"
1673
+ }),
1674
+ {
1675
+ status: 500,
1676
+ headers: { "Content-Type": "application/json" }
1677
+ }
1678
+ );
1679
+ }
1680
+ };
1681
+
1682
+ // src/adapters/mcp.ts
1683
+ var McpAdapter = class {
1684
+ constructor(options = {}) {
1685
+ this.options = options;
1686
+ }
1687
+ extractArgs(args) {
1688
+ return args;
1689
+ }
1690
+ async getCustomerRef(args, extra) {
1691
+ if (this.options.getCustomerRef) {
1692
+ const ref = await this.options.getCustomerRef(args, extra);
1693
+ return AdapterUtils.ensureCustomerRef(ref);
1694
+ }
1695
+ const customerRefFromExtra = typeof extra?.authInfo?.extra?.customer_ref === "string" ? String(extra.authInfo.extra.customer_ref) : void 0;
1696
+ const customerRefFromArgs = typeof args.auth === "object" && args.auth !== null && typeof args.auth.customer_ref === "string" ? String(args.auth.customer_ref) : void 0;
1697
+ const directCustomerRef = typeof args.customer_ref === "string" ? args.customer_ref : void 0;
1698
+ const customerRef = (customerRefFromExtra || customerRefFromArgs || directCustomerRef || "anonymous").trim();
1699
+ return AdapterUtils.ensureCustomerRef(customerRef);
1700
+ }
1701
+ formatResponse(result, _context) {
1702
+ const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
1703
+ const response = {
1704
+ content: [
1705
+ {
1706
+ type: "text",
1707
+ text: JSON.stringify(transformed, null, 2)
1708
+ }
1709
+ ]
1710
+ };
1711
+ if (transformed && typeof transformed === "object" && !Array.isArray(transformed)) {
1712
+ response.structuredContent = transformed;
1713
+ }
1714
+ return response;
1715
+ }
1716
+ /**
1717
+ * Emit a plain-narration paywall response — `content[0].text` carries
1718
+ * the gate's human message (LLM-actionable), `structuredContent`
1719
+ * carries the machine-readable gate payload, and `isError` stays
1720
+ * `false` per the MCP spec's own `isError` definition (paywall is
1721
+ * not a self-correctable tool execution error; it is a user-facing
1722
+ * control transfer to the UI).
1723
+ *
1724
+ * Hosts that read widget metadata from `tools/list` or tool-result
1725
+ * `_meta.ui` open the paywall iframe on top of this response;
1726
+ * `buildPayableHandler` stamps the `_meta.ui.resourceUri` envelope
1727
+ * before returning.
1728
+ */
1729
+ formatGate(gate, _context) {
1730
+ return {
1731
+ content: [{ type: "text", text: gate.message }],
1732
+ isError: false,
1733
+ structuredContent: gate
1734
+ };
1735
+ }
1736
+ formatError(error, _context) {
1737
+ return {
1738
+ content: [
1739
+ {
1740
+ type: "text",
1741
+ text: JSON.stringify(
1742
+ {
1743
+ success: false,
1744
+ error: error instanceof Error ? error.message : "Unknown error occurred"
1745
+ },
1746
+ null,
1747
+ 2
1748
+ )
1749
+ }
1750
+ ],
1751
+ isError: true
1752
+ };
1753
+ }
1754
+ };
1755
+
1756
+ // src/factory.ts
1757
+ var import_core3 = require("@solvapay/core");
1758
+
1759
+ // src/virtual-tools.ts
1760
+ var TOOL_GET_USER_INFO = {
1761
+ name: "get_user_info",
1762
+ 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.",
1763
+ inputSchema: {
1764
+ type: "object",
1765
+ properties: {},
1766
+ required: []
1767
+ }
1768
+ };
1769
+ var TOOL_UPGRADE = {
1770
+ name: "upgrade",
1771
+ 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.",
1772
+ inputSchema: {
1773
+ type: "object",
1774
+ properties: {
1775
+ planRef: {
1776
+ type: "string",
1777
+ 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.'
1778
+ }
1779
+ },
1780
+ required: []
1781
+ }
1782
+ };
1783
+ var TOOL_MANAGE_ACCOUNT = {
1784
+ name: "manage_account",
1785
+ 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.",
1786
+ inputSchema: {
1787
+ type: "object",
1788
+ properties: {},
1789
+ required: []
1790
+ }
1791
+ };
1792
+ function mcpTextResult(text) {
1793
+ return { content: [{ type: "text", text }] };
1794
+ }
1795
+ function mcpErrorResult(message) {
1796
+ return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
1797
+ }
1798
+ function createGetUserInfoHandler(apiClient, productRef, getCustomerRef) {
1799
+ return async (args, extra) => {
1800
+ const customerRef = getCustomerRef(args, extra);
1801
+ try {
1802
+ if (!apiClient.getUserInfo) {
1803
+ return mcpErrorResult("getUserInfo is not available on this API client");
1804
+ }
1805
+ const userInfo = await apiClient.getUserInfo({ customerRef, productRef });
1806
+ return mcpTextResult(JSON.stringify(userInfo, null, 2));
1807
+ } catch (error) {
1808
+ return mcpErrorResult(
1809
+ `Failed to retrieve user information: ${error instanceof Error ? error.message : "Unknown error"}`
1810
+ );
1811
+ }
1812
+ };
1813
+ }
1814
+ function createUpgradeHandler(apiClient, productRef, getCustomerRef) {
1815
+ return async (args, extra) => {
1816
+ const customerRef = getCustomerRef(args, extra);
1817
+ const planRef = typeof args.planRef === "string" ? args.planRef : void 0;
1818
+ try {
1819
+ const result = await apiClient.createCheckoutSession({
1820
+ customerRef,
1821
+ productRef,
1822
+ ...planRef && { planRef }
1823
+ });
1824
+ const checkoutUrl = result.checkoutUrl;
1825
+ if (planRef) {
1826
+ const responseText2 = `## Upgrade
1827
+
1828
+ **[Click here to upgrade \u2192](${checkoutUrl})**
1829
+
1830
+ After completing the checkout, your purchase will be activated immediately.`;
1831
+ return mcpTextResult(responseText2);
1832
+ }
1833
+ const responseText = `## Upgrade Your Subscription
1834
+
1835
+ **[Click here to view pricing options and upgrade \u2192](${checkoutUrl})**
1836
+
1837
+ You'll be able to compare options and select the one that's right for you.`;
1838
+ return mcpTextResult(responseText);
1839
+ } catch (error) {
1840
+ return mcpErrorResult(
1841
+ `Failed to create checkout session: ${error instanceof Error ? error.message : "Unknown error"}`
1842
+ );
1843
+ }
1844
+ };
1845
+ }
1846
+ function createManageAccountHandler(apiClient, productRef, getCustomerRef) {
1847
+ return async (args, extra) => {
1848
+ const customerRef = getCustomerRef(args, extra);
1849
+ try {
1850
+ const session = await apiClient.createCustomerSession({ customerRef, productRef });
1851
+ const portalUrl = session.customerUrl;
1852
+ const responseText = `## Manage Your Account
1853
+
1854
+ Access your account management portal to:
1855
+ - View your current account status
1856
+ - See billing history and invoices
1857
+ - Update payment methods
1858
+ - Cancel or modify your subscription
1859
+
1860
+ **[Open Account Portal \u2192](${portalUrl})**
1861
+
1862
+ This link is secure and will expire after a short period.`;
1863
+ return mcpTextResult(responseText);
1864
+ } catch (error) {
1865
+ return mcpErrorResult(
1866
+ `Failed to create customer portal session: ${error instanceof Error ? error.message : "Unknown error"}`
1867
+ );
1868
+ }
1869
+ };
1870
+ }
1871
+ function createVirtualTools(apiClient, options) {
1872
+ const { product, getCustomerRef, exclude = [] } = options;
1873
+ const excludeSet = new Set(exclude);
1874
+ const allTools = [
1875
+ {
1876
+ ...TOOL_GET_USER_INFO,
1877
+ handler: createGetUserInfoHandler(apiClient, product, getCustomerRef)
1878
+ },
1879
+ {
1880
+ ...TOOL_UPGRADE,
1881
+ handler: createUpgradeHandler(apiClient, product, getCustomerRef)
1882
+ },
1883
+ {
1884
+ ...TOOL_MANAGE_ACCOUNT,
1885
+ handler: createManageAccountHandler(apiClient, product, getCustomerRef)
1886
+ }
1887
+ ];
1888
+ return allTools.filter((t) => !excludeSet.has(t.name));
1889
+ }
1890
+
1891
+ // src/register-virtual-tools-mcp.ts
1892
+ var import_node_module = require("module");
1893
+ function getNodeRequire() {
1894
+ try {
1895
+ return (0, eval)("require");
1896
+ } catch {
1897
+ return (0, import_node_module.createRequire)(`${process.cwd()}/package.json`);
1898
+ }
1899
+ }
1900
+ var nodeRequire = getNodeRequire();
1901
+ function getZod() {
1902
+ try {
1903
+ const zodModule = nodeRequire("zod");
1904
+ return zodModule.z ?? zodModule;
1905
+ } catch {
1906
+ throw new Error(
1907
+ "zod is required to use registerVirtualToolsMcp(). Install it as a dependency in your MCP server project."
1908
+ );
1909
+ }
1910
+ }
1911
+ function isJsonSchemaObject(value) {
1912
+ return Boolean(value && typeof value === "object");
1913
+ }
1914
+ function toZodSchema(property) {
1915
+ const z = getZod();
1916
+ if (Array.isArray(property.enum) && property.enum.length > 0) {
1917
+ if (property.enum.every((value) => typeof value === "string")) {
1918
+ const [first, ...rest] = property.enum;
1919
+ return z.enum([first, ...rest]);
1920
+ }
1921
+ if (property.enum.length === 1) {
1922
+ return z.literal(property.enum[0]);
1923
+ }
1924
+ return z.union(property.enum.map((value) => z.literal(value)));
1925
+ }
1926
+ switch (property.type) {
1927
+ case "string":
1928
+ return z.string();
1929
+ case "number":
1930
+ return z.number();
1931
+ case "integer":
1932
+ return z.number().int();
1933
+ case "boolean":
1934
+ return z.boolean();
1935
+ case "array":
1936
+ return z.array(
1937
+ isJsonSchemaObject(property.items) ? toZodSchema(property.items) : z.any()
1938
+ );
1939
+ case "object":
1940
+ return z.object(jsonSchemaToZodRawShape(property.properties || {}));
1941
+ default:
1942
+ return z.any();
1943
+ }
1944
+ }
1945
+ function jsonSchemaToZodRawShape(properties, required = []) {
1946
+ const requiredSet = new Set(required);
1947
+ return Object.fromEntries(
1948
+ Object.entries(properties).map(([name, property]) => {
1949
+ const schema = toZodSchema(property);
1950
+ const finalSchema = requiredSet.has(name) ? schema : schema.optional();
1951
+ return [name, finalSchema];
1952
+ })
1953
+ );
1954
+ }
1955
+ function defaultGetCustomerRef(_args, extra) {
1956
+ const fromExtra = extra?.authInfo?.extra?.customer_ref;
1957
+ return typeof fromExtra === "string" && fromExtra.trim() ? fromExtra.trim() : "anonymous";
1958
+ }
1959
+ function registerVirtualToolsMcpImpl(server, apiClient, options) {
1960
+ const { filter, mapDefinition, wrapHandler, getCustomerRef, ...virtualToolsOptions } = options;
1961
+ const virtualTools = createVirtualTools(apiClient, {
1962
+ ...virtualToolsOptions,
1963
+ getCustomerRef: getCustomerRef || defaultGetCustomerRef
1964
+ });
1965
+ for (const toolDefinition of virtualTools) {
1966
+ if (filter && !filter(toolDefinition)) continue;
1967
+ const mappedDefinition = mapDefinition ? mapDefinition(toolDefinition) : toolDefinition;
1968
+ const wrappedHandler = wrapHandler ? wrapHandler(mappedDefinition.handler, mappedDefinition) : mappedDefinition.handler;
1969
+ server.registerTool(
1970
+ mappedDefinition.name,
1971
+ {
1972
+ description: mappedDefinition.description,
1973
+ inputSchema: jsonSchemaToZodRawShape(
1974
+ mappedDefinition.inputSchema.properties,
1975
+ mappedDefinition.inputSchema.required || []
1976
+ )
1977
+ },
1978
+ wrappedHandler
1979
+ );
1980
+ }
1981
+ }
1982
+
1983
+ // src/factory.ts
1984
+ function createSolvaPay(config) {
1985
+ let resolvedConfig;
1986
+ if (!config) {
1987
+ const envConfig = (0, import_core3.getSolvaPayConfig)();
1988
+ resolvedConfig = {
1989
+ apiKey: envConfig.apiKey,
1990
+ apiBaseUrl: envConfig.apiBaseUrl
1991
+ };
1992
+ } else {
1993
+ resolvedConfig = config;
1994
+ }
1995
+ const apiClient = resolvedConfig.apiClient || createSolvaPayClient({
1996
+ apiKey: resolvedConfig.apiKey,
1997
+ apiBaseUrl: resolvedConfig.apiBaseUrl
1998
+ });
1999
+ const paywall = new SolvaPayPaywall(apiClient, {
2000
+ debug: process.env.SOLVAPAY_DEBUG !== "false",
2001
+ limitsCacheTTL: resolvedConfig.limitsCacheTTL
2002
+ });
2003
+ return {
2004
+ // Direct access to API client for advanced operations
2005
+ apiClient,
2006
+ // Common API methods exposed directly for convenience
2007
+ ensureCustomer(customerRef, externalRef, options) {
2008
+ return paywall.ensureCustomer(customerRef, externalRef, options);
2009
+ },
2010
+ createPaymentIntent(params) {
2011
+ if (!apiClient.createPaymentIntent) {
2012
+ throw new import_core3.SolvaPayError("createPaymentIntent is not available on this API client");
2013
+ }
2014
+ return apiClient.createPaymentIntent(params);
2015
+ },
2016
+ createTopupPaymentIntent(params) {
2017
+ if (!apiClient.createTopupPaymentIntent) {
2018
+ throw new import_core3.SolvaPayError("createTopupPaymentIntent is not available on this API client");
2019
+ }
2020
+ return apiClient.createTopupPaymentIntent(params);
2021
+ },
2022
+ processPaymentIntent(params) {
2023
+ if (!apiClient.processPaymentIntent) {
2024
+ throw new import_core3.SolvaPayError("processPaymentIntent is not available on this API client");
2025
+ }
2026
+ return apiClient.processPaymentIntent(params);
2027
+ },
2028
+ checkLimits(params) {
2029
+ return apiClient.checkLimits(params);
2030
+ },
2031
+ trackUsage(params) {
2032
+ return apiClient.trackUsage(params);
2033
+ },
2034
+ createCustomer(params) {
2035
+ if (!apiClient.createCustomer) {
2036
+ throw new import_core3.SolvaPayError("createCustomer is not available on this API client");
2037
+ }
2038
+ return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
2039
+ },
2040
+ getCustomer(params) {
2041
+ return apiClient.getCustomer(params);
2042
+ },
2043
+ getCustomerBalance(params) {
2044
+ if (!apiClient.getCustomerBalance) {
2045
+ throw new import_core3.SolvaPayError("getCustomerBalance is not available on this API client");
2046
+ }
2047
+ return apiClient.getCustomerBalance(params);
2048
+ },
2049
+ createCheckoutSession(params) {
2050
+ return apiClient.createCheckoutSession({
2051
+ customerRef: params.customerRef,
2052
+ productRef: params.productRef,
2053
+ planRef: params.planRef
2054
+ });
2055
+ },
2056
+ createCustomerSession(params) {
2057
+ return apiClient.createCustomerSession(params);
2058
+ },
2059
+ activatePlan(params) {
2060
+ if (!apiClient.activatePlan) {
2061
+ throw new import_core3.SolvaPayError("activatePlan is not available on this API client");
2062
+ }
2063
+ return apiClient.activatePlan(params);
2064
+ },
2065
+ bootstrapMcpProduct(params) {
2066
+ if (!apiClient.bootstrapMcpProduct) {
2067
+ throw new import_core3.SolvaPayError("bootstrapMcpProduct is not available on this API client");
2068
+ }
2069
+ return apiClient.bootstrapMcpProduct(params);
2070
+ },
2071
+ configureMcpPlans(productRef, params) {
2072
+ if (!apiClient.configureMcpPlans) {
2073
+ throw new import_core3.SolvaPayError("configureMcpPlans is not available on this API client");
2074
+ }
2075
+ return apiClient.configureMcpPlans(productRef, params);
2076
+ },
2077
+ getVirtualTools(options) {
2078
+ return createVirtualTools(apiClient, options);
2079
+ },
2080
+ async registerVirtualToolsMcp(server, options) {
2081
+ await registerVirtualToolsMcpImpl(server, apiClient, options);
2082
+ },
2083
+ // Payable API for framework-specific handlers
2084
+ payable(options = {}) {
2085
+ const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
2086
+ const usageType = options.usageType || "requests";
2087
+ const metadata = { product, usageType };
2088
+ return {
2089
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2090
+ http(businessLogic, adapterOptions) {
2091
+ const adapter = new HttpAdapter({
2092
+ ...adapterOptions,
2093
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
2094
+ });
2095
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
2096
+ return async (req, reply) => {
2097
+ const handler = await handlerPromise;
2098
+ return handler([req, reply]);
2099
+ };
2100
+ },
2101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2102
+ next(businessLogic, adapterOptions) {
2103
+ const adapter = new NextAdapter({
2104
+ ...adapterOptions,
2105
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
2106
+ });
2107
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
2108
+ return async (request, context) => {
2109
+ const handler = await handlerPromise;
2110
+ return handler([request, context]);
2111
+ };
2112
+ },
2113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2114
+ mcp(businessLogic, adapterOptions) {
2115
+ const adapter = new McpAdapter({
2116
+ ...adapterOptions,
2117
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
2118
+ });
2119
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
2120
+ return async (args, extra) => {
2121
+ const handler = await handlerPromise;
2122
+ return handler(args, extra);
2123
+ };
2124
+ },
2125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2126
+ async function(businessLogic) {
2127
+ const getCustomerRef = (args) => {
2128
+ const configuredRef = options.getCustomerRef?.(args);
2129
+ if (typeof configuredRef === "string") {
2130
+ return configuredRef;
2131
+ }
2132
+ return args.auth?.customer_ref || "anonymous";
2133
+ };
2134
+ return paywall.protect(businessLogic, metadata, getCustomerRef);
2135
+ }
2136
+ };
2137
+ }
2138
+ };
2139
+ }
2140
+
2141
+ // src/helpers/customer.ts
2142
+ async function syncCustomerCore(request, options = {}) {
2143
+ try {
2144
+ const userResult = await getAuthenticatedUserCore(request, {
2145
+ includeEmail: options.includeEmail,
2146
+ includeName: options.includeName
2147
+ });
2148
+ if (isErrorResult(userResult)) {
2149
+ return userResult;
2150
+ }
2151
+ const { userId, email, name } = userResult;
2152
+ const solvaPay = options.solvaPay || createSolvaPay();
2153
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2154
+ email: email || void 0,
2155
+ name: name || void 0
2156
+ });
2157
+ return customerRef;
2158
+ } catch (error) {
2159
+ return handleRouteError(error, "Sync customer", "Failed to sync customer");
2160
+ }
2161
+ }
2162
+ async function getCustomerBalanceCore(request, options = {}) {
2163
+ try {
2164
+ const userResult = await getAuthenticatedUserCore(request);
2165
+ if (isErrorResult(userResult)) {
2166
+ return userResult;
2167
+ }
2168
+ const { userId, email, name } = userResult;
2169
+ const solvaPay = options.solvaPay || createSolvaPay();
2170
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2171
+ email: email || void 0,
2172
+ name: name || void 0
2173
+ });
2174
+ const result = await solvaPay.getCustomerBalance({ customerRef });
2175
+ return result;
2176
+ } catch (error) {
2177
+ return handleRouteError(error, "Get customer credits", "Failed to get customer credits");
2178
+ }
2179
+ }
2180
+
2181
+ // src/helpers/payment.ts
2182
+ async function createPaymentIntentCore(request, body, options = {}) {
2183
+ try {
2184
+ if (!body.planRef || !body.productRef) {
2185
+ return {
2186
+ error: "Missing required parameters: planRef and productRef are required",
2187
+ status: 400
2188
+ };
2189
+ }
2190
+ const customerResult = await syncCustomerCore(request, {
2191
+ solvaPay: options.solvaPay,
2192
+ includeEmail: options.includeEmail,
2193
+ includeName: options.includeName
2194
+ });
2195
+ if (isErrorResult(customerResult)) {
2196
+ return customerResult;
2197
+ }
2198
+ const customerRef = customerResult;
2199
+ const solvaPay = options.solvaPay || createSolvaPay();
2200
+ const paymentIntent = await solvaPay.createPaymentIntent({
2201
+ productRef: body.productRef,
2202
+ planRef: body.planRef,
2203
+ customerRef
2204
+ });
2205
+ return {
2206
+ processorPaymentId: paymentIntent.processorPaymentId,
2207
+ clientSecret: paymentIntent.clientSecret,
2208
+ publishableKey: paymentIntent.publishableKey,
2209
+ accountId: paymentIntent.accountId,
2210
+ customerRef
2211
+ };
2212
+ } catch (error) {
2213
+ return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
2214
+ }
2215
+ }
2216
+ async function createTopupPaymentIntentCore(request, body, options = {}) {
2217
+ try {
2218
+ if (!body.amount || body.amount <= 0) {
2219
+ return {
2220
+ error: "Missing or invalid amount: must be a positive number",
2221
+ status: 400
2222
+ };
2223
+ }
2224
+ if (!body.currency) {
2225
+ return {
2226
+ error: "Missing required parameter: currency",
2227
+ status: 400
2228
+ };
2229
+ }
2230
+ if (body.currency !== body.currency.toUpperCase()) {
2231
+ return {
2232
+ error: `Invalid currency "${body.currency}": must be an uppercase ISO 4217 code (e.g. "USD", "EUR")`,
2233
+ status: 400
2234
+ };
2235
+ }
2236
+ const customerResult = await syncCustomerCore(request, {
2237
+ solvaPay: options.solvaPay,
2238
+ includeEmail: options.includeEmail,
2239
+ includeName: options.includeName
2240
+ });
2241
+ if (isErrorResult(customerResult)) {
2242
+ return customerResult;
2243
+ }
2244
+ const customerRef = customerResult;
2245
+ const solvaPay = options.solvaPay || createSolvaPay();
2246
+ const paymentIntent = await solvaPay.createTopupPaymentIntent({
2247
+ customerRef,
2248
+ amount: body.amount,
2249
+ currency: body.currency,
2250
+ description: body.description
2251
+ });
2252
+ return {
2253
+ processorPaymentId: paymentIntent.processorPaymentId,
2254
+ clientSecret: paymentIntent.clientSecret,
2255
+ publishableKey: paymentIntent.publishableKey,
2256
+ accountId: paymentIntent.accountId,
2257
+ customerRef
2258
+ };
2259
+ } catch (error) {
2260
+ return handleRouteError(
2261
+ error,
2262
+ "Create topup payment intent",
2263
+ "Topup payment intent creation failed"
2264
+ );
2265
+ }
2266
+ }
2267
+ async function processPaymentIntentCore(request, body, options = {}) {
2268
+ try {
2269
+ if (!body.paymentIntentId || !body.productRef) {
2270
+ return {
2271
+ error: "paymentIntentId and productRef are required",
2272
+ status: 400
2273
+ };
2274
+ }
2275
+ const customerResult = await syncCustomerCore(request, {
2276
+ solvaPay: options.solvaPay
2277
+ });
2278
+ if (isErrorResult(customerResult)) {
2279
+ return customerResult;
2280
+ }
2281
+ const customerRef = customerResult;
2282
+ const solvaPay = options.solvaPay || createSolvaPay();
2283
+ const result = await solvaPay.processPaymentIntent({
2284
+ paymentIntentId: body.paymentIntentId,
2285
+ productRef: body.productRef,
2286
+ customerRef,
2287
+ planRef: body.planRef
2288
+ });
2289
+ return result;
2290
+ } catch (error) {
2291
+ return handleRouteError(error, "Process payment intent", "Payment processing failed");
2292
+ }
2293
+ }
2294
+
2295
+ // src/helpers/checkout.ts
2296
+ async function createCheckoutSessionCore(request, body, options = {}) {
2297
+ try {
2298
+ if (!body.productRef) {
2299
+ return {
2300
+ error: "Missing required parameter: productRef is required",
2301
+ status: 400
2302
+ };
2303
+ }
2304
+ const customerResult = await syncCustomerCore(request, {
2305
+ solvaPay: options.solvaPay,
2306
+ includeEmail: options.includeEmail,
2307
+ includeName: options.includeName
2308
+ });
2309
+ if (isErrorResult(customerResult)) {
2310
+ return customerResult;
2311
+ }
2312
+ const customerRef = customerResult;
2313
+ let returnUrl = body.returnUrl || options.returnUrl;
2314
+ if (!returnUrl) {
2315
+ try {
2316
+ const url = new URL(request.url);
2317
+ returnUrl = url.origin;
2318
+ } catch {
2319
+ }
2320
+ }
2321
+ const solvaPay = options.solvaPay || createSolvaPay();
2322
+ const session = await solvaPay.createCheckoutSession({
2323
+ productRef: body.productRef,
2324
+ customerRef,
2325
+ planRef: body.planRef || void 0,
2326
+ returnUrl
2327
+ });
2328
+ return {
2329
+ sessionId: session.sessionId,
2330
+ checkoutUrl: session.checkoutUrl
2331
+ };
2332
+ } catch (error) {
2333
+ return handleRouteError(error, "Create checkout session", "Checkout session creation failed");
2334
+ }
2335
+ }
2336
+ async function createCustomerSessionCore(request, options = {}) {
2337
+ try {
2338
+ const customerResult = await syncCustomerCore(request, {
2339
+ solvaPay: options.solvaPay,
2340
+ includeEmail: options.includeEmail,
2341
+ includeName: options.includeName
2342
+ });
2343
+ if (isErrorResult(customerResult)) {
2344
+ return customerResult;
2345
+ }
2346
+ const customerRef = customerResult;
2347
+ const solvaPay = options.solvaPay || createSolvaPay();
2348
+ const session = await solvaPay.createCustomerSession({
2349
+ customerRef
2350
+ });
2351
+ return session;
2352
+ } catch (error) {
2353
+ return handleRouteError(error, "Create customer session", "Customer session creation failed");
2354
+ }
2355
+ }
2356
+
2357
+ // src/helpers/renewal.ts
2358
+ var import_core4 = require("@solvapay/core");
2359
+ async function cancelPurchaseCore(request, body, options = {}) {
2360
+ try {
2361
+ if (!body.purchaseRef) {
2362
+ return {
2363
+ error: "Missing required parameter: purchaseRef is required",
2364
+ status: 400
2365
+ };
2366
+ }
2367
+ const solvaPay = options.solvaPay || createSolvaPay();
2368
+ if (!solvaPay.apiClient.cancelPurchase) {
2369
+ return {
2370
+ error: "Cancel purchase method not available on SDK client",
2371
+ status: 500
2372
+ };
2373
+ }
2374
+ let cancelledPurchase = await solvaPay.apiClient.cancelPurchase({
2375
+ purchaseRef: body.purchaseRef,
2376
+ reason: body.reason
2377
+ });
2378
+ if (!cancelledPurchase || typeof cancelledPurchase !== "object") {
2379
+ return {
2380
+ error: "Invalid response from cancel purchase endpoint",
2381
+ status: 500
2382
+ };
2383
+ }
2384
+ const responseObj = cancelledPurchase;
2385
+ if (responseObj.purchase && typeof responseObj.purchase === "object") {
2386
+ cancelledPurchase = responseObj.purchase;
2387
+ }
2388
+ if (!cancelledPurchase.reference) {
2389
+ return {
2390
+ error: "Cancel purchase response missing required fields",
2391
+ status: 500
2392
+ };
2393
+ }
2394
+ const isCancelled = cancelledPurchase.status === "cancelled" || cancelledPurchase.cancelledAt;
2395
+ if (!isCancelled) {
2396
+ return {
2397
+ error: `Purchase cancellation failed: backend returned status '${cancelledPurchase.status}' without cancelledAt timestamp`,
2398
+ status: 500
2399
+ };
2400
+ }
2401
+ await new Promise((resolve) => setTimeout(resolve, 500));
2402
+ return cancelledPurchase;
2403
+ } catch (error) {
2404
+ if (error instanceof import_core4.SolvaPayError) {
2405
+ const errorMessage = error.message;
2406
+ if (errorMessage.includes("not found")) {
2407
+ return {
2408
+ error: "Purchase not found",
2409
+ status: 404,
2410
+ details: errorMessage
2411
+ };
2412
+ }
2413
+ if (errorMessage.includes("cannot be cancelled") || errorMessage.includes("does not belong to provider")) {
2414
+ return {
2415
+ error: "Purchase cannot be cancelled or does not belong to provider",
2416
+ status: 400,
2417
+ details: errorMessage
2418
+ };
2419
+ }
2420
+ return {
2421
+ error: errorMessage,
2422
+ status: 500,
2423
+ details: errorMessage
2424
+ };
2425
+ }
2426
+ return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
2427
+ }
2428
+ }
2429
+ async function reactivatePurchaseCore(request, body, options = {}) {
2430
+ try {
2431
+ if (!body.purchaseRef) {
2432
+ return {
2433
+ error: "Missing required parameter: purchaseRef is required",
2434
+ status: 400
2435
+ };
2436
+ }
2437
+ const solvaPay = options.solvaPay || createSolvaPay();
2438
+ if (!solvaPay.apiClient.reactivatePurchase) {
2439
+ return {
2440
+ error: "Reactivate purchase method not available on SDK client",
2441
+ status: 500
2442
+ };
2443
+ }
2444
+ let reactivatedPurchase = await solvaPay.apiClient.reactivatePurchase({
2445
+ purchaseRef: body.purchaseRef
2446
+ });
2447
+ if (!reactivatedPurchase || typeof reactivatedPurchase !== "object") {
2448
+ return {
2449
+ error: "Invalid response from reactivate purchase endpoint",
2450
+ status: 500
2451
+ };
2452
+ }
2453
+ const responseObj = reactivatedPurchase;
2454
+ if (responseObj.purchase && typeof responseObj.purchase === "object") {
2455
+ reactivatedPurchase = responseObj.purchase;
2456
+ }
2457
+ if (!reactivatedPurchase.reference) {
2458
+ return {
2459
+ error: "Reactivate purchase response missing required fields",
2460
+ status: 500
2461
+ };
2462
+ }
2463
+ if (reactivatedPurchase.cancelledAt) {
2464
+ return {
2465
+ error: `Purchase reactivation failed: cancelledAt is still set`,
2466
+ status: 500
2467
+ };
2468
+ }
2469
+ await new Promise((resolve) => setTimeout(resolve, 500));
2470
+ return reactivatedPurchase;
2471
+ } catch (error) {
2472
+ if (error instanceof import_core4.SolvaPayError) {
2473
+ const errorMessage = error.message;
2474
+ if (errorMessage.includes("not found")) {
2475
+ return {
2476
+ error: "Purchase not found",
2477
+ status: 404,
2478
+ details: errorMessage
2479
+ };
2480
+ }
2481
+ if (errorMessage.includes("cannot be reactivated") || errorMessage.includes("not pending cancellation") || errorMessage.includes("already been fully cancelled") || errorMessage.includes("already ended")) {
2482
+ return {
2483
+ error: "Purchase cannot be reactivated",
2484
+ status: 400,
2485
+ details: errorMessage
2486
+ };
2487
+ }
2488
+ return {
2489
+ error: errorMessage,
2490
+ status: 500,
2491
+ details: errorMessage
2492
+ };
2493
+ }
2494
+ return handleRouteError(error, "Reactivate purchase", "Failed to reactivate purchase");
2495
+ }
2496
+ }
2497
+
2498
+ // src/helpers/activation.ts
2499
+ async function activatePlanCore(request, body, options = {}) {
2500
+ try {
2501
+ if (!body.productRef || !body.planRef) {
2502
+ return {
2503
+ error: "Missing required parameters: productRef and planRef are required",
2504
+ status: 400
2505
+ };
2506
+ }
2507
+ const customerResult = await syncCustomerCore(request, {
2508
+ solvaPay: options.solvaPay,
2509
+ includeEmail: options.includeEmail,
2510
+ includeName: options.includeName
2511
+ });
2512
+ if (isErrorResult(customerResult)) {
2513
+ return customerResult;
2514
+ }
2515
+ const customerRef = customerResult;
2516
+ const solvaPay = options.solvaPay || createSolvaPay();
2517
+ return await solvaPay.activatePlan({
2518
+ customerRef,
2519
+ productRef: body.productRef,
2520
+ planRef: body.planRef
2521
+ });
2522
+ } catch (error) {
2523
+ return handleRouteError(error, "Activate plan", "Plan activation failed");
2524
+ }
2525
+ }
2526
+
2527
+ // src/helpers/payment-method.ts
2528
+ async function getPaymentMethodCore(request, options = {}) {
2529
+ try {
2530
+ const customerResult = await syncCustomerCore(request, {
2531
+ solvaPay: options.solvaPay,
2532
+ includeEmail: options.includeEmail,
2533
+ includeName: options.includeName
2534
+ });
2535
+ if (isErrorResult(customerResult)) {
2536
+ return customerResult;
2537
+ }
2538
+ const customerRef = customerResult;
2539
+ const solvaPay = options.solvaPay || createSolvaPay();
2540
+ if (!solvaPay.apiClient.getPaymentMethod) {
2541
+ return {
2542
+ error: "getPaymentMethod is not implemented on this API client",
2543
+ status: 500
2544
+ };
2545
+ }
2546
+ return await solvaPay.apiClient.getPaymentMethod({ customerRef });
2547
+ } catch (error) {
2548
+ return handleRouteError(error, "Get payment method", "Failed to load payment method");
2549
+ }
2550
+ }
2551
+
2552
+ // src/helpers/plans.ts
2553
+ var import_core5 = require("@solvapay/core");
2554
+ async function listPlansCore(request, options = {}) {
2555
+ try {
2556
+ const url = new URL(request.url);
2557
+ const productRef = url.searchParams.get("productRef");
2558
+ if (!productRef) {
2559
+ return {
2560
+ error: "Missing required parameter: productRef",
2561
+ status: 400
2562
+ };
2563
+ }
2564
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2565
+ const config = (0, import_core5.getSolvaPayConfig)();
2566
+ if (!config.apiKey) return null;
2567
+ return createSolvaPayClient({
2568
+ apiKey: config.apiKey,
2569
+ apiBaseUrl: config.apiBaseUrl
2570
+ });
2571
+ })();
2572
+ if (!apiClient) {
2573
+ return {
2574
+ error: "Server configuration error: SolvaPay secret key not configured",
2575
+ status: 500
2576
+ };
2577
+ }
2578
+ if (!apiClient.listPlans) {
2579
+ return {
2580
+ error: "List plans method not available",
2581
+ status: 500
2582
+ };
2583
+ }
2584
+ const plans = await apiClient.listPlans(productRef);
2585
+ return {
2586
+ plans: plans || [],
2587
+ productRef
2588
+ };
2589
+ } catch (error) {
2590
+ return handleRouteError(error, "List plans", "Failed to fetch plans");
2591
+ }
2592
+ }
2593
+
2594
+ // src/helpers/merchant.ts
2595
+ var import_core6 = require("@solvapay/core");
2596
+ async function getMerchantCore(_request, options = {}) {
2597
+ try {
2598
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2599
+ const config = (0, import_core6.getSolvaPayConfig)();
2600
+ if (!config.apiKey) return null;
2601
+ return createSolvaPayClient({
2602
+ apiKey: config.apiKey,
2603
+ apiBaseUrl: config.apiBaseUrl
2604
+ });
2605
+ })();
2606
+ if (!apiClient) {
2607
+ return {
2608
+ error: "Server configuration error: SolvaPay secret key not configured",
2609
+ status: 500
2610
+ };
2611
+ }
2612
+ if (!apiClient.getMerchant) {
2613
+ return {
2614
+ error: "Get merchant method not available",
2615
+ status: 500
2616
+ };
2617
+ }
2618
+ const merchant = await apiClient.getMerchant();
2619
+ return merchant;
2620
+ } catch (error) {
2621
+ return handleRouteError(error, "Get merchant", "Failed to fetch merchant");
2622
+ }
2623
+ }
2624
+
2625
+ // src/helpers/product.ts
2626
+ var import_core7 = require("@solvapay/core");
2627
+ async function getProductCore(request, options = {}) {
2628
+ try {
2629
+ const url = new URL(request.url);
2630
+ const productRef = url.searchParams.get("productRef");
2631
+ if (!productRef) {
2632
+ return {
2633
+ error: "Missing required parameter: productRef",
2634
+ status: 400
2635
+ };
2636
+ }
2637
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2638
+ const config = (0, import_core7.getSolvaPayConfig)();
2639
+ if (!config.apiKey) return null;
2640
+ return createSolvaPayClient({
2641
+ apiKey: config.apiKey,
2642
+ apiBaseUrl: config.apiBaseUrl
2643
+ });
2644
+ })();
2645
+ if (!apiClient) {
2646
+ return {
2647
+ error: "Server configuration error: SolvaPay secret key not configured",
2648
+ status: 500
2649
+ };
2650
+ }
2651
+ if (!apiClient.getProduct) {
2652
+ return {
2653
+ error: "Get product method not available",
2654
+ status: 500
2655
+ };
2656
+ }
2657
+ const product = await apiClient.getProduct(productRef);
2658
+ return product;
2659
+ } catch (error) {
2660
+ return handleRouteError(error, "Get product", "Failed to fetch product");
2661
+ }
2662
+ }
2663
+
2664
+ // src/helpers/purchase.ts
2665
+ async function checkPurchaseCore(request, options = {}) {
2666
+ try {
2667
+ const userResult = await getAuthenticatedUserCore(request, {
2668
+ includeEmail: options.includeEmail,
2669
+ includeName: options.includeName
2670
+ });
2671
+ if (isErrorResult(userResult)) {
2672
+ return userResult;
2673
+ }
2674
+ const { userId, email, name } = userResult;
2675
+ const solvaPay = options.solvaPay || createSolvaPay();
2676
+ const cachedCustomerRef = request.headers.get("x-solvapay-customer-ref");
2677
+ if (cachedCustomerRef) {
2678
+ try {
2679
+ const customer = await solvaPay.getCustomer({ customerRef: cachedCustomerRef });
2680
+ if (customer && customer.customerRef) {
2681
+ if (customer.externalRef && customer.externalRef === userId) {
2682
+ const filteredPurchases = (customer.purchases || []).filter(
2683
+ (p) => p.status === "active"
2684
+ );
2685
+ return {
2686
+ customerRef: customer.customerRef,
2687
+ email: customer.email,
2688
+ name: customer.name,
2689
+ purchases: filteredPurchases
2690
+ };
2691
+ }
2692
+ }
2693
+ } catch {
2694
+ }
2695
+ }
2696
+ try {
2697
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2698
+ email: email || void 0,
2699
+ name: name || void 0
2700
+ });
2701
+ const customer = await solvaPay.getCustomer({ customerRef });
2702
+ const filteredPurchases = (customer.purchases || []).filter((p) => p.status === "active");
2703
+ return {
2704
+ customerRef: customer.customerRef || userId,
2705
+ email: customer.email,
2706
+ name: customer.name,
2707
+ purchases: filteredPurchases
2708
+ };
2709
+ } catch {
2710
+ return {
2711
+ customerRef: userId,
2712
+ purchases: []
2713
+ };
2714
+ }
2715
+ } catch (error) {
2716
+ return handleRouteError(error, "Check purchase", "Failed to check purchase");
2717
+ }
2718
+ }
2719
+
2720
+ // src/helpers/usage.ts
2721
+ async function trackUsageCore(request, body, options = {}) {
2722
+ try {
2723
+ const userResult = await getAuthenticatedUserCore(request);
2724
+ if (isErrorResult(userResult)) {
2725
+ return userResult;
2726
+ }
2727
+ const { userId, email, name } = userResult;
2728
+ const solvaPay = options.solvaPay || createSolvaPay();
2729
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2730
+ email: email || void 0,
2731
+ name: name || void 0
2732
+ });
2733
+ await solvaPay.trackUsage({
2734
+ customerRef,
2735
+ actionType: body.actionType,
2736
+ units: body.units,
2737
+ productRef: body.productRef,
2738
+ description: body.description,
2739
+ metadata: body.metadata
2740
+ });
2741
+ return { success: true };
2742
+ } catch (error) {
2743
+ return handleRouteError(error, "Track usage", "Track usage failed");
2744
+ }
2745
+ }
2746
+
2747
+ // src/edge.ts
2748
+ var import_core8 = require("@solvapay/core");
2749
+ function timingSafeEqual(a, b) {
2750
+ if (a.length !== b.length) return false;
2751
+ let mismatch = 0;
2752
+ for (let i = 0; i < a.length; i++) {
2753
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
2754
+ }
2755
+ return mismatch === 0;
2756
+ }
2757
+ async function verifyWebhook({
2758
+ body,
2759
+ signature,
2760
+ secret
2761
+ }) {
2762
+ const toleranceSec = 300;
2763
+ if (!signature) throw new import_core8.SolvaPayError("Missing webhook signature");
2764
+ const parts = signature.split(",");
2765
+ const tPart = parts.find((p) => p.startsWith("t="));
2766
+ const v1Part = parts.find((p) => p.startsWith("v1="));
2767
+ if (!tPart || !v1Part) {
2768
+ throw new import_core8.SolvaPayError("Malformed webhook signature");
2769
+ }
2770
+ const timestamp = parseInt(tPart.slice(2), 10);
2771
+ const receivedHmac = v1Part.slice(3);
2772
+ if (Number.isNaN(timestamp) || !receivedHmac) {
2773
+ throw new import_core8.SolvaPayError("Malformed webhook signature");
2774
+ }
2775
+ if (toleranceSec > 0) {
2776
+ const age = Math.abs(Math.floor(Date.now() / 1e3) - timestamp);
2777
+ if (age > toleranceSec) {
2778
+ throw new import_core8.SolvaPayError("Webhook signature timestamp too old");
2779
+ }
2780
+ }
2781
+ const enc = new TextEncoder();
2782
+ const key = await crypto.subtle.importKey(
2783
+ "raw",
2784
+ enc.encode(secret),
2785
+ { name: "HMAC", hash: "SHA-256" },
2786
+ false,
2787
+ ["sign"]
2788
+ );
2789
+ const sigBuf = await crypto.subtle.sign("HMAC", key, enc.encode(`${timestamp}.${body}`));
2790
+ const expectedHmac = Array.from(new Uint8Array(sigBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
2791
+ if (!timingSafeEqual(expectedHmac, receivedHmac)) {
2792
+ throw new import_core8.SolvaPayError("Invalid webhook signature");
2793
+ }
2794
+ try {
2795
+ return JSON.parse(body);
2796
+ } catch {
2797
+ throw new import_core8.SolvaPayError("Invalid webhook payload: body is not valid JSON");
2798
+ }
2799
+ }
2800
+
2801
+ // src/fetch/cors.ts
2802
+ var corsConfig = { origins: ["*"] };
2803
+ function configureCors(config) {
2804
+ corsConfig = config;
2805
+ }
2806
+ function getCorsHeaders(req) {
2807
+ const origin = req.headers.get("Origin");
2808
+ const headers = {
2809
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
2810
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, x-solvapay-customer-ref",
2811
+ "Access-Control-Max-Age": "86400"
2812
+ };
2813
+ if (corsConfig.origins.includes("*")) {
2814
+ headers["Access-Control-Allow-Origin"] = "*";
2815
+ } else if (origin && corsConfig.origins.includes(origin)) {
2816
+ headers["Access-Control-Allow-Origin"] = origin;
2817
+ headers["Vary"] = "Origin";
2818
+ }
2819
+ return headers;
2820
+ }
2821
+ function handleCors(req) {
2822
+ if (req.method !== "OPTIONS") {
2823
+ return null;
2824
+ }
2825
+ const headers = getCorsHeaders(req);
2826
+ return new Response(null, {
2827
+ status: 204,
2828
+ headers
2829
+ });
2830
+ }
2831
+
2832
+ // src/fetch/utils.ts
2833
+ function errorResponse(result, req) {
2834
+ const corsHeaders = req ? getCorsHeaders(req) : {};
2835
+ return new Response(
2836
+ JSON.stringify({ error: result.error, ...result.details ? { details: result.details } : {} }),
2837
+ {
2838
+ status: result.status,
2839
+ headers: {
2840
+ "Content-Type": "application/json",
2841
+ ...corsHeaders
2842
+ }
2843
+ }
2844
+ );
2845
+ }
2846
+ function jsonResponseWithCors(data, req, status = 200) {
2847
+ const corsHeaders = getCorsHeaders(req);
2848
+ return new Response(JSON.stringify(data), {
2849
+ status,
2850
+ headers: {
2851
+ "Content-Type": "application/json",
2852
+ ...corsHeaders
2853
+ }
2854
+ });
2855
+ }
2856
+
2857
+ // src/fetch/handlers.ts
2858
+ async function parseJsonBody(req) {
2859
+ try {
2860
+ return await req.json();
2861
+ } catch {
2862
+ return {};
2863
+ }
2864
+ }
2865
+ async function checkPurchase(req) {
2866
+ const corsResponse = handleCors(req);
2867
+ if (corsResponse) return corsResponse;
2868
+ const result = await checkPurchaseCore(req);
2869
+ if (isErrorResult(result)) {
2870
+ return errorResponse(result, req);
2871
+ }
2872
+ return jsonResponseWithCors(result, req);
2873
+ }
2874
+ async function trackUsage(req) {
2875
+ const corsResponse = handleCors(req);
2876
+ if (corsResponse) return corsResponse;
2877
+ const body = await parseJsonBody(req);
2878
+ const result = await trackUsageCore(req, body, {});
2879
+ if (isErrorResult(result)) {
2880
+ return errorResponse(result, req);
2881
+ }
2882
+ return jsonResponseWithCors(result, req);
2883
+ }
2884
+ async function createPaymentIntent(req) {
2885
+ const corsResponse = handleCors(req);
2886
+ if (corsResponse) return corsResponse;
2887
+ const body = await parseJsonBody(req);
2888
+ const result = await createPaymentIntentCore(req, body);
2889
+ if (isErrorResult(result)) {
2890
+ return errorResponse(result, req);
2891
+ }
2892
+ return jsonResponseWithCors(result, req);
2893
+ }
2894
+ async function processPayment(req) {
2895
+ const corsResponse = handleCors(req);
2896
+ if (corsResponse) return corsResponse;
2897
+ const body = await parseJsonBody(req);
2898
+ const result = await processPaymentIntentCore(req, body);
2899
+ if (isErrorResult(result)) {
2900
+ return errorResponse(result, req);
2901
+ }
2902
+ return jsonResponseWithCors(result, req);
2903
+ }
2904
+ async function createTopupPaymentIntent(req) {
2905
+ const corsResponse = handleCors(req);
2906
+ if (corsResponse) return corsResponse;
2907
+ const body = await parseJsonBody(req);
2908
+ const result = await createTopupPaymentIntentCore(req, body);
2909
+ if (isErrorResult(result)) {
2910
+ return errorResponse(result, req);
2911
+ }
2912
+ return jsonResponseWithCors(result, req);
2913
+ }
2914
+ async function customerBalance(req) {
2915
+ const corsResponse = handleCors(req);
2916
+ if (corsResponse) return corsResponse;
2917
+ const result = await getCustomerBalanceCore(req);
2918
+ if (isErrorResult(result)) {
2919
+ return errorResponse(result, req);
2920
+ }
2921
+ return jsonResponseWithCors(result, req);
2922
+ }
2923
+ async function cancelRenewal(req) {
2924
+ const corsResponse = handleCors(req);
2925
+ if (corsResponse) return corsResponse;
2926
+ const body = await parseJsonBody(req);
2927
+ const result = await cancelPurchaseCore(req, body);
2928
+ if (isErrorResult(result)) {
2929
+ return errorResponse(result, req);
2930
+ }
2931
+ return jsonResponseWithCors(result, req);
2932
+ }
2933
+ async function reactivateRenewal(req) {
2934
+ const corsResponse = handleCors(req);
2935
+ if (corsResponse) return corsResponse;
2936
+ const body = await parseJsonBody(req);
2937
+ const result = await reactivatePurchaseCore(req, body);
2938
+ if (isErrorResult(result)) {
2939
+ return errorResponse(result, req);
2940
+ }
2941
+ return jsonResponseWithCors(result, req);
2942
+ }
2943
+ async function activatePlan(req) {
2944
+ const corsResponse = handleCors(req);
2945
+ if (corsResponse) return corsResponse;
2946
+ const body = await parseJsonBody(req);
2947
+ const result = await activatePlanCore(req, body);
2948
+ if (isErrorResult(result)) {
2949
+ return errorResponse(result, req);
2950
+ }
2951
+ return jsonResponseWithCors(result, req);
2952
+ }
2953
+ async function getPaymentMethod(req) {
2954
+ const corsResponse = handleCors(req);
2955
+ if (corsResponse) return corsResponse;
2956
+ const result = await getPaymentMethodCore(req);
2957
+ if (isErrorResult(result)) {
2958
+ return errorResponse(result, req);
2959
+ }
2960
+ return jsonResponseWithCors(result, req);
2961
+ }
2962
+ async function listPlans(req) {
2963
+ const corsResponse = handleCors(req);
2964
+ if (corsResponse) return corsResponse;
2965
+ const result = await listPlansCore(req);
2966
+ if (isErrorResult(result)) {
2967
+ return errorResponse(result, req);
2968
+ }
2969
+ return jsonResponseWithCors(result, req);
2970
+ }
2971
+ async function syncCustomer(req) {
2972
+ const corsResponse = handleCors(req);
2973
+ if (corsResponse) return corsResponse;
2974
+ const result = await syncCustomerCore(req);
2975
+ if (isErrorResult(result)) {
2976
+ return errorResponse(result, req);
2977
+ }
2978
+ return jsonResponseWithCors({ customerRef: result }, req);
2979
+ }
2980
+ async function createCheckoutSession(req) {
2981
+ const corsResponse = handleCors(req);
2982
+ if (corsResponse) return corsResponse;
2983
+ const body = await parseJsonBody(req);
2984
+ const result = await createCheckoutSessionCore(req, body);
2985
+ if (isErrorResult(result)) {
2986
+ return errorResponse(result, req);
2987
+ }
2988
+ return jsonResponseWithCors(result, req);
2989
+ }
2990
+ async function createCustomerSession(req) {
2991
+ const corsResponse = handleCors(req);
2992
+ if (corsResponse) return corsResponse;
2993
+ const result = await createCustomerSessionCore(req);
2994
+ if (isErrorResult(result)) {
2995
+ return errorResponse(result, req);
2996
+ }
2997
+ return jsonResponseWithCors(result, req);
2998
+ }
2999
+ async function getMerchant(req) {
3000
+ const corsResponse = handleCors(req);
3001
+ if (corsResponse) return corsResponse;
3002
+ const result = await getMerchantCore(req);
3003
+ if (isErrorResult(result)) {
3004
+ return errorResponse(result, req);
3005
+ }
3006
+ return jsonResponseWithCors(result, req);
3007
+ }
3008
+ async function getProduct(req) {
3009
+ const corsResponse = handleCors(req);
3010
+ if (corsResponse) return corsResponse;
3011
+ const result = await getProductCore(req);
3012
+ if (isErrorResult(result)) {
3013
+ return errorResponse(result, req);
3014
+ }
3015
+ return jsonResponseWithCors(result, req);
3016
+ }
3017
+ function solvapayWebhook(options) {
3018
+ return async (req) => {
3019
+ const secret = options.secret || (typeof process !== "undefined" ? process.env.SOLVAPAY_WEBHOOK_SECRET : void 0);
3020
+ if (!secret) {
3021
+ return new Response(JSON.stringify({ error: "Webhook secret not configured" }), {
3022
+ status: 500,
3023
+ headers: { "Content-Type": "application/json" }
3024
+ });
3025
+ }
3026
+ const body = await req.text();
3027
+ const signature = req.headers.get("sv-signature") ?? "";
3028
+ let event;
3029
+ try {
3030
+ event = await verifyWebhook({ body, signature, secret });
3031
+ } catch {
3032
+ return new Response(JSON.stringify({ error: "Invalid webhook signature" }), {
3033
+ status: 401,
3034
+ headers: { "Content-Type": "application/json" }
3035
+ });
3036
+ }
3037
+ try {
3038
+ await options.onEvent(event);
3039
+ } catch {
3040
+ return new Response(JSON.stringify({ error: "Webhook handler failed" }), {
3041
+ status: 500,
3042
+ headers: { "Content-Type": "application/json" }
3043
+ });
3044
+ }
3045
+ return new Response(JSON.stringify({ received: true }), {
3046
+ status: 200,
3047
+ headers: { "Content-Type": "application/json" }
3048
+ });
3049
+ };
3050
+ }
3051
+ // Annotate the CommonJS export names for ESM import in node:
3052
+ 0 && (module.exports = {
3053
+ activatePlan,
3054
+ cancelRenewal,
3055
+ checkPurchase,
3056
+ configureCors,
3057
+ createCheckoutSession,
3058
+ createCustomerSession,
3059
+ createPaymentIntent,
3060
+ createTopupPaymentIntent,
3061
+ customerBalance,
3062
+ getMerchant,
3063
+ getPaymentMethod,
3064
+ getProduct,
3065
+ listPlans,
3066
+ processPayment,
3067
+ reactivateRenewal,
3068
+ solvapayWebhook,
3069
+ syncCustomer,
3070
+ trackUsage
3071
+ });