@solvapay/server 1.0.0-preview.9 → 1.0.1-preview.2

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