@solvapay/server 1.0.8-preview.8 → 1.0.8

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.
Files changed (148) hide show
  1. package/README.md +80 -2
  2. package/dist/edge.d.ts +495 -61
  3. package/dist/edge.js +648 -187
  4. package/dist/fetch/index.cjs +3071 -0
  5. package/dist/fetch/index.d.cts +112 -0
  6. package/dist/fetch/index.d.ts +112 -0
  7. package/dist/fetch/index.js +3017 -0
  8. package/dist/index.cjs +603 -5013
  9. package/dist/index.d.cts +490 -156
  10. package/dist/index.d.ts +490 -156
  11. package/dist/index.js +571 -695
  12. package/package.json +15 -12
  13. package/dist/chunk-MLKGABMK.js +0 -9
  14. package/dist/chunk-R5U7XKVJ.js +0 -16
  15. package/dist/dist-EPVKJAIP.js +0 -94
  16. package/dist/dist-JBJ4HMP7.js +0 -94
  17. package/dist/esm-5GYCIXIY.js +0 -3475
  18. package/dist/esm-UW7WCMEK.js +0 -3475
  19. package/dist/src/adapters/base.d.ts +0 -57
  20. package/dist/src/adapters/base.d.ts.map +0 -1
  21. package/dist/src/adapters/base.js +0 -73
  22. package/dist/src/adapters/base.js.map +0 -1
  23. package/dist/src/adapters/http.d.ts +0 -25
  24. package/dist/src/adapters/http.d.ts.map +0 -1
  25. package/dist/src/adapters/http.js +0 -99
  26. package/dist/src/adapters/http.js.map +0 -1
  27. package/dist/src/adapters/index.d.ts +0 -11
  28. package/dist/src/adapters/index.d.ts.map +0 -1
  29. package/dist/src/adapters/index.js +0 -10
  30. package/dist/src/adapters/index.js.map +0 -1
  31. package/dist/src/adapters/mcp.d.ts +0 -24
  32. package/dist/src/adapters/mcp.d.ts.map +0 -1
  33. package/dist/src/adapters/mcp.js +0 -75
  34. package/dist/src/adapters/mcp.js.map +0 -1
  35. package/dist/src/adapters/next.d.ts +0 -24
  36. package/dist/src/adapters/next.d.ts.map +0 -1
  37. package/dist/src/adapters/next.js +0 -109
  38. package/dist/src/adapters/next.js.map +0 -1
  39. package/dist/src/client.d.ts +0 -58
  40. package/dist/src/client.d.ts.map +0 -1
  41. package/dist/src/client.js +0 -495
  42. package/dist/src/client.js.map +0 -1
  43. package/dist/src/edge.d.ts +0 -22
  44. package/dist/src/edge.d.ts.map +0 -1
  45. package/dist/src/edge.js +0 -91
  46. package/dist/src/edge.js.map +0 -1
  47. package/dist/src/factory.d.ts +0 -605
  48. package/dist/src/factory.d.ts.map +0 -1
  49. package/dist/src/factory.js +0 -215
  50. package/dist/src/factory.js.map +0 -1
  51. package/dist/src/helpers/auth.d.ts +0 -47
  52. package/dist/src/helpers/auth.d.ts.map +0 -1
  53. package/dist/src/helpers/auth.js +0 -73
  54. package/dist/src/helpers/auth.js.map +0 -1
  55. package/dist/src/helpers/checkout.d.ts +0 -45
  56. package/dist/src/helpers/checkout.d.ts.map +0 -1
  57. package/dist/src/helpers/checkout.js +0 -89
  58. package/dist/src/helpers/checkout.js.map +0 -1
  59. package/dist/src/helpers/customer.d.ts +0 -51
  60. package/dist/src/helpers/customer.d.ts.map +0 -1
  61. package/dist/src/helpers/customer.js +0 -77
  62. package/dist/src/helpers/customer.js.map +0 -1
  63. package/dist/src/helpers/error.d.ts +0 -15
  64. package/dist/src/helpers/error.d.ts.map +0 -1
  65. package/dist/src/helpers/error.js +0 -35
  66. package/dist/src/helpers/error.js.map +0 -1
  67. package/dist/src/helpers/index.d.ts +0 -17
  68. package/dist/src/helpers/index.d.ts.map +0 -1
  69. package/dist/src/helpers/index.js +0 -23
  70. package/dist/src/helpers/index.js.map +0 -1
  71. package/dist/src/helpers/payment.d.ts +0 -107
  72. package/dist/src/helpers/payment.d.ts.map +0 -1
  73. package/dist/src/helpers/payment.js +0 -150
  74. package/dist/src/helpers/payment.js.map +0 -1
  75. package/dist/src/helpers/plans.d.ts +0 -19
  76. package/dist/src/helpers/plans.d.ts.map +0 -1
  77. package/dist/src/helpers/plans.js +0 -53
  78. package/dist/src/helpers/plans.js.map +0 -1
  79. package/dist/src/helpers/renewal.d.ts +0 -23
  80. package/dist/src/helpers/renewal.d.ts.map +0 -1
  81. package/dist/src/helpers/renewal.js +0 -90
  82. package/dist/src/helpers/renewal.js.map +0 -1
  83. package/dist/src/helpers/subscription.d.ts +0 -23
  84. package/dist/src/helpers/subscription.d.ts.map +0 -1
  85. package/dist/src/helpers/subscription.js +0 -101
  86. package/dist/src/helpers/subscription.js.map +0 -1
  87. package/dist/src/helpers/types.d.ts +0 -22
  88. package/dist/src/helpers/types.d.ts.map +0 -1
  89. package/dist/src/helpers/types.js +0 -7
  90. package/dist/src/helpers/types.js.map +0 -1
  91. package/dist/src/index.d.ts +0 -73
  92. package/dist/src/index.d.ts.map +0 -1
  93. package/dist/src/index.js +0 -106
  94. package/dist/src/index.js.map +0 -1
  95. package/dist/src/mcp/auth-bridge.d.ts +0 -9
  96. package/dist/src/mcp/auth-bridge.d.ts.map +0 -1
  97. package/dist/src/mcp/auth-bridge.js +0 -46
  98. package/dist/src/mcp/auth-bridge.js.map +0 -1
  99. package/dist/src/mcp/oauth-bridge.d.ts +0 -48
  100. package/dist/src/mcp/oauth-bridge.d.ts.map +0 -1
  101. package/dist/src/mcp/oauth-bridge.js +0 -110
  102. package/dist/src/mcp/oauth-bridge.js.map +0 -1
  103. package/dist/src/mcp-auth.d.ts +0 -17
  104. package/dist/src/mcp-auth.d.ts.map +0 -1
  105. package/dist/src/mcp-auth.js +0 -57
  106. package/dist/src/mcp-auth.js.map +0 -1
  107. package/dist/src/paywall.d.ts +0 -119
  108. package/dist/src/paywall.d.ts.map +0 -1
  109. package/dist/src/paywall.js +0 -700
  110. package/dist/src/paywall.js.map +0 -1
  111. package/dist/src/register-virtual-tools-mcp.d.ts +0 -23
  112. package/dist/src/register-virtual-tools-mcp.d.ts.map +0 -1
  113. package/dist/src/register-virtual-tools-mcp.js +0 -86
  114. package/dist/src/register-virtual-tools-mcp.js.map +0 -1
  115. package/dist/src/types/client.d.ts +0 -216
  116. package/dist/src/types/client.d.ts.map +0 -1
  117. package/dist/src/types/client.js +0 -7
  118. package/dist/src/types/client.js.map +0 -1
  119. package/dist/src/types/generated.d.ts +0 -2834
  120. package/dist/src/types/generated.d.ts.map +0 -1
  121. package/dist/src/types/generated.js +0 -6
  122. package/dist/src/types/generated.js.map +0 -1
  123. package/dist/src/types/index.d.ts +0 -13
  124. package/dist/src/types/index.d.ts.map +0 -1
  125. package/dist/src/types/index.js +0 -8
  126. package/dist/src/types/index.js.map +0 -1
  127. package/dist/src/types/options.d.ts +0 -126
  128. package/dist/src/types/options.d.ts.map +0 -1
  129. package/dist/src/types/options.js +0 -8
  130. package/dist/src/types/options.js.map +0 -1
  131. package/dist/src/types/paywall.d.ts +0 -64
  132. package/dist/src/types/paywall.d.ts.map +0 -1
  133. package/dist/src/types/paywall.js +0 -7
  134. package/dist/src/types/paywall.js.map +0 -1
  135. package/dist/src/types/webhook.d.ts +0 -50
  136. package/dist/src/types/webhook.d.ts.map +0 -1
  137. package/dist/src/types/webhook.js +0 -2
  138. package/dist/src/types/webhook.js.map +0 -1
  139. package/dist/src/utils.d.ts +0 -110
  140. package/dist/src/utils.d.ts.map +0 -1
  141. package/dist/src/utils.js +0 -217
  142. package/dist/src/utils.js.map +0 -1
  143. package/dist/src/virtual-tools.d.ts +0 -44
  144. package/dist/src/virtual-tools.d.ts.map +0 -1
  145. package/dist/src/virtual-tools.js +0 -140
  146. package/dist/src/virtual-tools.js.map +0 -1
  147. package/dist/tsconfig.tsbuildinfo +0 -1
  148. package/dist/webapi-K5XBCEO6.js +0 -3775
@@ -0,0 +1,3017 @@
1
+ // src/helpers/error.ts
2
+ import { SolvaPayError } from "@solvapay/core";
3
+ function isErrorResult(result) {
4
+ return typeof result === "object" && result !== null && "error" in result && "status" in result;
5
+ }
6
+ function handleRouteError(error, operationName, defaultMessage) {
7
+ console.error(`[${operationName}] Error:`, error);
8
+ if (error instanceof SolvaPayError) {
9
+ const errorMessage2 = error.message;
10
+ return {
11
+ error: errorMessage2,
12
+ status: 500,
13
+ details: errorMessage2
14
+ };
15
+ }
16
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
17
+ const message = defaultMessage || `${operationName} failed`;
18
+ return {
19
+ error: message,
20
+ status: 500,
21
+ details: errorMessage
22
+ };
23
+ }
24
+
25
+ // src/helpers/auth.ts
26
+ function base64UrlDecode(input) {
27
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
28
+ const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
29
+ const base64 = padded + padding;
30
+ if (typeof atob === "function") {
31
+ const binary = atob(base64);
32
+ let result = "";
33
+ for (let i = 0; i < binary.length; i++) {
34
+ result += String.fromCharCode(binary.charCodeAt(i));
35
+ }
36
+ try {
37
+ return decodeURIComponent(escape(result));
38
+ } catch {
39
+ return result;
40
+ }
41
+ }
42
+ const BufferCtor = globalThis.Buffer;
43
+ if (BufferCtor) {
44
+ return BufferCtor.from(base64, "base64").toString("utf-8");
45
+ }
46
+ throw new Error("No base64 decoder available in this runtime");
47
+ }
48
+ function decodeJwtUnverified(token) {
49
+ const parts = token.split(".");
50
+ if (parts.length !== 3) return null;
51
+ try {
52
+ const json = base64UrlDecode(parts[1]);
53
+ const payload = JSON.parse(json);
54
+ if (typeof payload !== "object" || payload === null) return null;
55
+ return payload;
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+ function extractBearerToken(request) {
61
+ const authHeader = request.headers.get("authorization");
62
+ if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
63
+ const token = authHeader.slice(7).trim();
64
+ return token.length > 0 ? token : null;
65
+ }
66
+ function readEnv(name) {
67
+ const proc = globalThis.process;
68
+ return proc?.env?.[name];
69
+ }
70
+ function isStrictMode() {
71
+ return readEnv("SOLVAPAY_AUTH_STRICT") === "true";
72
+ }
73
+ function getConfiguredSecret() {
74
+ return readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
75
+ }
76
+ async function verifyHs256(token, secret) {
77
+ try {
78
+ const { jwtVerify } = await import("jose");
79
+ const key = new TextEncoder().encode(secret);
80
+ const { payload } = await jwtVerify(token, key, { algorithms: ["HS256"] });
81
+ return payload;
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+ function pickName(payload) {
87
+ const metadataFullName = typeof payload.user_metadata?.full_name === "string" ? payload.user_metadata.full_name : null;
88
+ const metadataName = typeof payload.user_metadata?.name === "string" ? payload.user_metadata.name : null;
89
+ const claimName = typeof payload.name === "string" ? payload.name : null;
90
+ return metadataFullName || metadataName || claimName || null;
91
+ }
92
+ function pickEmail(payload) {
93
+ return typeof payload.email === "string" ? payload.email : null;
94
+ }
95
+ function unauthorized(details) {
96
+ return { error: "Unauthorized", status: 401, details };
97
+ }
98
+ async function getAuthenticatedUserCore(request, options = {}) {
99
+ try {
100
+ const includeEmail = options.includeEmail !== false;
101
+ const includeName = options.includeName !== false;
102
+ const headerUserId = request.headers.get("x-user-id");
103
+ if (headerUserId) {
104
+ let email = null;
105
+ let name = null;
106
+ if (includeEmail || includeName) {
107
+ const token2 = extractBearerToken(request);
108
+ if (token2) {
109
+ const secret2 = getConfiguredSecret();
110
+ const payload2 = secret2 ? await verifyHs256(token2, secret2) : isStrictMode() ? null : decodeJwtUnverified(token2);
111
+ if (payload2) {
112
+ if (includeEmail) email = pickEmail(payload2);
113
+ if (includeName) name = pickName(payload2);
114
+ }
115
+ }
116
+ }
117
+ return { userId: headerUserId, email, name };
118
+ }
119
+ const token = extractBearerToken(request);
120
+ if (!token) {
121
+ return unauthorized("User ID not found. Ensure middleware is configured.");
122
+ }
123
+ const secret = getConfiguredSecret();
124
+ let payload = null;
125
+ if (secret) {
126
+ payload = await verifyHs256(token, secret);
127
+ if (!payload) {
128
+ return unauthorized("Invalid or expired authentication token");
129
+ }
130
+ } else if (isStrictMode()) {
131
+ return unauthorized(
132
+ "Strict auth mode is enabled but no JWT secret is configured. Set SOLVAPAY_JWT_SECRET or SUPABASE_JWT_SECRET."
133
+ );
134
+ } else {
135
+ payload = decodeJwtUnverified(token);
136
+ if (!payload) {
137
+ return unauthorized("Malformed authentication token");
138
+ }
139
+ }
140
+ const userId = typeof payload.sub === "string" ? payload.sub : null;
141
+ if (!userId) {
142
+ return unauthorized("Authentication token missing subject (sub) claim");
143
+ }
144
+ return {
145
+ userId,
146
+ email: includeEmail ? pickEmail(payload) : null,
147
+ name: includeName ? pickName(payload) : null
148
+ };
149
+ } catch (error) {
150
+ return handleRouteError(error, "Get authenticated user", "Authentication failed");
151
+ }
152
+ }
153
+
154
+ // src/client.ts
155
+ import { SolvaPayError as SolvaPayError2 } from "@solvapay/core";
156
+ function createSolvaPayClient(opts) {
157
+ const base = opts.apiBaseUrl ?? "https://api.solvapay.com";
158
+ if (!opts.apiKey) throw new SolvaPayError2("Missing apiKey");
159
+ const headers = {
160
+ "Content-Type": "application/json",
161
+ Authorization: `Bearer ${opts.apiKey}`
162
+ };
163
+ const debug = process.env.SOLVAPAY_DEBUG === "true";
164
+ const log = (...args) => {
165
+ if (debug) {
166
+ console.log(...args);
167
+ }
168
+ };
169
+ return {
170
+ // POST: /v1/sdk/limits
171
+ async checkLimits(params) {
172
+ const url = `${base}/v1/sdk/limits`;
173
+ const res = await fetch(url, {
174
+ method: "POST",
175
+ headers,
176
+ body: JSON.stringify(params)
177
+ });
178
+ if (!res.ok) {
179
+ const error = await res.text();
180
+ log(`\u274C API Error: ${res.status} - ${error}`);
181
+ throw new SolvaPayError2(`Check limits failed (${res.status}): ${error}`);
182
+ }
183
+ const result = await res.json();
184
+ return result;
185
+ },
186
+ // POST: /v1/sdk/usages
187
+ async trackUsage(params) {
188
+ const url = `${base}/v1/sdk/usages`;
189
+ const res = await fetch(url, {
190
+ method: "POST",
191
+ headers,
192
+ body: JSON.stringify(params)
193
+ });
194
+ if (!res.ok) {
195
+ const error = await res.text();
196
+ log(`\u274C API Error: ${res.status} - ${error}`);
197
+ throw new SolvaPayError2(`Track usage failed (${res.status}): ${error}`);
198
+ }
199
+ },
200
+ // POST: /v1/sdk/customers
201
+ async createCustomer(params) {
202
+ const url = `${base}/v1/sdk/customers`;
203
+ const res = await fetch(url, {
204
+ method: "POST",
205
+ headers,
206
+ body: JSON.stringify(params)
207
+ });
208
+ if (!res.ok) {
209
+ const error = await res.text();
210
+ log(`\u274C API Error: ${res.status} - ${error}`);
211
+ throw new SolvaPayError2(`Create customer failed (${res.status}): ${error}`);
212
+ }
213
+ const result = await res.json();
214
+ return {
215
+ customerRef: result.reference || result.customerRef
216
+ };
217
+ },
218
+ // PATCH: /v1/sdk/customers/{customerRef}
219
+ async updateCustomer(customerRef, params) {
220
+ const url = `${base}/v1/sdk/customers/${encodeURIComponent(customerRef)}`;
221
+ const res = await fetch(url, {
222
+ method: "PATCH",
223
+ headers,
224
+ body: JSON.stringify(params)
225
+ });
226
+ if (!res.ok) {
227
+ const error = await res.text();
228
+ log(`\u274C API Error: ${res.status} - ${error}`);
229
+ throw new SolvaPayError2(`Update customer failed (${res.status}): ${error}`);
230
+ }
231
+ const result = await res.json();
232
+ return {
233
+ customerRef: result.reference || result.customerRef || customerRef
234
+ };
235
+ },
236
+ // GET: /v1/sdk/customers/{reference} or /v1/sdk/customers?externalRef={externalRef}|email={email}
237
+ async getCustomer(params) {
238
+ let url;
239
+ let isByExternalRef = false;
240
+ let isByEmail = false;
241
+ if (params.externalRef) {
242
+ url = `${base}/v1/sdk/customers?externalRef=${encodeURIComponent(params.externalRef)}`;
243
+ isByExternalRef = true;
244
+ } else if (params.email) {
245
+ url = `${base}/v1/sdk/customers?email=${encodeURIComponent(params.email)}`;
246
+ isByEmail = true;
247
+ } else if (params.customerRef) {
248
+ url = `${base}/v1/sdk/customers/${params.customerRef}`;
249
+ } else {
250
+ throw new SolvaPayError2("One of customerRef, externalRef, or email must be provided");
251
+ }
252
+ const res = await fetch(url, {
253
+ method: "GET",
254
+ headers
255
+ });
256
+ if (!res.ok) {
257
+ const error = await res.text();
258
+ log(`\u274C API Error: ${res.status} - ${error}`);
259
+ throw new SolvaPayError2(`Get customer failed (${res.status}): ${error}`);
260
+ }
261
+ const result = await res.json();
262
+ let customer = result;
263
+ if (isByExternalRef || isByEmail) {
264
+ const directCustomer = result && typeof result === "object" && (result.reference || result.customerRef || result.externalRef) ? result : void 0;
265
+ const wrappedCustomer = result && typeof result === "object" && result.customer ? result.customer : void 0;
266
+ const customers = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.customers) ? result.customers : [];
267
+ customer = directCustomer || wrappedCustomer || customers[0];
268
+ if (!customer) {
269
+ throw new SolvaPayError2(`No customer found with externalRef: ${params.externalRef}`);
270
+ }
271
+ }
272
+ return {
273
+ customerRef: customer.reference || customer.customerRef,
274
+ email: customer.email,
275
+ name: customer.name,
276
+ externalRef: customer.externalRef,
277
+ purchases: customer.purchases || []
278
+ };
279
+ },
280
+ // GET: /v1/sdk/merchant
281
+ async getMerchant() {
282
+ const url = `${base}/v1/sdk/merchant`;
283
+ const res = await fetch(url, {
284
+ method: "GET",
285
+ headers
286
+ });
287
+ if (!res.ok) {
288
+ const error = await res.text();
289
+ log(`\u274C API Error: ${res.status} - ${error}`);
290
+ throw new SolvaPayError2(`Get merchant failed (${res.status}): ${error}`);
291
+ }
292
+ return res.json();
293
+ },
294
+ // GET: /v1/sdk/platform-config
295
+ async getPlatformConfig() {
296
+ const url = `${base}/v1/sdk/platform-config`;
297
+ const res = await fetch(url, {
298
+ method: "GET",
299
+ headers
300
+ });
301
+ if (!res.ok) {
302
+ const error = await res.text();
303
+ log(`\u274C API Error: ${res.status} - ${error}`);
304
+ throw new SolvaPayError2(`Get platform config failed (${res.status}): ${error}`);
305
+ }
306
+ return res.json();
307
+ },
308
+ // GET: /v1/sdk/products/{productRef}
309
+ async getProduct(productRef) {
310
+ const url = `${base}/v1/sdk/products/${encodeURIComponent(productRef)}`;
311
+ const res = await fetch(url, {
312
+ method: "GET",
313
+ headers
314
+ });
315
+ if (!res.ok) {
316
+ const error = await res.text();
317
+ log(`\u274C API Error: ${res.status} - ${error}`);
318
+ throw new SolvaPayError2(`Get product failed (${res.status}): ${error}`);
319
+ }
320
+ const result = await res.json();
321
+ const data = result.data || {};
322
+ return { ...data, ...result };
323
+ },
324
+ // Product management methods (primarily for integration tests)
325
+ // GET: /v1/sdk/products
326
+ async listProducts() {
327
+ const url = `${base}/v1/sdk/products`;
328
+ const res = await fetch(url, {
329
+ method: "GET",
330
+ headers
331
+ });
332
+ if (!res.ok) {
333
+ const error = await res.text();
334
+ log(`\u274C API Error: ${res.status} - ${error}`);
335
+ throw new SolvaPayError2(`List products failed (${res.status}): ${error}`);
336
+ }
337
+ const result = await res.json();
338
+ const products = Array.isArray(result) ? result : result.products || [];
339
+ return products.map((product) => ({
340
+ ...product,
341
+ ...product.data || {}
342
+ }));
343
+ },
344
+ // POST: /v1/sdk/products
345
+ async createProduct(params) {
346
+ const url = `${base}/v1/sdk/products`;
347
+ const res = await fetch(url, {
348
+ method: "POST",
349
+ headers,
350
+ body: JSON.stringify(params)
351
+ });
352
+ if (!res.ok) {
353
+ const error = await res.text();
354
+ log(`\u274C API Error: ${res.status} - ${error}`);
355
+ throw new SolvaPayError2(`Create product failed (${res.status}): ${error}`);
356
+ }
357
+ const result = await res.json();
358
+ return result;
359
+ },
360
+ // POST: /v1/sdk/products/mcp/bootstrap
361
+ async bootstrapMcpProduct(params) {
362
+ const url = `${base}/v1/sdk/products/mcp/bootstrap`;
363
+ const res = await fetch(url, {
364
+ method: "POST",
365
+ headers,
366
+ body: JSON.stringify(params)
367
+ });
368
+ if (!res.ok) {
369
+ const error = await res.text();
370
+ log(`\u274C API Error: ${res.status} - ${error}`);
371
+ throw new SolvaPayError2(`Bootstrap MCP product failed (${res.status}): ${error}`);
372
+ }
373
+ return await res.json();
374
+ },
375
+ // PUT: /v1/sdk/products/{productRef}/mcp/plans
376
+ async configureMcpPlans(productRef, params) {
377
+ const url = `${base}/v1/sdk/products/${productRef}/mcp/plans`;
378
+ const res = await fetch(url, {
379
+ method: "PUT",
380
+ headers,
381
+ body: JSON.stringify(params)
382
+ });
383
+ if (!res.ok) {
384
+ const error = await res.text();
385
+ log(`\u274C API Error: ${res.status} - ${error}`);
386
+ throw new SolvaPayError2(`Configure MCP plans failed (${res.status}): ${error}`);
387
+ }
388
+ return await res.json();
389
+ },
390
+ // DELETE: /v1/sdk/products/{productRef}
391
+ async deleteProduct(productRef) {
392
+ const url = `${base}/v1/sdk/products/${productRef}`;
393
+ const res = await fetch(url, {
394
+ method: "DELETE",
395
+ headers
396
+ });
397
+ if (!res.ok && res.status !== 404) {
398
+ const error = await res.text();
399
+ log(`\u274C API Error: ${res.status} - ${error}`);
400
+ throw new SolvaPayError2(`Delete product failed (${res.status}): ${error}`);
401
+ }
402
+ },
403
+ // POST: /v1/sdk/products/{productRef}/clone
404
+ async cloneProduct(productRef, overrides) {
405
+ const url = `${base}/v1/sdk/products/${productRef}/clone`;
406
+ const res = await fetch(url, {
407
+ method: "POST",
408
+ headers,
409
+ body: JSON.stringify(overrides || {})
410
+ });
411
+ if (!res.ok) {
412
+ const error = await res.text();
413
+ log(`\u274C API Error: ${res.status} - ${error}`);
414
+ throw new SolvaPayError2(`Clone product failed (${res.status}): ${error}`);
415
+ }
416
+ return await res.json();
417
+ },
418
+ // GET: /v1/sdk/products/{productRef}/plans
419
+ async listPlans(productRef) {
420
+ const url = `${base}/v1/sdk/products/${productRef}/plans`;
421
+ const res = await fetch(url, {
422
+ method: "GET",
423
+ headers
424
+ });
425
+ if (!res.ok) {
426
+ const error = await res.text();
427
+ log(`\u274C API Error: ${res.status} - ${error}`);
428
+ throw new SolvaPayError2(`List plans failed (${res.status}): ${error}`);
429
+ }
430
+ const result = await res.json();
431
+ const plans = Array.isArray(result) ? result : result.plans || [];
432
+ return plans.map((plan) => {
433
+ const data = plan.data || {};
434
+ const price = plan.price ?? data.price;
435
+ const unwrapped = {
436
+ ...data,
437
+ ...plan,
438
+ ...price !== void 0 && { price }
439
+ };
440
+ delete unwrapped.data;
441
+ return unwrapped;
442
+ });
443
+ },
444
+ // POST: /v1/sdk/products/{productRef}/plans
445
+ async createPlan(params) {
446
+ const url = `${base}/v1/sdk/products/${params.productRef}/plans`;
447
+ const res = await fetch(url, {
448
+ method: "POST",
449
+ headers,
450
+ body: JSON.stringify(params)
451
+ });
452
+ if (!res.ok) {
453
+ const error = await res.text();
454
+ log(`\u274C API Error: ${res.status} - ${error}`);
455
+ throw new SolvaPayError2(`Create plan failed (${res.status}): ${error}`);
456
+ }
457
+ const result = await res.json();
458
+ return result;
459
+ },
460
+ // PUT: /v1/sdk/products/{productRef}/plans/{planRef}
461
+ async updatePlan(productRef, planRef, params) {
462
+ const url = `${base}/v1/sdk/products/${productRef}/plans/${planRef}`;
463
+ const res = await fetch(url, {
464
+ method: "PUT",
465
+ headers,
466
+ body: JSON.stringify(params)
467
+ });
468
+ if (!res.ok) {
469
+ const error = await res.text();
470
+ log(`\u274C API Error: ${res.status} - ${error}`);
471
+ throw new SolvaPayError2(`Update plan failed (${res.status}): ${error}`);
472
+ }
473
+ return await res.json();
474
+ },
475
+ // DELETE: /v1/sdk/products/{productRef}/plans/{planRef}
476
+ async deletePlan(productRef, planRef) {
477
+ const url = `${base}/v1/sdk/products/${productRef}/plans/${planRef}`;
478
+ const res = await fetch(url, {
479
+ method: "DELETE",
480
+ headers
481
+ });
482
+ if (!res.ok && res.status !== 404) {
483
+ const error = await res.text();
484
+ log(`\u274C API Error: ${res.status} - ${error}`);
485
+ throw new SolvaPayError2(`Delete plan failed (${res.status}): ${error}`);
486
+ }
487
+ },
488
+ // POST: /payment-intents
489
+ async createPaymentIntent(params) {
490
+ const idempotencyKey = params.idempotencyKey || `payment-${params.planRef}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
491
+ const url = `${base}/v1/sdk/payment-intents`;
492
+ const res = await fetch(url, {
493
+ method: "POST",
494
+ headers: {
495
+ ...headers,
496
+ "Idempotency-Key": idempotencyKey
497
+ },
498
+ body: JSON.stringify({
499
+ productRef: params.productRef,
500
+ planRef: params.planRef,
501
+ customerRef: params.customerRef
502
+ })
503
+ });
504
+ if (!res.ok) {
505
+ const error = await res.text();
506
+ log(`\u274C API Error: ${res.status} - ${error}`);
507
+ throw new SolvaPayError2(`Create payment intent failed (${res.status}): ${error}`);
508
+ }
509
+ return await res.json();
510
+ },
511
+ // POST: /v1/sdk/payment-intents (purpose: credit_topup)
512
+ async createTopupPaymentIntent(params) {
513
+ const idempotencyKey = params.idempotencyKey || `topup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
514
+ const url = `${base}/v1/sdk/payment-intents`;
515
+ const res = await fetch(url, {
516
+ method: "POST",
517
+ headers: {
518
+ ...headers,
519
+ "Idempotency-Key": idempotencyKey
520
+ },
521
+ body: JSON.stringify({
522
+ customerRef: params.customerRef,
523
+ purpose: "credit_topup",
524
+ amount: params.amount,
525
+ currency: params.currency,
526
+ description: params.description
527
+ })
528
+ });
529
+ if (!res.ok) {
530
+ const error = await res.text();
531
+ log(`\u274C API Error: ${res.status} - ${error}`);
532
+ throw new SolvaPayError2(`Create topup payment intent failed (${res.status}): ${error}`);
533
+ }
534
+ return await res.json();
535
+ },
536
+ // POST: /v1/sdk/payment-intents/{paymentIntentId}/process
537
+ async processPaymentIntent(params) {
538
+ const url = `${base}/v1/sdk/payment-intents/${params.paymentIntentId}/process`;
539
+ const res = await fetch(url, {
540
+ method: "POST",
541
+ headers: {
542
+ ...headers,
543
+ "Content-Type": "application/json"
544
+ },
545
+ body: JSON.stringify({
546
+ productRef: params.productRef,
547
+ customerRef: params.customerRef,
548
+ planRef: params.planRef
549
+ })
550
+ });
551
+ if (!res.ok) {
552
+ const error = await res.text();
553
+ log(`\u274C API Error: ${res.status} - ${error}`);
554
+ throw new SolvaPayError2(`Process payment failed (${res.status}): ${error}`);
555
+ }
556
+ const result = await res.json();
557
+ return result;
558
+ },
559
+ // POST: /v1/sdk/purchases/{purchaseRef}/cancel
560
+ async cancelPurchase(params) {
561
+ const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/cancel`;
562
+ const requestOptions = {
563
+ method: "POST",
564
+ headers
565
+ };
566
+ if (params.reason) {
567
+ requestOptions.body = JSON.stringify({ reason: params.reason });
568
+ }
569
+ const res = await fetch(url, requestOptions);
570
+ if (!res.ok) {
571
+ const error = await res.text();
572
+ log(`\u274C API Error: ${res.status} - ${error}`);
573
+ if (res.status === 404) {
574
+ throw new SolvaPayError2(`Purchase not found: ${error}`);
575
+ }
576
+ if (res.status === 400) {
577
+ throw new SolvaPayError2(
578
+ `Purchase cannot be cancelled or does not belong to provider: ${error}`
579
+ );
580
+ }
581
+ throw new SolvaPayError2(`Cancel purchase failed (${res.status}): ${error}`);
582
+ }
583
+ const responseText = await res.text();
584
+ let responseData;
585
+ try {
586
+ responseData = JSON.parse(responseText);
587
+ } catch (parseError) {
588
+ log(`\u274C Failed to parse response as JSON: ${parseError}`);
589
+ throw new SolvaPayError2(
590
+ `Invalid JSON response from cancel purchase endpoint: ${responseText.substring(0, 200)}`
591
+ );
592
+ }
593
+ if (!responseData || typeof responseData !== "object") {
594
+ log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
595
+ throw new SolvaPayError2(`Invalid response structure from cancel purchase endpoint`);
596
+ }
597
+ let result;
598
+ if (responseData.purchase && typeof responseData.purchase === "object") {
599
+ result = responseData.purchase;
600
+ } else if (responseData.reference) {
601
+ result = responseData;
602
+ } else {
603
+ result = responseData.purchase || responseData;
604
+ }
605
+ if (!result || typeof result !== "object") {
606
+ log(`\u274C Invalid purchase data in response. Full response:`, responseData);
607
+ throw new SolvaPayError2(`Invalid purchase data in cancel purchase response`);
608
+ }
609
+ return result;
610
+ },
611
+ // POST: /v1/sdk/purchases/{purchaseRef}/reactivate
612
+ async reactivatePurchase(params) {
613
+ const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/reactivate`;
614
+ const res = await fetch(url, {
615
+ method: "POST",
616
+ headers
617
+ });
618
+ if (!res.ok) {
619
+ const error = await res.text();
620
+ log(`\u274C API Error: ${res.status} - ${error}`);
621
+ if (res.status === 404) {
622
+ throw new SolvaPayError2(`Purchase not found: ${error}`);
623
+ }
624
+ if (res.status === 400) {
625
+ throw new SolvaPayError2(
626
+ `Purchase cannot be reactivated: ${error}`
627
+ );
628
+ }
629
+ throw new SolvaPayError2(`Reactivate purchase failed (${res.status}): ${error}`);
630
+ }
631
+ const responseText = await res.text();
632
+ let responseData;
633
+ try {
634
+ responseData = JSON.parse(responseText);
635
+ } catch (parseError) {
636
+ log(`\u274C Failed to parse response as JSON: ${parseError}`);
637
+ throw new SolvaPayError2(
638
+ `Invalid JSON response from reactivate purchase endpoint: ${responseText.substring(0, 200)}`
639
+ );
640
+ }
641
+ if (!responseData || typeof responseData !== "object") {
642
+ log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
643
+ throw new SolvaPayError2(`Invalid response structure from reactivate purchase endpoint`);
644
+ }
645
+ let result;
646
+ if (responseData.purchase && typeof responseData.purchase === "object") {
647
+ result = responseData.purchase;
648
+ } else if (responseData.reference) {
649
+ result = responseData;
650
+ } else {
651
+ result = responseData.purchase || responseData;
652
+ }
653
+ if (!result || typeof result !== "object") {
654
+ log(`\u274C Invalid purchase data in response. Full response:`, responseData);
655
+ throw new SolvaPayError2(`Invalid purchase data in reactivate purchase response`);
656
+ }
657
+ return result;
658
+ },
659
+ // POST: /v1/sdk/user-info
660
+ async getUserInfo(params) {
661
+ const url = `${base}/v1/sdk/user-info`;
662
+ const res = await fetch(url, {
663
+ method: "POST",
664
+ headers,
665
+ body: JSON.stringify(params)
666
+ });
667
+ if (!res.ok) {
668
+ const error = await res.text();
669
+ log(`\u274C API Error: ${res.status} - ${error}`);
670
+ throw new SolvaPayError2(`Get user info failed (${res.status}): ${error}`);
671
+ }
672
+ return await res.json();
673
+ },
674
+ // GET: /v1/sdk/customers/:customerRef/balance
675
+ async getCustomerBalance(params) {
676
+ const url = `${base}/v1/sdk/customers/${params.customerRef}/balance`;
677
+ const res = await fetch(url, {
678
+ method: "GET",
679
+ headers
680
+ });
681
+ if (!res.ok) {
682
+ const error = await res.text();
683
+ log(`\u274C API Error: ${res.status} - ${error}`);
684
+ throw new SolvaPayError2(`Get customer balance failed (${res.status}): ${error}`);
685
+ }
686
+ return await res.json();
687
+ },
688
+ // POST: /v1/sdk/checkout-sessions
689
+ async createCheckoutSession(params) {
690
+ const url = `${base}/v1/sdk/checkout-sessions`;
691
+ const res = await fetch(url, {
692
+ method: "POST",
693
+ headers,
694
+ body: JSON.stringify(params)
695
+ });
696
+ if (!res.ok) {
697
+ const error = await res.text();
698
+ log(`\u274C API Error: ${res.status} - ${error}`);
699
+ throw new SolvaPayError2(`Create checkout session failed (${res.status}): ${error}`);
700
+ }
701
+ const result = await res.json();
702
+ return result;
703
+ },
704
+ // POST: /v1/sdk/customers/customer-sessions
705
+ async createCustomerSession(params) {
706
+ const url = `${base}/v1/sdk/customers/customer-sessions`;
707
+ const res = await fetch(url, {
708
+ method: "POST",
709
+ headers,
710
+ body: JSON.stringify(params)
711
+ });
712
+ if (!res.ok) {
713
+ const error = await res.text();
714
+ log(`\u274C API Error: ${res.status} - ${error}`);
715
+ throw new SolvaPayError2(`Create customer session failed (${res.status}): ${error}`);
716
+ }
717
+ const result = await res.json();
718
+ return result;
719
+ },
720
+ // POST: /v1/sdk/activate
721
+ async activatePlan(params) {
722
+ const url = `${base}/v1/sdk/activate`;
723
+ const res = await fetch(url, {
724
+ method: "POST",
725
+ headers,
726
+ body: JSON.stringify(params)
727
+ });
728
+ if (!res.ok) {
729
+ const error = await res.text();
730
+ log(`\u274C API Error: ${res.status} - ${error}`);
731
+ throw new SolvaPayError2(`Activate plan failed (${res.status}): ${error}`);
732
+ }
733
+ return await res.json();
734
+ },
735
+ async getPaymentMethod(params) {
736
+ const url = new URL(`${base}/v1/sdk/payment-method`);
737
+ url.searchParams.set("customerRef", params.customerRef);
738
+ const res = await fetch(url.toString(), { method: "GET", headers });
739
+ if (!res.ok) {
740
+ const error = await res.text();
741
+ log(`\u274C API Error: ${res.status} - ${error}`);
742
+ throw new SolvaPayError2(`Get payment method failed (${res.status}): ${error}`);
743
+ }
744
+ return await res.json();
745
+ }
746
+ };
747
+ }
748
+
749
+ // src/paywall-state.ts
750
+ function classifyPaywallState(limits) {
751
+ if (!limits) return { kind: "upgrade_required" };
752
+ if (limits.activationRequired === true) {
753
+ return { kind: "activation_required" };
754
+ }
755
+ const activePlan = limits.plans?.find((p) => p.reference === limits.plan);
756
+ const isUsageBased = activePlan?.type === "usage-based" || limits.balance !== void 0;
757
+ const creditBalance = limits.balance?.creditBalance ?? limits.creditBalance;
758
+ if (isUsageBased) {
759
+ if (creditBalance === 0) return { kind: "topup_required" };
760
+ if (creditBalance === void 0 && limits.remaining === 0) {
761
+ return { kind: "topup_required" };
762
+ }
763
+ }
764
+ return { kind: "upgrade_required" };
765
+ }
766
+ function buildGateMessage(state, gate) {
767
+ const url = gate.checkoutUrl && gate.checkoutUrl.length > 0 ? gate.checkoutUrl : null;
768
+ const openClause = url ? `, or open ${url} in a browser` : "";
769
+ switch (state.kind) {
770
+ case "activation_required":
771
+ return `Your plan needs activation before you can use this tool. Call the \`activate_plan\` tool to activate it${openClause}.`;
772
+ case "topup_required":
773
+ return `You're out of credits. Call the \`topup\` tool to add more${openClause}.`;
774
+ case "upgrade_required":
775
+ return `You don't have an active plan for this tool. Call the \`upgrade\` tool to pick a plan${openClause}.`;
776
+ case "reactivation_required":
777
+ return `Your previous plan is no longer active. Call the \`manage_account\` tool to reactivate it, or the \`upgrade\` tool to pick a new plan.`;
778
+ }
779
+ }
780
+
781
+ // src/utils.ts
782
+ async function withRetry(fn, options = {}) {
783
+ const {
784
+ maxRetries = 2,
785
+ initialDelay = 500,
786
+ backoffStrategy = "fixed",
787
+ shouldRetry,
788
+ onRetry
789
+ } = options;
790
+ let lastError;
791
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
792
+ try {
793
+ return await fn();
794
+ } catch (error) {
795
+ lastError = error instanceof Error ? error : new Error(String(error));
796
+ const isLastAttempt = attempt === maxRetries;
797
+ if (isLastAttempt) {
798
+ throw lastError;
799
+ }
800
+ if (shouldRetry && !shouldRetry(lastError, attempt)) {
801
+ throw lastError;
802
+ }
803
+ const delay = calculateDelay(initialDelay, attempt, backoffStrategy);
804
+ if (onRetry) {
805
+ onRetry(lastError, attempt);
806
+ }
807
+ await sleep(delay);
808
+ }
809
+ }
810
+ throw lastError;
811
+ }
812
+ function calculateDelay(initialDelay, attempt, strategy) {
813
+ switch (strategy) {
814
+ case "fixed":
815
+ return initialDelay;
816
+ case "linear":
817
+ return initialDelay * (attempt + 1);
818
+ case "exponential":
819
+ return initialDelay * Math.pow(2, attempt);
820
+ default:
821
+ return initialDelay;
822
+ }
823
+ }
824
+ function sleep(ms) {
825
+ return new Promise((resolve) => setTimeout(resolve, ms));
826
+ }
827
+ function createRequestDeduplicator(options = {}) {
828
+ const { cacheTTL = 2e3, maxCacheSize = 1e3, cacheErrors = true } = options;
829
+ const inFlightRequests = /* @__PURE__ */ new Map();
830
+ const resultCache = /* @__PURE__ */ new Map();
831
+ let _cleanupInterval = null;
832
+ if (cacheTTL > 0) {
833
+ _cleanupInterval = setInterval(
834
+ () => {
835
+ const now = Date.now();
836
+ const entriesToDelete = [];
837
+ for (const [key, cached] of resultCache.entries()) {
838
+ if (now - cached.timestamp >= cacheTTL) {
839
+ entriesToDelete.push(key);
840
+ }
841
+ }
842
+ for (const key of entriesToDelete) {
843
+ resultCache.delete(key);
844
+ }
845
+ if (resultCache.size > maxCacheSize) {
846
+ const sortedEntries = Array.from(resultCache.entries()).sort(
847
+ (a, b) => a[1].timestamp - b[1].timestamp
848
+ );
849
+ const toRemove = sortedEntries.slice(0, resultCache.size - maxCacheSize);
850
+ for (const [key] of toRemove) {
851
+ resultCache.delete(key);
852
+ }
853
+ }
854
+ },
855
+ Math.min(cacheTTL, 1e3)
856
+ );
857
+ }
858
+ const deduplicate = async (key, fn) => {
859
+ if (cacheTTL > 0) {
860
+ const cached = resultCache.get(key);
861
+ if (cached && Date.now() - cached.timestamp < cacheTTL) {
862
+ return cached.data;
863
+ } else if (cached) {
864
+ resultCache.delete(key);
865
+ }
866
+ }
867
+ let requestPromise = inFlightRequests.get(key);
868
+ if (!requestPromise) {
869
+ requestPromise = (async () => {
870
+ try {
871
+ const result = await fn();
872
+ if (cacheTTL > 0) {
873
+ resultCache.set(key, {
874
+ data: result,
875
+ timestamp: Date.now()
876
+ });
877
+ }
878
+ return result;
879
+ } catch (error) {
880
+ if (cacheTTL > 0 && cacheErrors) {
881
+ resultCache.set(key, {
882
+ data: error,
883
+ timestamp: Date.now()
884
+ });
885
+ }
886
+ throw error;
887
+ } finally {
888
+ inFlightRequests.delete(key);
889
+ }
890
+ })();
891
+ const existingPromise = inFlightRequests.get(key);
892
+ if (existingPromise) {
893
+ requestPromise = existingPromise;
894
+ } else {
895
+ inFlightRequests.set(key, requestPromise);
896
+ }
897
+ }
898
+ return requestPromise;
899
+ };
900
+ const clearCache = (key) => {
901
+ resultCache.delete(key);
902
+ };
903
+ const clearAllCache = () => {
904
+ resultCache.clear();
905
+ };
906
+ const getStats = () => ({
907
+ inFlight: inFlightRequests.size,
908
+ cached: resultCache.size
909
+ });
910
+ return {
911
+ deduplicate,
912
+ clearCache,
913
+ clearAllCache,
914
+ getStats
915
+ };
916
+ }
917
+
918
+ // src/paywall.ts
919
+ var PaywallError = class extends Error {
920
+ /**
921
+ * Creates a new PaywallError instance.
922
+ *
923
+ * @param message - Error message
924
+ * @param structuredContent - Structured content with checkout URLs and metadata
925
+ */
926
+ constructor(message, structuredContent) {
927
+ super(message);
928
+ this.structuredContent = structuredContent;
929
+ this.name = "PaywallError";
930
+ }
931
+ };
932
+ function paywallErrorToClientPayload(error) {
933
+ const sc = error.structuredContent;
934
+ const base = {
935
+ success: false,
936
+ error: sc.kind === "activation_required" ? "Activation required" : "Payment required",
937
+ product: sc.product,
938
+ checkoutUrl: sc.checkoutUrl,
939
+ message: sc.message
940
+ };
941
+ if (sc.kind === "activation_required") {
942
+ base.kind = "activation_required";
943
+ if (sc.plans !== void 0) base.plans = sc.plans;
944
+ if (sc.balance !== void 0) base.balance = sc.balance;
945
+ if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
946
+ if (sc.confirmationUrl !== void 0) base.confirmationUrl = sc.confirmationUrl;
947
+ } else {
948
+ base.kind = "payment_required";
949
+ if (sc.balance !== void 0) base.balance = sc.balance;
950
+ if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
951
+ }
952
+ return base;
953
+ }
954
+ var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
955
+ cacheTTL: 6e4,
956
+ // Cache results for 60 seconds (reduces API calls significantly)
957
+ maxCacheSize: 1e3,
958
+ // Maximum cache entries
959
+ cacheErrors: false
960
+ // Don't cache errors - retry on next request
961
+ });
962
+ var EXTRA_FORWARD_KEY = "__solvapayExtra";
963
+ var SolvaPayPaywall = class {
964
+ constructor(apiClient, options = {}) {
965
+ this.apiClient = apiClient;
966
+ this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG === "true";
967
+ this.limitsCacheTTL = options.limitsCacheTTL ?? 1e4;
968
+ }
969
+ customerCreationAttempts = /* @__PURE__ */ new Set();
970
+ customerRefMapping = /* @__PURE__ */ new Map();
971
+ debug;
972
+ limitsCache = /* @__PURE__ */ new Map();
973
+ limitsCacheTTL;
974
+ log(...args) {
975
+ if (this.debug) {
976
+ console.log(...args);
977
+ }
978
+ }
979
+ resolveProduct(metadata) {
980
+ return metadata.product || process.env.SOLVAPAY_PRODUCT || "default-product";
981
+ }
982
+ generateRequestId() {
983
+ const timestamp = Date.now();
984
+ const random = Math.random().toString(36).substring(2, 11);
985
+ return `solvapay_${timestamp}_${random}`;
986
+ }
987
+ /**
988
+ * Pure decision routine — performs customer resolution, limits cache
989
+ * lookup / fresh `checkLimits` fetch, and returns a `PaywallDecision`
990
+ * describing whether the handler should run.
991
+ *
992
+ * Side effects kept in lockstep with the legacy `protect()` path:
993
+ * - creates the backend customer on first use (`ensureCustomer`),
994
+ * - updates the limits cache (consume-one-unit bookkeeping), and
995
+ * - emits a `paywall` usage event on gate outcomes.
996
+ *
997
+ * `trackUsage` for the `success` / `fail` outcome is emitted by the
998
+ * caller (adapter or `protect()`) once it has actually invoked the
999
+ * handler — `decide()` never counts handler execution as usage.
1000
+ *
1001
+ * @since 1.1.0
1002
+ */
1003
+ async decide(args, metadata = {}, getCustomerRef) {
1004
+ const product = this.resolveProduct(metadata);
1005
+ const usageType = metadata.usageType || "requests";
1006
+ const requestId = this.generateRequestId();
1007
+ const startTime = Date.now();
1008
+ const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
1009
+ let backendCustomerRef;
1010
+ if (inputCustomerRef.startsWith("cus_")) {
1011
+ backendCustomerRef = inputCustomerRef;
1012
+ } else {
1013
+ backendCustomerRef = await this.ensureCustomer(inputCustomerRef, inputCustomerRef);
1014
+ }
1015
+ const limitsCacheKey = `${backendCustomerRef}:${product}:${usageType}`;
1016
+ const cachedLimits = this.limitsCache.get(limitsCacheKey);
1017
+ const now = Date.now();
1018
+ let withinLimits;
1019
+ let remaining;
1020
+ let checkoutUrl;
1021
+ let resolvedMeterName;
1022
+ let lastLimitsCheck;
1023
+ const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
1024
+ if (hasFreshCachedLimits) {
1025
+ checkoutUrl = cachedLimits.checkoutUrl;
1026
+ resolvedMeterName = cachedLimits.meterName;
1027
+ lastLimitsCheck = cachedLimits.limits;
1028
+ if (cachedLimits.remaining > 0) {
1029
+ cachedLimits.remaining--;
1030
+ if (cachedLimits.remaining <= 0) {
1031
+ this.limitsCache.delete(limitsCacheKey);
1032
+ }
1033
+ withinLimits = true;
1034
+ remaining = cachedLimits.remaining;
1035
+ } else {
1036
+ withinLimits = false;
1037
+ remaining = 0;
1038
+ this.limitsCache.delete(limitsCacheKey);
1039
+ }
1040
+ } else {
1041
+ if (cachedLimits) {
1042
+ this.limitsCache.delete(limitsCacheKey);
1043
+ }
1044
+ const limitsCheck = await this.apiClient.checkLimits({
1045
+ customerRef: backendCustomerRef,
1046
+ productRef: product,
1047
+ meterName: usageType
1048
+ });
1049
+ lastLimitsCheck = limitsCheck;
1050
+ withinLimits = limitsCheck.withinLimits;
1051
+ remaining = limitsCheck.remaining;
1052
+ checkoutUrl = limitsCheck.checkoutUrl;
1053
+ resolvedMeterName = limitsCheck.meterName;
1054
+ const consumedAllowance = withinLimits && remaining > 0;
1055
+ if (consumedAllowance) {
1056
+ remaining = Math.max(0, remaining - 1);
1057
+ }
1058
+ if (consumedAllowance) {
1059
+ this.limitsCache.set(limitsCacheKey, {
1060
+ remaining,
1061
+ checkoutUrl,
1062
+ meterName: resolvedMeterName,
1063
+ timestamp: now,
1064
+ limits: limitsCheck
1065
+ });
1066
+ }
1067
+ }
1068
+ if (!withinLimits) {
1069
+ const latencyMs = Date.now() - startTime;
1070
+ this.trackUsage(
1071
+ backendCustomerRef,
1072
+ product,
1073
+ resolvedMeterName || usageType,
1074
+ "paywall",
1075
+ requestId,
1076
+ latencyMs
1077
+ );
1078
+ const state = classifyPaywallState(lastLimitsCheck ?? null);
1079
+ const preMessageGate = lastLimitsCheck?.activationRequired ? {
1080
+ kind: "activation_required",
1081
+ product,
1082
+ message: "",
1083
+ checkoutUrl: lastLimitsCheck.confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "",
1084
+ ...lastLimitsCheck.confirmationUrl !== void 0 ? { confirmationUrl: lastLimitsCheck.confirmationUrl } : {},
1085
+ ...lastLimitsCheck.plans !== void 0 ? { plans: lastLimitsCheck.plans } : {},
1086
+ ...lastLimitsCheck.balance !== void 0 ? { balance: lastLimitsCheck.balance } : {},
1087
+ ...lastLimitsCheck.product !== void 0 ? { productDetails: lastLimitsCheck.product } : {}
1088
+ } : {
1089
+ kind: "payment_required",
1090
+ product,
1091
+ checkoutUrl: checkoutUrl || "",
1092
+ message: "",
1093
+ ...lastLimitsCheck?.balance !== void 0 ? { balance: lastLimitsCheck.balance } : {},
1094
+ ...lastLimitsCheck?.product !== void 0 ? { productDetails: lastLimitsCheck.product } : {}
1095
+ };
1096
+ const gate = {
1097
+ ...preMessageGate,
1098
+ message: buildGateMessage(state, preMessageGate)
1099
+ };
1100
+ return {
1101
+ outcome: "gate",
1102
+ gate,
1103
+ limits: lastLimitsCheck ?? null,
1104
+ customerRef: backendCustomerRef
1105
+ };
1106
+ }
1107
+ return {
1108
+ outcome: "allow",
1109
+ args,
1110
+ limits: lastLimitsCheck,
1111
+ customerRef: backendCustomerRef
1112
+ };
1113
+ }
1114
+ /**
1115
+ * Execute the handler for an already-obtained `allow` decision and
1116
+ * emit the post-handler `trackUsage('success' | 'fail', ...)` event.
1117
+ *
1118
+ * Exposed for adapter integration — the adapter layer drives the
1119
+ * paywall through `decide()` + `runAllow()` so `formatGate` can own
1120
+ * gate outcomes without routing through `PaywallError`. `protect()`
1121
+ * continues to offer the self-contained throw-based surface for
1122
+ * legacy consumers.
1123
+ *
1124
+ * `runAllow` intentionally does NOT re-throw `PaywallError` — if a
1125
+ * handler calls `ctx.gate(reason)` and throws from deep code, the
1126
+ * adapter catches that at the `formatGate` boundary instead.
1127
+ *
1128
+ * @since 1.1.0
1129
+ */
1130
+ async runAllow(decision, handler, metadata, args) {
1131
+ const product = this.resolveProduct(metadata);
1132
+ const usageType = metadata.usageType || "requests";
1133
+ const requestId = this.generateRequestId();
1134
+ const startTime = Date.now();
1135
+ const forwardedExtra = args[EXTRA_FORWARD_KEY];
1136
+ const handlerContext = {
1137
+ customerRef: decision.customerRef,
1138
+ limits: decision.limits,
1139
+ ...forwardedExtra !== void 0 ? { extra: forwardedExtra } : {}
1140
+ };
1141
+ try {
1142
+ const result = await handler(args, handlerContext);
1143
+ const latencyMs = Date.now() - startTime;
1144
+ this.trackUsage(
1145
+ decision.customerRef,
1146
+ product,
1147
+ decision.limits.meterName || usageType,
1148
+ "success",
1149
+ requestId,
1150
+ latencyMs
1151
+ );
1152
+ return result;
1153
+ } catch (error) {
1154
+ if (error instanceof Error) {
1155
+ const errorType = error instanceof PaywallError ? "PaywallError" : "API Error";
1156
+ this.log(`\u274C Error in paywall [${errorType}]: ${error.message}`);
1157
+ } else {
1158
+ this.log(`\u274C Error in paywall:`, error);
1159
+ }
1160
+ if (!(error instanceof PaywallError)) {
1161
+ const latencyMs = Date.now() - startTime;
1162
+ this.trackUsage(
1163
+ decision.customerRef,
1164
+ product,
1165
+ decision.limits.meterName || usageType,
1166
+ "fail",
1167
+ requestId,
1168
+ latencyMs
1169
+ );
1170
+ }
1171
+ throw error;
1172
+ }
1173
+ }
1174
+ /**
1175
+ * Core protection method - works for both MCP and HTTP
1176
+ *
1177
+ * The `handler` may optionally declare a second positional argument
1178
+ * of type `ProtectHandlerContext` to receive the resolved customer
1179
+ * ref, the pre-check `LimitResponseWithPlan`, and an opaque `extra`
1180
+ * bag threaded through from the adapter layer. One-arg handlers
1181
+ * ignore the second argument and continue to work unchanged.
1182
+ *
1183
+ * Implemented on top of `decide()`: pre-check runs through the same
1184
+ * decision routine, and gate outcomes are raised as a `PaywallError`
1185
+ * to preserve the legacy throw-based signal for consumers that
1186
+ * haven't migrated to adapter-level `formatGate` routing.
1187
+ */
1188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1189
+ async protect(handler, metadata = {}, getCustomerRef) {
1190
+ return async (args) => {
1191
+ const decision = await this.decide(args, metadata, getCustomerRef);
1192
+ if (decision.outcome === "gate") {
1193
+ const message = decision.gate.kind === "activation_required" ? "Activation required" : "Payment required";
1194
+ this.log(`\u274C Error in paywall [PaywallError]: ${message}`);
1195
+ throw new PaywallError(message, decision.gate);
1196
+ }
1197
+ return this.runAllow(decision, handler, metadata, args);
1198
+ };
1199
+ }
1200
+ /**
1201
+ * Ensures a customer exists in the backend, creating them if necessary.
1202
+ * This is a public helper for testing, pre-creating customers, and internal use.
1203
+ * Only attempts creation once per customer (idempotent).
1204
+ * Returns the backend customer reference to use in API calls.
1205
+ *
1206
+ * @param customerRef - The customer reference used as a cache key (e.g., Supabase user ID)
1207
+ * @param externalRef - Optional external reference for backend lookup (e.g., Supabase user ID)
1208
+ * If provided, will lookup existing customer by externalRef before creating new one.
1209
+ * The externalRef is stored on the SolvaPay backend for customer lookup.
1210
+ * @param options - Optional customer details (email, name) for customer creation
1211
+ */
1212
+ async ensureCustomer(customerRef, externalRef, options) {
1213
+ if (this.customerRefMapping.has(customerRef)) {
1214
+ return this.customerRefMapping.get(customerRef);
1215
+ }
1216
+ if (customerRef === "anonymous") {
1217
+ return customerRef;
1218
+ }
1219
+ if (customerRef.startsWith("cus_")) {
1220
+ return customerRef;
1221
+ }
1222
+ const cacheKey = externalRef || customerRef;
1223
+ if (this.customerRefMapping.has(customerRef)) {
1224
+ const cached = this.customerRefMapping.get(customerRef);
1225
+ return cached;
1226
+ }
1227
+ const backendRef = await sharedCustomerLookupDeduplicator.deduplicate(cacheKey, async () => {
1228
+ if (externalRef) {
1229
+ try {
1230
+ const existingCustomer = await this.apiClient.getCustomer({ externalRef });
1231
+ if (existingCustomer && existingCustomer.customerRef) {
1232
+ const ref = existingCustomer.customerRef;
1233
+ this.customerRefMapping.set(customerRef, ref);
1234
+ this.customerCreationAttempts.add(customerRef);
1235
+ if (externalRef !== customerRef) {
1236
+ this.customerCreationAttempts.add(externalRef);
1237
+ }
1238
+ return ref;
1239
+ }
1240
+ } catch (error) {
1241
+ const errorMessage = error instanceof Error ? error.message : String(error);
1242
+ if (!errorMessage.includes("404") && !errorMessage.includes("not found")) {
1243
+ this.log(`\u26A0\uFE0F Error looking up customer by externalRef: ${errorMessage}`);
1244
+ }
1245
+ }
1246
+ }
1247
+ if (this.customerCreationAttempts.has(customerRef) || externalRef && this.customerCreationAttempts.has(externalRef)) {
1248
+ const mappedRef = this.customerRefMapping.get(customerRef);
1249
+ return mappedRef || customerRef;
1250
+ }
1251
+ if (!this.apiClient.createCustomer) {
1252
+ console.warn(
1253
+ `\u26A0\uFE0F Cannot auto-create customer ${customerRef}: createCustomer method not available on API client`
1254
+ );
1255
+ return customerRef;
1256
+ }
1257
+ this.customerCreationAttempts.add(customerRef);
1258
+ try {
1259
+ const createParams = {
1260
+ email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`,
1261
+ metadata: {}
1262
+ };
1263
+ if (options?.name) {
1264
+ createParams.name = options.name;
1265
+ }
1266
+ if (externalRef) {
1267
+ createParams.externalRef = externalRef;
1268
+ }
1269
+ const result = await this.apiClient.createCustomer(createParams);
1270
+ const resultObj = result;
1271
+ const ref = resultObj.customerRef || resultObj.reference || customerRef;
1272
+ this.customerRefMapping.set(customerRef, ref);
1273
+ return ref;
1274
+ } catch (error) {
1275
+ const errorMessage = error instanceof Error ? error.message : String(error);
1276
+ if (errorMessage.includes("409") || errorMessage.includes("already exists")) {
1277
+ if (externalRef) {
1278
+ try {
1279
+ const searchResult = await this.apiClient.getCustomer({ externalRef });
1280
+ if (searchResult && searchResult.customerRef) {
1281
+ this.customerRefMapping.set(customerRef, searchResult.customerRef);
1282
+ return searchResult.customerRef;
1283
+ }
1284
+ } catch (lookupError) {
1285
+ this.log(
1286
+ `\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`,
1287
+ lookupError instanceof Error ? lookupError.message : lookupError
1288
+ );
1289
+ }
1290
+ }
1291
+ const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
1292
+ if (externalRef && isEmailConflict && options?.email) {
1293
+ try {
1294
+ const byEmail = await this.apiClient.getCustomer({ email: options.email });
1295
+ if (byEmail && byEmail.customerRef) {
1296
+ this.customerRefMapping.set(customerRef, byEmail.customerRef);
1297
+ this.log(
1298
+ `\u26A0\uFE0F Resolved customer ${customerRef} by email after conflict; using existing customer ${byEmail.customerRef}`
1299
+ );
1300
+ if (!byEmail.externalRef && this.apiClient.updateCustomer) {
1301
+ try {
1302
+ await this.apiClient.updateCustomer(byEmail.customerRef, { externalRef });
1303
+ } catch (backfillError) {
1304
+ this.log(
1305
+ `\u26A0\uFE0F Failed to backfill externalRef on ${byEmail.customerRef}:`,
1306
+ backfillError instanceof Error ? backfillError.message : backfillError
1307
+ );
1308
+ }
1309
+ }
1310
+ return byEmail.customerRef;
1311
+ }
1312
+ } catch (emailLookupError) {
1313
+ this.log(
1314
+ `\u26A0\uFE0F Email lookup failed after customer conflict for ${customerRef}:`,
1315
+ emailLookupError instanceof Error ? emailLookupError.message : emailLookupError
1316
+ );
1317
+ }
1318
+ try {
1319
+ const retryParams = {
1320
+ email: `${customerRef}-${Date.now()}@auto-created.local`,
1321
+ externalRef,
1322
+ metadata: {}
1323
+ };
1324
+ if (options?.name) {
1325
+ retryParams.name = options.name;
1326
+ }
1327
+ const retryResult = await this.apiClient.createCustomer(retryParams);
1328
+ const retryObj = retryResult;
1329
+ const retryRef = retryObj.customerRef || retryObj.reference || customerRef;
1330
+ this.customerRefMapping.set(customerRef, retryRef);
1331
+ this.log(
1332
+ `\u26A0\uFE0F Retried customer creation for ${customerRef} with generated email after email conflict`
1333
+ );
1334
+ return retryRef;
1335
+ } catch (retryError) {
1336
+ this.log(
1337
+ `\u26A0\uFE0F Retry create customer with generated email failed for ${customerRef}:`,
1338
+ retryError instanceof Error ? retryError.message : retryError
1339
+ );
1340
+ }
1341
+ }
1342
+ const unresolvedMessage = errorMessage || "Customer already exists but could not be resolved";
1343
+ throw new Error(
1344
+ `Failed to resolve existing customer for ${customerRef} after conflict: ${unresolvedMessage}. Ensure the existing customer is linked to this externalRef.`
1345
+ );
1346
+ }
1347
+ this.log(
1348
+ `\u274C Failed to auto-create customer ${customerRef}:`,
1349
+ error instanceof Error ? error.message : error
1350
+ );
1351
+ throw error;
1352
+ }
1353
+ });
1354
+ if (backendRef !== customerRef) {
1355
+ this.customerRefMapping.set(customerRef, backendRef);
1356
+ }
1357
+ return backendRef;
1358
+ }
1359
+ async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration) {
1360
+ await withRetry(
1361
+ () => this.apiClient.trackUsage({
1362
+ customerRef,
1363
+ actionType: "api_call",
1364
+ units: 1,
1365
+ outcome,
1366
+ productRef,
1367
+ duration: actionDuration,
1368
+ metadata: { action: action || "api_requests", requestId },
1369
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1370
+ }),
1371
+ {
1372
+ maxRetries: 2,
1373
+ initialDelay: 500,
1374
+ shouldRetry: (error) => error.message.includes("Customer not found"),
1375
+ onRetry: (_error, attempt) => {
1376
+ console.warn(`\u26A0\uFE0F Customer not found (attempt ${attempt + 1}/3), retrying in 500ms...`);
1377
+ }
1378
+ }
1379
+ ).catch((error) => {
1380
+ console.error("Usage tracking failed:", error);
1381
+ });
1382
+ }
1383
+ };
1384
+
1385
+ // src/adapters/base.ts
1386
+ var EXTRA_FORWARD_KEY2 = "__solvapayExtra";
1387
+ var AdapterUtils = class {
1388
+ /**
1389
+ * Ensure customer reference is properly formatted
1390
+ */
1391
+ static ensureCustomerRef(customerRef) {
1392
+ if (!customerRef || customerRef === "anonymous") {
1393
+ return "anonymous";
1394
+ }
1395
+ return customerRef;
1396
+ }
1397
+ /**
1398
+ * Extract customer ref from JWT token
1399
+ */
1400
+ static async extractFromJWT(token, options) {
1401
+ try {
1402
+ const { jwtVerify } = await import("jose");
1403
+ const jwtSecret = new TextEncoder().encode(
1404
+ options?.secret || process.env.OAUTH_JWKS_SECRET || "test-jwt-secret"
1405
+ );
1406
+ const { payload } = await jwtVerify(token, jwtSecret, {
1407
+ issuer: options?.issuer || process.env.OAUTH_ISSUER || "http://localhost:3000",
1408
+ audience: options?.audience || process.env.OAUTH_CLIENT_ID || "test-client-id"
1409
+ });
1410
+ return payload.sub || null;
1411
+ } catch {
1412
+ return null;
1413
+ }
1414
+ }
1415
+ };
1416
+ async function createAdapterHandler(adapter, paywall, metadata, businessLogic) {
1417
+ const backendRefCache = /* @__PURE__ */ new Map();
1418
+ return async (context, extra) => {
1419
+ let args;
1420
+ let customerRef;
1421
+ try {
1422
+ args = await adapter.extractArgs(context);
1423
+ customerRef = await adapter.getCustomerRef(context, extra);
1424
+ let backendRef = backendRefCache.get(customerRef);
1425
+ if (!backendRef) {
1426
+ backendRef = await paywall.ensureCustomer(customerRef, customerRef);
1427
+ backendRefCache.set(customerRef, backendRef);
1428
+ }
1429
+ args.auth = { customer_ref: backendRef };
1430
+ } catch (error) {
1431
+ return adapter.formatError(error, context);
1432
+ }
1433
+ const decideGetCustomerRef = (args2) => args2.auth?.customer_ref || "anonymous";
1434
+ try {
1435
+ const decision = await paywall.decide(args, metadata, decideGetCustomerRef);
1436
+ if (decision.outcome === "gate") {
1437
+ return adapter.formatGate(decision.gate, context);
1438
+ }
1439
+ if (extra !== void 0) {
1440
+ ;
1441
+ args[EXTRA_FORWARD_KEY2] = extra;
1442
+ }
1443
+ try {
1444
+ const result = await paywall.runAllow(decision, businessLogic, metadata, args);
1445
+ return adapter.formatResponse(result, context);
1446
+ } finally {
1447
+ if (EXTRA_FORWARD_KEY2 in args) {
1448
+ delete args[EXTRA_FORWARD_KEY2];
1449
+ }
1450
+ }
1451
+ } catch (error) {
1452
+ if (error instanceof PaywallError) {
1453
+ return adapter.formatGate(error.structuredContent, context);
1454
+ }
1455
+ return adapter.formatError(error, context);
1456
+ }
1457
+ };
1458
+ }
1459
+
1460
+ // src/adapters/http.ts
1461
+ var HttpAdapter = class {
1462
+ constructor(options = {}) {
1463
+ this.options = options;
1464
+ }
1465
+ extractArgs([req, _reply]) {
1466
+ if (this.options.extractArgs) {
1467
+ return this.options.extractArgs(req);
1468
+ }
1469
+ return {
1470
+ ...req.body || {},
1471
+ ...req.params || {},
1472
+ ...req.query || {}
1473
+ };
1474
+ }
1475
+ async getCustomerRef([req, _reply]) {
1476
+ if (this.options.getCustomerRef) {
1477
+ const ref = await this.options.getCustomerRef(req);
1478
+ return AdapterUtils.ensureCustomerRef(ref);
1479
+ }
1480
+ const headerRef = req.headers?.["x-customer-ref"];
1481
+ if (headerRef) {
1482
+ return AdapterUtils.ensureCustomerRef(headerRef);
1483
+ }
1484
+ const authHeader = req.headers?.["authorization"];
1485
+ if (authHeader && authHeader.startsWith("Bearer ")) {
1486
+ const token = authHeader.substring(7);
1487
+ const jwtSub = await AdapterUtils.extractFromJWT(token);
1488
+ if (jwtSub) {
1489
+ return AdapterUtils.ensureCustomerRef(jwtSub);
1490
+ }
1491
+ }
1492
+ return "anonymous";
1493
+ }
1494
+ formatResponse(result, [_req, reply]) {
1495
+ if (this.options.transformResponse) {
1496
+ return this.options.transformResponse(result, reply);
1497
+ }
1498
+ if (reply && reply.status && typeof reply.json === "function") {
1499
+ reply.json(result);
1500
+ return;
1501
+ }
1502
+ return result;
1503
+ }
1504
+ /**
1505
+ * Emit a 402 Payment Required response with the same JSON body shape
1506
+ * REST consumers have always received (`{success:false, error, product,
1507
+ * checkoutUrl, message, ...}`). The shape is reused via
1508
+ * `paywallErrorToClientPayload` so HTTP / Next / hosted-proxy clients
1509
+ * don't have to branch on an SDK version.
1510
+ */
1511
+ formatGate(gate, [_req, reply]) {
1512
+ const errorResponse2 = paywallErrorToClientPayload(new PaywallError(gate.message, gate));
1513
+ if (reply && reply.status && typeof reply.json === "function") {
1514
+ reply.status(402).json(errorResponse2);
1515
+ return;
1516
+ }
1517
+ if (reply && reply.code) {
1518
+ reply.code(402);
1519
+ }
1520
+ return errorResponse2;
1521
+ }
1522
+ formatError(error, [_req, reply]) {
1523
+ const errorResponse2 = {
1524
+ success: false,
1525
+ error: error instanceof Error ? error.message : "Internal server error"
1526
+ };
1527
+ if (reply && reply.status && typeof reply.json === "function") {
1528
+ reply.status(500).json(errorResponse2);
1529
+ return;
1530
+ }
1531
+ if (reply && reply.code) {
1532
+ reply.code(500);
1533
+ }
1534
+ return errorResponse2;
1535
+ }
1536
+ };
1537
+
1538
+ // src/adapters/next.ts
1539
+ var NextAdapter = class {
1540
+ constructor(options = {}) {
1541
+ this.options = options;
1542
+ }
1543
+ async extractArgs([request, context]) {
1544
+ if (this.options.extractArgs) {
1545
+ return await this.options.extractArgs(request, context);
1546
+ }
1547
+ const url = new URL(request.url);
1548
+ const query = Object.fromEntries(url.searchParams.entries());
1549
+ let body = {};
1550
+ try {
1551
+ if (request.method !== "GET" && request.headers.get("content-type")?.includes("application/json")) {
1552
+ body = await request.json();
1553
+ }
1554
+ } catch {
1555
+ }
1556
+ let routeParams = {};
1557
+ if (context?.params) {
1558
+ if (typeof context.params === "object" && "then" in context.params) {
1559
+ routeParams = await context.params;
1560
+ } else {
1561
+ routeParams = context.params;
1562
+ }
1563
+ }
1564
+ return {
1565
+ ...body,
1566
+ ...query,
1567
+ ...routeParams
1568
+ };
1569
+ }
1570
+ async getCustomerRef([request]) {
1571
+ if (this.options.getCustomerRef) {
1572
+ const ref = await this.options.getCustomerRef(request);
1573
+ return AdapterUtils.ensureCustomerRef(ref);
1574
+ }
1575
+ const authHeader = request.headers.get("authorization");
1576
+ if (authHeader && authHeader.startsWith("Bearer ")) {
1577
+ const token = authHeader.substring(7);
1578
+ const jwtSub = await AdapterUtils.extractFromJWT(token);
1579
+ if (jwtSub) {
1580
+ return AdapterUtils.ensureCustomerRef(jwtSub);
1581
+ }
1582
+ }
1583
+ const userId = request.headers.get("x-user-id");
1584
+ if (userId) {
1585
+ return AdapterUtils.ensureCustomerRef(userId);
1586
+ }
1587
+ const headerRef = request.headers.get("x-customer-ref");
1588
+ if (headerRef) {
1589
+ return AdapterUtils.ensureCustomerRef(headerRef);
1590
+ }
1591
+ return "demo_user";
1592
+ }
1593
+ formatResponse(result, _context) {
1594
+ const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
1595
+ return new Response(JSON.stringify(transformed), {
1596
+ status: 200,
1597
+ headers: { "Content-Type": "application/json" }
1598
+ });
1599
+ }
1600
+ /**
1601
+ * Emit a 402 Payment Required `Response` with the same JSON body
1602
+ * REST consumers have always received. Reuses
1603
+ * `paywallErrorToClientPayload` so HTTP / Next / hosted-proxy
1604
+ * clients don't have to branch on an SDK version.
1605
+ */
1606
+ formatGate(gate, _context) {
1607
+ return new Response(
1608
+ JSON.stringify(paywallErrorToClientPayload(new PaywallError(gate.message, gate))),
1609
+ {
1610
+ status: 402,
1611
+ headers: { "Content-Type": "application/json" }
1612
+ }
1613
+ );
1614
+ }
1615
+ formatError(error, _context) {
1616
+ return new Response(
1617
+ JSON.stringify({
1618
+ success: false,
1619
+ error: error instanceof Error ? error.message : "Internal server error"
1620
+ }),
1621
+ {
1622
+ status: 500,
1623
+ headers: { "Content-Type": "application/json" }
1624
+ }
1625
+ );
1626
+ }
1627
+ };
1628
+
1629
+ // src/adapters/mcp.ts
1630
+ var McpAdapter = class {
1631
+ constructor(options = {}) {
1632
+ this.options = options;
1633
+ }
1634
+ extractArgs(args) {
1635
+ return args;
1636
+ }
1637
+ async getCustomerRef(args, extra) {
1638
+ if (this.options.getCustomerRef) {
1639
+ const ref = await this.options.getCustomerRef(args, extra);
1640
+ return AdapterUtils.ensureCustomerRef(ref);
1641
+ }
1642
+ const customerRefFromExtra = typeof extra?.authInfo?.extra?.customer_ref === "string" ? String(extra.authInfo.extra.customer_ref) : void 0;
1643
+ const customerRefFromArgs = typeof args.auth === "object" && args.auth !== null && typeof args.auth.customer_ref === "string" ? String(args.auth.customer_ref) : void 0;
1644
+ const directCustomerRef = typeof args.customer_ref === "string" ? args.customer_ref : void 0;
1645
+ const customerRef = (customerRefFromExtra || customerRefFromArgs || directCustomerRef || "anonymous").trim();
1646
+ return AdapterUtils.ensureCustomerRef(customerRef);
1647
+ }
1648
+ formatResponse(result, _context) {
1649
+ const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
1650
+ const response = {
1651
+ content: [
1652
+ {
1653
+ type: "text",
1654
+ text: JSON.stringify(transformed, null, 2)
1655
+ }
1656
+ ]
1657
+ };
1658
+ if (transformed && typeof transformed === "object" && !Array.isArray(transformed)) {
1659
+ response.structuredContent = transformed;
1660
+ }
1661
+ return response;
1662
+ }
1663
+ /**
1664
+ * Emit a plain-narration paywall response — `content[0].text` carries
1665
+ * the gate's human message (LLM-actionable), `structuredContent`
1666
+ * carries the machine-readable gate payload, and `isError` stays
1667
+ * `false` per the MCP spec's own `isError` definition (paywall is
1668
+ * not a self-correctable tool execution error; it is a user-facing
1669
+ * control transfer to the UI).
1670
+ *
1671
+ * Hosts that read widget metadata from `tools/list` or tool-result
1672
+ * `_meta.ui` open the paywall iframe on top of this response;
1673
+ * `buildPayableHandler` stamps the `_meta.ui.resourceUri` envelope
1674
+ * before returning.
1675
+ */
1676
+ formatGate(gate, _context) {
1677
+ return {
1678
+ content: [{ type: "text", text: gate.message }],
1679
+ isError: false,
1680
+ structuredContent: gate
1681
+ };
1682
+ }
1683
+ formatError(error, _context) {
1684
+ return {
1685
+ content: [
1686
+ {
1687
+ type: "text",
1688
+ text: JSON.stringify(
1689
+ {
1690
+ success: false,
1691
+ error: error instanceof Error ? error.message : "Unknown error occurred"
1692
+ },
1693
+ null,
1694
+ 2
1695
+ )
1696
+ }
1697
+ ],
1698
+ isError: true
1699
+ };
1700
+ }
1701
+ };
1702
+
1703
+ // src/factory.ts
1704
+ import { SolvaPayError as SolvaPayError3, getSolvaPayConfig } from "@solvapay/core";
1705
+
1706
+ // src/virtual-tools.ts
1707
+ var TOOL_GET_USER_INFO = {
1708
+ name: "get_user_info",
1709
+ description: "Get information about the current user and their purchase status for this MCP server. Returns user profile (reference, name, email) and active purchase details including product name, type, dates, and usage limit if applicable.",
1710
+ inputSchema: {
1711
+ type: "object",
1712
+ properties: {},
1713
+ required: []
1714
+ }
1715
+ };
1716
+ var TOOL_UPGRADE = {
1717
+ name: "upgrade",
1718
+ description: "Get available pricing options and checkout URLs for upgrading. Returns a list of available pricing options with their details (price, features) and checkout URLs. Users can click on a checkout URL to purchase. If a specific planRef is provided, returns only the checkout URL for that pricing option.",
1719
+ inputSchema: {
1720
+ type: "object",
1721
+ properties: {
1722
+ planRef: {
1723
+ type: "string",
1724
+ description: 'Optional pricing reference (e.g., "pln_abc123") to get a checkout URL for a specific option. If not provided, returns all available pricing options with their checkout URLs.'
1725
+ }
1726
+ },
1727
+ required: []
1728
+ }
1729
+ };
1730
+ var TOOL_MANAGE_ACCOUNT = {
1731
+ name: "manage_account",
1732
+ description: "Get a URL to the customer portal where users can view and manage their account. The portal shows current account status, billing history, and allows subscription changes. Returns a secure, time-limited URL that the user can click to access their account management page.",
1733
+ inputSchema: {
1734
+ type: "object",
1735
+ properties: {},
1736
+ required: []
1737
+ }
1738
+ };
1739
+ function mcpTextResult(text) {
1740
+ return { content: [{ type: "text", text }] };
1741
+ }
1742
+ function mcpErrorResult(message) {
1743
+ return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
1744
+ }
1745
+ function createGetUserInfoHandler(apiClient, productRef, getCustomerRef) {
1746
+ return async (args, extra) => {
1747
+ const customerRef = getCustomerRef(args, extra);
1748
+ try {
1749
+ if (!apiClient.getUserInfo) {
1750
+ return mcpErrorResult("getUserInfo is not available on this API client");
1751
+ }
1752
+ const userInfo = await apiClient.getUserInfo({ customerRef, productRef });
1753
+ return mcpTextResult(JSON.stringify(userInfo, null, 2));
1754
+ } catch (error) {
1755
+ return mcpErrorResult(
1756
+ `Failed to retrieve user information: ${error instanceof Error ? error.message : "Unknown error"}`
1757
+ );
1758
+ }
1759
+ };
1760
+ }
1761
+ function createUpgradeHandler(apiClient, productRef, getCustomerRef) {
1762
+ return async (args, extra) => {
1763
+ const customerRef = getCustomerRef(args, extra);
1764
+ const planRef = typeof args.planRef === "string" ? args.planRef : void 0;
1765
+ try {
1766
+ const result = await apiClient.createCheckoutSession({
1767
+ customerRef,
1768
+ productRef,
1769
+ ...planRef && { planRef }
1770
+ });
1771
+ const checkoutUrl = result.checkoutUrl;
1772
+ if (planRef) {
1773
+ const responseText2 = `## Upgrade
1774
+
1775
+ **[Click here to upgrade \u2192](${checkoutUrl})**
1776
+
1777
+ After completing the checkout, your purchase will be activated immediately.`;
1778
+ return mcpTextResult(responseText2);
1779
+ }
1780
+ const responseText = `## Upgrade Your Subscription
1781
+
1782
+ **[Click here to view pricing options and upgrade \u2192](${checkoutUrl})**
1783
+
1784
+ You'll be able to compare options and select the one that's right for you.`;
1785
+ return mcpTextResult(responseText);
1786
+ } catch (error) {
1787
+ return mcpErrorResult(
1788
+ `Failed to create checkout session: ${error instanceof Error ? error.message : "Unknown error"}`
1789
+ );
1790
+ }
1791
+ };
1792
+ }
1793
+ function createManageAccountHandler(apiClient, productRef, getCustomerRef) {
1794
+ return async (args, extra) => {
1795
+ const customerRef = getCustomerRef(args, extra);
1796
+ try {
1797
+ const session = await apiClient.createCustomerSession({ customerRef, productRef });
1798
+ const portalUrl = session.customerUrl;
1799
+ const responseText = `## Manage Your Account
1800
+
1801
+ Access your account management portal to:
1802
+ - View your current account status
1803
+ - See billing history and invoices
1804
+ - Update payment methods
1805
+ - Cancel or modify your subscription
1806
+
1807
+ **[Open Account Portal \u2192](${portalUrl})**
1808
+
1809
+ This link is secure and will expire after a short period.`;
1810
+ return mcpTextResult(responseText);
1811
+ } catch (error) {
1812
+ return mcpErrorResult(
1813
+ `Failed to create customer portal session: ${error instanceof Error ? error.message : "Unknown error"}`
1814
+ );
1815
+ }
1816
+ };
1817
+ }
1818
+ function createVirtualTools(apiClient, options) {
1819
+ const { product, getCustomerRef, exclude = [] } = options;
1820
+ const excludeSet = new Set(exclude);
1821
+ const allTools = [
1822
+ {
1823
+ ...TOOL_GET_USER_INFO,
1824
+ handler: createGetUserInfoHandler(apiClient, product, getCustomerRef)
1825
+ },
1826
+ {
1827
+ ...TOOL_UPGRADE,
1828
+ handler: createUpgradeHandler(apiClient, product, getCustomerRef)
1829
+ },
1830
+ {
1831
+ ...TOOL_MANAGE_ACCOUNT,
1832
+ handler: createManageAccountHandler(apiClient, product, getCustomerRef)
1833
+ }
1834
+ ];
1835
+ return allTools.filter((t) => !excludeSet.has(t.name));
1836
+ }
1837
+
1838
+ // src/register-virtual-tools-mcp.ts
1839
+ import { createRequire } from "module";
1840
+ function getNodeRequire() {
1841
+ try {
1842
+ return (0, eval)("require");
1843
+ } catch {
1844
+ return createRequire(`${process.cwd()}/package.json`);
1845
+ }
1846
+ }
1847
+ var nodeRequire = getNodeRequire();
1848
+ function getZod() {
1849
+ try {
1850
+ const zodModule = nodeRequire("zod");
1851
+ return zodModule.z ?? zodModule;
1852
+ } catch {
1853
+ throw new Error(
1854
+ "zod is required to use registerVirtualToolsMcp(). Install it as a dependency in your MCP server project."
1855
+ );
1856
+ }
1857
+ }
1858
+ function isJsonSchemaObject(value) {
1859
+ return Boolean(value && typeof value === "object");
1860
+ }
1861
+ function toZodSchema(property) {
1862
+ const z = getZod();
1863
+ if (Array.isArray(property.enum) && property.enum.length > 0) {
1864
+ if (property.enum.every((value) => typeof value === "string")) {
1865
+ const [first, ...rest] = property.enum;
1866
+ return z.enum([first, ...rest]);
1867
+ }
1868
+ if (property.enum.length === 1) {
1869
+ return z.literal(property.enum[0]);
1870
+ }
1871
+ return z.union(property.enum.map((value) => z.literal(value)));
1872
+ }
1873
+ switch (property.type) {
1874
+ case "string":
1875
+ return z.string();
1876
+ case "number":
1877
+ return z.number();
1878
+ case "integer":
1879
+ return z.number().int();
1880
+ case "boolean":
1881
+ return z.boolean();
1882
+ case "array":
1883
+ return z.array(
1884
+ isJsonSchemaObject(property.items) ? toZodSchema(property.items) : z.any()
1885
+ );
1886
+ case "object":
1887
+ return z.object(jsonSchemaToZodRawShape(property.properties || {}));
1888
+ default:
1889
+ return z.any();
1890
+ }
1891
+ }
1892
+ function jsonSchemaToZodRawShape(properties, required = []) {
1893
+ const requiredSet = new Set(required);
1894
+ return Object.fromEntries(
1895
+ Object.entries(properties).map(([name, property]) => {
1896
+ const schema = toZodSchema(property);
1897
+ const finalSchema = requiredSet.has(name) ? schema : schema.optional();
1898
+ return [name, finalSchema];
1899
+ })
1900
+ );
1901
+ }
1902
+ function defaultGetCustomerRef(_args, extra) {
1903
+ const fromExtra = extra?.authInfo?.extra?.customer_ref;
1904
+ return typeof fromExtra === "string" && fromExtra.trim() ? fromExtra.trim() : "anonymous";
1905
+ }
1906
+ function registerVirtualToolsMcpImpl(server, apiClient, options) {
1907
+ const { filter, mapDefinition, wrapHandler, getCustomerRef, ...virtualToolsOptions } = options;
1908
+ const virtualTools = createVirtualTools(apiClient, {
1909
+ ...virtualToolsOptions,
1910
+ getCustomerRef: getCustomerRef || defaultGetCustomerRef
1911
+ });
1912
+ for (const toolDefinition of virtualTools) {
1913
+ if (filter && !filter(toolDefinition)) continue;
1914
+ const mappedDefinition = mapDefinition ? mapDefinition(toolDefinition) : toolDefinition;
1915
+ const wrappedHandler = wrapHandler ? wrapHandler(mappedDefinition.handler, mappedDefinition) : mappedDefinition.handler;
1916
+ server.registerTool(
1917
+ mappedDefinition.name,
1918
+ {
1919
+ description: mappedDefinition.description,
1920
+ inputSchema: jsonSchemaToZodRawShape(
1921
+ mappedDefinition.inputSchema.properties,
1922
+ mappedDefinition.inputSchema.required || []
1923
+ )
1924
+ },
1925
+ wrappedHandler
1926
+ );
1927
+ }
1928
+ }
1929
+
1930
+ // src/factory.ts
1931
+ function createSolvaPay(config) {
1932
+ let resolvedConfig;
1933
+ if (!config) {
1934
+ const envConfig = getSolvaPayConfig();
1935
+ resolvedConfig = {
1936
+ apiKey: envConfig.apiKey,
1937
+ apiBaseUrl: envConfig.apiBaseUrl
1938
+ };
1939
+ } else {
1940
+ resolvedConfig = config;
1941
+ }
1942
+ const apiClient = resolvedConfig.apiClient || createSolvaPayClient({
1943
+ apiKey: resolvedConfig.apiKey,
1944
+ apiBaseUrl: resolvedConfig.apiBaseUrl
1945
+ });
1946
+ const paywall = new SolvaPayPaywall(apiClient, {
1947
+ debug: process.env.SOLVAPAY_DEBUG !== "false",
1948
+ limitsCacheTTL: resolvedConfig.limitsCacheTTL
1949
+ });
1950
+ return {
1951
+ // Direct access to API client for advanced operations
1952
+ apiClient,
1953
+ // Common API methods exposed directly for convenience
1954
+ ensureCustomer(customerRef, externalRef, options) {
1955
+ return paywall.ensureCustomer(customerRef, externalRef, options);
1956
+ },
1957
+ createPaymentIntent(params) {
1958
+ if (!apiClient.createPaymentIntent) {
1959
+ throw new SolvaPayError3("createPaymentIntent is not available on this API client");
1960
+ }
1961
+ return apiClient.createPaymentIntent(params);
1962
+ },
1963
+ createTopupPaymentIntent(params) {
1964
+ if (!apiClient.createTopupPaymentIntent) {
1965
+ throw new SolvaPayError3("createTopupPaymentIntent is not available on this API client");
1966
+ }
1967
+ return apiClient.createTopupPaymentIntent(params);
1968
+ },
1969
+ processPaymentIntent(params) {
1970
+ if (!apiClient.processPaymentIntent) {
1971
+ throw new SolvaPayError3("processPaymentIntent is not available on this API client");
1972
+ }
1973
+ return apiClient.processPaymentIntent(params);
1974
+ },
1975
+ checkLimits(params) {
1976
+ return apiClient.checkLimits(params);
1977
+ },
1978
+ trackUsage(params) {
1979
+ return apiClient.trackUsage(params);
1980
+ },
1981
+ createCustomer(params) {
1982
+ if (!apiClient.createCustomer) {
1983
+ throw new SolvaPayError3("createCustomer is not available on this API client");
1984
+ }
1985
+ return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
1986
+ },
1987
+ getCustomer(params) {
1988
+ return apiClient.getCustomer(params);
1989
+ },
1990
+ getCustomerBalance(params) {
1991
+ if (!apiClient.getCustomerBalance) {
1992
+ throw new SolvaPayError3("getCustomerBalance is not available on this API client");
1993
+ }
1994
+ return apiClient.getCustomerBalance(params);
1995
+ },
1996
+ createCheckoutSession(params) {
1997
+ return apiClient.createCheckoutSession({
1998
+ customerRef: params.customerRef,
1999
+ productRef: params.productRef,
2000
+ planRef: params.planRef
2001
+ });
2002
+ },
2003
+ createCustomerSession(params) {
2004
+ return apiClient.createCustomerSession(params);
2005
+ },
2006
+ activatePlan(params) {
2007
+ if (!apiClient.activatePlan) {
2008
+ throw new SolvaPayError3("activatePlan is not available on this API client");
2009
+ }
2010
+ return apiClient.activatePlan(params);
2011
+ },
2012
+ bootstrapMcpProduct(params) {
2013
+ if (!apiClient.bootstrapMcpProduct) {
2014
+ throw new SolvaPayError3("bootstrapMcpProduct is not available on this API client");
2015
+ }
2016
+ return apiClient.bootstrapMcpProduct(params);
2017
+ },
2018
+ configureMcpPlans(productRef, params) {
2019
+ if (!apiClient.configureMcpPlans) {
2020
+ throw new SolvaPayError3("configureMcpPlans is not available on this API client");
2021
+ }
2022
+ return apiClient.configureMcpPlans(productRef, params);
2023
+ },
2024
+ getVirtualTools(options) {
2025
+ return createVirtualTools(apiClient, options);
2026
+ },
2027
+ async registerVirtualToolsMcp(server, options) {
2028
+ await registerVirtualToolsMcpImpl(server, apiClient, options);
2029
+ },
2030
+ // Payable API for framework-specific handlers
2031
+ payable(options = {}) {
2032
+ const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
2033
+ const usageType = options.usageType || "requests";
2034
+ const metadata = { product, usageType };
2035
+ return {
2036
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2037
+ http(businessLogic, adapterOptions) {
2038
+ const adapter = new HttpAdapter({
2039
+ ...adapterOptions,
2040
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
2041
+ });
2042
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
2043
+ return async (req, reply) => {
2044
+ const handler = await handlerPromise;
2045
+ return handler([req, reply]);
2046
+ };
2047
+ },
2048
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2049
+ next(businessLogic, adapterOptions) {
2050
+ const adapter = new NextAdapter({
2051
+ ...adapterOptions,
2052
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
2053
+ });
2054
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
2055
+ return async (request, context) => {
2056
+ const handler = await handlerPromise;
2057
+ return handler([request, context]);
2058
+ };
2059
+ },
2060
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2061
+ mcp(businessLogic, adapterOptions) {
2062
+ const adapter = new McpAdapter({
2063
+ ...adapterOptions,
2064
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
2065
+ });
2066
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
2067
+ return async (args, extra) => {
2068
+ const handler = await handlerPromise;
2069
+ return handler(args, extra);
2070
+ };
2071
+ },
2072
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2073
+ async function(businessLogic) {
2074
+ const getCustomerRef = (args) => {
2075
+ const configuredRef = options.getCustomerRef?.(args);
2076
+ if (typeof configuredRef === "string") {
2077
+ return configuredRef;
2078
+ }
2079
+ return args.auth?.customer_ref || "anonymous";
2080
+ };
2081
+ return paywall.protect(businessLogic, metadata, getCustomerRef);
2082
+ }
2083
+ };
2084
+ }
2085
+ };
2086
+ }
2087
+
2088
+ // src/helpers/customer.ts
2089
+ async function syncCustomerCore(request, options = {}) {
2090
+ try {
2091
+ const userResult = await getAuthenticatedUserCore(request, {
2092
+ includeEmail: options.includeEmail,
2093
+ includeName: options.includeName
2094
+ });
2095
+ if (isErrorResult(userResult)) {
2096
+ return userResult;
2097
+ }
2098
+ const { userId, email, name } = userResult;
2099
+ const solvaPay = options.solvaPay || createSolvaPay();
2100
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2101
+ email: email || void 0,
2102
+ name: name || void 0
2103
+ });
2104
+ return customerRef;
2105
+ } catch (error) {
2106
+ return handleRouteError(error, "Sync customer", "Failed to sync customer");
2107
+ }
2108
+ }
2109
+ async function getCustomerBalanceCore(request, options = {}) {
2110
+ try {
2111
+ const userResult = await getAuthenticatedUserCore(request);
2112
+ if (isErrorResult(userResult)) {
2113
+ return userResult;
2114
+ }
2115
+ const { userId, email, name } = userResult;
2116
+ const solvaPay = options.solvaPay || createSolvaPay();
2117
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2118
+ email: email || void 0,
2119
+ name: name || void 0
2120
+ });
2121
+ const result = await solvaPay.getCustomerBalance({ customerRef });
2122
+ return result;
2123
+ } catch (error) {
2124
+ return handleRouteError(error, "Get customer credits", "Failed to get customer credits");
2125
+ }
2126
+ }
2127
+
2128
+ // src/helpers/payment.ts
2129
+ async function createPaymentIntentCore(request, body, options = {}) {
2130
+ try {
2131
+ if (!body.planRef || !body.productRef) {
2132
+ return {
2133
+ error: "Missing required parameters: planRef and productRef are required",
2134
+ status: 400
2135
+ };
2136
+ }
2137
+ const customerResult = await syncCustomerCore(request, {
2138
+ solvaPay: options.solvaPay,
2139
+ includeEmail: options.includeEmail,
2140
+ includeName: options.includeName
2141
+ });
2142
+ if (isErrorResult(customerResult)) {
2143
+ return customerResult;
2144
+ }
2145
+ const customerRef = customerResult;
2146
+ const solvaPay = options.solvaPay || createSolvaPay();
2147
+ const paymentIntent = await solvaPay.createPaymentIntent({
2148
+ productRef: body.productRef,
2149
+ planRef: body.planRef,
2150
+ customerRef
2151
+ });
2152
+ return {
2153
+ processorPaymentId: paymentIntent.processorPaymentId,
2154
+ clientSecret: paymentIntent.clientSecret,
2155
+ publishableKey: paymentIntent.publishableKey,
2156
+ accountId: paymentIntent.accountId,
2157
+ customerRef
2158
+ };
2159
+ } catch (error) {
2160
+ return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
2161
+ }
2162
+ }
2163
+ async function createTopupPaymentIntentCore(request, body, options = {}) {
2164
+ try {
2165
+ if (!body.amount || body.amount <= 0) {
2166
+ return {
2167
+ error: "Missing or invalid amount: must be a positive number",
2168
+ status: 400
2169
+ };
2170
+ }
2171
+ if (!body.currency) {
2172
+ return {
2173
+ error: "Missing required parameter: currency",
2174
+ status: 400
2175
+ };
2176
+ }
2177
+ if (body.currency !== body.currency.toUpperCase()) {
2178
+ return {
2179
+ error: `Invalid currency "${body.currency}": must be an uppercase ISO 4217 code (e.g. "USD", "EUR")`,
2180
+ status: 400
2181
+ };
2182
+ }
2183
+ const customerResult = await syncCustomerCore(request, {
2184
+ solvaPay: options.solvaPay,
2185
+ includeEmail: options.includeEmail,
2186
+ includeName: options.includeName
2187
+ });
2188
+ if (isErrorResult(customerResult)) {
2189
+ return customerResult;
2190
+ }
2191
+ const customerRef = customerResult;
2192
+ const solvaPay = options.solvaPay || createSolvaPay();
2193
+ const paymentIntent = await solvaPay.createTopupPaymentIntent({
2194
+ customerRef,
2195
+ amount: body.amount,
2196
+ currency: body.currency,
2197
+ description: body.description
2198
+ });
2199
+ return {
2200
+ processorPaymentId: paymentIntent.processorPaymentId,
2201
+ clientSecret: paymentIntent.clientSecret,
2202
+ publishableKey: paymentIntent.publishableKey,
2203
+ accountId: paymentIntent.accountId,
2204
+ customerRef
2205
+ };
2206
+ } catch (error) {
2207
+ return handleRouteError(
2208
+ error,
2209
+ "Create topup payment intent",
2210
+ "Topup payment intent creation failed"
2211
+ );
2212
+ }
2213
+ }
2214
+ async function processPaymentIntentCore(request, body, options = {}) {
2215
+ try {
2216
+ if (!body.paymentIntentId || !body.productRef) {
2217
+ return {
2218
+ error: "paymentIntentId and productRef are required",
2219
+ status: 400
2220
+ };
2221
+ }
2222
+ const customerResult = await syncCustomerCore(request, {
2223
+ solvaPay: options.solvaPay
2224
+ });
2225
+ if (isErrorResult(customerResult)) {
2226
+ return customerResult;
2227
+ }
2228
+ const customerRef = customerResult;
2229
+ const solvaPay = options.solvaPay || createSolvaPay();
2230
+ const result = await solvaPay.processPaymentIntent({
2231
+ paymentIntentId: body.paymentIntentId,
2232
+ productRef: body.productRef,
2233
+ customerRef,
2234
+ planRef: body.planRef
2235
+ });
2236
+ return result;
2237
+ } catch (error) {
2238
+ return handleRouteError(error, "Process payment intent", "Payment processing failed");
2239
+ }
2240
+ }
2241
+
2242
+ // src/helpers/checkout.ts
2243
+ async function createCheckoutSessionCore(request, body, options = {}) {
2244
+ try {
2245
+ if (!body.productRef) {
2246
+ return {
2247
+ error: "Missing required parameter: productRef is required",
2248
+ status: 400
2249
+ };
2250
+ }
2251
+ const customerResult = await syncCustomerCore(request, {
2252
+ solvaPay: options.solvaPay,
2253
+ includeEmail: options.includeEmail,
2254
+ includeName: options.includeName
2255
+ });
2256
+ if (isErrorResult(customerResult)) {
2257
+ return customerResult;
2258
+ }
2259
+ const customerRef = customerResult;
2260
+ let returnUrl = body.returnUrl || options.returnUrl;
2261
+ if (!returnUrl) {
2262
+ try {
2263
+ const url = new URL(request.url);
2264
+ returnUrl = url.origin;
2265
+ } catch {
2266
+ }
2267
+ }
2268
+ const solvaPay = options.solvaPay || createSolvaPay();
2269
+ const session = await solvaPay.createCheckoutSession({
2270
+ productRef: body.productRef,
2271
+ customerRef,
2272
+ planRef: body.planRef || void 0,
2273
+ returnUrl
2274
+ });
2275
+ return {
2276
+ sessionId: session.sessionId,
2277
+ checkoutUrl: session.checkoutUrl
2278
+ };
2279
+ } catch (error) {
2280
+ return handleRouteError(error, "Create checkout session", "Checkout session creation failed");
2281
+ }
2282
+ }
2283
+ async function createCustomerSessionCore(request, options = {}) {
2284
+ try {
2285
+ const customerResult = await syncCustomerCore(request, {
2286
+ solvaPay: options.solvaPay,
2287
+ includeEmail: options.includeEmail,
2288
+ includeName: options.includeName
2289
+ });
2290
+ if (isErrorResult(customerResult)) {
2291
+ return customerResult;
2292
+ }
2293
+ const customerRef = customerResult;
2294
+ const solvaPay = options.solvaPay || createSolvaPay();
2295
+ const session = await solvaPay.createCustomerSession({
2296
+ customerRef
2297
+ });
2298
+ return session;
2299
+ } catch (error) {
2300
+ return handleRouteError(error, "Create customer session", "Customer session creation failed");
2301
+ }
2302
+ }
2303
+
2304
+ // src/helpers/renewal.ts
2305
+ import { SolvaPayError as SolvaPayError4 } from "@solvapay/core";
2306
+ async function cancelPurchaseCore(request, body, options = {}) {
2307
+ try {
2308
+ if (!body.purchaseRef) {
2309
+ return {
2310
+ error: "Missing required parameter: purchaseRef is required",
2311
+ status: 400
2312
+ };
2313
+ }
2314
+ const solvaPay = options.solvaPay || createSolvaPay();
2315
+ if (!solvaPay.apiClient.cancelPurchase) {
2316
+ return {
2317
+ error: "Cancel purchase method not available on SDK client",
2318
+ status: 500
2319
+ };
2320
+ }
2321
+ let cancelledPurchase = await solvaPay.apiClient.cancelPurchase({
2322
+ purchaseRef: body.purchaseRef,
2323
+ reason: body.reason
2324
+ });
2325
+ if (!cancelledPurchase || typeof cancelledPurchase !== "object") {
2326
+ return {
2327
+ error: "Invalid response from cancel purchase endpoint",
2328
+ status: 500
2329
+ };
2330
+ }
2331
+ const responseObj = cancelledPurchase;
2332
+ if (responseObj.purchase && typeof responseObj.purchase === "object") {
2333
+ cancelledPurchase = responseObj.purchase;
2334
+ }
2335
+ if (!cancelledPurchase.reference) {
2336
+ return {
2337
+ error: "Cancel purchase response missing required fields",
2338
+ status: 500
2339
+ };
2340
+ }
2341
+ const isCancelled = cancelledPurchase.status === "cancelled" || cancelledPurchase.cancelledAt;
2342
+ if (!isCancelled) {
2343
+ return {
2344
+ error: `Purchase cancellation failed: backend returned status '${cancelledPurchase.status}' without cancelledAt timestamp`,
2345
+ status: 500
2346
+ };
2347
+ }
2348
+ await new Promise((resolve) => setTimeout(resolve, 500));
2349
+ return cancelledPurchase;
2350
+ } catch (error) {
2351
+ if (error instanceof SolvaPayError4) {
2352
+ const errorMessage = error.message;
2353
+ if (errorMessage.includes("not found")) {
2354
+ return {
2355
+ error: "Purchase not found",
2356
+ status: 404,
2357
+ details: errorMessage
2358
+ };
2359
+ }
2360
+ if (errorMessage.includes("cannot be cancelled") || errorMessage.includes("does not belong to provider")) {
2361
+ return {
2362
+ error: "Purchase cannot be cancelled or does not belong to provider",
2363
+ status: 400,
2364
+ details: errorMessage
2365
+ };
2366
+ }
2367
+ return {
2368
+ error: errorMessage,
2369
+ status: 500,
2370
+ details: errorMessage
2371
+ };
2372
+ }
2373
+ return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
2374
+ }
2375
+ }
2376
+ async function reactivatePurchaseCore(request, body, options = {}) {
2377
+ try {
2378
+ if (!body.purchaseRef) {
2379
+ return {
2380
+ error: "Missing required parameter: purchaseRef is required",
2381
+ status: 400
2382
+ };
2383
+ }
2384
+ const solvaPay = options.solvaPay || createSolvaPay();
2385
+ if (!solvaPay.apiClient.reactivatePurchase) {
2386
+ return {
2387
+ error: "Reactivate purchase method not available on SDK client",
2388
+ status: 500
2389
+ };
2390
+ }
2391
+ let reactivatedPurchase = await solvaPay.apiClient.reactivatePurchase({
2392
+ purchaseRef: body.purchaseRef
2393
+ });
2394
+ if (!reactivatedPurchase || typeof reactivatedPurchase !== "object") {
2395
+ return {
2396
+ error: "Invalid response from reactivate purchase endpoint",
2397
+ status: 500
2398
+ };
2399
+ }
2400
+ const responseObj = reactivatedPurchase;
2401
+ if (responseObj.purchase && typeof responseObj.purchase === "object") {
2402
+ reactivatedPurchase = responseObj.purchase;
2403
+ }
2404
+ if (!reactivatedPurchase.reference) {
2405
+ return {
2406
+ error: "Reactivate purchase response missing required fields",
2407
+ status: 500
2408
+ };
2409
+ }
2410
+ if (reactivatedPurchase.cancelledAt) {
2411
+ return {
2412
+ error: `Purchase reactivation failed: cancelledAt is still set`,
2413
+ status: 500
2414
+ };
2415
+ }
2416
+ await new Promise((resolve) => setTimeout(resolve, 500));
2417
+ return reactivatedPurchase;
2418
+ } catch (error) {
2419
+ if (error instanceof SolvaPayError4) {
2420
+ const errorMessage = error.message;
2421
+ if (errorMessage.includes("not found")) {
2422
+ return {
2423
+ error: "Purchase not found",
2424
+ status: 404,
2425
+ details: errorMessage
2426
+ };
2427
+ }
2428
+ if (errorMessage.includes("cannot be reactivated") || errorMessage.includes("not pending cancellation") || errorMessage.includes("already been fully cancelled") || errorMessage.includes("already ended")) {
2429
+ return {
2430
+ error: "Purchase cannot be reactivated",
2431
+ status: 400,
2432
+ details: errorMessage
2433
+ };
2434
+ }
2435
+ return {
2436
+ error: errorMessage,
2437
+ status: 500,
2438
+ details: errorMessage
2439
+ };
2440
+ }
2441
+ return handleRouteError(error, "Reactivate purchase", "Failed to reactivate purchase");
2442
+ }
2443
+ }
2444
+
2445
+ // src/helpers/activation.ts
2446
+ async function activatePlanCore(request, body, options = {}) {
2447
+ try {
2448
+ if (!body.productRef || !body.planRef) {
2449
+ return {
2450
+ error: "Missing required parameters: productRef and planRef are required",
2451
+ status: 400
2452
+ };
2453
+ }
2454
+ const customerResult = await syncCustomerCore(request, {
2455
+ solvaPay: options.solvaPay,
2456
+ includeEmail: options.includeEmail,
2457
+ includeName: options.includeName
2458
+ });
2459
+ if (isErrorResult(customerResult)) {
2460
+ return customerResult;
2461
+ }
2462
+ const customerRef = customerResult;
2463
+ const solvaPay = options.solvaPay || createSolvaPay();
2464
+ return await solvaPay.activatePlan({
2465
+ customerRef,
2466
+ productRef: body.productRef,
2467
+ planRef: body.planRef
2468
+ });
2469
+ } catch (error) {
2470
+ return handleRouteError(error, "Activate plan", "Plan activation failed");
2471
+ }
2472
+ }
2473
+
2474
+ // src/helpers/payment-method.ts
2475
+ async function getPaymentMethodCore(request, options = {}) {
2476
+ try {
2477
+ const customerResult = await syncCustomerCore(request, {
2478
+ solvaPay: options.solvaPay,
2479
+ includeEmail: options.includeEmail,
2480
+ includeName: options.includeName
2481
+ });
2482
+ if (isErrorResult(customerResult)) {
2483
+ return customerResult;
2484
+ }
2485
+ const customerRef = customerResult;
2486
+ const solvaPay = options.solvaPay || createSolvaPay();
2487
+ if (!solvaPay.apiClient.getPaymentMethod) {
2488
+ return {
2489
+ error: "getPaymentMethod is not implemented on this API client",
2490
+ status: 500
2491
+ };
2492
+ }
2493
+ return await solvaPay.apiClient.getPaymentMethod({ customerRef });
2494
+ } catch (error) {
2495
+ return handleRouteError(error, "Get payment method", "Failed to load payment method");
2496
+ }
2497
+ }
2498
+
2499
+ // src/helpers/plans.ts
2500
+ import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
2501
+ async function listPlansCore(request, options = {}) {
2502
+ try {
2503
+ const url = new URL(request.url);
2504
+ const productRef = url.searchParams.get("productRef");
2505
+ if (!productRef) {
2506
+ return {
2507
+ error: "Missing required parameter: productRef",
2508
+ status: 400
2509
+ };
2510
+ }
2511
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2512
+ const config = getSolvaPayConfig2();
2513
+ if (!config.apiKey) return null;
2514
+ return createSolvaPayClient({
2515
+ apiKey: config.apiKey,
2516
+ apiBaseUrl: config.apiBaseUrl
2517
+ });
2518
+ })();
2519
+ if (!apiClient) {
2520
+ return {
2521
+ error: "Server configuration error: SolvaPay secret key not configured",
2522
+ status: 500
2523
+ };
2524
+ }
2525
+ if (!apiClient.listPlans) {
2526
+ return {
2527
+ error: "List plans method not available",
2528
+ status: 500
2529
+ };
2530
+ }
2531
+ const plans = await apiClient.listPlans(productRef);
2532
+ return {
2533
+ plans: plans || [],
2534
+ productRef
2535
+ };
2536
+ } catch (error) {
2537
+ return handleRouteError(error, "List plans", "Failed to fetch plans");
2538
+ }
2539
+ }
2540
+
2541
+ // src/helpers/merchant.ts
2542
+ import { getSolvaPayConfig as getSolvaPayConfig3 } from "@solvapay/core";
2543
+ async function getMerchantCore(_request, options = {}) {
2544
+ try {
2545
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2546
+ const config = getSolvaPayConfig3();
2547
+ if (!config.apiKey) return null;
2548
+ return createSolvaPayClient({
2549
+ apiKey: config.apiKey,
2550
+ apiBaseUrl: config.apiBaseUrl
2551
+ });
2552
+ })();
2553
+ if (!apiClient) {
2554
+ return {
2555
+ error: "Server configuration error: SolvaPay secret key not configured",
2556
+ status: 500
2557
+ };
2558
+ }
2559
+ if (!apiClient.getMerchant) {
2560
+ return {
2561
+ error: "Get merchant method not available",
2562
+ status: 500
2563
+ };
2564
+ }
2565
+ const merchant = await apiClient.getMerchant();
2566
+ return merchant;
2567
+ } catch (error) {
2568
+ return handleRouteError(error, "Get merchant", "Failed to fetch merchant");
2569
+ }
2570
+ }
2571
+
2572
+ // src/helpers/product.ts
2573
+ import { getSolvaPayConfig as getSolvaPayConfig4 } from "@solvapay/core";
2574
+ async function getProductCore(request, options = {}) {
2575
+ try {
2576
+ const url = new URL(request.url);
2577
+ const productRef = url.searchParams.get("productRef");
2578
+ if (!productRef) {
2579
+ return {
2580
+ error: "Missing required parameter: productRef",
2581
+ status: 400
2582
+ };
2583
+ }
2584
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2585
+ const config = getSolvaPayConfig4();
2586
+ if (!config.apiKey) return null;
2587
+ return createSolvaPayClient({
2588
+ apiKey: config.apiKey,
2589
+ apiBaseUrl: config.apiBaseUrl
2590
+ });
2591
+ })();
2592
+ if (!apiClient) {
2593
+ return {
2594
+ error: "Server configuration error: SolvaPay secret key not configured",
2595
+ status: 500
2596
+ };
2597
+ }
2598
+ if (!apiClient.getProduct) {
2599
+ return {
2600
+ error: "Get product method not available",
2601
+ status: 500
2602
+ };
2603
+ }
2604
+ const product = await apiClient.getProduct(productRef);
2605
+ return product;
2606
+ } catch (error) {
2607
+ return handleRouteError(error, "Get product", "Failed to fetch product");
2608
+ }
2609
+ }
2610
+
2611
+ // src/helpers/purchase.ts
2612
+ async function checkPurchaseCore(request, options = {}) {
2613
+ try {
2614
+ const userResult = await getAuthenticatedUserCore(request, {
2615
+ includeEmail: options.includeEmail,
2616
+ includeName: options.includeName
2617
+ });
2618
+ if (isErrorResult(userResult)) {
2619
+ return userResult;
2620
+ }
2621
+ const { userId, email, name } = userResult;
2622
+ const solvaPay = options.solvaPay || createSolvaPay();
2623
+ const cachedCustomerRef = request.headers.get("x-solvapay-customer-ref");
2624
+ if (cachedCustomerRef) {
2625
+ try {
2626
+ const customer = await solvaPay.getCustomer({ customerRef: cachedCustomerRef });
2627
+ if (customer && customer.customerRef) {
2628
+ if (customer.externalRef && customer.externalRef === userId) {
2629
+ const filteredPurchases = (customer.purchases || []).filter(
2630
+ (p) => p.status === "active"
2631
+ );
2632
+ return {
2633
+ customerRef: customer.customerRef,
2634
+ email: customer.email,
2635
+ name: customer.name,
2636
+ purchases: filteredPurchases
2637
+ };
2638
+ }
2639
+ }
2640
+ } catch {
2641
+ }
2642
+ }
2643
+ try {
2644
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2645
+ email: email || void 0,
2646
+ name: name || void 0
2647
+ });
2648
+ const customer = await solvaPay.getCustomer({ customerRef });
2649
+ const filteredPurchases = (customer.purchases || []).filter((p) => p.status === "active");
2650
+ return {
2651
+ customerRef: customer.customerRef || userId,
2652
+ email: customer.email,
2653
+ name: customer.name,
2654
+ purchases: filteredPurchases
2655
+ };
2656
+ } catch {
2657
+ return {
2658
+ customerRef: userId,
2659
+ purchases: []
2660
+ };
2661
+ }
2662
+ } catch (error) {
2663
+ return handleRouteError(error, "Check purchase", "Failed to check purchase");
2664
+ }
2665
+ }
2666
+
2667
+ // src/helpers/usage.ts
2668
+ async function trackUsageCore(request, body, options = {}) {
2669
+ try {
2670
+ const userResult = await getAuthenticatedUserCore(request);
2671
+ if (isErrorResult(userResult)) {
2672
+ return userResult;
2673
+ }
2674
+ const { userId, email, name } = userResult;
2675
+ const solvaPay = options.solvaPay || createSolvaPay();
2676
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2677
+ email: email || void 0,
2678
+ name: name || void 0
2679
+ });
2680
+ await solvaPay.trackUsage({
2681
+ customerRef,
2682
+ actionType: body.actionType,
2683
+ units: body.units,
2684
+ productRef: body.productRef,
2685
+ description: body.description,
2686
+ metadata: body.metadata
2687
+ });
2688
+ return { success: true };
2689
+ } catch (error) {
2690
+ return handleRouteError(error, "Track usage", "Track usage failed");
2691
+ }
2692
+ }
2693
+
2694
+ // src/edge.ts
2695
+ import { SolvaPayError as SolvaPayError5 } from "@solvapay/core";
2696
+ function timingSafeEqual(a, b) {
2697
+ if (a.length !== b.length) return false;
2698
+ let mismatch = 0;
2699
+ for (let i = 0; i < a.length; i++) {
2700
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
2701
+ }
2702
+ return mismatch === 0;
2703
+ }
2704
+ async function verifyWebhook({
2705
+ body,
2706
+ signature,
2707
+ secret
2708
+ }) {
2709
+ const toleranceSec = 300;
2710
+ if (!signature) throw new SolvaPayError5("Missing webhook signature");
2711
+ const parts = signature.split(",");
2712
+ const tPart = parts.find((p) => p.startsWith("t="));
2713
+ const v1Part = parts.find((p) => p.startsWith("v1="));
2714
+ if (!tPart || !v1Part) {
2715
+ throw new SolvaPayError5("Malformed webhook signature");
2716
+ }
2717
+ const timestamp = parseInt(tPart.slice(2), 10);
2718
+ const receivedHmac = v1Part.slice(3);
2719
+ if (Number.isNaN(timestamp) || !receivedHmac) {
2720
+ throw new SolvaPayError5("Malformed webhook signature");
2721
+ }
2722
+ if (toleranceSec > 0) {
2723
+ const age = Math.abs(Math.floor(Date.now() / 1e3) - timestamp);
2724
+ if (age > toleranceSec) {
2725
+ throw new SolvaPayError5("Webhook signature timestamp too old");
2726
+ }
2727
+ }
2728
+ const enc = new TextEncoder();
2729
+ const key = await crypto.subtle.importKey(
2730
+ "raw",
2731
+ enc.encode(secret),
2732
+ { name: "HMAC", hash: "SHA-256" },
2733
+ false,
2734
+ ["sign"]
2735
+ );
2736
+ const sigBuf = await crypto.subtle.sign("HMAC", key, enc.encode(`${timestamp}.${body}`));
2737
+ const expectedHmac = Array.from(new Uint8Array(sigBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
2738
+ if (!timingSafeEqual(expectedHmac, receivedHmac)) {
2739
+ throw new SolvaPayError5("Invalid webhook signature");
2740
+ }
2741
+ try {
2742
+ return JSON.parse(body);
2743
+ } catch {
2744
+ throw new SolvaPayError5("Invalid webhook payload: body is not valid JSON");
2745
+ }
2746
+ }
2747
+
2748
+ // src/fetch/cors.ts
2749
+ var corsConfig = { origins: ["*"] };
2750
+ function configureCors(config) {
2751
+ corsConfig = config;
2752
+ }
2753
+ function getCorsHeaders(req) {
2754
+ const origin = req.headers.get("Origin");
2755
+ const headers = {
2756
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
2757
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, x-solvapay-customer-ref",
2758
+ "Access-Control-Max-Age": "86400"
2759
+ };
2760
+ if (corsConfig.origins.includes("*")) {
2761
+ headers["Access-Control-Allow-Origin"] = "*";
2762
+ } else if (origin && corsConfig.origins.includes(origin)) {
2763
+ headers["Access-Control-Allow-Origin"] = origin;
2764
+ headers["Vary"] = "Origin";
2765
+ }
2766
+ return headers;
2767
+ }
2768
+ function handleCors(req) {
2769
+ if (req.method !== "OPTIONS") {
2770
+ return null;
2771
+ }
2772
+ const headers = getCorsHeaders(req);
2773
+ return new Response(null, {
2774
+ status: 204,
2775
+ headers
2776
+ });
2777
+ }
2778
+
2779
+ // src/fetch/utils.ts
2780
+ function errorResponse(result, req) {
2781
+ const corsHeaders = req ? getCorsHeaders(req) : {};
2782
+ return new Response(
2783
+ JSON.stringify({ error: result.error, ...result.details ? { details: result.details } : {} }),
2784
+ {
2785
+ status: result.status,
2786
+ headers: {
2787
+ "Content-Type": "application/json",
2788
+ ...corsHeaders
2789
+ }
2790
+ }
2791
+ );
2792
+ }
2793
+ function jsonResponseWithCors(data, req, status = 200) {
2794
+ const corsHeaders = getCorsHeaders(req);
2795
+ return new Response(JSON.stringify(data), {
2796
+ status,
2797
+ headers: {
2798
+ "Content-Type": "application/json",
2799
+ ...corsHeaders
2800
+ }
2801
+ });
2802
+ }
2803
+
2804
+ // src/fetch/handlers.ts
2805
+ async function parseJsonBody(req) {
2806
+ try {
2807
+ return await req.json();
2808
+ } catch {
2809
+ return {};
2810
+ }
2811
+ }
2812
+ async function checkPurchase(req) {
2813
+ const corsResponse = handleCors(req);
2814
+ if (corsResponse) return corsResponse;
2815
+ const result = await checkPurchaseCore(req);
2816
+ if (isErrorResult(result)) {
2817
+ return errorResponse(result, req);
2818
+ }
2819
+ return jsonResponseWithCors(result, req);
2820
+ }
2821
+ async function trackUsage(req) {
2822
+ const corsResponse = handleCors(req);
2823
+ if (corsResponse) return corsResponse;
2824
+ const body = await parseJsonBody(req);
2825
+ const result = await trackUsageCore(req, body, {});
2826
+ if (isErrorResult(result)) {
2827
+ return errorResponse(result, req);
2828
+ }
2829
+ return jsonResponseWithCors(result, req);
2830
+ }
2831
+ async function createPaymentIntent(req) {
2832
+ const corsResponse = handleCors(req);
2833
+ if (corsResponse) return corsResponse;
2834
+ const body = await parseJsonBody(req);
2835
+ const result = await createPaymentIntentCore(req, body);
2836
+ if (isErrorResult(result)) {
2837
+ return errorResponse(result, req);
2838
+ }
2839
+ return jsonResponseWithCors(result, req);
2840
+ }
2841
+ async function processPayment(req) {
2842
+ const corsResponse = handleCors(req);
2843
+ if (corsResponse) return corsResponse;
2844
+ const body = await parseJsonBody(req);
2845
+ const result = await processPaymentIntentCore(req, body);
2846
+ if (isErrorResult(result)) {
2847
+ return errorResponse(result, req);
2848
+ }
2849
+ return jsonResponseWithCors(result, req);
2850
+ }
2851
+ async function createTopupPaymentIntent(req) {
2852
+ const corsResponse = handleCors(req);
2853
+ if (corsResponse) return corsResponse;
2854
+ const body = await parseJsonBody(req);
2855
+ const result = await createTopupPaymentIntentCore(req, body);
2856
+ if (isErrorResult(result)) {
2857
+ return errorResponse(result, req);
2858
+ }
2859
+ return jsonResponseWithCors(result, req);
2860
+ }
2861
+ async function customerBalance(req) {
2862
+ const corsResponse = handleCors(req);
2863
+ if (corsResponse) return corsResponse;
2864
+ const result = await getCustomerBalanceCore(req);
2865
+ if (isErrorResult(result)) {
2866
+ return errorResponse(result, req);
2867
+ }
2868
+ return jsonResponseWithCors(result, req);
2869
+ }
2870
+ async function cancelRenewal(req) {
2871
+ const corsResponse = handleCors(req);
2872
+ if (corsResponse) return corsResponse;
2873
+ const body = await parseJsonBody(req);
2874
+ const result = await cancelPurchaseCore(req, body);
2875
+ if (isErrorResult(result)) {
2876
+ return errorResponse(result, req);
2877
+ }
2878
+ return jsonResponseWithCors(result, req);
2879
+ }
2880
+ async function reactivateRenewal(req) {
2881
+ const corsResponse = handleCors(req);
2882
+ if (corsResponse) return corsResponse;
2883
+ const body = await parseJsonBody(req);
2884
+ const result = await reactivatePurchaseCore(req, body);
2885
+ if (isErrorResult(result)) {
2886
+ return errorResponse(result, req);
2887
+ }
2888
+ return jsonResponseWithCors(result, req);
2889
+ }
2890
+ async function activatePlan(req) {
2891
+ const corsResponse = handleCors(req);
2892
+ if (corsResponse) return corsResponse;
2893
+ const body = await parseJsonBody(req);
2894
+ const result = await activatePlanCore(req, body);
2895
+ if (isErrorResult(result)) {
2896
+ return errorResponse(result, req);
2897
+ }
2898
+ return jsonResponseWithCors(result, req);
2899
+ }
2900
+ async function getPaymentMethod(req) {
2901
+ const corsResponse = handleCors(req);
2902
+ if (corsResponse) return corsResponse;
2903
+ const result = await getPaymentMethodCore(req);
2904
+ if (isErrorResult(result)) {
2905
+ return errorResponse(result, req);
2906
+ }
2907
+ return jsonResponseWithCors(result, req);
2908
+ }
2909
+ async function listPlans(req) {
2910
+ const corsResponse = handleCors(req);
2911
+ if (corsResponse) return corsResponse;
2912
+ const result = await listPlansCore(req);
2913
+ if (isErrorResult(result)) {
2914
+ return errorResponse(result, req);
2915
+ }
2916
+ return jsonResponseWithCors(result, req);
2917
+ }
2918
+ async function syncCustomer(req) {
2919
+ const corsResponse = handleCors(req);
2920
+ if (corsResponse) return corsResponse;
2921
+ const result = await syncCustomerCore(req);
2922
+ if (isErrorResult(result)) {
2923
+ return errorResponse(result, req);
2924
+ }
2925
+ return jsonResponseWithCors({ customerRef: result }, req);
2926
+ }
2927
+ async function createCheckoutSession(req) {
2928
+ const corsResponse = handleCors(req);
2929
+ if (corsResponse) return corsResponse;
2930
+ const body = await parseJsonBody(req);
2931
+ const result = await createCheckoutSessionCore(req, body);
2932
+ if (isErrorResult(result)) {
2933
+ return errorResponse(result, req);
2934
+ }
2935
+ return jsonResponseWithCors(result, req);
2936
+ }
2937
+ async function createCustomerSession(req) {
2938
+ const corsResponse = handleCors(req);
2939
+ if (corsResponse) return corsResponse;
2940
+ const result = await createCustomerSessionCore(req);
2941
+ if (isErrorResult(result)) {
2942
+ return errorResponse(result, req);
2943
+ }
2944
+ return jsonResponseWithCors(result, req);
2945
+ }
2946
+ async function getMerchant(req) {
2947
+ const corsResponse = handleCors(req);
2948
+ if (corsResponse) return corsResponse;
2949
+ const result = await getMerchantCore(req);
2950
+ if (isErrorResult(result)) {
2951
+ return errorResponse(result, req);
2952
+ }
2953
+ return jsonResponseWithCors(result, req);
2954
+ }
2955
+ async function getProduct(req) {
2956
+ const corsResponse = handleCors(req);
2957
+ if (corsResponse) return corsResponse;
2958
+ const result = await getProductCore(req);
2959
+ if (isErrorResult(result)) {
2960
+ return errorResponse(result, req);
2961
+ }
2962
+ return jsonResponseWithCors(result, req);
2963
+ }
2964
+ function solvapayWebhook(options) {
2965
+ return async (req) => {
2966
+ const secret = options.secret || (typeof process !== "undefined" ? process.env.SOLVAPAY_WEBHOOK_SECRET : void 0);
2967
+ if (!secret) {
2968
+ return new Response(JSON.stringify({ error: "Webhook secret not configured" }), {
2969
+ status: 500,
2970
+ headers: { "Content-Type": "application/json" }
2971
+ });
2972
+ }
2973
+ const body = await req.text();
2974
+ const signature = req.headers.get("sv-signature") ?? "";
2975
+ let event;
2976
+ try {
2977
+ event = await verifyWebhook({ body, signature, secret });
2978
+ } catch {
2979
+ return new Response(JSON.stringify({ error: "Invalid webhook signature" }), {
2980
+ status: 401,
2981
+ headers: { "Content-Type": "application/json" }
2982
+ });
2983
+ }
2984
+ try {
2985
+ await options.onEvent(event);
2986
+ } catch {
2987
+ return new Response(JSON.stringify({ error: "Webhook handler failed" }), {
2988
+ status: 500,
2989
+ headers: { "Content-Type": "application/json" }
2990
+ });
2991
+ }
2992
+ return new Response(JSON.stringify({ received: true }), {
2993
+ status: 200,
2994
+ headers: { "Content-Type": "application/json" }
2995
+ });
2996
+ };
2997
+ }
2998
+ export {
2999
+ activatePlan,
3000
+ cancelRenewal,
3001
+ checkPurchase,
3002
+ configureCors,
3003
+ createCheckoutSession,
3004
+ createCustomerSession,
3005
+ createPaymentIntent,
3006
+ createTopupPaymentIntent,
3007
+ customerBalance,
3008
+ getMerchant,
3009
+ getPaymentMethod,
3010
+ getProduct,
3011
+ listPlans,
3012
+ processPayment,
3013
+ reactivateRenewal,
3014
+ solvapayWebhook,
3015
+ syncCustomer,
3016
+ trackUsage
3017
+ };