@solvapay/server 1.0.0-preview.1 → 1.0.0-preview.10

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
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-R5U7XKVJ.js";
4
4
 
5
5
  // src/edge.ts
6
- import { SolvaPayError as SolvaPayError3 } from "@solvapay/core";
6
+ import { SolvaPayError as SolvaPayError5 } from "@solvapay/core";
7
7
 
8
8
  // src/client.ts
9
9
  import { SolvaPayError } from "@solvapay/core";
@@ -14,15 +14,16 @@ function createSolvaPayClient(opts) {
14
14
  "Content-Type": "application/json",
15
15
  "Authorization": `Bearer ${opts.apiKey}`
16
16
  };
17
- console.log(`\u{1F50C} SolvaPay API Client initialized`);
18
- console.log(` Backend URL: ${base}`);
19
- console.log(` API Key: ${opts.apiKey.substring(0, 10)}...`);
17
+ const debug = process.env.SOLVAPAY_DEBUG === "true";
18
+ const log = (...args) => {
19
+ if (debug) {
20
+ console.log(...args);
21
+ }
22
+ };
20
23
  return {
21
24
  // POST: /v1/sdk/limits
22
25
  async checkLimits(params) {
23
26
  const url = `${base}/v1/sdk/limits`;
24
- console.log(`\u{1F4E1} API Request: POST ${url}`);
25
- console.log(` Params:`, JSON.stringify(params, null, 2));
26
27
  const res = await fetch(url, {
27
28
  method: "POST",
28
29
  headers,
@@ -30,24 +31,15 @@ function createSolvaPayClient(opts) {
30
31
  });
31
32
  if (!res.ok) {
32
33
  const error = await res.text();
33
- console.error(`\u274C API Error: ${res.status} - ${error}`);
34
+ log(`\u274C API Error: ${res.status} - ${error}`);
34
35
  throw new SolvaPayError(`Check limits failed (${res.status}): ${error}`);
35
36
  }
36
37
  const result = await res.json();
37
- console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
38
- console.log(`\u{1F50D} DEBUG - checkLimits breakdown:`);
39
- console.log(` - withinLimits: ${result.withinLimits}`);
40
- console.log(` - remaining: ${result.remaining}`);
41
- console.log(` - plan: ${result.plan || "N/A"}`);
42
- console.log(` - checkoutUrl: ${result.checkoutUrl || "N/A"}`);
43
- console.log(` - Full response keys:`, Object.keys(result));
44
38
  return result;
45
39
  },
46
40
  // POST: /v1/sdk/usages
47
41
  async trackUsage(params) {
48
42
  const url = `${base}/v1/sdk/usages`;
49
- console.log(`\u{1F4E1} API Request: POST ${url}`);
50
- console.log(` Params:`, JSON.stringify(params, null, 2));
51
43
  const res = await fetch(url, {
52
44
  method: "POST",
53
45
  headers,
@@ -55,16 +47,13 @@ function createSolvaPayClient(opts) {
55
47
  });
56
48
  if (!res.ok) {
57
49
  const error = await res.text();
58
- console.error(`\u274C API Error: ${res.status} - ${error}`);
50
+ log(`\u274C API Error: ${res.status} - ${error}`);
59
51
  throw new SolvaPayError(`Track usage failed (${res.status}): ${error}`);
60
52
  }
61
- console.log(`\u2705 Usage tracked successfully`);
62
53
  },
63
54
  // POST: /v1/sdk/customers
64
55
  async createCustomer(params) {
65
56
  const url = `${base}/v1/sdk/customers`;
66
- console.log(`\u{1F4E1} API Request: POST ${url}`);
67
- console.log(` Params:`, JSON.stringify(params, null, 2));
68
57
  const res = await fetch(url, {
69
58
  method: "POST",
70
59
  headers,
@@ -72,51 +61,69 @@ function createSolvaPayClient(opts) {
72
61
  });
73
62
  if (!res.ok) {
74
63
  const error = await res.text();
75
- console.error(`\u274C API Error: ${res.status} - ${error}`);
64
+ log(`\u274C API Error: ${res.status} - ${error}`);
76
65
  throw new SolvaPayError(`Create customer failed (${res.status}): ${error}`);
77
66
  }
78
67
  const result = await res.json();
79
- console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
80
- console.log(`\u{1F50D} DEBUG - createCustomer response:`);
81
- console.log(` - reference/customerRef: ${result.reference || result.customerRef}`);
82
- console.log(` - Has plan info: ${result.plan ? "YES" : "NO"}`);
83
- console.log(` - Has subscription info: ${result.subscription ? "YES" : "NO"}`);
84
- console.log(` - Full response keys:`, Object.keys(result));
85
68
  return result;
86
69
  },
87
70
  // GET: /v1/sdk/customers/{reference}
88
71
  async getCustomer(params) {
89
72
  const url = `${base}/v1/sdk/customers/${params.customerRef}`;
90
- console.log(`\u{1F4E1} API Request: GET ${url}`);
91
73
  const res = await fetch(url, {
92
74
  method: "GET",
93
75
  headers
94
76
  });
95
77
  if (!res.ok) {
96
78
  const error = await res.text();
97
- console.error(`\u274C API Error: ${res.status} - ${error}`);
79
+ log(`\u274C API Error: ${res.status} - ${error}`);
98
80
  throw new SolvaPayError(`Get customer failed (${res.status}): ${error}`);
99
81
  }
100
82
  const result = await res.json();
101
- console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
102
- return result;
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}`);
102
+ }
103
+ const result = await res.json();
104
+ const customer = Array.isArray(result) ? result[0] : result;
105
+ return {
106
+ customerRef: customer.reference || customer.customerRef,
107
+ email: customer.email,
108
+ name: customer.name,
109
+ externalRef: customer.externalRef,
110
+ subscriptions: customer.subscriptions || []
111
+ };
103
112
  },
104
113
  // Management methods (primarily for integration tests)
105
114
  // GET: /v1/sdk/agents
106
115
  async listAgents() {
107
116
  const url = `${base}/v1/sdk/agents`;
108
- console.log(`\u{1F4E1} API Request: GET ${url}`);
109
117
  const res = await fetch(url, {
110
118
  method: "GET",
111
119
  headers
112
120
  });
113
121
  if (!res.ok) {
114
122
  const error = await res.text();
115
- console.error(`\u274C API Error: ${res.status} - ${error}`);
123
+ log(`\u274C API Error: ${res.status} - ${error}`);
116
124
  throw new SolvaPayError(`List agents failed (${res.status}): ${error}`);
117
125
  }
118
126
  const result = await res.json();
119
- console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
120
127
  const agents = Array.isArray(result) ? result : result.agents || [];
121
128
  return agents.map((agent) => ({
122
129
  ...agent,
@@ -126,8 +133,6 @@ function createSolvaPayClient(opts) {
126
133
  // POST: /v1/sdk/agents
127
134
  async createAgent(params) {
128
135
  const url = `${base}/v1/sdk/agents`;
129
- console.log(`\u{1F4E1} API Request: POST ${url}`);
130
- console.log(` Params:`, JSON.stringify(params, null, 2));
131
136
  const res = await fetch(url, {
132
137
  method: "POST",
133
138
  headers,
@@ -135,54 +140,54 @@ function createSolvaPayClient(opts) {
135
140
  });
136
141
  if (!res.ok) {
137
142
  const error = await res.text();
138
- console.error(`\u274C API Error: ${res.status} - ${error}`);
143
+ log(`\u274C API Error: ${res.status} - ${error}`);
139
144
  throw new SolvaPayError(`Create agent failed (${res.status}): ${error}`);
140
145
  }
141
146
  const result = await res.json();
142
- console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
143
147
  return result;
144
148
  },
145
149
  // DELETE: /v1/sdk/agents/{agentRef}
146
150
  async deleteAgent(agentRef) {
147
151
  const url = `${base}/v1/sdk/agents/${agentRef}`;
148
- console.log(`\u{1F4E1} API Request: DELETE ${url}`);
149
152
  const res = await fetch(url, {
150
153
  method: "DELETE",
151
154
  headers
152
155
  });
153
156
  if (!res.ok && res.status !== 404) {
154
157
  const error = await res.text();
155
- console.error(`\u274C API Error: ${res.status} - ${error}`);
158
+ log(`\u274C API Error: ${res.status} - ${error}`);
156
159
  throw new SolvaPayError(`Delete agent failed (${res.status}): ${error}`);
157
160
  }
158
- console.log(`\u2705 Agent deleted successfully`);
159
161
  },
160
162
  // GET: /v1/sdk/agents/{agentRef}/plans
161
163
  async listPlans(agentRef) {
162
164
  const url = `${base}/v1/sdk/agents/${agentRef}/plans`;
163
- console.log(`\u{1F4E1} API Request: GET ${url}`);
164
165
  const res = await fetch(url, {
165
166
  method: "GET",
166
167
  headers
167
168
  });
168
169
  if (!res.ok) {
169
170
  const error = await res.text();
170
- console.error(`\u274C API Error: ${res.status} - ${error}`);
171
+ log(`\u274C API Error: ${res.status} - ${error}`);
171
172
  throw new SolvaPayError(`List plans failed (${res.status}): ${error}`);
172
173
  }
173
174
  const result = await res.json();
174
- console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
175
175
  const plans = Array.isArray(result) ? result : result.plans || [];
176
- return plans.map((plan) => ({
177
- ...plan,
178
- ...plan.data || {}
179
- }));
176
+ return plans.map((plan) => {
177
+ const price = plan.price ?? plan.data?.price;
178
+ const unwrapped = {
179
+ ...plan.data || {},
180
+ ...plan,
181
+ // Explicitly preserve price field to ensure it's not lost
182
+ ...price !== void 0 && { price }
183
+ };
184
+ delete unwrapped.data;
185
+ return unwrapped;
186
+ });
180
187
  },
181
188
  // POST: /v1/sdk/agents/{agentRef}/plans
182
189
  async createPlan(params) {
183
190
  const url = `${base}/v1/sdk/agents/${params.agentRef}/plans`;
184
- console.log(`\u{1F4E1} API Request: POST ${url}`);
185
- console.log(` Params:`, JSON.stringify(params, null, 2));
186
191
  const res = await fetch(url, {
187
192
  method: "POST",
188
193
  headers,
@@ -190,37 +195,29 @@ function createSolvaPayClient(opts) {
190
195
  });
191
196
  if (!res.ok) {
192
197
  const error = await res.text();
193
- console.error(`\u274C API Error: ${res.status} - ${error}`);
198
+ log(`\u274C API Error: ${res.status} - ${error}`);
194
199
  throw new SolvaPayError(`Create plan failed (${res.status}): ${error}`);
195
200
  }
196
201
  const result = await res.json();
197
- console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
198
202
  return result;
199
203
  },
200
204
  // DELETE: /v1/sdk/agents/{agentRef}/plans/{planRef}
201
205
  async deletePlan(agentRef, planRef) {
202
206
  const url = `${base}/v1/sdk/agents/${agentRef}/plans/${planRef}`;
203
- console.log(`\u{1F4E1} API Request: DELETE ${url}`);
204
207
  const res = await fetch(url, {
205
208
  method: "DELETE",
206
209
  headers
207
210
  });
208
211
  if (!res.ok && res.status !== 404) {
209
212
  const error = await res.text();
210
- console.error(`\u274C API Error: ${res.status} - ${error}`);
213
+ log(`\u274C API Error: ${res.status} - ${error}`);
211
214
  throw new SolvaPayError(`Delete plan failed (${res.status}): ${error}`);
212
215
  }
213
- console.log(`\u2705 Plan deleted successfully`);
214
216
  },
215
217
  // POST: /payment-intents
216
218
  async createPaymentIntent(params) {
217
219
  const idempotencyKey = params.idempotencyKey || `payment-${params.planRef}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
218
220
  const url = `${base}/v1/sdk/payment-intents`;
219
- console.log(`\u{1F4E1} API Request: POST ${url}`);
220
- console.log(` Agent Ref: ${params.agentRef}`);
221
- console.log(` Plan Ref: ${params.planRef}`);
222
- console.log(` Customer Ref: ${params.customerRef}`);
223
- console.log(` Idempotency Key: ${idempotencyKey}`);
224
221
  const res = await fetch(url, {
225
222
  method: "POST",
226
223
  headers: {
@@ -235,16 +232,113 @@ function createSolvaPayClient(opts) {
235
232
  });
236
233
  if (!res.ok) {
237
234
  const error = await res.text();
238
- console.error(`\u274C API Error: ${res.status} - ${error}`);
235
+ log(`\u274C API Error: ${res.status} - ${error}`);
239
236
  throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
240
237
  }
241
238
  const result = await res.json();
242
- console.log(`\u2705 Payment intent created:`, {
243
- id: result.id,
244
- hasClientSecret: !!result.clientSecret,
245
- hasPublishableKey: !!result.publishableKey,
246
- accountId: result.accountId
239
+ return result;
240
+ },
241
+ // POST: /v1/sdk/payment-intents/{paymentIntentId}/process
242
+ async processPayment(params) {
243
+ const url = `${base}/v1/sdk/payment-intents/${params.paymentIntentId}/process`;
244
+ const res = await fetch(url, {
245
+ method: "POST",
246
+ headers: {
247
+ ...headers,
248
+ "Content-Type": "application/json"
249
+ },
250
+ body: JSON.stringify({
251
+ agentRef: params.agentRef,
252
+ customerRef: params.customerRef,
253
+ planRef: params.planRef
254
+ })
247
255
  });
256
+ if (!res.ok) {
257
+ const error = await res.text();
258
+ log(`\u274C API Error: ${res.status} - ${error}`);
259
+ throw new SolvaPayError(`Process payment failed (${res.status}): ${error}`);
260
+ }
261
+ const result = await res.json();
262
+ return result;
263
+ },
264
+ // POST: /v1/sdk/subscriptions/{subscriptionRef}/cancel
265
+ async cancelSubscription(params) {
266
+ const url = `${base}/v1/sdk/subscriptions/${params.subscriptionRef}/cancel`;
267
+ const requestOptions = {
268
+ method: "POST",
269
+ headers
270
+ };
271
+ if (params.reason) {
272
+ requestOptions.body = JSON.stringify({ reason: params.reason });
273
+ }
274
+ const res = await fetch(url, requestOptions);
275
+ if (!res.ok) {
276
+ const error = await res.text();
277
+ log(`\u274C API Error: ${res.status} - ${error}`);
278
+ if (res.status === 404) {
279
+ throw new SolvaPayError(`Subscription not found: ${error}`);
280
+ }
281
+ if (res.status === 400) {
282
+ throw new SolvaPayError(`Subscription cannot be cancelled or does not belong to provider: ${error}`);
283
+ }
284
+ throw new SolvaPayError(`Cancel subscription failed (${res.status}): ${error}`);
285
+ }
286
+ const responseText = await res.text();
287
+ let responseData;
288
+ try {
289
+ responseData = JSON.parse(responseText);
290
+ } catch (parseError) {
291
+ log(`\u274C Failed to parse response as JSON: ${parseError}`);
292
+ throw new SolvaPayError(`Invalid JSON response from cancel subscription endpoint: ${responseText.substring(0, 200)}`);
293
+ }
294
+ if (!responseData || typeof responseData !== "object") {
295
+ log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
296
+ throw new SolvaPayError(`Invalid response structure from cancel subscription endpoint`);
297
+ }
298
+ let result;
299
+ if (responseData.subscription && typeof responseData.subscription === "object") {
300
+ result = responseData.subscription;
301
+ } else if (responseData.reference) {
302
+ result = responseData;
303
+ } else {
304
+ result = responseData.subscription || responseData;
305
+ }
306
+ 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`);
309
+ }
310
+ return result;
311
+ },
312
+ // POST: /v1/sdk/checkout-sessions
313
+ async createCheckoutSession(params) {
314
+ const url = `${base}/v1/sdk/checkout-sessions`;
315
+ const res = await fetch(url, {
316
+ method: "POST",
317
+ headers,
318
+ body: JSON.stringify(params)
319
+ });
320
+ if (!res.ok) {
321
+ const error = await res.text();
322
+ log(`\u274C API Error: ${res.status} - ${error}`);
323
+ throw new SolvaPayError(`Create checkout session failed (${res.status}): ${error}`);
324
+ }
325
+ const result = await res.json();
326
+ return result;
327
+ },
328
+ // POST: /v1/sdk/customers/customer-sessions
329
+ async createCustomerSession(params) {
330
+ const url = `${base}/v1/sdk/customers/customer-sessions`;
331
+ const res = await fetch(url, {
332
+ method: "POST",
333
+ headers,
334
+ body: JSON.stringify(params)
335
+ });
336
+ if (!res.ok) {
337
+ const error = await res.text();
338
+ log(`\u274C API Error: ${res.status} - ${error}`);
339
+ throw new SolvaPayError(`Create customer session failed (${res.status}): ${error}`);
340
+ }
341
+ const result = await res.json();
248
342
  return result;
249
343
  }
250
344
  };
@@ -296,6 +390,95 @@ function calculateDelay(initialDelay, attempt, strategy) {
296
390
  function sleep(ms) {
297
391
  return new Promise((resolve) => setTimeout(resolve, ms));
298
392
  }
393
+ function createRequestDeduplicator(options = {}) {
394
+ const {
395
+ cacheTTL = 2e3,
396
+ maxCacheSize = 1e3,
397
+ cacheErrors = true
398
+ } = options;
399
+ const inFlightRequests = /* @__PURE__ */ new Map();
400
+ const resultCache = /* @__PURE__ */ new Map();
401
+ let cleanupInterval = null;
402
+ 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);
409
+ }
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) {
418
+ resultCache.delete(key);
419
+ }
420
+ }
421
+ }, Math.min(cacheTTL, 1e3));
422
+ }
423
+ const deduplicate = async (key, fn) => {
424
+ if (cacheTTL > 0) {
425
+ const cached = resultCache.get(key);
426
+ if (cached && Date.now() - cached.timestamp < cacheTTL) {
427
+ return cached.data;
428
+ } else if (cached) {
429
+ resultCache.delete(key);
430
+ }
431
+ }
432
+ let requestPromise = inFlightRequests.get(key);
433
+ if (!requestPromise) {
434
+ requestPromise = (async () => {
435
+ try {
436
+ const result = await fn();
437
+ if (cacheTTL > 0) {
438
+ resultCache.set(key, {
439
+ data: result,
440
+ timestamp: Date.now()
441
+ });
442
+ }
443
+ return result;
444
+ } catch (error) {
445
+ if (cacheTTL > 0 && cacheErrors) {
446
+ resultCache.set(key, {
447
+ data: error,
448
+ timestamp: Date.now()
449
+ });
450
+ }
451
+ throw error;
452
+ } finally {
453
+ inFlightRequests.delete(key);
454
+ }
455
+ })();
456
+ const existingPromise = inFlightRequests.get(key);
457
+ if (existingPromise) {
458
+ requestPromise = existingPromise;
459
+ } else {
460
+ inFlightRequests.set(key, requestPromise);
461
+ }
462
+ }
463
+ return requestPromise;
464
+ };
465
+ const clearCache = (key) => {
466
+ resultCache.delete(key);
467
+ };
468
+ const clearAllCache = () => {
469
+ resultCache.clear();
470
+ };
471
+ const getStats = () => ({
472
+ inFlight: inFlightRequests.size,
473
+ cached: resultCache.size
474
+ });
475
+ return {
476
+ deduplicate,
477
+ clearCache,
478
+ clearAllCache,
479
+ getStats
480
+ };
481
+ }
299
482
 
300
483
  // src/paywall.ts
301
484
  var PaywallError = class extends Error {
@@ -305,10 +488,18 @@ var PaywallError = class extends Error {
305
488
  this.name = "PaywallError";
306
489
  }
307
490
  };
491
+ var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
492
+ cacheTTL: 6e4,
493
+ // Cache results for 60 seconds (reduces API calls significantly)
494
+ maxCacheSize: 1e3,
495
+ // Maximum cache entries
496
+ cacheErrors: false
497
+ // Don't cache errors - retry on next request
498
+ });
308
499
  var SolvaPayPaywall = class {
309
500
  constructor(apiClient, options = {}) {
310
501
  this.apiClient = apiClient;
311
- this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG !== "false";
502
+ this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG === "true";
312
503
  }
313
504
  customerCreationAttempts = /* @__PURE__ */ new Set();
314
505
  customerRefMapping = /* @__PURE__ */ new Map();
@@ -348,15 +539,12 @@ var SolvaPayPaywall = class {
348
539
  const backendCustomerRef = await this.ensureCustomer(inputCustomerRef);
349
540
  try {
350
541
  const planRef = metadata.plan || toolName;
351
- this.log(`\u{1F50D} Checking limits for customer: ${backendCustomerRef}, agent: ${agent}, plan: ${planRef}`);
352
542
  const limitsCheck = await this.apiClient.checkLimits({
353
543
  customerRef: backendCustomerRef,
354
544
  agentRef: agent
355
545
  });
356
- this.log(`\u2713 Limits check passed:`, limitsCheck);
357
546
  if (!limitsCheck.withinLimits) {
358
547
  const latencyMs2 = Date.now() - startTime;
359
- this.log(`\u{1F6AB} Paywall triggered - tracking usage`);
360
548
  await this.trackUsage(backendCustomerRef, agent, planRef, toolName, "paywall", requestId, latencyMs2);
361
549
  throw new PaywallError("Payment required", {
362
550
  kind: "payment_required",
@@ -365,16 +553,17 @@ var SolvaPayPaywall = class {
365
553
  message: `Plan subscription required. Remaining: ${limitsCheck.remaining}`
366
554
  });
367
555
  }
368
- this.log(`\u26A1 Executing handler: ${toolName}`);
369
556
  const result = await handler(args);
370
- this.log(`\u2713 Handler completed successfully`);
371
557
  const latencyMs = Date.now() - startTime;
372
- this.log(`\u{1F4CA} Tracking successful usage`);
373
558
  await this.trackUsage(backendCustomerRef, agent, planRef, toolName, "success", requestId, latencyMs);
374
- this.log(`\u2705 Request completed successfully`);
375
559
  return result;
376
560
  } catch (error) {
377
- this.log(`\u274C Error in paywall:`, error);
561
+ if (error instanceof Error) {
562
+ const errorType = error instanceof PaywallError ? "PaywallError" : "API Error";
563
+ this.log(`\u274C Error in paywall [${errorType}]: ${error.message}`);
564
+ } else {
565
+ this.log(`\u274C Error in paywall:`, error);
566
+ }
378
567
  const latencyMs = Date.now() - startTime;
379
568
  const outcome = error instanceof PaywallError ? "paywall" : "fail";
380
569
  const planRef = metadata.plan || toolName;
@@ -388,41 +577,80 @@ var SolvaPayPaywall = class {
388
577
  * This is a public helper for testing, pre-creating customers, and internal use.
389
578
  * Only attempts creation once per customer (idempotent).
390
579
  * Returns the backend customer reference to use in API calls.
580
+ *
581
+ * @param customerRef - The customer reference used as a cache key (e.g., Supabase user ID)
582
+ * @param externalRef - Optional external reference for backend lookup (e.g., Supabase user ID)
583
+ * If provided, will lookup existing customer by externalRef before creating new one.
584
+ * The externalRef is stored on the SolvaPay backend for customer lookup.
585
+ * @param options - Optional customer details (email, name) for customer creation
391
586
  */
392
- async ensureCustomer(customerRef) {
587
+ async ensureCustomer(customerRef, externalRef, options) {
393
588
  if (this.customerRefMapping.has(customerRef)) {
394
589
  return this.customerRefMapping.get(customerRef);
395
590
  }
396
591
  if (customerRef === "anonymous") {
397
592
  return customerRef;
398
593
  }
399
- if (this.customerCreationAttempts.has(customerRef)) {
400
- return customerRef;
401
- }
402
- if (!this.apiClient.createCustomer) {
403
- console.warn(`\u26A0\uFE0F Cannot auto-create customer ${customerRef}: createCustomer method not available on API client`);
404
- return customerRef;
594
+ const cacheKey = externalRef || customerRef;
595
+ if (this.customerRefMapping.has(customerRef)) {
596
+ const cached = this.customerRefMapping.get(customerRef);
597
+ return cached;
405
598
  }
406
- this.customerCreationAttempts.add(customerRef);
407
- try {
408
- this.log(`\u{1F527} Auto-creating customer: ${customerRef}`);
409
- const result = await this.apiClient.createCustomer({
410
- email: `${customerRef}@auto-created.local`,
411
- name: customerRef
412
- });
413
- const backendRef = result.customerRef || result.reference || customerRef;
414
- this.log(`\u2705 Successfully created customer: ${customerRef} -> ${backendRef}`, result);
415
- this.log(`\u{1F50D} DEBUG - ensureCustomer analysis:`);
416
- this.log(` - Input customerRef: ${customerRef}`);
417
- this.log(` - Backend customerRef: ${backendRef}`);
418
- this.log(` - Has plan in response: ${result.plan ? "YES - " + result.plan : "NO"}`);
419
- this.log(` - Has subscription in response: ${result.subscription ? "YES" : "NO"}`);
599
+ const backendRef = await sharedCustomerLookupDeduplicator.deduplicate(
600
+ cacheKey,
601
+ async () => {
602
+ if (externalRef && this.apiClient.getCustomerByExternalRef) {
603
+ try {
604
+ const existingCustomer = await this.apiClient.getCustomerByExternalRef({ externalRef });
605
+ if (existingCustomer && existingCustomer.customerRef) {
606
+ const ref = existingCustomer.customerRef;
607
+ this.customerRefMapping.set(customerRef, ref);
608
+ this.customerCreationAttempts.add(customerRef);
609
+ if (externalRef !== customerRef) {
610
+ this.customerCreationAttempts.add(externalRef);
611
+ }
612
+ return ref;
613
+ }
614
+ } catch (error) {
615
+ const errorMessage = error instanceof Error ? error.message : String(error);
616
+ if (!errorMessage.includes("404") && !errorMessage.includes("not found")) {
617
+ this.log(`\u26A0\uFE0F Error looking up customer by externalRef: ${errorMessage}`);
618
+ }
619
+ }
620
+ }
621
+ if (this.customerCreationAttempts.has(customerRef) || externalRef && this.customerCreationAttempts.has(externalRef)) {
622
+ const mappedRef = this.customerRefMapping.get(customerRef);
623
+ return mappedRef || customerRef;
624
+ }
625
+ if (!this.apiClient.createCustomer) {
626
+ console.warn(`\u26A0\uFE0F Cannot auto-create customer ${customerRef}: createCustomer method not available on API client`);
627
+ return customerRef;
628
+ }
629
+ this.customerCreationAttempts.add(customerRef);
630
+ try {
631
+ const createParams = {
632
+ email: options?.email || `${customerRef}@auto-created.local`
633
+ };
634
+ if (options?.name) {
635
+ createParams.name = options.name;
636
+ }
637
+ if (externalRef) {
638
+ createParams.externalRef = externalRef;
639
+ }
640
+ const result = await this.apiClient.createCustomer(createParams);
641
+ const ref = result.customerRef || result.reference || customerRef;
642
+ this.customerRefMapping.set(customerRef, ref);
643
+ return ref;
644
+ } catch (error) {
645
+ this.log(`\u274C Failed to auto-create customer ${customerRef}:`, error instanceof Error ? error.message : error);
646
+ return customerRef;
647
+ }
648
+ }
649
+ );
650
+ if (backendRef !== customerRef) {
420
651
  this.customerRefMapping.set(customerRef, backendRef);
421
- return backendRef;
422
- } catch (error) {
423
- this.log(`\u274C Failed to auto-create customer ${customerRef}:`, error instanceof Error ? error.message : error);
424
- return customerRef;
425
652
  }
653
+ return backendRef;
426
654
  }
427
655
  async trackUsage(customerRef, agentRef, planRef, toolName, outcome, requestId, actionDuration) {
428
656
  await withRetry(
@@ -714,11 +942,21 @@ var McpAdapter = class {
714
942
  };
715
943
 
716
944
  // src/factory.ts
717
- import { SolvaPayError as SolvaPayError2 } from "@solvapay/core";
945
+ import { SolvaPayError as SolvaPayError2, getSolvaPayConfig } from "@solvapay/core";
718
946
  function createSolvaPay(config) {
719
- const apiClient = config.apiClient || createSolvaPayClient({
720
- apiKey: config.apiKey,
721
- apiBaseUrl: config.apiBaseUrl
947
+ let resolvedConfig;
948
+ if (!config) {
949
+ const envConfig = getSolvaPayConfig();
950
+ resolvedConfig = {
951
+ apiKey: envConfig.apiKey,
952
+ apiBaseUrl: envConfig.apiBaseUrl
953
+ };
954
+ } else {
955
+ resolvedConfig = config;
956
+ }
957
+ const apiClient = resolvedConfig.apiClient || createSolvaPayClient({
958
+ apiKey: resolvedConfig.apiKey,
959
+ apiBaseUrl: resolvedConfig.apiBaseUrl
722
960
  });
723
961
  const paywall = new SolvaPayPaywall(apiClient, {
724
962
  debug: process.env.SOLVAPAY_DEBUG !== "false"
@@ -727,8 +965,8 @@ function createSolvaPay(config) {
727
965
  // Direct access to API client for advanced operations
728
966
  apiClient,
729
967
  // Common API methods exposed directly for convenience
730
- ensureCustomer(customerRef) {
731
- return paywall.ensureCustomer(customerRef);
968
+ ensureCustomer(customerRef, externalRef, options) {
969
+ return paywall.ensureCustomer(customerRef, externalRef, options);
732
970
  },
733
971
  createPaymentIntent(params) {
734
972
  if (!apiClient.createPaymentIntent) {
@@ -736,6 +974,12 @@ function createSolvaPay(config) {
736
974
  }
737
975
  return apiClient.createPaymentIntent(params);
738
976
  },
977
+ processPayment(params) {
978
+ if (!apiClient.processPayment) {
979
+ throw new SolvaPayError2("processPayment is not available on this API client");
980
+ }
981
+ return apiClient.processPayment(params);
982
+ },
739
983
  checkLimits(params) {
740
984
  return apiClient.checkLimits(params);
741
985
  },
@@ -754,6 +998,12 @@ function createSolvaPay(config) {
754
998
  }
755
999
  return apiClient.getCustomer(params);
756
1000
  },
1001
+ createCheckoutSession(params) {
1002
+ return apiClient.createCheckoutSession(params);
1003
+ },
1004
+ createCustomerSession(params) {
1005
+ return apiClient.createCustomerSession(params);
1006
+ },
757
1007
  // Payable API for framework-specific handlers
758
1008
  payable(options = {}) {
759
1009
  const agent = options.agentRef || options.agent || process.env.SOLVAPAY_AGENT || getPackageJsonName() || "default-agent";
@@ -817,6 +1067,314 @@ function getPackageJsonName() {
817
1067
  }
818
1068
  }
819
1069
 
1070
+ // src/helpers/error.ts
1071
+ import { SolvaPayError as SolvaPayError3 } from "@solvapay/core";
1072
+ function isErrorResult(result) {
1073
+ return typeof result === "object" && result !== null && "error" in result && "status" in result;
1074
+ }
1075
+ function handleRouteError(error, operationName, defaultMessage) {
1076
+ console.error(`[${operationName}] Error:`, error);
1077
+ if (error instanceof SolvaPayError3) {
1078
+ return {
1079
+ error: error.message,
1080
+ status: 500,
1081
+ details: error.message
1082
+ };
1083
+ }
1084
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1085
+ const message = defaultMessage || `${operationName} failed`;
1086
+ return {
1087
+ error: message,
1088
+ status: 500,
1089
+ details: errorMessage
1090
+ };
1091
+ }
1092
+
1093
+ // src/helpers/auth.ts
1094
+ async function getAuthenticatedUserCore(request, options = {}) {
1095
+ try {
1096
+ const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
1097
+ const userIdOrError = requireUserId(request);
1098
+ if (userIdOrError instanceof Response) {
1099
+ const clonedResponse = userIdOrError.clone();
1100
+ const body = await clonedResponse.json().catch(() => ({ error: "Unauthorized" }));
1101
+ return {
1102
+ error: body.error || "Unauthorized",
1103
+ status: userIdOrError.status,
1104
+ details: body.error || "Unauthorized"
1105
+ };
1106
+ }
1107
+ const userId = userIdOrError;
1108
+ const email = options.includeEmail !== false ? await getUserEmailFromRequest(request) : null;
1109
+ const name = options.includeName !== false ? await getUserNameFromRequest(request) : null;
1110
+ return {
1111
+ userId,
1112
+ email,
1113
+ name
1114
+ };
1115
+ } catch (error) {
1116
+ return handleRouteError(error, "Get authenticated user", "Authentication failed");
1117
+ }
1118
+ }
1119
+
1120
+ // src/helpers/customer.ts
1121
+ async function syncCustomerCore(request, options = {}) {
1122
+ try {
1123
+ const userResult = await getAuthenticatedUserCore(request, {
1124
+ includeEmail: options.includeEmail,
1125
+ includeName: options.includeName
1126
+ });
1127
+ if (isErrorResult(userResult)) {
1128
+ return userResult;
1129
+ }
1130
+ const { userId, email, name } = userResult;
1131
+ const solvaPay = options.solvaPay || createSolvaPay();
1132
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
1133
+ email: email || void 0,
1134
+ name: name || void 0
1135
+ });
1136
+ return customerRef;
1137
+ } catch (error) {
1138
+ return handleRouteError(error, "Sync customer", "Failed to sync customer");
1139
+ }
1140
+ }
1141
+
1142
+ // src/helpers/payment.ts
1143
+ async function createPaymentIntentCore(request, body, options = {}) {
1144
+ try {
1145
+ if (!body.planRef || !body.agentRef) {
1146
+ return {
1147
+ error: "Missing required parameters: planRef and agentRef are required",
1148
+ status: 400
1149
+ };
1150
+ }
1151
+ const customerResult = await syncCustomerCore(request, {
1152
+ solvaPay: options.solvaPay,
1153
+ includeEmail: options.includeEmail,
1154
+ includeName: options.includeName
1155
+ });
1156
+ if (isErrorResult(customerResult)) {
1157
+ return customerResult;
1158
+ }
1159
+ const customerRef = customerResult;
1160
+ const solvaPay = options.solvaPay || createSolvaPay();
1161
+ const paymentIntent = await solvaPay.createPaymentIntent({
1162
+ agentRef: body.agentRef,
1163
+ planRef: body.planRef,
1164
+ customerRef
1165
+ });
1166
+ return {
1167
+ id: paymentIntent.id,
1168
+ clientSecret: paymentIntent.clientSecret,
1169
+ publishableKey: paymentIntent.publishableKey,
1170
+ accountId: paymentIntent.accountId,
1171
+ customerRef
1172
+ // Return the backend customer reference
1173
+ };
1174
+ } catch (error) {
1175
+ return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
1176
+ }
1177
+ }
1178
+ async function processPaymentCore(request, body, options = {}) {
1179
+ try {
1180
+ if (!body.paymentIntentId || !body.agentRef) {
1181
+ return {
1182
+ error: "paymentIntentId and agentRef are required",
1183
+ status: 400
1184
+ };
1185
+ }
1186
+ const customerResult = await syncCustomerCore(request, {
1187
+ solvaPay: options.solvaPay
1188
+ });
1189
+ if (isErrorResult(customerResult)) {
1190
+ return customerResult;
1191
+ }
1192
+ const customerRef = customerResult;
1193
+ const solvaPay = options.solvaPay || createSolvaPay();
1194
+ const result = await solvaPay.processPayment({
1195
+ paymentIntentId: body.paymentIntentId,
1196
+ agentRef: body.agentRef,
1197
+ customerRef,
1198
+ planRef: body.planRef
1199
+ });
1200
+ return result;
1201
+ } catch (error) {
1202
+ return handleRouteError(error, "Process payment", "Payment processing failed");
1203
+ }
1204
+ }
1205
+
1206
+ // src/helpers/checkout.ts
1207
+ async function createCheckoutSessionCore(request, body, options = {}) {
1208
+ try {
1209
+ if (!body.agentRef) {
1210
+ return {
1211
+ error: "Missing required parameter: agentRef is required",
1212
+ status: 400
1213
+ };
1214
+ }
1215
+ const customerResult = await syncCustomerCore(request, {
1216
+ solvaPay: options.solvaPay,
1217
+ includeEmail: options.includeEmail,
1218
+ includeName: options.includeName
1219
+ });
1220
+ if (isErrorResult(customerResult)) {
1221
+ return customerResult;
1222
+ }
1223
+ const customerRef = customerResult;
1224
+ const solvaPay = options.solvaPay || createSolvaPay();
1225
+ const session = await solvaPay.createCheckoutSession({
1226
+ agentRef: body.agentRef,
1227
+ customerRef,
1228
+ planRef: body.planRef || void 0
1229
+ });
1230
+ return {
1231
+ sessionId: session.sessionId,
1232
+ checkoutUrl: session.checkoutUrl
1233
+ };
1234
+ } catch (error) {
1235
+ return handleRouteError(error, "Create checkout session", "Checkout session creation failed");
1236
+ }
1237
+ }
1238
+ async function createCustomerSessionCore(request, options = {}) {
1239
+ try {
1240
+ const customerResult = await syncCustomerCore(request, {
1241
+ solvaPay: options.solvaPay,
1242
+ includeEmail: options.includeEmail,
1243
+ includeName: options.includeName
1244
+ });
1245
+ if (isErrorResult(customerResult)) {
1246
+ return customerResult;
1247
+ }
1248
+ const customerRef = customerResult;
1249
+ const solvaPay = options.solvaPay || createSolvaPay();
1250
+ const session = await solvaPay.createCustomerSession({
1251
+ customerRef
1252
+ });
1253
+ return session;
1254
+ } catch (error) {
1255
+ return handleRouteError(error, "Create customer session", "Customer session creation failed");
1256
+ }
1257
+ }
1258
+
1259
+ // src/helpers/subscription.ts
1260
+ import { SolvaPayError as SolvaPayError4 } from "@solvapay/core";
1261
+ async function cancelSubscriptionCore(request, body, options = {}) {
1262
+ try {
1263
+ const userResult = await getAuthenticatedUserCore(request);
1264
+ if (isErrorResult(userResult)) {
1265
+ return userResult;
1266
+ }
1267
+ const { userId } = userResult;
1268
+ if (!body.subscriptionRef) {
1269
+ return {
1270
+ error: "Missing required parameter: subscriptionRef is required",
1271
+ status: 400
1272
+ };
1273
+ }
1274
+ const solvaPay = options.solvaPay || createSolvaPay();
1275
+ if (!solvaPay.apiClient.cancelSubscription) {
1276
+ return {
1277
+ error: "Cancel subscription method not available on SDK client",
1278
+ status: 500
1279
+ };
1280
+ }
1281
+ let cancelledSubscription = await solvaPay.apiClient.cancelSubscription({
1282
+ subscriptionRef: body.subscriptionRef,
1283
+ reason: body.reason
1284
+ });
1285
+ if (!cancelledSubscription || typeof cancelledSubscription !== "object") {
1286
+ return {
1287
+ error: "Invalid response from cancel subscription endpoint",
1288
+ status: 500
1289
+ };
1290
+ }
1291
+ const responseAny = cancelledSubscription;
1292
+ if (responseAny.subscription && typeof responseAny.subscription === "object") {
1293
+ cancelledSubscription = responseAny.subscription;
1294
+ }
1295
+ if (!cancelledSubscription.reference) {
1296
+ return {
1297
+ error: "Cancel subscription response missing required fields",
1298
+ status: 500
1299
+ };
1300
+ }
1301
+ const isCancelled = cancelledSubscription.status === "cancelled" || cancelledSubscription.cancelledAt;
1302
+ if (!isCancelled) {
1303
+ return {
1304
+ error: `Subscription cancellation failed: backend returned status '${cancelledSubscription.status}' without cancelledAt timestamp`,
1305
+ status: 500
1306
+ };
1307
+ }
1308
+ await new Promise((resolve) => setTimeout(resolve, 500));
1309
+ return cancelledSubscription;
1310
+ } catch (error) {
1311
+ if (error instanceof SolvaPayError4) {
1312
+ const errorMessage = error.message;
1313
+ if (errorMessage.includes("Subscription not found")) {
1314
+ return {
1315
+ error: "Subscription not found",
1316
+ status: 404,
1317
+ details: errorMessage
1318
+ };
1319
+ }
1320
+ if (errorMessage.includes("cannot be cancelled") || errorMessage.includes("does not belong to provider")) {
1321
+ return {
1322
+ error: "Subscription cannot be cancelled or does not belong to provider",
1323
+ status: 400,
1324
+ details: errorMessage
1325
+ };
1326
+ }
1327
+ return {
1328
+ error: errorMessage,
1329
+ status: 500,
1330
+ details: errorMessage
1331
+ };
1332
+ }
1333
+ return handleRouteError(error, "Cancel subscription", "Failed to cancel subscription");
1334
+ }
1335
+ }
1336
+
1337
+ // src/helpers/plans.ts
1338
+ import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
1339
+ async function listPlansCore(request) {
1340
+ try {
1341
+ const url = new URL(request.url);
1342
+ const agentRef = url.searchParams.get("agentRef");
1343
+ if (!agentRef) {
1344
+ return {
1345
+ error: "Missing required parameter: agentRef",
1346
+ status: 400
1347
+ };
1348
+ }
1349
+ const config = getSolvaPayConfig2();
1350
+ const solvapaySecretKey = config.apiKey;
1351
+ const solvapayApiBaseUrl = config.apiBaseUrl;
1352
+ if (!solvapaySecretKey) {
1353
+ return {
1354
+ error: "Server configuration error: SolvaPay secret key not configured",
1355
+ status: 500
1356
+ };
1357
+ }
1358
+ const apiClient = createSolvaPayClient({
1359
+ apiKey: solvapaySecretKey,
1360
+ apiBaseUrl: solvapayApiBaseUrl
1361
+ });
1362
+ if (!apiClient.listPlans) {
1363
+ return {
1364
+ error: "List plans method not available",
1365
+ status: 500
1366
+ };
1367
+ }
1368
+ const plans = await apiClient.listPlans(agentRef);
1369
+ return {
1370
+ plans: plans || [],
1371
+ agentRef
1372
+ };
1373
+ } catch (error) {
1374
+ return handleRouteError(error, "List plans", "Failed to fetch plans");
1375
+ }
1376
+ }
1377
+
820
1378
  // src/edge.ts
821
1379
  async function verifyWebhook({
822
1380
  body,
@@ -834,14 +1392,24 @@ async function verifyWebhook({
834
1392
  const sigBuf = await crypto.subtle.sign("HMAC", key, enc.encode(body));
835
1393
  const hex = Array.from(new Uint8Array(sigBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
836
1394
  if (hex !== signature) {
837
- throw new SolvaPayError3("Invalid webhook signature");
1395
+ throw new SolvaPayError5("Invalid webhook signature");
838
1396
  }
839
1397
  return JSON.parse(body);
840
1398
  }
841
1399
  export {
842
1400
  PaywallError,
1401
+ cancelSubscriptionCore,
1402
+ createCheckoutSessionCore,
1403
+ createCustomerSessionCore,
1404
+ createPaymentIntentCore,
843
1405
  createSolvaPay,
844
1406
  createSolvaPayClient,
1407
+ getAuthenticatedUserCore,
1408
+ handleRouteError,
1409
+ isErrorResult,
1410
+ listPlansCore,
1411
+ processPaymentCore,
1412
+ syncCustomerCore,
845
1413
  verifyWebhook,
846
1414
  withRetry
847
1415
  };