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