@solvapay/server 1.0.0-preview.1
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 +242 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/edge.d.ts +845 -0
- package/dist/edge.js +847 -0
- package/dist/esm-5GYCIXIY.js +3475 -0
- package/dist/index.cjs +5093 -0
- package/dist/index.d.cts +825 -0
- package/dist/index.d.ts +825 -0
- package/dist/index.js +834 -0
- package/package.json +60 -0
package/dist/edge.js
ADDED
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./chunk-R5U7XKVJ.js";
|
|
4
|
+
|
|
5
|
+
// src/edge.ts
|
|
6
|
+
import { SolvaPayError as SolvaPayError3 } from "@solvapay/core";
|
|
7
|
+
|
|
8
|
+
// src/client.ts
|
|
9
|
+
import { SolvaPayError } from "@solvapay/core";
|
|
10
|
+
function createSolvaPayClient(opts) {
|
|
11
|
+
const base = opts.apiBaseUrl ?? "https://api-dev.solvapay.com";
|
|
12
|
+
if (!opts.apiKey) throw new SolvaPayError("Missing apiKey");
|
|
13
|
+
const headers = {
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
"Authorization": `Bearer ${opts.apiKey}`
|
|
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)}...`);
|
|
20
|
+
return {
|
|
21
|
+
// POST: /v1/sdk/limits
|
|
22
|
+
async checkLimits(params) {
|
|
23
|
+
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
|
+
const res = await fetch(url, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers,
|
|
29
|
+
body: JSON.stringify(params)
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const error = await res.text();
|
|
33
|
+
console.error(`\u274C API Error: ${res.status} - ${error}`);
|
|
34
|
+
throw new SolvaPayError(`Check limits failed (${res.status}): ${error}`);
|
|
35
|
+
}
|
|
36
|
+
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
|
+
return result;
|
|
45
|
+
},
|
|
46
|
+
// POST: /v1/sdk/usages
|
|
47
|
+
async trackUsage(params) {
|
|
48
|
+
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
|
+
const res = await fetch(url, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers,
|
|
54
|
+
body: JSON.stringify(params)
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const error = await res.text();
|
|
58
|
+
console.error(`\u274C API Error: ${res.status} - ${error}`);
|
|
59
|
+
throw new SolvaPayError(`Track usage failed (${res.status}): ${error}`);
|
|
60
|
+
}
|
|
61
|
+
console.log(`\u2705 Usage tracked successfully`);
|
|
62
|
+
},
|
|
63
|
+
// POST: /v1/sdk/customers
|
|
64
|
+
async createCustomer(params) {
|
|
65
|
+
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
|
+
const res = await fetch(url, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers,
|
|
71
|
+
body: JSON.stringify(params)
|
|
72
|
+
});
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
const error = await res.text();
|
|
75
|
+
console.error(`\u274C API Error: ${res.status} - ${error}`);
|
|
76
|
+
throw new SolvaPayError(`Create customer failed (${res.status}): ${error}`);
|
|
77
|
+
}
|
|
78
|
+
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
|
+
return result;
|
|
86
|
+
},
|
|
87
|
+
// GET: /v1/sdk/customers/{reference}
|
|
88
|
+
async getCustomer(params) {
|
|
89
|
+
const url = `${base}/v1/sdk/customers/${params.customerRef}`;
|
|
90
|
+
console.log(`\u{1F4E1} API Request: GET ${url}`);
|
|
91
|
+
const res = await fetch(url, {
|
|
92
|
+
method: "GET",
|
|
93
|
+
headers
|
|
94
|
+
});
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
const error = await res.text();
|
|
97
|
+
console.error(`\u274C API Error: ${res.status} - ${error}`);
|
|
98
|
+
throw new SolvaPayError(`Get customer failed (${res.status}): ${error}`);
|
|
99
|
+
}
|
|
100
|
+
const result = await res.json();
|
|
101
|
+
console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
102
|
+
return result;
|
|
103
|
+
},
|
|
104
|
+
// Management methods (primarily for integration tests)
|
|
105
|
+
// GET: /v1/sdk/agents
|
|
106
|
+
async listAgents() {
|
|
107
|
+
const url = `${base}/v1/sdk/agents`;
|
|
108
|
+
console.log(`\u{1F4E1} API Request: GET ${url}`);
|
|
109
|
+
const res = await fetch(url, {
|
|
110
|
+
method: "GET",
|
|
111
|
+
headers
|
|
112
|
+
});
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
const error = await res.text();
|
|
115
|
+
console.error(`\u274C API Error: ${res.status} - ${error}`);
|
|
116
|
+
throw new SolvaPayError(`List agents failed (${res.status}): ${error}`);
|
|
117
|
+
}
|
|
118
|
+
const result = await res.json();
|
|
119
|
+
console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
120
|
+
const agents = Array.isArray(result) ? result : result.agents || [];
|
|
121
|
+
return agents.map((agent) => ({
|
|
122
|
+
...agent,
|
|
123
|
+
...agent.data || {}
|
|
124
|
+
}));
|
|
125
|
+
},
|
|
126
|
+
// POST: /v1/sdk/agents
|
|
127
|
+
async createAgent(params) {
|
|
128
|
+
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
|
+
const res = await fetch(url, {
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers,
|
|
134
|
+
body: JSON.stringify(params)
|
|
135
|
+
});
|
|
136
|
+
if (!res.ok) {
|
|
137
|
+
const error = await res.text();
|
|
138
|
+
console.error(`\u274C API Error: ${res.status} - ${error}`);
|
|
139
|
+
throw new SolvaPayError(`Create agent failed (${res.status}): ${error}`);
|
|
140
|
+
}
|
|
141
|
+
const result = await res.json();
|
|
142
|
+
console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
143
|
+
return result;
|
|
144
|
+
},
|
|
145
|
+
// DELETE: /v1/sdk/agents/{agentRef}
|
|
146
|
+
async deleteAgent(agentRef) {
|
|
147
|
+
const url = `${base}/v1/sdk/agents/${agentRef}`;
|
|
148
|
+
console.log(`\u{1F4E1} API Request: DELETE ${url}`);
|
|
149
|
+
const res = await fetch(url, {
|
|
150
|
+
method: "DELETE",
|
|
151
|
+
headers
|
|
152
|
+
});
|
|
153
|
+
if (!res.ok && res.status !== 404) {
|
|
154
|
+
const error = await res.text();
|
|
155
|
+
console.error(`\u274C API Error: ${res.status} - ${error}`);
|
|
156
|
+
throw new SolvaPayError(`Delete agent failed (${res.status}): ${error}`);
|
|
157
|
+
}
|
|
158
|
+
console.log(`\u2705 Agent deleted successfully`);
|
|
159
|
+
},
|
|
160
|
+
// GET: /v1/sdk/agents/{agentRef}/plans
|
|
161
|
+
async listPlans(agentRef) {
|
|
162
|
+
const url = `${base}/v1/sdk/agents/${agentRef}/plans`;
|
|
163
|
+
console.log(`\u{1F4E1} API Request: GET ${url}`);
|
|
164
|
+
const res = await fetch(url, {
|
|
165
|
+
method: "GET",
|
|
166
|
+
headers
|
|
167
|
+
});
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
const error = await res.text();
|
|
170
|
+
console.error(`\u274C API Error: ${res.status} - ${error}`);
|
|
171
|
+
throw new SolvaPayError(`List plans failed (${res.status}): ${error}`);
|
|
172
|
+
}
|
|
173
|
+
const result = await res.json();
|
|
174
|
+
console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
175
|
+
const plans = Array.isArray(result) ? result : result.plans || [];
|
|
176
|
+
return plans.map((plan) => ({
|
|
177
|
+
...plan,
|
|
178
|
+
...plan.data || {}
|
|
179
|
+
}));
|
|
180
|
+
},
|
|
181
|
+
// POST: /v1/sdk/agents/{agentRef}/plans
|
|
182
|
+
async createPlan(params) {
|
|
183
|
+
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
|
+
const res = await fetch(url, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers,
|
|
189
|
+
body: JSON.stringify(params)
|
|
190
|
+
});
|
|
191
|
+
if (!res.ok) {
|
|
192
|
+
const error = await res.text();
|
|
193
|
+
console.error(`\u274C API Error: ${res.status} - ${error}`);
|
|
194
|
+
throw new SolvaPayError(`Create plan failed (${res.status}): ${error}`);
|
|
195
|
+
}
|
|
196
|
+
const result = await res.json();
|
|
197
|
+
console.log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
198
|
+
return result;
|
|
199
|
+
},
|
|
200
|
+
// DELETE: /v1/sdk/agents/{agentRef}/plans/{planRef}
|
|
201
|
+
async deletePlan(agentRef, planRef) {
|
|
202
|
+
const url = `${base}/v1/sdk/agents/${agentRef}/plans/${planRef}`;
|
|
203
|
+
console.log(`\u{1F4E1} API Request: DELETE ${url}`);
|
|
204
|
+
const res = await fetch(url, {
|
|
205
|
+
method: "DELETE",
|
|
206
|
+
headers
|
|
207
|
+
});
|
|
208
|
+
if (!res.ok && res.status !== 404) {
|
|
209
|
+
const error = await res.text();
|
|
210
|
+
console.error(`\u274C API Error: ${res.status} - ${error}`);
|
|
211
|
+
throw new SolvaPayError(`Delete plan failed (${res.status}): ${error}`);
|
|
212
|
+
}
|
|
213
|
+
console.log(`\u2705 Plan deleted successfully`);
|
|
214
|
+
},
|
|
215
|
+
// POST: /payment-intents
|
|
216
|
+
async createPaymentIntent(params) {
|
|
217
|
+
const idempotencyKey = params.idempotencyKey || `payment-${params.planRef}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
218
|
+
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
|
+
const res = await fetch(url, {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: {
|
|
227
|
+
...headers,
|
|
228
|
+
"Idempotency-Key": idempotencyKey
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify({
|
|
231
|
+
agentRef: params.agentRef,
|
|
232
|
+
planRef: params.planRef,
|
|
233
|
+
customerReference: params.customerRef
|
|
234
|
+
})
|
|
235
|
+
});
|
|
236
|
+
if (!res.ok) {
|
|
237
|
+
const error = await res.text();
|
|
238
|
+
console.error(`\u274C API Error: ${res.status} - ${error}`);
|
|
239
|
+
throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
|
|
240
|
+
}
|
|
241
|
+
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
|
|
247
|
+
});
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/utils.ts
|
|
254
|
+
async function withRetry(fn, options = {}) {
|
|
255
|
+
const {
|
|
256
|
+
maxRetries = 2,
|
|
257
|
+
initialDelay = 500,
|
|
258
|
+
backoffStrategy = "fixed",
|
|
259
|
+
shouldRetry,
|
|
260
|
+
onRetry
|
|
261
|
+
} = options;
|
|
262
|
+
let lastError;
|
|
263
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
264
|
+
try {
|
|
265
|
+
return await fn();
|
|
266
|
+
} catch (error) {
|
|
267
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
268
|
+
const isLastAttempt = attempt === maxRetries;
|
|
269
|
+
if (isLastAttempt) {
|
|
270
|
+
throw lastError;
|
|
271
|
+
}
|
|
272
|
+
if (shouldRetry && !shouldRetry(lastError, attempt)) {
|
|
273
|
+
throw lastError;
|
|
274
|
+
}
|
|
275
|
+
const delay = calculateDelay(initialDelay, attempt, backoffStrategy);
|
|
276
|
+
if (onRetry) {
|
|
277
|
+
onRetry(lastError, attempt);
|
|
278
|
+
}
|
|
279
|
+
await sleep(delay);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
throw lastError;
|
|
283
|
+
}
|
|
284
|
+
function calculateDelay(initialDelay, attempt, strategy) {
|
|
285
|
+
switch (strategy) {
|
|
286
|
+
case "fixed":
|
|
287
|
+
return initialDelay;
|
|
288
|
+
case "linear":
|
|
289
|
+
return initialDelay * (attempt + 1);
|
|
290
|
+
case "exponential":
|
|
291
|
+
return initialDelay * Math.pow(2, attempt);
|
|
292
|
+
default:
|
|
293
|
+
return initialDelay;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function sleep(ms) {
|
|
297
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/paywall.ts
|
|
301
|
+
var PaywallError = class extends Error {
|
|
302
|
+
constructor(message, structuredContent) {
|
|
303
|
+
super(message);
|
|
304
|
+
this.structuredContent = structuredContent;
|
|
305
|
+
this.name = "PaywallError";
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
var SolvaPayPaywall = class {
|
|
309
|
+
constructor(apiClient, options = {}) {
|
|
310
|
+
this.apiClient = apiClient;
|
|
311
|
+
this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG !== "false";
|
|
312
|
+
}
|
|
313
|
+
customerCreationAttempts = /* @__PURE__ */ new Set();
|
|
314
|
+
customerRefMapping = /* @__PURE__ */ new Map();
|
|
315
|
+
// input ref -> backend ref
|
|
316
|
+
debug;
|
|
317
|
+
log(...args) {
|
|
318
|
+
if (this.debug) {
|
|
319
|
+
console.log(...args);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
resolveAgent(metadata) {
|
|
323
|
+
return metadata.agent || process.env.SOLVAPAY_AGENT || this.getPackageJsonName() || "default-agent";
|
|
324
|
+
}
|
|
325
|
+
getPackageJsonName() {
|
|
326
|
+
try {
|
|
327
|
+
const pkg = __require(process.cwd() + "/package.json");
|
|
328
|
+
return pkg.name;
|
|
329
|
+
} catch {
|
|
330
|
+
return void 0;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
generateRequestId() {
|
|
334
|
+
const timestamp = Date.now();
|
|
335
|
+
const random = Math.random().toString(36).substring(2, 11);
|
|
336
|
+
return `solvapay_${timestamp}_${random}`;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Core protection method - works for both MCP and HTTP
|
|
340
|
+
*/
|
|
341
|
+
async protect(handler, metadata = {}, getCustomerRef) {
|
|
342
|
+
const agent = this.resolveAgent(metadata);
|
|
343
|
+
const toolName = handler.name || "anonymous";
|
|
344
|
+
return async (args) => {
|
|
345
|
+
const startTime = Date.now();
|
|
346
|
+
const requestId = this.generateRequestId();
|
|
347
|
+
const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
|
|
348
|
+
const backendCustomerRef = await this.ensureCustomer(inputCustomerRef);
|
|
349
|
+
try {
|
|
350
|
+
const planRef = metadata.plan || toolName;
|
|
351
|
+
this.log(`\u{1F50D} Checking limits for customer: ${backendCustomerRef}, agent: ${agent}, plan: ${planRef}`);
|
|
352
|
+
const limitsCheck = await this.apiClient.checkLimits({
|
|
353
|
+
customerRef: backendCustomerRef,
|
|
354
|
+
agentRef: agent
|
|
355
|
+
});
|
|
356
|
+
this.log(`\u2713 Limits check passed:`, limitsCheck);
|
|
357
|
+
if (!limitsCheck.withinLimits) {
|
|
358
|
+
const latencyMs2 = Date.now() - startTime;
|
|
359
|
+
this.log(`\u{1F6AB} Paywall triggered - tracking usage`);
|
|
360
|
+
await this.trackUsage(backendCustomerRef, agent, planRef, toolName, "paywall", requestId, latencyMs2);
|
|
361
|
+
throw new PaywallError("Payment required", {
|
|
362
|
+
kind: "payment_required",
|
|
363
|
+
agent,
|
|
364
|
+
checkoutUrl: limitsCheck.checkoutUrl || "",
|
|
365
|
+
message: `Plan subscription required. Remaining: ${limitsCheck.remaining}`
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
this.log(`\u26A1 Executing handler: ${toolName}`);
|
|
369
|
+
const result = await handler(args);
|
|
370
|
+
this.log(`\u2713 Handler completed successfully`);
|
|
371
|
+
const latencyMs = Date.now() - startTime;
|
|
372
|
+
this.log(`\u{1F4CA} Tracking successful usage`);
|
|
373
|
+
await this.trackUsage(backendCustomerRef, agent, planRef, toolName, "success", requestId, latencyMs);
|
|
374
|
+
this.log(`\u2705 Request completed successfully`);
|
|
375
|
+
return result;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
this.log(`\u274C Error in paywall:`, error);
|
|
378
|
+
const latencyMs = Date.now() - startTime;
|
|
379
|
+
const outcome = error instanceof PaywallError ? "paywall" : "fail";
|
|
380
|
+
const planRef = metadata.plan || toolName;
|
|
381
|
+
await this.trackUsage(backendCustomerRef, agent, planRef, toolName, outcome, requestId, latencyMs);
|
|
382
|
+
throw error;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Ensures a customer exists in the backend, creating them if necessary.
|
|
388
|
+
* This is a public helper for testing, pre-creating customers, and internal use.
|
|
389
|
+
* Only attempts creation once per customer (idempotent).
|
|
390
|
+
* Returns the backend customer reference to use in API calls.
|
|
391
|
+
*/
|
|
392
|
+
async ensureCustomer(customerRef) {
|
|
393
|
+
if (this.customerRefMapping.has(customerRef)) {
|
|
394
|
+
return this.customerRefMapping.get(customerRef);
|
|
395
|
+
}
|
|
396
|
+
if (customerRef === "anonymous") {
|
|
397
|
+
return customerRef;
|
|
398
|
+
}
|
|
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;
|
|
405
|
+
}
|
|
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"}`);
|
|
420
|
+
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
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async trackUsage(customerRef, agentRef, planRef, toolName, outcome, requestId, actionDuration) {
|
|
428
|
+
await withRetry(
|
|
429
|
+
() => this.apiClient.trackUsage({
|
|
430
|
+
customerRef,
|
|
431
|
+
agentRef,
|
|
432
|
+
planRef,
|
|
433
|
+
outcome,
|
|
434
|
+
action: toolName,
|
|
435
|
+
requestId,
|
|
436
|
+
actionDuration,
|
|
437
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
438
|
+
}),
|
|
439
|
+
{
|
|
440
|
+
maxRetries: 2,
|
|
441
|
+
initialDelay: 500,
|
|
442
|
+
shouldRetry: (error) => error.message.includes("Customer not found"),
|
|
443
|
+
// TODO: review if this is needed and what to check for
|
|
444
|
+
onRetry: (error, attempt) => {
|
|
445
|
+
console.warn(`\u26A0\uFE0F Customer not found (attempt ${attempt + 1}/3), retrying in 500ms...`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
).catch((error) => {
|
|
449
|
+
console.error("Usage tracking failed:", error);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// src/adapters/base.ts
|
|
455
|
+
var AdapterUtils = class {
|
|
456
|
+
/**
|
|
457
|
+
* Ensure customer reference is properly formatted
|
|
458
|
+
*/
|
|
459
|
+
static ensureCustomerRef(customerRef) {
|
|
460
|
+
if (!customerRef || customerRef === "anonymous") {
|
|
461
|
+
return "anonymous";
|
|
462
|
+
}
|
|
463
|
+
if (!customerRef.startsWith("customer_") && !customerRef.startsWith("demo_")) {
|
|
464
|
+
return `customer_${customerRef.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
465
|
+
}
|
|
466
|
+
return customerRef;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Extract customer ref from JWT token
|
|
470
|
+
*/
|
|
471
|
+
static async extractFromJWT(token, options) {
|
|
472
|
+
try {
|
|
473
|
+
const { jwtVerify } = await import("./esm-5GYCIXIY.js");
|
|
474
|
+
const jwtSecret = new TextEncoder().encode(
|
|
475
|
+
options?.secret || process.env.OAUTH_JWKS_SECRET || "test-jwt-secret"
|
|
476
|
+
);
|
|
477
|
+
const { payload } = await jwtVerify(token, jwtSecret, {
|
|
478
|
+
issuer: options?.issuer || process.env.OAUTH_ISSUER || "http://localhost:3000",
|
|
479
|
+
audience: options?.audience || process.env.OAUTH_CLIENT_ID || "test-client-id"
|
|
480
|
+
});
|
|
481
|
+
return payload.sub || null;
|
|
482
|
+
} catch (error) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
async function createAdapterHandler(adapter, paywall, metadata, businessLogic) {
|
|
488
|
+
return async (context) => {
|
|
489
|
+
try {
|
|
490
|
+
const args = await adapter.extractArgs(context);
|
|
491
|
+
const customerRef = await adapter.getCustomerRef(context);
|
|
492
|
+
args.auth = { customer_ref: customerRef };
|
|
493
|
+
const getCustomerRef = (args2) => args2.auth.customer_ref;
|
|
494
|
+
const protectedHandler = await paywall.protect(businessLogic, metadata, getCustomerRef);
|
|
495
|
+
const result = await protectedHandler(args);
|
|
496
|
+
return adapter.formatResponse(result, context);
|
|
497
|
+
} catch (error) {
|
|
498
|
+
return adapter.formatError(error, context);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/adapters/http.ts
|
|
504
|
+
var HttpAdapter = class {
|
|
505
|
+
constructor(options = {}) {
|
|
506
|
+
this.options = options;
|
|
507
|
+
}
|
|
508
|
+
extractArgs([req, _reply]) {
|
|
509
|
+
if (this.options.extractArgs) {
|
|
510
|
+
return this.options.extractArgs(req);
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
...req.body || {},
|
|
514
|
+
...req.params || {},
|
|
515
|
+
...req.query || {}
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
async getCustomerRef([req, _reply]) {
|
|
519
|
+
if (this.options.getCustomerRef) {
|
|
520
|
+
const ref = await this.options.getCustomerRef(req);
|
|
521
|
+
return AdapterUtils.ensureCustomerRef(ref);
|
|
522
|
+
}
|
|
523
|
+
const headerRef = req.headers?.["x-customer-ref"];
|
|
524
|
+
if (headerRef) {
|
|
525
|
+
return AdapterUtils.ensureCustomerRef(headerRef);
|
|
526
|
+
}
|
|
527
|
+
const authHeader = req.headers?.["authorization"];
|
|
528
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
529
|
+
const token = authHeader.substring(7);
|
|
530
|
+
const jwtSub = await AdapterUtils.extractFromJWT(token);
|
|
531
|
+
if (jwtSub) {
|
|
532
|
+
return AdapterUtils.ensureCustomerRef(jwtSub);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return "anonymous";
|
|
536
|
+
}
|
|
537
|
+
formatResponse(result, [_req, reply]) {
|
|
538
|
+
if (this.options.transformResponse) {
|
|
539
|
+
return this.options.transformResponse(result, reply);
|
|
540
|
+
}
|
|
541
|
+
if (reply && reply.status && typeof reply.json === "function") {
|
|
542
|
+
reply.json(result);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
return result;
|
|
546
|
+
}
|
|
547
|
+
formatError(error, [_req, reply]) {
|
|
548
|
+
if (error instanceof PaywallError) {
|
|
549
|
+
const errorResponse2 = {
|
|
550
|
+
success: false,
|
|
551
|
+
error: "Payment required",
|
|
552
|
+
agent: error.structuredContent.agent,
|
|
553
|
+
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
554
|
+
message: error.structuredContent.message
|
|
555
|
+
};
|
|
556
|
+
if (reply && reply.status && typeof reply.json === "function") {
|
|
557
|
+
reply.status(402).json(errorResponse2);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (reply && reply.code) {
|
|
561
|
+
reply.code(402);
|
|
562
|
+
}
|
|
563
|
+
return errorResponse2;
|
|
564
|
+
}
|
|
565
|
+
const errorResponse = {
|
|
566
|
+
success: false,
|
|
567
|
+
error: error instanceof Error ? error.message : "Internal server error"
|
|
568
|
+
};
|
|
569
|
+
if (reply && reply.status && typeof reply.json === "function") {
|
|
570
|
+
reply.status(500).json(errorResponse);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (reply && reply.code) {
|
|
574
|
+
reply.code(500);
|
|
575
|
+
}
|
|
576
|
+
return errorResponse;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// src/adapters/next.ts
|
|
581
|
+
var NextAdapter = class {
|
|
582
|
+
constructor(options = {}) {
|
|
583
|
+
this.options = options;
|
|
584
|
+
}
|
|
585
|
+
async extractArgs([request, context]) {
|
|
586
|
+
if (this.options.extractArgs) {
|
|
587
|
+
return await this.options.extractArgs(request, context);
|
|
588
|
+
}
|
|
589
|
+
const url = new URL(request.url);
|
|
590
|
+
const query = Object.fromEntries(url.searchParams.entries());
|
|
591
|
+
let body = {};
|
|
592
|
+
try {
|
|
593
|
+
if (request.method !== "GET" && request.headers.get("content-type")?.includes("application/json")) {
|
|
594
|
+
body = await request.json();
|
|
595
|
+
}
|
|
596
|
+
} catch (error) {
|
|
597
|
+
}
|
|
598
|
+
let routeParams = {};
|
|
599
|
+
if (context?.params) {
|
|
600
|
+
if (typeof context.params === "object" && "then" in context.params) {
|
|
601
|
+
routeParams = await context.params;
|
|
602
|
+
} else {
|
|
603
|
+
routeParams = context.params;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
...body,
|
|
608
|
+
...query,
|
|
609
|
+
...routeParams
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
async getCustomerRef([request]) {
|
|
613
|
+
if (this.options.getCustomerRef) {
|
|
614
|
+
const ref = await this.options.getCustomerRef(request);
|
|
615
|
+
return AdapterUtils.ensureCustomerRef(ref);
|
|
616
|
+
}
|
|
617
|
+
const authHeader = request.headers.get("authorization");
|
|
618
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
619
|
+
const token = authHeader.substring(7);
|
|
620
|
+
const jwtSub = await AdapterUtils.extractFromJWT(token);
|
|
621
|
+
if (jwtSub) {
|
|
622
|
+
return AdapterUtils.ensureCustomerRef(jwtSub);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const headerRef = request.headers.get("x-customer-ref");
|
|
626
|
+
if (headerRef) {
|
|
627
|
+
return AdapterUtils.ensureCustomerRef(headerRef);
|
|
628
|
+
}
|
|
629
|
+
return "demo_user";
|
|
630
|
+
}
|
|
631
|
+
formatResponse(result, _context) {
|
|
632
|
+
const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
|
|
633
|
+
return new Response(JSON.stringify(transformed), {
|
|
634
|
+
status: 200,
|
|
635
|
+
headers: { "Content-Type": "application/json" }
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
formatError(error, _context) {
|
|
639
|
+
if (error instanceof PaywallError) {
|
|
640
|
+
return new Response(JSON.stringify({
|
|
641
|
+
success: false,
|
|
642
|
+
error: "Payment required",
|
|
643
|
+
agent: error.structuredContent.agent,
|
|
644
|
+
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
645
|
+
message: error.structuredContent.message
|
|
646
|
+
}), {
|
|
647
|
+
status: 402,
|
|
648
|
+
headers: { "Content-Type": "application/json" }
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
return new Response(JSON.stringify({
|
|
652
|
+
success: false,
|
|
653
|
+
error: error instanceof Error ? error.message : "Internal server error"
|
|
654
|
+
}), {
|
|
655
|
+
status: 500,
|
|
656
|
+
headers: { "Content-Type": "application/json" }
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
// src/adapters/mcp.ts
|
|
662
|
+
var McpAdapter = class {
|
|
663
|
+
constructor(options = {}) {
|
|
664
|
+
this.options = options;
|
|
665
|
+
}
|
|
666
|
+
extractArgs(args) {
|
|
667
|
+
return args;
|
|
668
|
+
}
|
|
669
|
+
async getCustomerRef(args) {
|
|
670
|
+
if (this.options.getCustomerRef) {
|
|
671
|
+
const ref = await this.options.getCustomerRef(args);
|
|
672
|
+
return AdapterUtils.ensureCustomerRef(ref);
|
|
673
|
+
}
|
|
674
|
+
const customerRef = args?.auth?.customer_ref || "anonymous";
|
|
675
|
+
return AdapterUtils.ensureCustomerRef(customerRef);
|
|
676
|
+
}
|
|
677
|
+
formatResponse(result, _context) {
|
|
678
|
+
const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
|
|
679
|
+
return {
|
|
680
|
+
content: [{
|
|
681
|
+
type: "text",
|
|
682
|
+
text: JSON.stringify(transformed, null, 2)
|
|
683
|
+
}]
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
formatError(error, _context) {
|
|
687
|
+
if (error instanceof PaywallError) {
|
|
688
|
+
return {
|
|
689
|
+
content: [{
|
|
690
|
+
type: "text",
|
|
691
|
+
text: JSON.stringify({
|
|
692
|
+
success: false,
|
|
693
|
+
error: "Payment required",
|
|
694
|
+
agent: error.structuredContent.agent,
|
|
695
|
+
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
696
|
+
message: error.structuredContent.message
|
|
697
|
+
}, null, 2)
|
|
698
|
+
}],
|
|
699
|
+
isError: true,
|
|
700
|
+
structuredContent: error.structuredContent
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
content: [{
|
|
705
|
+
type: "text",
|
|
706
|
+
text: JSON.stringify({
|
|
707
|
+
success: false,
|
|
708
|
+
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
709
|
+
}, null, 2)
|
|
710
|
+
}],
|
|
711
|
+
isError: true
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
// src/factory.ts
|
|
717
|
+
import { SolvaPayError as SolvaPayError2 } from "@solvapay/core";
|
|
718
|
+
function createSolvaPay(config) {
|
|
719
|
+
const apiClient = config.apiClient || createSolvaPayClient({
|
|
720
|
+
apiKey: config.apiKey,
|
|
721
|
+
apiBaseUrl: config.apiBaseUrl
|
|
722
|
+
});
|
|
723
|
+
const paywall = new SolvaPayPaywall(apiClient, {
|
|
724
|
+
debug: process.env.SOLVAPAY_DEBUG !== "false"
|
|
725
|
+
});
|
|
726
|
+
return {
|
|
727
|
+
// Direct access to API client for advanced operations
|
|
728
|
+
apiClient,
|
|
729
|
+
// Common API methods exposed directly for convenience
|
|
730
|
+
ensureCustomer(customerRef) {
|
|
731
|
+
return paywall.ensureCustomer(customerRef);
|
|
732
|
+
},
|
|
733
|
+
createPaymentIntent(params) {
|
|
734
|
+
if (!apiClient.createPaymentIntent) {
|
|
735
|
+
throw new SolvaPayError2("createPaymentIntent is not available on this API client");
|
|
736
|
+
}
|
|
737
|
+
return apiClient.createPaymentIntent(params);
|
|
738
|
+
},
|
|
739
|
+
checkLimits(params) {
|
|
740
|
+
return apiClient.checkLimits(params);
|
|
741
|
+
},
|
|
742
|
+
trackUsage(params) {
|
|
743
|
+
return apiClient.trackUsage(params);
|
|
744
|
+
},
|
|
745
|
+
createCustomer(params) {
|
|
746
|
+
if (!apiClient.createCustomer) {
|
|
747
|
+
throw new SolvaPayError2("createCustomer is not available on this API client");
|
|
748
|
+
}
|
|
749
|
+
return apiClient.createCustomer(params);
|
|
750
|
+
},
|
|
751
|
+
getCustomer(params) {
|
|
752
|
+
if (!apiClient.getCustomer) {
|
|
753
|
+
throw new SolvaPayError2("getCustomer is not available on this API client");
|
|
754
|
+
}
|
|
755
|
+
return apiClient.getCustomer(params);
|
|
756
|
+
},
|
|
757
|
+
// Payable API for framework-specific handlers
|
|
758
|
+
payable(options = {}) {
|
|
759
|
+
const agent = options.agentRef || options.agent || process.env.SOLVAPAY_AGENT || getPackageJsonName() || "default-agent";
|
|
760
|
+
const plan = options.planRef || options.plan || agent;
|
|
761
|
+
const metadata = { agent, plan };
|
|
762
|
+
return {
|
|
763
|
+
// HTTP adapter for Express/Fastify
|
|
764
|
+
http(businessLogic, adapterOptions) {
|
|
765
|
+
const adapter = new HttpAdapter(adapterOptions);
|
|
766
|
+
return async (req, reply) => {
|
|
767
|
+
const handler = await createAdapterHandler(
|
|
768
|
+
adapter,
|
|
769
|
+
paywall,
|
|
770
|
+
metadata,
|
|
771
|
+
businessLogic
|
|
772
|
+
);
|
|
773
|
+
return handler([req, reply]);
|
|
774
|
+
};
|
|
775
|
+
},
|
|
776
|
+
// Next.js adapter for App Router
|
|
777
|
+
next(businessLogic, adapterOptions) {
|
|
778
|
+
const adapter = new NextAdapter(adapterOptions);
|
|
779
|
+
return async (request, context) => {
|
|
780
|
+
const handler = await createAdapterHandler(
|
|
781
|
+
adapter,
|
|
782
|
+
paywall,
|
|
783
|
+
metadata,
|
|
784
|
+
businessLogic
|
|
785
|
+
);
|
|
786
|
+
return handler([request, context]);
|
|
787
|
+
};
|
|
788
|
+
},
|
|
789
|
+
// MCP adapter for Model Context Protocol
|
|
790
|
+
mcp(businessLogic, adapterOptions) {
|
|
791
|
+
const adapter = new McpAdapter(adapterOptions);
|
|
792
|
+
return async (args) => {
|
|
793
|
+
const handler = await createAdapterHandler(
|
|
794
|
+
adapter,
|
|
795
|
+
paywall,
|
|
796
|
+
metadata,
|
|
797
|
+
businessLogic
|
|
798
|
+
);
|
|
799
|
+
return handler(args);
|
|
800
|
+
};
|
|
801
|
+
},
|
|
802
|
+
// Pure function adapter for direct protection
|
|
803
|
+
async function(businessLogic) {
|
|
804
|
+
const getCustomerRef = (args) => args.auth?.customer_ref || "anonymous";
|
|
805
|
+
return paywall.protect(businessLogic, metadata, getCustomerRef);
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function getPackageJsonName() {
|
|
812
|
+
try {
|
|
813
|
+
const pkg = __require(process.cwd() + "/package.json");
|
|
814
|
+
return pkg.name;
|
|
815
|
+
} catch {
|
|
816
|
+
return void 0;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/edge.ts
|
|
821
|
+
async function verifyWebhook({
|
|
822
|
+
body,
|
|
823
|
+
signature,
|
|
824
|
+
secret
|
|
825
|
+
}) {
|
|
826
|
+
const enc = new TextEncoder();
|
|
827
|
+
const key = await crypto.subtle.importKey(
|
|
828
|
+
"raw",
|
|
829
|
+
enc.encode(secret),
|
|
830
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
831
|
+
false,
|
|
832
|
+
["sign"]
|
|
833
|
+
);
|
|
834
|
+
const sigBuf = await crypto.subtle.sign("HMAC", key, enc.encode(body));
|
|
835
|
+
const hex = Array.from(new Uint8Array(sigBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
836
|
+
if (hex !== signature) {
|
|
837
|
+
throw new SolvaPayError3("Invalid webhook signature");
|
|
838
|
+
}
|
|
839
|
+
return JSON.parse(body);
|
|
840
|
+
}
|
|
841
|
+
export {
|
|
842
|
+
PaywallError,
|
|
843
|
+
createSolvaPay,
|
|
844
|
+
createSolvaPayClient,
|
|
845
|
+
verifyWebhook,
|
|
846
|
+
withRetry
|
|
847
|
+
};
|