@t402/core 2.0.0

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 (66) hide show
  1. package/README.md +293 -0
  2. package/dist/cjs/client/index.d.ts +103 -0
  3. package/dist/cjs/client/index.js +510 -0
  4. package/dist/cjs/client/index.js.map +1 -0
  5. package/dist/cjs/facilitator/index.d.ts +192 -0
  6. package/dist/cjs/facilitator/index.js +398 -0
  7. package/dist/cjs/facilitator/index.js.map +1 -0
  8. package/dist/cjs/http/index.d.ts +52 -0
  9. package/dist/cjs/http/index.js +763 -0
  10. package/dist/cjs/http/index.js.map +1 -0
  11. package/dist/cjs/index.d.ts +3 -0
  12. package/dist/cjs/index.js +31 -0
  13. package/dist/cjs/index.js.map +1 -0
  14. package/dist/cjs/mechanisms-CmrqNl1M.d.ts +238 -0
  15. package/dist/cjs/mechanisms-DsJn3ZiM.d.ts +238 -0
  16. package/dist/cjs/server/index.d.ts +2 -0
  17. package/dist/cjs/server/index.js +1241 -0
  18. package/dist/cjs/server/index.js.map +1 -0
  19. package/dist/cjs/t402HTTPClient-m6cjzTek.d.ts +243 -0
  20. package/dist/cjs/t402HTTPResourceServer-B-xmYMwj.d.ts +719 -0
  21. package/dist/cjs/t402HTTPResourceServer-Bcfxp2UO.d.ts +719 -0
  22. package/dist/cjs/types/index.d.ts +1 -0
  23. package/dist/cjs/types/index.js +19 -0
  24. package/dist/cjs/types/index.js.map +1 -0
  25. package/dist/cjs/types/v1/index.d.ts +1 -0
  26. package/dist/cjs/types/v1/index.js +19 -0
  27. package/dist/cjs/types/v1/index.js.map +1 -0
  28. package/dist/cjs/utils/index.d.ts +48 -0
  29. package/dist/cjs/utils/index.js +108 -0
  30. package/dist/cjs/utils/index.js.map +1 -0
  31. package/dist/esm/chunk-3IUBYRYG.mjs +78 -0
  32. package/dist/esm/chunk-3IUBYRYG.mjs.map +1 -0
  33. package/dist/esm/chunk-3VTYR43U.mjs +7 -0
  34. package/dist/esm/chunk-3VTYR43U.mjs.map +1 -0
  35. package/dist/esm/chunk-BJTO5JO5.mjs +11 -0
  36. package/dist/esm/chunk-BJTO5JO5.mjs.map +1 -0
  37. package/dist/esm/chunk-D5DYKKCZ.mjs +722 -0
  38. package/dist/esm/chunk-D5DYKKCZ.mjs.map +1 -0
  39. package/dist/esm/client/index.d.mts +103 -0
  40. package/dist/esm/client/index.mjs +356 -0
  41. package/dist/esm/client/index.mjs.map +1 -0
  42. package/dist/esm/facilitator/index.d.mts +192 -0
  43. package/dist/esm/facilitator/index.mjs +373 -0
  44. package/dist/esm/facilitator/index.mjs.map +1 -0
  45. package/dist/esm/http/index.d.mts +52 -0
  46. package/dist/esm/http/index.mjs +28 -0
  47. package/dist/esm/http/index.mjs.map +1 -0
  48. package/dist/esm/index.d.mts +3 -0
  49. package/dist/esm/index.mjs +8 -0
  50. package/dist/esm/index.mjs.map +1 -0
  51. package/dist/esm/mechanisms-CmrqNl1M.d.mts +238 -0
  52. package/dist/esm/server/index.d.mts +2 -0
  53. package/dist/esm/server/index.mjs +562 -0
  54. package/dist/esm/server/index.mjs.map +1 -0
  55. package/dist/esm/t402HTTPClient-C285YGCp.d.mts +243 -0
  56. package/dist/esm/t402HTTPResourceServer-k_l3d8ua.d.mts +719 -0
  57. package/dist/esm/types/index.d.mts +1 -0
  58. package/dist/esm/types/index.mjs +1 -0
  59. package/dist/esm/types/index.mjs.map +1 -0
  60. package/dist/esm/types/v1/index.d.mts +1 -0
  61. package/dist/esm/types/v1/index.mjs +1 -0
  62. package/dist/esm/types/v1/index.mjs.map +1 -0
  63. package/dist/esm/utils/index.d.mts +48 -0
  64. package/dist/esm/utils/index.mjs +20 -0
  65. package/dist/esm/utils/index.mjs.map +1 -0
  66. package/package.json +129 -0
@@ -0,0 +1,1241 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/server/index.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ HTTPFacilitatorClient: () => HTTPFacilitatorClient,
24
+ RouteConfigurationError: () => RouteConfigurationError,
25
+ t402HTTPResourceServer: () => t402HTTPResourceServer,
26
+ t402ResourceServer: () => t402ResourceServer
27
+ });
28
+ module.exports = __toCommonJS(server_exports);
29
+
30
+ // src/utils/index.ts
31
+ var findSchemesByNetwork = (map, network) => {
32
+ let implementationsByScheme = map.get(network);
33
+ if (!implementationsByScheme) {
34
+ for (const [registeredNetworkPattern, implementations] of map.entries()) {
35
+ const pattern = registeredNetworkPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
36
+ const regex = new RegExp(`^${pattern}$`);
37
+ if (regex.test(network)) {
38
+ implementationsByScheme = implementations;
39
+ break;
40
+ }
41
+ }
42
+ }
43
+ return implementationsByScheme;
44
+ };
45
+ var findByNetworkAndScheme = (map, scheme, network) => {
46
+ return findSchemesByNetwork(map, network)?.get(scheme);
47
+ };
48
+ var Base64EncodedRegex = /^[A-Za-z0-9+/]*={0,2}$/;
49
+ function safeBase64Encode(data) {
50
+ if (typeof globalThis !== "undefined" && typeof globalThis.btoa === "function") {
51
+ return globalThis.btoa(data);
52
+ }
53
+ return Buffer.from(data).toString("base64");
54
+ }
55
+ function safeBase64Decode(data) {
56
+ if (typeof globalThis !== "undefined" && typeof globalThis.atob === "function") {
57
+ return globalThis.atob(data);
58
+ }
59
+ return Buffer.from(data, "base64").toString("utf-8");
60
+ }
61
+ function deepEqual(obj1, obj2) {
62
+ const normalize = (obj) => {
63
+ if (obj === null || obj === void 0) return JSON.stringify(obj);
64
+ if (typeof obj !== "object") return JSON.stringify(obj);
65
+ if (Array.isArray(obj)) {
66
+ return JSON.stringify(
67
+ obj.map(
68
+ (item) => typeof item === "object" && item !== null ? JSON.parse(normalize(item)) : item
69
+ )
70
+ );
71
+ }
72
+ const sorted = {};
73
+ Object.keys(obj).sort().forEach((key) => {
74
+ const value = obj[key];
75
+ sorted[key] = typeof value === "object" && value !== null ? JSON.parse(normalize(value)) : value;
76
+ });
77
+ return JSON.stringify(sorted);
78
+ };
79
+ try {
80
+ return normalize(obj1) === normalize(obj2);
81
+ } catch {
82
+ return JSON.stringify(obj1) === JSON.stringify(obj2);
83
+ }
84
+ }
85
+
86
+ // src/http/httpFacilitatorClient.ts
87
+ var DEFAULT_FACILITATOR_URL = "https://t402.org/facilitator";
88
+ var HTTPFacilitatorClient = class {
89
+ /**
90
+ * Creates a new HTTPFacilitatorClient instance.
91
+ *
92
+ * @param config - Configuration options for the facilitator client
93
+ */
94
+ constructor(config) {
95
+ this.url = config?.url || DEFAULT_FACILITATOR_URL;
96
+ this._createAuthHeaders = config?.createAuthHeaders;
97
+ }
98
+ /**
99
+ * Verify a payment with the facilitator
100
+ *
101
+ * @param paymentPayload - The payment to verify
102
+ * @param paymentRequirements - The requirements to verify against
103
+ * @returns Verification response
104
+ */
105
+ async verify(paymentPayload, paymentRequirements) {
106
+ let headers = {
107
+ "Content-Type": "application/json"
108
+ };
109
+ if (this._createAuthHeaders) {
110
+ const authHeaders = await this.createAuthHeaders("verify");
111
+ headers = { ...headers, ...authHeaders.headers };
112
+ }
113
+ const response = await fetch(`${this.url}/verify`, {
114
+ method: "POST",
115
+ headers,
116
+ body: JSON.stringify({
117
+ t402Version: paymentPayload.t402Version,
118
+ paymentPayload: this.toJsonSafe(paymentPayload),
119
+ paymentRequirements: this.toJsonSafe(paymentRequirements)
120
+ })
121
+ });
122
+ if (!response.ok) {
123
+ const errorText = await response.text().catch(() => response.statusText);
124
+ throw new Error(`Facilitator verify failed (${response.status}): ${errorText}`);
125
+ }
126
+ return await response.json();
127
+ }
128
+ /**
129
+ * Settle a payment with the facilitator
130
+ *
131
+ * @param paymentPayload - The payment to settle
132
+ * @param paymentRequirements - The requirements for settlement
133
+ * @returns Settlement response
134
+ */
135
+ async settle(paymentPayload, paymentRequirements) {
136
+ let headers = {
137
+ "Content-Type": "application/json"
138
+ };
139
+ if (this._createAuthHeaders) {
140
+ const authHeaders = await this.createAuthHeaders("settle");
141
+ headers = { ...headers, ...authHeaders.headers };
142
+ }
143
+ const response = await fetch(`${this.url}/settle`, {
144
+ method: "POST",
145
+ headers,
146
+ body: JSON.stringify({
147
+ t402Version: paymentPayload.t402Version,
148
+ paymentPayload: this.toJsonSafe(paymentPayload),
149
+ paymentRequirements: this.toJsonSafe(paymentRequirements)
150
+ })
151
+ });
152
+ if (!response.ok) {
153
+ const errorText = await response.text().catch(() => response.statusText);
154
+ throw new Error(`Facilitator settle failed (${response.status}): ${errorText}`);
155
+ }
156
+ return await response.json();
157
+ }
158
+ /**
159
+ * Get supported payment kinds and extensions from the facilitator
160
+ *
161
+ * @returns Supported payment kinds and extensions
162
+ */
163
+ async getSupported() {
164
+ let headers = {
165
+ "Content-Type": "application/json"
166
+ };
167
+ if (this._createAuthHeaders) {
168
+ const authHeaders = await this.createAuthHeaders("supported");
169
+ headers = { ...headers, ...authHeaders.headers };
170
+ }
171
+ const response = await fetch(`${this.url}/supported`, {
172
+ method: "GET",
173
+ headers
174
+ });
175
+ if (!response.ok) {
176
+ const errorText = await response.text().catch(() => response.statusText);
177
+ throw new Error(`Facilitator getSupported failed (${response.status}): ${errorText}`);
178
+ }
179
+ return await response.json();
180
+ }
181
+ /**
182
+ * Creates authentication headers for a specific path.
183
+ *
184
+ * @param path - The path to create authentication headers for (e.g., "verify", "settle", "supported")
185
+ * @returns An object containing the authentication headers for the specified path
186
+ */
187
+ async createAuthHeaders(path) {
188
+ if (this._createAuthHeaders) {
189
+ const authHeaders = await this._createAuthHeaders();
190
+ return {
191
+ headers: authHeaders[path] ?? {}
192
+ };
193
+ }
194
+ return {
195
+ headers: {}
196
+ };
197
+ }
198
+ /**
199
+ * Helper to convert objects to JSON-safe format.
200
+ * Handles BigInt and other non-JSON types.
201
+ *
202
+ * @param obj - The object to convert
203
+ * @returns The JSON-safe representation of the object
204
+ */
205
+ toJsonSafe(obj) {
206
+ return JSON.parse(
207
+ JSON.stringify(obj, (_, value) => typeof value === "bigint" ? value.toString() : value)
208
+ );
209
+ }
210
+ };
211
+
212
+ // src/index.ts
213
+ var t402Version = 2;
214
+
215
+ // src/server/t402ResourceServer.ts
216
+ var t402ResourceServer = class {
217
+ /**
218
+ * Creates a new t402ResourceServer instance.
219
+ *
220
+ * @param facilitatorClients - Optional facilitator client(s) for payment processing
221
+ */
222
+ constructor(facilitatorClients) {
223
+ this.registeredServerSchemes = /* @__PURE__ */ new Map();
224
+ this.supportedResponsesMap = /* @__PURE__ */ new Map();
225
+ this.facilitatorClientsMap = /* @__PURE__ */ new Map();
226
+ this.registeredExtensions = /* @__PURE__ */ new Map();
227
+ this.beforeVerifyHooks = [];
228
+ this.afterVerifyHooks = [];
229
+ this.onVerifyFailureHooks = [];
230
+ this.beforeSettleHooks = [];
231
+ this.afterSettleHooks = [];
232
+ this.onSettleFailureHooks = [];
233
+ if (!facilitatorClients) {
234
+ this.facilitatorClients = [new HTTPFacilitatorClient()];
235
+ } else if (Array.isArray(facilitatorClients)) {
236
+ this.facilitatorClients = facilitatorClients.length > 0 ? facilitatorClients : [new HTTPFacilitatorClient()];
237
+ } else {
238
+ this.facilitatorClients = [facilitatorClients];
239
+ }
240
+ }
241
+ /**
242
+ * Register a scheme/network server implementation.
243
+ *
244
+ * @param network - The network identifier
245
+ * @param server - The scheme/network server implementation
246
+ * @returns The t402ResourceServer instance for chaining
247
+ */
248
+ register(network, server) {
249
+ if (!this.registeredServerSchemes.has(network)) {
250
+ this.registeredServerSchemes.set(network, /* @__PURE__ */ new Map());
251
+ }
252
+ const serverByScheme = this.registeredServerSchemes.get(network);
253
+ if (!serverByScheme.has(server.scheme)) {
254
+ serverByScheme.set(server.scheme, server);
255
+ }
256
+ return this;
257
+ }
258
+ /**
259
+ * Check if a scheme is registered for a given network.
260
+ *
261
+ * @param network - The network identifier
262
+ * @param scheme - The payment scheme name
263
+ * @returns True if the scheme is registered for the network, false otherwise
264
+ */
265
+ hasRegisteredScheme(network, scheme) {
266
+ return !!findByNetworkAndScheme(this.registeredServerSchemes, scheme, network);
267
+ }
268
+ /**
269
+ * Registers a resource service extension that can enrich extension declarations.
270
+ *
271
+ * @param extension - The extension to register
272
+ * @returns The t402ResourceServer instance for chaining
273
+ */
274
+ registerExtension(extension) {
275
+ this.registeredExtensions.set(extension.key, extension);
276
+ return this;
277
+ }
278
+ /**
279
+ * Enriches declared extensions using registered extension hooks.
280
+ *
281
+ * @param declaredExtensions - Extensions declared on the route
282
+ * @param transportContext - Transport-specific context (HTTP, A2A, MCP, etc.)
283
+ * @returns Enriched extensions map
284
+ */
285
+ enrichExtensions(declaredExtensions, transportContext) {
286
+ const enriched = {};
287
+ for (const [key, declaration] of Object.entries(declaredExtensions)) {
288
+ const extension = this.registeredExtensions.get(key);
289
+ if (extension?.enrichDeclaration) {
290
+ enriched[key] = extension.enrichDeclaration(declaration, transportContext);
291
+ } else {
292
+ enriched[key] = declaration;
293
+ }
294
+ }
295
+ return enriched;
296
+ }
297
+ /**
298
+ * Register a hook to execute before payment verification.
299
+ * Can abort verification by returning { abort: true, reason: string }
300
+ *
301
+ * @param hook - The hook function to register
302
+ * @returns The t402ResourceServer instance for chaining
303
+ */
304
+ onBeforeVerify(hook) {
305
+ this.beforeVerifyHooks.push(hook);
306
+ return this;
307
+ }
308
+ /**
309
+ * Register a hook to execute after successful payment verification.
310
+ *
311
+ * @param hook - The hook function to register
312
+ * @returns The t402ResourceServer instance for chaining
313
+ */
314
+ onAfterVerify(hook) {
315
+ this.afterVerifyHooks.push(hook);
316
+ return this;
317
+ }
318
+ /**
319
+ * Register a hook to execute when payment verification fails.
320
+ * Can recover from failure by returning { recovered: true, result: VerifyResponse }
321
+ *
322
+ * @param hook - The hook function to register
323
+ * @returns The t402ResourceServer instance for chaining
324
+ */
325
+ onVerifyFailure(hook) {
326
+ this.onVerifyFailureHooks.push(hook);
327
+ return this;
328
+ }
329
+ /**
330
+ * Register a hook to execute before payment settlement.
331
+ * Can abort settlement by returning { abort: true, reason: string }
332
+ *
333
+ * @param hook - The hook function to register
334
+ * @returns The t402ResourceServer instance for chaining
335
+ */
336
+ onBeforeSettle(hook) {
337
+ this.beforeSettleHooks.push(hook);
338
+ return this;
339
+ }
340
+ /**
341
+ * Register a hook to execute after successful payment settlement.
342
+ *
343
+ * @param hook - The hook function to register
344
+ * @returns The t402ResourceServer instance for chaining
345
+ */
346
+ onAfterSettle(hook) {
347
+ this.afterSettleHooks.push(hook);
348
+ return this;
349
+ }
350
+ /**
351
+ * Register a hook to execute when payment settlement fails.
352
+ * Can recover from failure by returning { recovered: true, result: SettleResponse }
353
+ *
354
+ * @param hook - The hook function to register
355
+ * @returns The t402ResourceServer instance for chaining
356
+ */
357
+ onSettleFailure(hook) {
358
+ this.onSettleFailureHooks.push(hook);
359
+ return this;
360
+ }
361
+ /**
362
+ * Initialize by fetching supported kinds from all facilitators
363
+ * Creates mappings for supported responses and facilitator clients
364
+ * Earlier facilitators in the array get precedence
365
+ */
366
+ async initialize() {
367
+ this.supportedResponsesMap.clear();
368
+ this.facilitatorClientsMap.clear();
369
+ for (const facilitatorClient of this.facilitatorClients) {
370
+ try {
371
+ const supported = await facilitatorClient.getSupported();
372
+ for (const kind of supported.kinds) {
373
+ const t402Version2 = kind.t402Version;
374
+ if (!this.supportedResponsesMap.has(t402Version2)) {
375
+ this.supportedResponsesMap.set(t402Version2, /* @__PURE__ */ new Map());
376
+ }
377
+ const responseVersionMap = this.supportedResponsesMap.get(t402Version2);
378
+ if (!this.facilitatorClientsMap.has(t402Version2)) {
379
+ this.facilitatorClientsMap.set(t402Version2, /* @__PURE__ */ new Map());
380
+ }
381
+ const clientVersionMap = this.facilitatorClientsMap.get(t402Version2);
382
+ if (!responseVersionMap.has(kind.network)) {
383
+ responseVersionMap.set(kind.network, /* @__PURE__ */ new Map());
384
+ }
385
+ const responseNetworkMap = responseVersionMap.get(kind.network);
386
+ if (!clientVersionMap.has(kind.network)) {
387
+ clientVersionMap.set(kind.network, /* @__PURE__ */ new Map());
388
+ }
389
+ const clientNetworkMap = clientVersionMap.get(kind.network);
390
+ if (!responseNetworkMap.has(kind.scheme)) {
391
+ responseNetworkMap.set(kind.scheme, supported);
392
+ clientNetworkMap.set(kind.scheme, facilitatorClient);
393
+ }
394
+ }
395
+ } catch (error) {
396
+ console.warn(`Failed to fetch supported kinds from facilitator: ${error}`);
397
+ }
398
+ }
399
+ }
400
+ /**
401
+ * Get supported kind for a specific version, network, and scheme
402
+ *
403
+ * @param t402Version - The t402 version
404
+ * @param network - The network identifier
405
+ * @param scheme - The payment scheme
406
+ * @returns The supported kind or undefined if not found
407
+ */
408
+ getSupportedKind(t402Version2, network, scheme) {
409
+ const versionMap = this.supportedResponsesMap.get(t402Version2);
410
+ if (!versionMap) return void 0;
411
+ const supportedResponse = findByNetworkAndScheme(versionMap, scheme, network);
412
+ if (!supportedResponse) return void 0;
413
+ return supportedResponse.kinds.find(
414
+ (kind) => kind.t402Version === t402Version2 && kind.network === network && kind.scheme === scheme
415
+ );
416
+ }
417
+ /**
418
+ * Get facilitator extensions for a specific version, network, and scheme
419
+ *
420
+ * @param t402Version - The t402 version
421
+ * @param network - The network identifier
422
+ * @param scheme - The payment scheme
423
+ * @returns The facilitator extensions or empty array if not found
424
+ */
425
+ getFacilitatorExtensions(t402Version2, network, scheme) {
426
+ const versionMap = this.supportedResponsesMap.get(t402Version2);
427
+ if (!versionMap) return [];
428
+ const supportedResponse = findByNetworkAndScheme(versionMap, scheme, network);
429
+ return supportedResponse?.extensions || [];
430
+ }
431
+ /**
432
+ * Build payment requirements for a protected resource
433
+ *
434
+ * @param resourceConfig - Configuration for the protected resource
435
+ * @returns Array of payment requirements
436
+ */
437
+ async buildPaymentRequirements(resourceConfig) {
438
+ const requirements = [];
439
+ const scheme = resourceConfig.scheme;
440
+ const SchemeNetworkServer = findByNetworkAndScheme(
441
+ this.registeredServerSchemes,
442
+ scheme,
443
+ resourceConfig.network
444
+ );
445
+ if (!SchemeNetworkServer) {
446
+ console.warn(
447
+ `No server implementation registered for scheme: ${scheme}, network: ${resourceConfig.network}`
448
+ );
449
+ return requirements;
450
+ }
451
+ const supportedKind = this.getSupportedKind(
452
+ t402Version,
453
+ resourceConfig.network,
454
+ SchemeNetworkServer.scheme
455
+ );
456
+ if (!supportedKind) {
457
+ throw new Error(
458
+ `Facilitator does not support ${SchemeNetworkServer.scheme} on ${resourceConfig.network}. Make sure to call initialize() to fetch supported kinds from facilitators.`
459
+ );
460
+ }
461
+ const facilitatorExtensions = this.getFacilitatorExtensions(
462
+ t402Version,
463
+ resourceConfig.network,
464
+ SchemeNetworkServer.scheme
465
+ );
466
+ const parsedPrice = await SchemeNetworkServer.parsePrice(
467
+ resourceConfig.price,
468
+ resourceConfig.network
469
+ );
470
+ const baseRequirements = {
471
+ scheme: SchemeNetworkServer.scheme,
472
+ network: resourceConfig.network,
473
+ amount: parsedPrice.amount,
474
+ asset: parsedPrice.asset,
475
+ payTo: resourceConfig.payTo,
476
+ maxTimeoutSeconds: resourceConfig.maxTimeoutSeconds || 300,
477
+ // Default 5 minutes
478
+ extra: {
479
+ ...parsedPrice.extra
480
+ }
481
+ };
482
+ const requirement = await SchemeNetworkServer.enhancePaymentRequirements(
483
+ baseRequirements,
484
+ {
485
+ ...supportedKind,
486
+ t402Version
487
+ },
488
+ facilitatorExtensions
489
+ );
490
+ requirements.push(requirement);
491
+ return requirements;
492
+ }
493
+ /**
494
+ * Build payment requirements from multiple payment options
495
+ * This method handles resolving dynamic payTo/price functions and builds requirements for each option
496
+ *
497
+ * @param paymentOptions - Array of payment options to convert
498
+ * @param context - HTTP request context for resolving dynamic functions
499
+ * @returns Array of payment requirements (one per option)
500
+ */
501
+ async buildPaymentRequirementsFromOptions(paymentOptions, context) {
502
+ const allRequirements = [];
503
+ for (const option of paymentOptions) {
504
+ const resolvedPayTo = typeof option.payTo === "function" ? await option.payTo(context) : option.payTo;
505
+ const resolvedPrice = typeof option.price === "function" ? await option.price(context) : option.price;
506
+ const resourceConfig = {
507
+ scheme: option.scheme,
508
+ payTo: resolvedPayTo,
509
+ price: resolvedPrice,
510
+ network: option.network,
511
+ maxTimeoutSeconds: option.maxTimeoutSeconds
512
+ };
513
+ const requirements = await this.buildPaymentRequirements(resourceConfig);
514
+ allRequirements.push(...requirements);
515
+ }
516
+ return allRequirements;
517
+ }
518
+ /**
519
+ * Create a payment required response
520
+ *
521
+ * @param requirements - Payment requirements
522
+ * @param resourceInfo - Resource information
523
+ * @param error - Error message
524
+ * @param extensions - Optional extensions
525
+ * @returns Payment required response object
526
+ */
527
+ createPaymentRequiredResponse(requirements, resourceInfo, error, extensions) {
528
+ const response = {
529
+ t402Version: 2,
530
+ error,
531
+ resource: resourceInfo,
532
+ accepts: requirements
533
+ };
534
+ if (extensions && Object.keys(extensions).length > 0) {
535
+ response.extensions = extensions;
536
+ }
537
+ return response;
538
+ }
539
+ /**
540
+ * Verify a payment against requirements
541
+ *
542
+ * @param paymentPayload - The payment payload to verify
543
+ * @param requirements - The payment requirements
544
+ * @returns Verification response
545
+ */
546
+ async verifyPayment(paymentPayload, requirements) {
547
+ const context = {
548
+ paymentPayload,
549
+ requirements
550
+ };
551
+ for (const hook of this.beforeVerifyHooks) {
552
+ const result = await hook(context);
553
+ if (result && "abort" in result && result.abort) {
554
+ return {
555
+ isValid: false,
556
+ invalidReason: result.reason
557
+ };
558
+ }
559
+ }
560
+ try {
561
+ const facilitatorClient = this.getFacilitatorClient(
562
+ paymentPayload.t402Version,
563
+ requirements.network,
564
+ requirements.scheme
565
+ );
566
+ let verifyResult;
567
+ if (!facilitatorClient) {
568
+ let lastError;
569
+ for (const client of this.facilitatorClients) {
570
+ try {
571
+ verifyResult = await client.verify(paymentPayload, requirements);
572
+ break;
573
+ } catch (error) {
574
+ lastError = error;
575
+ }
576
+ }
577
+ if (!verifyResult) {
578
+ throw lastError || new Error(
579
+ `No facilitator supports ${requirements.scheme} on ${requirements.network} for v${paymentPayload.t402Version}`
580
+ );
581
+ }
582
+ } else {
583
+ verifyResult = await facilitatorClient.verify(paymentPayload, requirements);
584
+ }
585
+ const resultContext = {
586
+ ...context,
587
+ result: verifyResult
588
+ };
589
+ for (const hook of this.afterVerifyHooks) {
590
+ await hook(resultContext);
591
+ }
592
+ return verifyResult;
593
+ } catch (error) {
594
+ const failureContext = {
595
+ ...context,
596
+ error
597
+ };
598
+ for (const hook of this.onVerifyFailureHooks) {
599
+ const result = await hook(failureContext);
600
+ if (result && "recovered" in result && result.recovered) {
601
+ return result.result;
602
+ }
603
+ }
604
+ throw error;
605
+ }
606
+ }
607
+ /**
608
+ * Settle a verified payment
609
+ *
610
+ * @param paymentPayload - The payment payload to settle
611
+ * @param requirements - The payment requirements
612
+ * @returns Settlement response
613
+ */
614
+ async settlePayment(paymentPayload, requirements) {
615
+ const context = {
616
+ paymentPayload,
617
+ requirements
618
+ };
619
+ for (const hook of this.beforeSettleHooks) {
620
+ const result = await hook(context);
621
+ if (result && "abort" in result && result.abort) {
622
+ throw new Error(`Settlement aborted: ${result.reason}`);
623
+ }
624
+ }
625
+ try {
626
+ const facilitatorClient = this.getFacilitatorClient(
627
+ paymentPayload.t402Version,
628
+ requirements.network,
629
+ requirements.scheme
630
+ );
631
+ let settleResult;
632
+ if (!facilitatorClient) {
633
+ let lastError;
634
+ for (const client of this.facilitatorClients) {
635
+ try {
636
+ settleResult = await client.settle(paymentPayload, requirements);
637
+ break;
638
+ } catch (error) {
639
+ lastError = error;
640
+ }
641
+ }
642
+ if (!settleResult) {
643
+ throw lastError || new Error(
644
+ `No facilitator supports ${requirements.scheme} on ${requirements.network} for v${paymentPayload.t402Version}`
645
+ );
646
+ }
647
+ } else {
648
+ settleResult = await facilitatorClient.settle(paymentPayload, requirements);
649
+ }
650
+ const resultContext = {
651
+ ...context,
652
+ result: settleResult
653
+ };
654
+ for (const hook of this.afterSettleHooks) {
655
+ await hook(resultContext);
656
+ }
657
+ return settleResult;
658
+ } catch (error) {
659
+ const failureContext = {
660
+ ...context,
661
+ error
662
+ };
663
+ for (const hook of this.onSettleFailureHooks) {
664
+ const result = await hook(failureContext);
665
+ if (result && "recovered" in result && result.recovered) {
666
+ return result.result;
667
+ }
668
+ }
669
+ throw error;
670
+ }
671
+ }
672
+ /**
673
+ * Find matching payment requirements for a payment
674
+ *
675
+ * @param availableRequirements - Array of available payment requirements
676
+ * @param paymentPayload - The payment payload
677
+ * @returns Matching payment requirements or undefined
678
+ */
679
+ findMatchingRequirements(availableRequirements, paymentPayload) {
680
+ switch (paymentPayload.t402Version) {
681
+ case 2:
682
+ return availableRequirements.find(
683
+ (paymentRequirements) => deepEqual(paymentRequirements, paymentPayload.accepted)
684
+ );
685
+ case 1:
686
+ return availableRequirements.find(
687
+ (req) => req.scheme === paymentPayload.accepted.scheme && req.network === paymentPayload.accepted.network
688
+ );
689
+ default:
690
+ throw new Error(
691
+ `Unsupported t402 version: ${paymentPayload.t402Version}`
692
+ );
693
+ }
694
+ }
695
+ /**
696
+ * Process a payment request
697
+ *
698
+ * @param paymentPayload - Optional payment payload if provided
699
+ * @param resourceConfig - Configuration for the protected resource
700
+ * @param resourceInfo - Information about the resource being accessed
701
+ * @param extensions - Optional extensions to include in the response
702
+ * @returns Processing result
703
+ */
704
+ async processPaymentRequest(paymentPayload, resourceConfig, resourceInfo, extensions) {
705
+ const requirements = await this.buildPaymentRequirements(resourceConfig);
706
+ if (!paymentPayload) {
707
+ return {
708
+ success: false,
709
+ requiresPayment: this.createPaymentRequiredResponse(
710
+ requirements,
711
+ resourceInfo,
712
+ "Payment required",
713
+ extensions
714
+ )
715
+ };
716
+ }
717
+ const matchingRequirements = this.findMatchingRequirements(requirements, paymentPayload);
718
+ if (!matchingRequirements) {
719
+ return {
720
+ success: false,
721
+ requiresPayment: this.createPaymentRequiredResponse(
722
+ requirements,
723
+ resourceInfo,
724
+ "No matching payment requirements found",
725
+ extensions
726
+ )
727
+ };
728
+ }
729
+ const verificationResult = await this.verifyPayment(paymentPayload, matchingRequirements);
730
+ if (!verificationResult.isValid) {
731
+ return {
732
+ success: false,
733
+ error: verificationResult.invalidReason,
734
+ verificationResult
735
+ };
736
+ }
737
+ return {
738
+ success: true,
739
+ verificationResult
740
+ };
741
+ }
742
+ /**
743
+ * Get facilitator client for a specific version, network, and scheme
744
+ *
745
+ * @param t402Version - The t402 version
746
+ * @param network - The network identifier
747
+ * @param scheme - The payment scheme
748
+ * @returns The facilitator client or undefined if not found
749
+ */
750
+ getFacilitatorClient(t402Version2, network, scheme) {
751
+ const versionMap = this.facilitatorClientsMap.get(t402Version2);
752
+ if (!versionMap) return void 0;
753
+ return findByNetworkAndScheme(versionMap, scheme, network);
754
+ }
755
+ };
756
+
757
+ // src/http/index.ts
758
+ function decodePaymentSignatureHeader(paymentSignatureHeader) {
759
+ if (!Base64EncodedRegex.test(paymentSignatureHeader)) {
760
+ throw new Error("Invalid payment signature header");
761
+ }
762
+ return JSON.parse(safeBase64Decode(paymentSignatureHeader));
763
+ }
764
+ function encodePaymentRequiredHeader(paymentRequired) {
765
+ return safeBase64Encode(JSON.stringify(paymentRequired));
766
+ }
767
+ function encodePaymentResponseHeader(paymentResponse) {
768
+ return safeBase64Encode(JSON.stringify(paymentResponse));
769
+ }
770
+
771
+ // src/http/t402HTTPResourceServer.ts
772
+ var RouteConfigurationError = class extends Error {
773
+ /**
774
+ * Creates a new RouteConfigurationError with the given validation errors.
775
+ *
776
+ * @param errors - The validation errors that caused this exception.
777
+ */
778
+ constructor(errors) {
779
+ const message = `t402 Route Configuration Errors:
780
+ ${errors.map((e) => ` - ${e.message}`).join("\n")}`;
781
+ super(message);
782
+ this.name = "RouteConfigurationError";
783
+ this.errors = errors;
784
+ }
785
+ };
786
+ var t402HTTPResourceServer = class {
787
+ /**
788
+ * Creates a new t402HTTPResourceServer instance.
789
+ *
790
+ * @param ResourceServer - The core t402ResourceServer instance to use
791
+ * @param routes - Route configuration for payment-protected endpoints
792
+ */
793
+ constructor(ResourceServer, routes) {
794
+ this.compiledRoutes = [];
795
+ this.ResourceServer = ResourceServer;
796
+ this.routesConfig = routes;
797
+ const normalizedRoutes = typeof routes === "object" && !("accepts" in routes) ? routes : { "*": routes };
798
+ for (const [pattern, config] of Object.entries(normalizedRoutes)) {
799
+ const parsed = this.parseRoutePattern(pattern);
800
+ this.compiledRoutes.push({
801
+ verb: parsed.verb,
802
+ regex: parsed.regex,
803
+ config
804
+ });
805
+ }
806
+ }
807
+ /**
808
+ * Initialize the HTTP resource server.
809
+ *
810
+ * This method initializes the underlying resource server (fetching facilitator support)
811
+ * and then validates that all route payment configurations have corresponding
812
+ * registered schemes and facilitator support.
813
+ *
814
+ * @throws RouteConfigurationError if any route's payment options don't have
815
+ * corresponding registered schemes or facilitator support
816
+ *
817
+ * @example
818
+ * ```typescript
819
+ * const httpServer = new t402HTTPResourceServer(server, routes);
820
+ * await httpServer.initialize();
821
+ * ```
822
+ */
823
+ async initialize() {
824
+ await this.ResourceServer.initialize();
825
+ const errors = this.validateRouteConfiguration();
826
+ if (errors.length > 0) {
827
+ throw new RouteConfigurationError(errors);
828
+ }
829
+ }
830
+ /**
831
+ * Register a custom paywall provider for generating HTML
832
+ *
833
+ * @param provider - PaywallProvider instance
834
+ * @returns This service instance for chaining
835
+ */
836
+ registerPaywallProvider(provider) {
837
+ this.paywallProvider = provider;
838
+ return this;
839
+ }
840
+ /**
841
+ * Process HTTP request and return response instructions
842
+ * This is the main entry point for framework middleware
843
+ *
844
+ * @param context - HTTP request context
845
+ * @param paywallConfig - Optional paywall configuration
846
+ * @returns Process result indicating next action for middleware
847
+ */
848
+ async processHTTPRequest(context, paywallConfig) {
849
+ const { adapter, path, method } = context;
850
+ const routeConfig = this.getRouteConfig(path, method);
851
+ if (!routeConfig) {
852
+ return { type: "no-payment-required" };
853
+ }
854
+ const paymentOptions = this.normalizePaymentOptions(routeConfig);
855
+ const paymentPayload = this.extractPayment(adapter);
856
+ const resourceInfo = {
857
+ url: routeConfig.resource || context.adapter.getUrl(),
858
+ description: routeConfig.description || "",
859
+ mimeType: routeConfig.mimeType || ""
860
+ };
861
+ const requirements = await this.ResourceServer.buildPaymentRequirementsFromOptions(
862
+ paymentOptions,
863
+ context
864
+ );
865
+ let extensions = routeConfig.extensions;
866
+ if (extensions) {
867
+ extensions = this.ResourceServer.enrichExtensions(extensions, context);
868
+ }
869
+ const paymentRequired = this.ResourceServer.createPaymentRequiredResponse(
870
+ requirements,
871
+ resourceInfo,
872
+ !paymentPayload ? "Payment required" : void 0,
873
+ extensions
874
+ );
875
+ if (!paymentPayload) {
876
+ const unpaidBody = routeConfig.unpaidResponseBody ? await routeConfig.unpaidResponseBody(context) : void 0;
877
+ return {
878
+ type: "payment-error",
879
+ response: this.createHTTPResponse(
880
+ paymentRequired,
881
+ this.isWebBrowser(adapter),
882
+ paywallConfig,
883
+ routeConfig.customPaywallHtml,
884
+ unpaidBody
885
+ )
886
+ };
887
+ }
888
+ try {
889
+ const matchingRequirements = this.ResourceServer.findMatchingRequirements(
890
+ paymentRequired.accepts,
891
+ paymentPayload
892
+ );
893
+ if (!matchingRequirements) {
894
+ const errorResponse = this.ResourceServer.createPaymentRequiredResponse(
895
+ requirements,
896
+ resourceInfo,
897
+ "No matching payment requirements",
898
+ routeConfig.extensions
899
+ );
900
+ return {
901
+ type: "payment-error",
902
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
903
+ };
904
+ }
905
+ const verifyResult = await this.ResourceServer.verifyPayment(
906
+ paymentPayload,
907
+ matchingRequirements
908
+ );
909
+ if (!verifyResult.isValid) {
910
+ const errorResponse = this.ResourceServer.createPaymentRequiredResponse(
911
+ requirements,
912
+ resourceInfo,
913
+ verifyResult.invalidReason,
914
+ routeConfig.extensions
915
+ );
916
+ return {
917
+ type: "payment-error",
918
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
919
+ };
920
+ }
921
+ return {
922
+ type: "payment-verified",
923
+ paymentPayload,
924
+ paymentRequirements: matchingRequirements
925
+ };
926
+ } catch (error) {
927
+ const errorResponse = this.ResourceServer.createPaymentRequiredResponse(
928
+ requirements,
929
+ resourceInfo,
930
+ error instanceof Error ? error.message : "Payment verification failed",
931
+ routeConfig.extensions
932
+ );
933
+ return {
934
+ type: "payment-error",
935
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
936
+ };
937
+ }
938
+ }
939
+ /**
940
+ * Process settlement after successful response
941
+ *
942
+ * @param paymentPayload - The verified payment payload
943
+ * @param requirements - The matching payment requirements
944
+ * @returns ProcessSettleResultResponse - SettleResponse with headers if success or errorReason if failure
945
+ */
946
+ async processSettlement(paymentPayload, requirements) {
947
+ try {
948
+ const settleResponse = await this.ResourceServer.settlePayment(paymentPayload, requirements);
949
+ if (!settleResponse.success) {
950
+ return {
951
+ ...settleResponse,
952
+ success: false,
953
+ errorReason: settleResponse.errorReason || "Settlement failed"
954
+ };
955
+ }
956
+ return {
957
+ ...settleResponse,
958
+ success: true,
959
+ headers: this.createSettlementHeaders(settleResponse, requirements),
960
+ requirements
961
+ };
962
+ } catch (error) {
963
+ throw new Error(error instanceof Error ? error.message : "Settlement failed");
964
+ }
965
+ }
966
+ /**
967
+ * Check if a request requires payment based on route configuration
968
+ *
969
+ * @param context - HTTP request context
970
+ * @returns True if the route requires payment, false otherwise
971
+ */
972
+ requiresPayment(context) {
973
+ const routeConfig = this.getRouteConfig(context.path, context.method);
974
+ return routeConfig !== void 0;
975
+ }
976
+ /**
977
+ * Normalizes a RouteConfig's accepts field into an array of PaymentOptions
978
+ * Handles both single PaymentOption and array formats
979
+ *
980
+ * @param routeConfig - Route configuration
981
+ * @returns Array of payment options
982
+ */
983
+ normalizePaymentOptions(routeConfig) {
984
+ return Array.isArray(routeConfig.accepts) ? routeConfig.accepts : [routeConfig.accepts];
985
+ }
986
+ /**
987
+ * Validates that all payment options in routes have corresponding registered schemes
988
+ * and facilitator support.
989
+ *
990
+ * @returns Array of validation errors (empty if all routes are valid)
991
+ */
992
+ validateRouteConfiguration() {
993
+ const errors = [];
994
+ const normalizedRoutes = typeof this.routesConfig === "object" && !("accepts" in this.routesConfig) ? Object.entries(this.routesConfig) : [["*", this.routesConfig]];
995
+ for (const [pattern, config] of normalizedRoutes) {
996
+ const paymentOptions = this.normalizePaymentOptions(config);
997
+ for (const option of paymentOptions) {
998
+ if (!this.ResourceServer.hasRegisteredScheme(option.network, option.scheme)) {
999
+ errors.push({
1000
+ routePattern: pattern,
1001
+ scheme: option.scheme,
1002
+ network: option.network,
1003
+ reason: "missing_scheme",
1004
+ message: `Route "${pattern}": No scheme implementation registered for "${option.scheme}" on network "${option.network}"`
1005
+ });
1006
+ continue;
1007
+ }
1008
+ const supportedKind = this.ResourceServer.getSupportedKind(
1009
+ t402Version,
1010
+ option.network,
1011
+ option.scheme
1012
+ );
1013
+ if (!supportedKind) {
1014
+ errors.push({
1015
+ routePattern: pattern,
1016
+ scheme: option.scheme,
1017
+ network: option.network,
1018
+ reason: "missing_facilitator",
1019
+ message: `Route "${pattern}": Facilitator does not support scheme "${option.scheme}" on network "${option.network}"`
1020
+ });
1021
+ }
1022
+ }
1023
+ }
1024
+ return errors;
1025
+ }
1026
+ /**
1027
+ * Get route configuration for a request
1028
+ *
1029
+ * @param path - Request path
1030
+ * @param method - HTTP method
1031
+ * @returns Route configuration or undefined if no match
1032
+ */
1033
+ getRouteConfig(path, method) {
1034
+ const normalizedPath = this.normalizePath(path);
1035
+ const upperMethod = method.toUpperCase();
1036
+ const matchingRoute = this.compiledRoutes.find(
1037
+ (route) => route.regex.test(normalizedPath) && (route.verb === "*" || route.verb === upperMethod)
1038
+ );
1039
+ return matchingRoute?.config;
1040
+ }
1041
+ /**
1042
+ * Extract payment from HTTP headers (handles v1 and v2)
1043
+ *
1044
+ * @param adapter - HTTP adapter
1045
+ * @returns Decoded payment payload or null
1046
+ */
1047
+ extractPayment(adapter) {
1048
+ const header = adapter.getHeader("payment-signature") || adapter.getHeader("PAYMENT-SIGNATURE");
1049
+ if (header) {
1050
+ try {
1051
+ return decodePaymentSignatureHeader(header);
1052
+ } catch (error) {
1053
+ console.warn("Failed to decode PAYMENT-SIGNATURE header:", error);
1054
+ }
1055
+ }
1056
+ return null;
1057
+ }
1058
+ /**
1059
+ * Check if request is from a web browser
1060
+ *
1061
+ * @param adapter - HTTP adapter
1062
+ * @returns True if request appears to be from a browser
1063
+ */
1064
+ isWebBrowser(adapter) {
1065
+ const accept = adapter.getAcceptHeader();
1066
+ const userAgent = adapter.getUserAgent();
1067
+ return accept.includes("text/html") && userAgent.includes("Mozilla");
1068
+ }
1069
+ /**
1070
+ * Create HTTP response instructions from payment required
1071
+ *
1072
+ * @param paymentRequired - Payment requirements
1073
+ * @param isWebBrowser - Whether request is from browser
1074
+ * @param paywallConfig - Paywall configuration
1075
+ * @param customHtml - Custom HTML template
1076
+ * @param unpaidResponse - Optional custom response (content type and body) for unpaid API requests
1077
+ * @returns Response instructions
1078
+ */
1079
+ createHTTPResponse(paymentRequired, isWebBrowser, paywallConfig, customHtml, unpaidResponse) {
1080
+ if (isWebBrowser) {
1081
+ const html = this.generatePaywallHTML(paymentRequired, paywallConfig, customHtml);
1082
+ return {
1083
+ status: 402,
1084
+ headers: { "Content-Type": "text/html" },
1085
+ body: html,
1086
+ isHtml: true
1087
+ };
1088
+ }
1089
+ const response = this.createHTTPPaymentRequiredResponse(paymentRequired);
1090
+ const contentType = unpaidResponse ? unpaidResponse.contentType : "application/json";
1091
+ const body = unpaidResponse ? unpaidResponse.body : {};
1092
+ return {
1093
+ status: 402,
1094
+ headers: {
1095
+ "Content-Type": contentType,
1096
+ ...response.headers
1097
+ },
1098
+ body
1099
+ };
1100
+ }
1101
+ /**
1102
+ * Create HTTP payment required response (v1 puts in body, v2 puts in header)
1103
+ *
1104
+ * @param paymentRequired - Payment required object
1105
+ * @returns Headers and body for the HTTP response
1106
+ */
1107
+ createHTTPPaymentRequiredResponse(paymentRequired) {
1108
+ return {
1109
+ headers: {
1110
+ "PAYMENT-REQUIRED": encodePaymentRequiredHeader(paymentRequired)
1111
+ }
1112
+ };
1113
+ }
1114
+ /**
1115
+ * Create settlement response headers
1116
+ *
1117
+ * @param settleResponse - Settlement response
1118
+ * @param requirements - Payment requirements that were settled
1119
+ * @returns Headers to add to response
1120
+ */
1121
+ createSettlementHeaders(settleResponse, requirements) {
1122
+ const encoded = encodePaymentResponseHeader({
1123
+ ...settleResponse,
1124
+ requirements
1125
+ });
1126
+ return { "PAYMENT-RESPONSE": encoded };
1127
+ }
1128
+ /**
1129
+ * Parse route pattern into verb and regex
1130
+ *
1131
+ * @param pattern - Route pattern like "GET /api/*" or "/api/[id]"
1132
+ * @returns Parsed pattern with verb and regex
1133
+ */
1134
+ parseRoutePattern(pattern) {
1135
+ const [verb, path] = pattern.includes(" ") ? pattern.split(/\s+/) : ["*", pattern];
1136
+ const regex = new RegExp(
1137
+ `^${path.replace(/[$()+.?^{|}]/g, "\\$&").replace(/\*/g, ".*?").replace(/\[([^\]]+)\]/g, "[^/]+").replace(/\//g, "\\/")}$`,
1138
+ "i"
1139
+ );
1140
+ return { verb: verb.toUpperCase(), regex };
1141
+ }
1142
+ /**
1143
+ * Normalize path for matching
1144
+ *
1145
+ * @param path - Raw path from request
1146
+ * @returns Normalized path
1147
+ */
1148
+ normalizePath(path) {
1149
+ try {
1150
+ const pathWithoutQuery = path.split(/[?#]/)[0];
1151
+ const decodedPath = decodeURIComponent(pathWithoutQuery);
1152
+ return decodedPath.replace(/\\/g, "/").replace(/\/+/g, "/").replace(/(.+?)\/+$/, "$1");
1153
+ } catch {
1154
+ return path;
1155
+ }
1156
+ }
1157
+ /**
1158
+ * Generate paywall HTML for browser requests
1159
+ *
1160
+ * @param paymentRequired - Payment required response
1161
+ * @param paywallConfig - Optional paywall configuration
1162
+ * @param customHtml - Optional custom HTML template
1163
+ * @returns HTML string
1164
+ */
1165
+ generatePaywallHTML(paymentRequired, paywallConfig, customHtml) {
1166
+ if (customHtml) {
1167
+ return customHtml;
1168
+ }
1169
+ if (this.paywallProvider) {
1170
+ return this.paywallProvider.generateHtml(paymentRequired, paywallConfig);
1171
+ }
1172
+ try {
1173
+ const paywall = require("@t402/paywall");
1174
+ const displayAmount2 = this.getDisplayAmount(paymentRequired);
1175
+ const resource2 = paymentRequired.resource;
1176
+ return paywall.getPaywallHtml({
1177
+ amount: displayAmount2,
1178
+ paymentRequired,
1179
+ currentUrl: resource2?.url || paywallConfig?.currentUrl || "",
1180
+ testnet: paywallConfig?.testnet ?? true,
1181
+ appName: paywallConfig?.appName,
1182
+ appLogo: paywallConfig?.appLogo,
1183
+ sessionTokenEndpoint: paywallConfig?.sessionTokenEndpoint
1184
+ });
1185
+ } catch {
1186
+ }
1187
+ const resource = paymentRequired.resource;
1188
+ const displayAmount = this.getDisplayAmount(paymentRequired);
1189
+ return `
1190
+ <!DOCTYPE html>
1191
+ <html>
1192
+ <head>
1193
+ <title>Payment Required</title>
1194
+ <meta charset="UTF-8">
1195
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1196
+ </head>
1197
+ <body>
1198
+ <div style="max-width: 600px; margin: 50px auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
1199
+ ${paywallConfig?.appLogo ? `<img src="${paywallConfig.appLogo}" alt="${paywallConfig.appName || "App"}" style="max-width: 200px; margin-bottom: 20px;">` : ""}
1200
+ <h1>Payment Required</h1>
1201
+ ${resource ? `<p><strong>Resource:</strong> ${resource.description || resource.url}</p>` : ""}
1202
+ <p><strong>Amount:</strong> $${displayAmount.toFixed(2)} USDC</p>
1203
+ <div id="payment-widget"
1204
+ data-requirements='${JSON.stringify(paymentRequired)}'
1205
+ data-app-name="${paywallConfig?.appName || ""}"
1206
+ data-testnet="${paywallConfig?.testnet || false}">
1207
+ <!-- Install @t402/paywall for full wallet integration -->
1208
+ <p style="margin-top: 2rem; padding: 1rem; background: #fef3c7; border-radius: 0.5rem;">
1209
+ <strong>Note:</strong> Install <code>@t402/paywall</code> for full wallet connection and payment UI.
1210
+ </p>
1211
+ </div>
1212
+ </div>
1213
+ </body>
1214
+ </html>
1215
+ `;
1216
+ }
1217
+ /**
1218
+ * Extract display amount from payment requirements.
1219
+ *
1220
+ * @param paymentRequired - The payment required object
1221
+ * @returns The display amount in decimal format
1222
+ */
1223
+ getDisplayAmount(paymentRequired) {
1224
+ const accepts = paymentRequired.accepts;
1225
+ if (accepts && accepts.length > 0) {
1226
+ const firstReq = accepts[0];
1227
+ if ("amount" in firstReq) {
1228
+ return parseFloat(firstReq.amount) / 1e6;
1229
+ }
1230
+ }
1231
+ return 0;
1232
+ }
1233
+ };
1234
+ // Annotate the CommonJS export names for ESM import in node:
1235
+ 0 && (module.exports = {
1236
+ HTTPFacilitatorClient,
1237
+ RouteConfigurationError,
1238
+ t402HTTPResourceServer,
1239
+ t402ResourceServer
1240
+ });
1241
+ //# sourceMappingURL=index.js.map