@solvapay/server 1.0.0-preview.6 → 1.0.0-preview.7
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/README.md +30 -0
- package/dist/edge.d.ts +139 -53
- package/dist/edge.js +310 -86
- package/dist/index.cjs +309 -85
- package/dist/index.d.cts +139 -53
- package/dist/index.d.ts +139 -53
- package/dist/index.js +310 -86
- package/package.json +2 -2
package/dist/edge.js
CHANGED
|
@@ -20,15 +20,10 @@ function createSolvaPayClient(opts) {
|
|
|
20
20
|
console.log(...args);
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
|
-
log(`\u{1F50C} SolvaPay API Client initialized`);
|
|
24
|
-
log(` Backend URL: ${base}`);
|
|
25
|
-
log(` API Key: ${opts.apiKey.substring(0, 10)}...`);
|
|
26
23
|
return {
|
|
27
24
|
// POST: /v1/sdk/limits
|
|
28
25
|
async checkLimits(params) {
|
|
29
26
|
const url = `${base}/v1/sdk/limits`;
|
|
30
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
31
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
32
27
|
const res = await fetch(url, {
|
|
33
28
|
method: "POST",
|
|
34
29
|
headers,
|
|
@@ -40,20 +35,11 @@ function createSolvaPayClient(opts) {
|
|
|
40
35
|
throw new SolvaPayError(`Check limits failed (${res.status}): ${error}`);
|
|
41
36
|
}
|
|
42
37
|
const result = await res.json();
|
|
43
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
44
|
-
log(`\u{1F50D} DEBUG - checkLimits breakdown:`);
|
|
45
|
-
log(` - withinLimits: ${result.withinLimits}`);
|
|
46
|
-
log(` - remaining: ${result.remaining}`);
|
|
47
|
-
log(` - plan: ${result.plan || "N/A"}`);
|
|
48
|
-
log(` - checkoutUrl: ${result.checkoutUrl || "N/A"}`);
|
|
49
|
-
log(` - Full response keys:`, Object.keys(result));
|
|
50
38
|
return result;
|
|
51
39
|
},
|
|
52
40
|
// POST: /v1/sdk/usages
|
|
53
41
|
async trackUsage(params) {
|
|
54
42
|
const url = `${base}/v1/sdk/usages`;
|
|
55
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
56
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
57
43
|
const res = await fetch(url, {
|
|
58
44
|
method: "POST",
|
|
59
45
|
headers,
|
|
@@ -64,13 +50,10 @@ function createSolvaPayClient(opts) {
|
|
|
64
50
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
65
51
|
throw new SolvaPayError(`Track usage failed (${res.status}): ${error}`);
|
|
66
52
|
}
|
|
67
|
-
log(`\u2705 Usage tracked successfully`);
|
|
68
53
|
},
|
|
69
54
|
// POST: /v1/sdk/customers
|
|
70
55
|
async createCustomer(params) {
|
|
71
56
|
const url = `${base}/v1/sdk/customers`;
|
|
72
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
73
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
74
57
|
const res = await fetch(url, {
|
|
75
58
|
method: "POST",
|
|
76
59
|
headers,
|
|
@@ -82,18 +65,11 @@ function createSolvaPayClient(opts) {
|
|
|
82
65
|
throw new SolvaPayError(`Create customer failed (${res.status}): ${error}`);
|
|
83
66
|
}
|
|
84
67
|
const result = await res.json();
|
|
85
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
86
|
-
log(`\u{1F50D} DEBUG - createCustomer response:`);
|
|
87
|
-
log(` - reference/customerRef: ${result.reference || result.customerRef}`);
|
|
88
|
-
log(` - Has plan info: ${result.plan ? "YES" : "NO"}`);
|
|
89
|
-
log(` - Has subscription info: ${result.subscription ? "YES" : "NO"}`);
|
|
90
|
-
log(` - Full response keys:`, Object.keys(result));
|
|
91
68
|
return result;
|
|
92
69
|
},
|
|
93
70
|
// GET: /v1/sdk/customers/{reference}
|
|
94
71
|
async getCustomer(params) {
|
|
95
72
|
const url = `${base}/v1/sdk/customers/${params.customerRef}`;
|
|
96
|
-
log(`\u{1F4E1} API Request: GET ${url}`);
|
|
97
73
|
const res = await fetch(url, {
|
|
98
74
|
method: "GET",
|
|
99
75
|
headers
|
|
@@ -104,14 +80,40 @@ function createSolvaPayClient(opts) {
|
|
|
104
80
|
throw new SolvaPayError(`Get customer failed (${res.status}): ${error}`);
|
|
105
81
|
}
|
|
106
82
|
const result = await res.json();
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
};
|
|
109
112
|
},
|
|
110
113
|
// Management methods (primarily for integration tests)
|
|
111
114
|
// GET: /v1/sdk/agents
|
|
112
115
|
async listAgents() {
|
|
113
116
|
const url = `${base}/v1/sdk/agents`;
|
|
114
|
-
log(`\u{1F4E1} API Request: GET ${url}`);
|
|
115
117
|
const res = await fetch(url, {
|
|
116
118
|
method: "GET",
|
|
117
119
|
headers
|
|
@@ -122,7 +124,6 @@ function createSolvaPayClient(opts) {
|
|
|
122
124
|
throw new SolvaPayError(`List agents failed (${res.status}): ${error}`);
|
|
123
125
|
}
|
|
124
126
|
const result = await res.json();
|
|
125
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
126
127
|
const agents = Array.isArray(result) ? result : result.agents || [];
|
|
127
128
|
return agents.map((agent) => ({
|
|
128
129
|
...agent,
|
|
@@ -132,8 +133,6 @@ function createSolvaPayClient(opts) {
|
|
|
132
133
|
// POST: /v1/sdk/agents
|
|
133
134
|
async createAgent(params) {
|
|
134
135
|
const url = `${base}/v1/sdk/agents`;
|
|
135
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
136
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
137
136
|
const res = await fetch(url, {
|
|
138
137
|
method: "POST",
|
|
139
138
|
headers,
|
|
@@ -145,13 +144,11 @@ function createSolvaPayClient(opts) {
|
|
|
145
144
|
throw new SolvaPayError(`Create agent failed (${res.status}): ${error}`);
|
|
146
145
|
}
|
|
147
146
|
const result = await res.json();
|
|
148
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
149
147
|
return result;
|
|
150
148
|
},
|
|
151
149
|
// DELETE: /v1/sdk/agents/{agentRef}
|
|
152
150
|
async deleteAgent(agentRef) {
|
|
153
151
|
const url = `${base}/v1/sdk/agents/${agentRef}`;
|
|
154
|
-
log(`\u{1F4E1} API Request: DELETE ${url}`);
|
|
155
152
|
const res = await fetch(url, {
|
|
156
153
|
method: "DELETE",
|
|
157
154
|
headers
|
|
@@ -161,12 +158,10 @@ function createSolvaPayClient(opts) {
|
|
|
161
158
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
162
159
|
throw new SolvaPayError(`Delete agent failed (${res.status}): ${error}`);
|
|
163
160
|
}
|
|
164
|
-
log(`\u2705 Agent deleted successfully`);
|
|
165
161
|
},
|
|
166
162
|
// GET: /v1/sdk/agents/{agentRef}/plans
|
|
167
163
|
async listPlans(agentRef) {
|
|
168
164
|
const url = `${base}/v1/sdk/agents/${agentRef}/plans`;
|
|
169
|
-
log(`\u{1F4E1} API Request: GET ${url}`);
|
|
170
165
|
const res = await fetch(url, {
|
|
171
166
|
method: "GET",
|
|
172
167
|
headers
|
|
@@ -177,18 +172,22 @@ function createSolvaPayClient(opts) {
|
|
|
177
172
|
throw new SolvaPayError(`List plans failed (${res.status}): ${error}`);
|
|
178
173
|
}
|
|
179
174
|
const result = await res.json();
|
|
180
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
181
175
|
const plans = Array.isArray(result) ? result : result.plans || [];
|
|
182
|
-
return plans.map((plan) =>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
});
|
|
186
187
|
},
|
|
187
188
|
// POST: /v1/sdk/agents/{agentRef}/plans
|
|
188
189
|
async createPlan(params) {
|
|
189
190
|
const url = `${base}/v1/sdk/agents/${params.agentRef}/plans`;
|
|
190
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
191
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
192
191
|
const res = await fetch(url, {
|
|
193
192
|
method: "POST",
|
|
194
193
|
headers,
|
|
@@ -200,13 +199,11 @@ function createSolvaPayClient(opts) {
|
|
|
200
199
|
throw new SolvaPayError(`Create plan failed (${res.status}): ${error}`);
|
|
201
200
|
}
|
|
202
201
|
const result = await res.json();
|
|
203
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
204
202
|
return result;
|
|
205
203
|
},
|
|
206
204
|
// DELETE: /v1/sdk/agents/{agentRef}/plans/{planRef}
|
|
207
205
|
async deletePlan(agentRef, planRef) {
|
|
208
206
|
const url = `${base}/v1/sdk/agents/${agentRef}/plans/${planRef}`;
|
|
209
|
-
log(`\u{1F4E1} API Request: DELETE ${url}`);
|
|
210
207
|
const res = await fetch(url, {
|
|
211
208
|
method: "DELETE",
|
|
212
209
|
headers
|
|
@@ -216,17 +213,11 @@ function createSolvaPayClient(opts) {
|
|
|
216
213
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
217
214
|
throw new SolvaPayError(`Delete plan failed (${res.status}): ${error}`);
|
|
218
215
|
}
|
|
219
|
-
log(`\u2705 Plan deleted successfully`);
|
|
220
216
|
},
|
|
221
217
|
// POST: /payment-intents
|
|
222
218
|
async createPaymentIntent(params) {
|
|
223
219
|
const idempotencyKey = params.idempotencyKey || `payment-${params.planRef}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
224
220
|
const url = `${base}/v1/sdk/payment-intents`;
|
|
225
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
226
|
-
log(` Agent Ref: ${params.agentRef}`);
|
|
227
|
-
log(` Plan Ref: ${params.planRef}`);
|
|
228
|
-
log(` Customer Ref: ${params.customerRef}`);
|
|
229
|
-
log(` Idempotency Key: ${idempotencyKey}`);
|
|
230
221
|
const res = await fetch(url, {
|
|
231
222
|
method: "POST",
|
|
232
223
|
headers: {
|
|
@@ -245,12 +236,77 @@ function createSolvaPayClient(opts) {
|
|
|
245
236
|
throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
|
|
246
237
|
}
|
|
247
238
|
const result = await res.json();
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
})
|
|
253
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
|
+
}
|
|
254
310
|
return result;
|
|
255
311
|
}
|
|
256
312
|
};
|
|
@@ -302,6 +358,95 @@ function calculateDelay(initialDelay, attempt, strategy) {
|
|
|
302
358
|
function sleep(ms) {
|
|
303
359
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
304
360
|
}
|
|
361
|
+
function createRequestDeduplicator(options = {}) {
|
|
362
|
+
const {
|
|
363
|
+
cacheTTL = 2e3,
|
|
364
|
+
maxCacheSize = 1e3,
|
|
365
|
+
cacheErrors = true
|
|
366
|
+
} = options;
|
|
367
|
+
const inFlightRequests = /* @__PURE__ */ new Map();
|
|
368
|
+
const resultCache = /* @__PURE__ */ new Map();
|
|
369
|
+
let cleanupInterval = null;
|
|
370
|
+
if (cacheTTL > 0) {
|
|
371
|
+
cleanupInterval = setInterval(() => {
|
|
372
|
+
const now = Date.now();
|
|
373
|
+
const entriesToDelete = [];
|
|
374
|
+
for (const [key, cached] of resultCache.entries()) {
|
|
375
|
+
if (now - cached.timestamp >= cacheTTL) {
|
|
376
|
+
entriesToDelete.push(key);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
for (const key of entriesToDelete) {
|
|
380
|
+
resultCache.delete(key);
|
|
381
|
+
}
|
|
382
|
+
if (resultCache.size > maxCacheSize) {
|
|
383
|
+
const sortedEntries = Array.from(resultCache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
384
|
+
const toRemove = sortedEntries.slice(0, resultCache.size - maxCacheSize);
|
|
385
|
+
for (const [key] of toRemove) {
|
|
386
|
+
resultCache.delete(key);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}, Math.min(cacheTTL, 1e3));
|
|
390
|
+
}
|
|
391
|
+
const deduplicate = async (key, fn) => {
|
|
392
|
+
if (cacheTTL > 0) {
|
|
393
|
+
const cached = resultCache.get(key);
|
|
394
|
+
if (cached && Date.now() - cached.timestamp < cacheTTL) {
|
|
395
|
+
return cached.data;
|
|
396
|
+
} else if (cached) {
|
|
397
|
+
resultCache.delete(key);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
let requestPromise = inFlightRequests.get(key);
|
|
401
|
+
if (!requestPromise) {
|
|
402
|
+
requestPromise = (async () => {
|
|
403
|
+
try {
|
|
404
|
+
const result = await fn();
|
|
405
|
+
if (cacheTTL > 0) {
|
|
406
|
+
resultCache.set(key, {
|
|
407
|
+
data: result,
|
|
408
|
+
timestamp: Date.now()
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
return result;
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (cacheTTL > 0 && cacheErrors) {
|
|
414
|
+
resultCache.set(key, {
|
|
415
|
+
data: error,
|
|
416
|
+
timestamp: Date.now()
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
throw error;
|
|
420
|
+
} finally {
|
|
421
|
+
inFlightRequests.delete(key);
|
|
422
|
+
}
|
|
423
|
+
})();
|
|
424
|
+
const existingPromise = inFlightRequests.get(key);
|
|
425
|
+
if (existingPromise) {
|
|
426
|
+
requestPromise = existingPromise;
|
|
427
|
+
} else {
|
|
428
|
+
inFlightRequests.set(key, requestPromise);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return requestPromise;
|
|
432
|
+
};
|
|
433
|
+
const clearCache = (key) => {
|
|
434
|
+
resultCache.delete(key);
|
|
435
|
+
};
|
|
436
|
+
const clearAllCache = () => {
|
|
437
|
+
resultCache.clear();
|
|
438
|
+
};
|
|
439
|
+
const getStats = () => ({
|
|
440
|
+
inFlight: inFlightRequests.size,
|
|
441
|
+
cached: resultCache.size
|
|
442
|
+
});
|
|
443
|
+
return {
|
|
444
|
+
deduplicate,
|
|
445
|
+
clearCache,
|
|
446
|
+
clearAllCache,
|
|
447
|
+
getStats
|
|
448
|
+
};
|
|
449
|
+
}
|
|
305
450
|
|
|
306
451
|
// src/paywall.ts
|
|
307
452
|
var PaywallError = class extends Error {
|
|
@@ -311,6 +456,14 @@ var PaywallError = class extends Error {
|
|
|
311
456
|
this.name = "PaywallError";
|
|
312
457
|
}
|
|
313
458
|
};
|
|
459
|
+
var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
|
|
460
|
+
cacheTTL: 5e3,
|
|
461
|
+
// Cache results for 5 seconds (sufficient for concurrent requests)
|
|
462
|
+
maxCacheSize: 1e3,
|
|
463
|
+
// Maximum cache entries
|
|
464
|
+
cacheErrors: false
|
|
465
|
+
// Don't cache errors - retry on next request
|
|
466
|
+
});
|
|
314
467
|
var SolvaPayPaywall = class {
|
|
315
468
|
constructor(apiClient, options = {}) {
|
|
316
469
|
this.apiClient = apiClient;
|
|
@@ -380,7 +533,12 @@ var SolvaPayPaywall = class {
|
|
|
380
533
|
this.log(`\u2705 Request completed successfully`);
|
|
381
534
|
return result;
|
|
382
535
|
} catch (error) {
|
|
383
|
-
|
|
536
|
+
if (error instanceof Error) {
|
|
537
|
+
const errorType = error instanceof PaywallError ? "PaywallError" : "API Error";
|
|
538
|
+
this.log(`\u274C Error in paywall [${errorType}]: ${error.message}`);
|
|
539
|
+
} else {
|
|
540
|
+
this.log(`\u274C Error in paywall:`, error);
|
|
541
|
+
}
|
|
384
542
|
const latencyMs = Date.now() - startTime;
|
|
385
543
|
const outcome = error instanceof PaywallError ? "paywall" : "fail";
|
|
386
544
|
const planRef = metadata.plan || toolName;
|
|
@@ -394,41 +552,91 @@ var SolvaPayPaywall = class {
|
|
|
394
552
|
* This is a public helper for testing, pre-creating customers, and internal use.
|
|
395
553
|
* Only attempts creation once per customer (idempotent).
|
|
396
554
|
* Returns the backend customer reference to use in API calls.
|
|
555
|
+
*
|
|
556
|
+
* @param customerRef - The customer reference (e.g., Supabase user ID)
|
|
557
|
+
* @param externalRef - Optional external reference for backend lookup (e.g., Supabase user ID)
|
|
558
|
+
* If provided, will lookup existing customer by externalRef before creating new one
|
|
559
|
+
* @param options - Optional customer details (email, name) for customer creation
|
|
397
560
|
*/
|
|
398
|
-
async ensureCustomer(customerRef) {
|
|
561
|
+
async ensureCustomer(customerRef, externalRef, options) {
|
|
399
562
|
if (this.customerRefMapping.has(customerRef)) {
|
|
400
563
|
return this.customerRefMapping.get(customerRef);
|
|
401
564
|
}
|
|
402
565
|
if (customerRef === "anonymous") {
|
|
403
566
|
return customerRef;
|
|
404
567
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return customerRef;
|
|
568
|
+
const cacheKey = externalRef || customerRef;
|
|
569
|
+
if (this.customerRefMapping.has(customerRef)) {
|
|
570
|
+
const cached = this.customerRefMapping.get(customerRef);
|
|
571
|
+
this.log(`\u2705 [PER-INSTANCE CACHE HIT] Using cached customer lookup: ${customerRef} -> ${cached}`);
|
|
572
|
+
return cached;
|
|
411
573
|
}
|
|
412
|
-
this.
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
574
|
+
const hadPerInstanceCache = this.customerRefMapping.has(customerRef);
|
|
575
|
+
const backendRef = await sharedCustomerLookupDeduplicator.deduplicate(
|
|
576
|
+
cacheKey,
|
|
577
|
+
async () => {
|
|
578
|
+
if (externalRef && this.apiClient.getCustomerByExternalRef) {
|
|
579
|
+
try {
|
|
580
|
+
this.log(`\u{1F50D} Looking up customer by externalRef: ${externalRef}`);
|
|
581
|
+
const existingCustomer = await this.apiClient.getCustomerByExternalRef({ externalRef });
|
|
582
|
+
if (existingCustomer && existingCustomer.customerRef) {
|
|
583
|
+
const ref = existingCustomer.customerRef;
|
|
584
|
+
this.log(`\u2705 Found existing customer by externalRef: ${externalRef} -> ${ref}`);
|
|
585
|
+
this.customerRefMapping.set(customerRef, ref);
|
|
586
|
+
this.customerCreationAttempts.add(customerRef);
|
|
587
|
+
if (externalRef !== customerRef) {
|
|
588
|
+
this.customerCreationAttempts.add(externalRef);
|
|
589
|
+
}
|
|
590
|
+
return ref;
|
|
591
|
+
}
|
|
592
|
+
} catch (error) {
|
|
593
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
594
|
+
if (errorMessage.includes("404") || errorMessage.includes("not found")) {
|
|
595
|
+
this.log(`\u{1F50D} Customer not found by externalRef, will create new: ${externalRef}`);
|
|
596
|
+
} else {
|
|
597
|
+
this.log(`\u26A0\uFE0F Error looking up customer by externalRef: ${errorMessage}`);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (this.customerCreationAttempts.has(customerRef) || externalRef && this.customerCreationAttempts.has(externalRef)) {
|
|
602
|
+
const mappedRef = this.customerRefMapping.get(customerRef);
|
|
603
|
+
return mappedRef || customerRef;
|
|
604
|
+
}
|
|
605
|
+
if (!this.apiClient.createCustomer) {
|
|
606
|
+
console.warn(`\u26A0\uFE0F Cannot auto-create customer ${customerRef}: createCustomer method not available on API client`);
|
|
607
|
+
return customerRef;
|
|
608
|
+
}
|
|
609
|
+
this.customerCreationAttempts.add(customerRef);
|
|
610
|
+
try {
|
|
611
|
+
this.log(`\u{1F527} Auto-creating customer: ${customerRef}${externalRef ? ` (externalRef: ${externalRef})` : ""}`);
|
|
612
|
+
const createParams = {
|
|
613
|
+
email: options?.email || `${customerRef}@auto-created.local`,
|
|
614
|
+
name: options?.name || customerRef
|
|
615
|
+
};
|
|
616
|
+
if (externalRef) {
|
|
617
|
+
createParams.externalRef = externalRef;
|
|
618
|
+
}
|
|
619
|
+
const result = await this.apiClient.createCustomer(createParams);
|
|
620
|
+
const ref = result.customerRef || result.reference || customerRef;
|
|
621
|
+
this.log(`\u2705 Successfully created customer: ${customerRef} -> ${ref}`, result);
|
|
622
|
+
this.log(`\u{1F50D} DEBUG - ensureCustomer analysis:`);
|
|
623
|
+
this.log(` - Input customerRef: ${customerRef}`);
|
|
624
|
+
this.log(` - ExternalRef: ${externalRef || "none"}`);
|
|
625
|
+
this.log(` - Backend customerRef: ${ref}`);
|
|
626
|
+
this.log(` - Has plan in response: ${result.plan ? "YES - " + result.plan : "NO"}`);
|
|
627
|
+
this.log(` - Has subscription in response: ${result.subscription ? "YES" : "NO"}`);
|
|
628
|
+
this.customerRefMapping.set(customerRef, ref);
|
|
629
|
+
return ref;
|
|
630
|
+
} catch (error) {
|
|
631
|
+
this.log(`\u274C Failed to auto-create customer ${customerRef}:`, error instanceof Error ? error.message : error);
|
|
632
|
+
return customerRef;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
);
|
|
636
|
+
if (backendRef !== customerRef) {
|
|
426
637
|
this.customerRefMapping.set(customerRef, backendRef);
|
|
427
|
-
return backendRef;
|
|
428
|
-
} catch (error) {
|
|
429
|
-
this.log(`\u274C Failed to auto-create customer ${customerRef}:`, error instanceof Error ? error.message : error);
|
|
430
|
-
return customerRef;
|
|
431
638
|
}
|
|
639
|
+
return backendRef;
|
|
432
640
|
}
|
|
433
641
|
async trackUsage(customerRef, agentRef, planRef, toolName, outcome, requestId, actionDuration) {
|
|
434
642
|
await withRetry(
|
|
@@ -720,11 +928,21 @@ var McpAdapter = class {
|
|
|
720
928
|
};
|
|
721
929
|
|
|
722
930
|
// src/factory.ts
|
|
723
|
-
import { SolvaPayError as SolvaPayError2 } from "@solvapay/core";
|
|
931
|
+
import { SolvaPayError as SolvaPayError2, getSolvaPayConfig } from "@solvapay/core";
|
|
724
932
|
function createSolvaPay(config) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
933
|
+
let resolvedConfig;
|
|
934
|
+
if (!config) {
|
|
935
|
+
const envConfig = getSolvaPayConfig();
|
|
936
|
+
resolvedConfig = {
|
|
937
|
+
apiKey: envConfig.apiKey,
|
|
938
|
+
apiBaseUrl: envConfig.apiBaseUrl
|
|
939
|
+
};
|
|
940
|
+
} else {
|
|
941
|
+
resolvedConfig = config;
|
|
942
|
+
}
|
|
943
|
+
const apiClient = resolvedConfig.apiClient || createSolvaPayClient({
|
|
944
|
+
apiKey: resolvedConfig.apiKey,
|
|
945
|
+
apiBaseUrl: resolvedConfig.apiBaseUrl
|
|
728
946
|
});
|
|
729
947
|
const paywall = new SolvaPayPaywall(apiClient, {
|
|
730
948
|
debug: process.env.SOLVAPAY_DEBUG !== "false"
|
|
@@ -733,8 +951,8 @@ function createSolvaPay(config) {
|
|
|
733
951
|
// Direct access to API client for advanced operations
|
|
734
952
|
apiClient,
|
|
735
953
|
// Common API methods exposed directly for convenience
|
|
736
|
-
ensureCustomer(customerRef) {
|
|
737
|
-
return paywall.ensureCustomer(customerRef);
|
|
954
|
+
ensureCustomer(customerRef, externalRef, options) {
|
|
955
|
+
return paywall.ensureCustomer(customerRef, externalRef, options);
|
|
738
956
|
},
|
|
739
957
|
createPaymentIntent(params) {
|
|
740
958
|
if (!apiClient.createPaymentIntent) {
|
|
@@ -742,6 +960,12 @@ function createSolvaPay(config) {
|
|
|
742
960
|
}
|
|
743
961
|
return apiClient.createPaymentIntent(params);
|
|
744
962
|
},
|
|
963
|
+
processPayment(params) {
|
|
964
|
+
if (!apiClient.processPayment) {
|
|
965
|
+
throw new SolvaPayError2("processPayment is not available on this API client");
|
|
966
|
+
}
|
|
967
|
+
return apiClient.processPayment(params);
|
|
968
|
+
},
|
|
745
969
|
checkLimits(params) {
|
|
746
970
|
return apiClient.checkLimits(params);
|
|
747
971
|
},
|