@parallel-protocol/x402 0.7.0 → 0.8.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.
@@ -1,4 +1,4 @@
1
- import { X402_ERROR_CODES, X402RuntimeError, X402ConfigError, isPriceRejection } from './chunk-AFRDHGDT.js';
1
+ import { X402_ERROR_CODES, X402RuntimeError, X402ConfigError, isPriceRejection } from './chunk-QAECRSWF.js';
2
2
  import { getTokenDecimals as getTokenDecimals$1 } from '@parallel-protocol/chains';
3
3
  import { z } from 'zod';
4
4
 
@@ -170,26 +170,32 @@ var facilitatorResponseSchema = z.discriminatedUnion("success", [
170
170
  chain: z.string(),
171
171
  gasSponsored: z.boolean(),
172
172
  networkId: z.string(),
173
- amountSettled: z.string().optional()
173
+ amountSettled: z.string().optional(),
174
+ alreadySettled: z.boolean().optional()
174
175
  }),
175
176
  z.object({
176
177
  success: z.literal(false),
177
178
  error: z.object({
178
179
  code: z.string(),
179
180
  message: z.string(),
180
- hint: z.string().optional()
181
+ hint: z.string().optional(),
182
+ status: z.number().optional(),
183
+ retryAfter: z.string().optional()
181
184
  })
182
185
  })
183
186
  ]);
184
187
  var verifyResponseSchema = z.object({
185
- isValid: z.literal(true)
188
+ isValid: z.literal(true),
189
+ settleToken: z.string().min(1)
186
190
  });
187
191
  var settleSuccessSchema = z.object({
188
192
  txHash: txHashSchema,
189
193
  route: z.string(),
190
194
  gasSponsored: z.boolean(),
191
195
  // Metered routes: the amount actually charged (smallest unit).
192
- amountSettled: z.string().optional()
196
+ amountSettled: z.string().optional(),
197
+ // The facilitator answered from a recorded outcome, nothing was broadcast.
198
+ alreadySettled: z.boolean().optional()
193
199
  });
194
200
  var settleErrorSchema = z.object({
195
201
  error: z.string(),
@@ -203,6 +209,20 @@ var settleResponseSchema = z.union([
203
209
  // src/client.ts
204
210
  var FACILITATOR_TIMEOUT_MS = 1e4;
205
211
  var SETTLE_TIMEOUT_MS = 6e4;
212
+ var RETRY_AFTER_SHAPE = /^(?:\d{1,10}|[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT)$/;
213
+ async function readErrorBody(response) {
214
+ const json = await response.json().catch(() => null);
215
+ if (typeof json !== "object" || json === null) return {};
216
+ const { error, message } = json;
217
+ return {
218
+ ...typeof error === "string" && { error },
219
+ ...typeof message === "string" && { message }
220
+ };
221
+ }
222
+ function retryAfterOf(response) {
223
+ const value = response.headers.get("retry-after");
224
+ return value !== null && RETRY_AFTER_SHAPE.test(value) ? value : void 0;
225
+ }
206
226
  function decodePaymentHeader(paymentHeader) {
207
227
  try {
208
228
  return decodeBase64(paymentHeader);
@@ -226,9 +246,15 @@ var FacilitatorClient = class {
226
246
  signal: AbortSignal.timeout(FACILITATOR_TIMEOUT_MS)
227
247
  });
228
248
  if (!response.ok) {
229
- const errorBody = await response.json().catch(() => null);
230
- const code = errorBody?.error ?? X402_ERROR_CODES.FACILITATOR_UNAVAILABLE;
231
- throw new X402RuntimeError(code);
249
+ const body = await readErrorBody(response);
250
+ throw new X402RuntimeError(
251
+ body.error ?? X402_ERROR_CODES.FACILITATOR_UNAVAILABLE,
252
+ {
253
+ detail: body.message,
254
+ status: response.status,
255
+ retryAfter: retryAfterOf(response)
256
+ }
257
+ );
232
258
  }
233
259
  json = await response.json();
234
260
  } catch (e) {
@@ -236,7 +262,8 @@ var FacilitatorClient = class {
236
262
  throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
237
263
  }
238
264
  try {
239
- verifyResponseSchema.parse(json);
265
+ const { settleToken } = verifyResponseSchema.parse(json);
266
+ return { settleToken };
240
267
  } catch {
241
268
  throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
242
269
  }
@@ -248,25 +275,42 @@ var FacilitatorClient = class {
248
275
  const response = await fetch(`${this.config.url}/x402/settle`, {
249
276
  method: "POST",
250
277
  headers: this.buildHeaders(),
251
- // settleAmount only travels for metered routes — exact bodies are unchanged.
278
+ // settleAmount only travels for metered routes; the settle token
279
+ // travels whenever verify issued one.
252
280
  body: JSON.stringify({
253
281
  paymentPayload,
254
282
  paymentRequirements,
255
283
  ...options?.amount !== void 0 && {
256
284
  settleAmount: options.amount
285
+ },
286
+ ...options?.settleToken !== void 0 && {
287
+ settleToken: options.settleToken
257
288
  }
258
289
  }),
259
290
  signal: AbortSignal.timeout(SETTLE_TIMEOUT_MS)
260
291
  });
261
292
  if (!response.ok) {
262
- const errorBody = await response.json().catch(() => null);
263
- const code = errorBody?.error ?? "SUBMISSION_FAILED";
264
- const message = errorBody?.message ?? "Settlement failed";
265
- return { success: false, error: { code, message } };
293
+ const body = await readErrorBody(response);
294
+ if (body.error === void 0 && response.status >= 500) {
295
+ throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
296
+ }
297
+ const retryAfter = retryAfterOf(response);
298
+ return {
299
+ success: false,
300
+ error: {
301
+ code: body.error ?? "SUBMISSION_FAILED",
302
+ message: body.message ?? "Settlement failed",
303
+ status: response.status,
304
+ ...retryAfter !== void 0 && { retryAfter }
305
+ }
306
+ };
266
307
  }
267
308
  json = await response.json();
268
309
  } catch (e) {
269
310
  if (e instanceof X402RuntimeError) throw e;
311
+ if (e instanceof Error && e.name === "TimeoutError") {
312
+ throw new X402RuntimeError(X402_ERROR_CODES.SETTLE_TIMEOUT);
313
+ }
270
314
  throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
271
315
  }
272
316
  try {
@@ -281,7 +325,8 @@ var FacilitatorClient = class {
281
325
  networkId: toEip155Network(paymentRequirements.network),
282
326
  ...parsed.amountSettled !== void 0 && {
283
327
  amountSettled: parsed.amountSettled
284
- }
328
+ },
329
+ ...parsed.alreadySettled && { alreadySettled: true }
285
330
  };
286
331
  }
287
332
  return {
@@ -379,10 +424,21 @@ async function resolvePrice(config, ctx) {
379
424
  }
380
425
  var SETTLE_AMOUNT_HEADER = "payment-settle-amount";
381
426
  var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
427
+ var SETTLEMENT_RESERVE_SECONDS = 120;
382
428
  function handlerTimeoutFor(config) {
383
429
  if ((config.scheme ?? "exact") !== "upto") return HANDLER_TIMEOUT_MS;
384
430
  const seconds = config.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
385
- return Math.max(HANDLER_TIMEOUT_MS, (seconds - 60) * 1e3);
431
+ return Math.max(
432
+ HANDLER_TIMEOUT_MS,
433
+ (seconds - SETTLEMENT_RESERVE_SECONDS) * 1e3
434
+ );
435
+ }
436
+ function jsonResult(status, body, headers = {}) {
437
+ return {
438
+ status,
439
+ headers: { "Content-Type": "application/json", ...headers },
440
+ body
441
+ };
386
442
  }
387
443
  function resolveSettleAmount(raw, maxAmount, decimals) {
388
444
  if (raw === void 0)
@@ -410,7 +466,10 @@ var INVALID_METER_RESPONSE = {
410
466
  message: "The route's meter function failed or returned something that is not an amount; the response was discarded and nothing was charged."
411
467
  };
412
468
  function invalidSettleAmountResponse(reason) {
413
- return reason === "meter" ? INVALID_METER_RESPONSE : INVALID_SETTLE_AMOUNT_RESPONSE;
469
+ return jsonResult(
470
+ 402,
471
+ reason === "meter" ? INVALID_METER_RESPONSE : INVALID_SETTLE_AMOUNT_RESPONSE
472
+ );
414
473
  }
415
474
  var AMOUNT_FORMAT = /^\d+(?:\.\d+)?$/;
416
475
  async function resolveCharge(verified, settleHeader, response) {
@@ -558,23 +617,37 @@ function buildFacilitatorRequirements(config, asset, price) {
558
617
  }
559
618
  };
560
619
  }
561
- function paymentRequiredResponse(url, config, resolvedTokens, price, error) {
620
+ var CODE_SHAPE = /^[A-Z][A-Z0-9_]{2,63}$/;
621
+ var MESSAGE_MAX_LENGTH = 300;
622
+ function shapedCode(code) {
623
+ return typeof code === "string" && CODE_SHAPE.test(code) ? code : void 0;
624
+ }
625
+ function boundedMessage(message) {
626
+ if (typeof message !== "string") return void 0;
627
+ const oneLine = message.replace(/[\p{Cc}\p{Cf}]+/gu, " ").replace(/\s+/g, " ").trim();
628
+ if (!oneLine) return void 0;
629
+ const chars = Array.from(oneLine);
630
+ return chars.length > MESSAGE_MAX_LENGTH ? `${chars.slice(0, MESSAGE_MAX_LENGTH).join("")}\u2026` : oneLine;
631
+ }
632
+ function paymentRequiredResponse(url, config, resolvedTokens, price, refusal) {
562
633
  const paymentRequired = buildPaymentRequired(
563
634
  url,
564
635
  config,
565
636
  resolvedTokens,
566
637
  price
567
638
  );
568
- if (error) paymentRequired.error = error;
569
- return {
570
- status: 402,
571
- headers: {
572
- "Content-Type": "application/json",
573
- "payment-required": encodeBase64(paymentRequired),
574
- "access-control-expose-headers": "payment-required"
575
- },
576
- body: paymentRequired
577
- };
639
+ if (refusal) {
640
+ paymentRequired.error = shapedCode(refusal.code) ?? X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE;
641
+ const message = boundedMessage(refusal.detail);
642
+ if (message) paymentRequired.message = message;
643
+ }
644
+ return jsonResult(402, paymentRequired, {
645
+ "payment-required": encodeBase64(paymentRequired),
646
+ "access-control-expose-headers": "payment-required",
647
+ ...refusal?.retryAfter !== void 0 && {
648
+ "Retry-After": refusal.retryAfter
649
+ }
650
+ });
578
651
  }
579
652
  function createPaymentGate(config) {
580
653
  try {
@@ -630,10 +703,11 @@ function createPaymentGate(config) {
630
703
  asset,
631
704
  price
632
705
  );
706
+ let settleToken;
633
707
  try {
634
- await client.verify(paymentHeader, requirements);
708
+ ({ settleToken } = await client.verify(paymentHeader, requirements));
635
709
  } catch (e) {
636
- const code = e instanceof X402RuntimeError ? e.code : X402_ERROR_CODES.FACILITATOR_UNAVAILABLE;
710
+ const refusal = e instanceof X402RuntimeError ? { code: e.code, detail: e.detail, retryAfter: e.retryAfter } : { code: X402_ERROR_CODES.FACILITATOR_UNAVAILABLE };
637
711
  return {
638
712
  type: "error",
639
713
  result: paymentRequiredResponse(
@@ -641,7 +715,7 @@ function createPaymentGate(config) {
641
715
  routeConfig,
642
716
  resolvedTokens,
643
717
  price,
644
- code
718
+ refusal
645
719
  )
646
720
  };
647
721
  }
@@ -652,12 +726,71 @@ function createPaymentGate(config) {
652
726
  maxAmount: BigInt(requirements.maxAmountRequired),
653
727
  decimals: resolveDecimals(routeConfig, asset),
654
728
  handlerTimeoutMs: handlerTimeoutFor(routeConfig),
655
- // Exact routes keep the historical two-argument client call.
656
- settle: (options) => options ? client.settle(paymentHeader, requirements, options) : client.settle(paymentHeader, requirements),
729
+ settle: (options) => client.settle(paymentHeader, requirements, { ...options, settleToken }),
657
730
  ...routeConfig.meter && { meter: routeConfig.meter, request }
658
731
  };
659
732
  };
660
733
  }
734
+ var UNKNOWN_SETTLEMENT_MESSAGE = "The payment could not be settled.";
735
+ var MERCHANT_SETTLEMENT_MESSAGE = "The merchant could not complete the settlement of this payment. The payment was not taken: do not sign another payment for this request before checking with the merchant.";
736
+ var AGENT_MESSAGES = {
737
+ [X402_ERROR_CODES.SETTLE_TIMEOUT]: "The facilitator did not confirm the settlement in time. The payment may still settle: do not sign another payment for this request before checking with the merchant.",
738
+ [X402_ERROR_CODES.FACILITATOR_UNAVAILABLE]: "The facilitator could not be reached to settle the payment. It may still have settled: do not sign another payment for this request before checking with the merchant.",
739
+ [X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE]: "The facilitator answered the settlement with an unexpected response. The payment may have settled: do not sign another payment for this request before checking with the merchant.",
740
+ SETTLE_IN_PROGRESS: "The facilitator is still settling this payment. Do not sign another payment for this request before checking with the merchant.",
741
+ RECEIPT_UNAVAILABLE: "The facilitator sent the transaction but could not read its confirmation. The payment may have settled: do not sign another payment for this request before checking with the merchant.",
742
+ SETTLE_TOKEN_INVALID: MERCHANT_SETTLEMENT_MESSAGE,
743
+ SETTLE_TOKEN_REQUIRED: MERCHANT_SETTLEMENT_MESSAGE,
744
+ AMOUNT_MISMATCH: "The facilitator settled a different amount than this request asked for. The response was not served: do not sign another payment for this request before checking with the merchant.",
745
+ UNKNOWN: UNKNOWN_SETTLEMENT_MESSAGE
746
+ };
747
+ function settlementFailure(code, failure) {
748
+ const reason = shapedCode(code) ?? "UNKNOWN";
749
+ return {
750
+ settled: false,
751
+ reason,
752
+ message: AGENT_MESSAGES[reason] ?? boundedMessage(failure?.message) ?? UNKNOWN_SETTLEMENT_MESSAGE,
753
+ ...failure?.retryAfter !== void 0 && {
754
+ retryAfter: failure.retryAfter
755
+ }
756
+ };
757
+ }
758
+ var RETRY_ONCE_CODES = /* @__PURE__ */ new Set([
759
+ X402_ERROR_CODES.SETTLE_TIMEOUT,
760
+ X402_ERROR_CODES.FACILITATOR_UNAVAILABLE
761
+ ]);
762
+ async function settleVerified(verified, options) {
763
+ let response;
764
+ for (let attempt = 0; response === void 0; attempt++) {
765
+ try {
766
+ response = await verified.settle(options);
767
+ } catch (e) {
768
+ const code = e instanceof X402RuntimeError ? e.code : "UNKNOWN";
769
+ if (attempt > 0 || !RETRY_ONCE_CODES.has(code)) {
770
+ return settlementFailure(code);
771
+ }
772
+ }
773
+ }
774
+ if (!response.success) {
775
+ return settlementFailure(response.error.code, response.error);
776
+ }
777
+ if (options?.amount !== void 0 && response.amountSettled !== options.amount) {
778
+ return settlementFailure("AMOUNT_MISMATCH");
779
+ }
780
+ return { settled: true, confirmation: response };
781
+ }
782
+ function settlementFailureResponse(failure) {
783
+ const body = {
784
+ error: "PAYMENT_SETTLEMENT_FAILED",
785
+ reason: failure.reason,
786
+ message: failure.message
787
+ };
788
+ return jsonResult(402, body, {
789
+ ...failure.retryAfter !== void 0 && {
790
+ "Retry-After": failure.retryAfter
791
+ }
792
+ });
793
+ }
661
794
  function createPaymentMiddleware(config) {
662
795
  const gate = createPaymentGate(config);
663
796
  return async (adapter) => {
@@ -687,11 +820,7 @@ function createPaymentMiddleware(config) {
687
820
  });
688
821
  if (resolved.kind === "invalid") {
689
822
  handlerResult.discardResponse();
690
- return {
691
- status: 402,
692
- headers: { "Content-Type": "application/json" },
693
- body: invalidSettleAmountResponse(resolved.reason)
694
- };
823
+ return invalidSettleAmountResponse(resolved.reason);
695
824
  }
696
825
  if (resolved.kind === "skip") {
697
826
  handlerResult.sendResponse();
@@ -699,37 +828,23 @@ function createPaymentMiddleware(config) {
699
828
  }
700
829
  settleOptions = { amount: resolved.amount };
701
830
  }
702
- let settled = false;
703
- try {
704
- const settleResponse = settleOptions ? await gateResult.settle(settleOptions) : await gateResult.settle();
705
- if (settleResponse.success) {
706
- handlerResult.setResponseHeader(
707
- "payment-response",
708
- encodeBase64(settleResponse)
709
- );
710
- handlerResult.setResponseHeader(
711
- "access-control-expose-headers",
712
- "payment-response"
713
- );
714
- settled = true;
715
- }
716
- } catch {
717
- }
718
- if (!settled) {
831
+ const outcome = await settleVerified(gateResult, settleOptions);
832
+ if (!outcome.settled) {
719
833
  handlerResult.discardResponse();
720
- return {
721
- status: 402,
722
- headers: { "Content-Type": "application/json" },
723
- body: {
724
- error: "PAYMENT_SETTLEMENT_FAILED",
725
- message: "Payment could not be confirmed after multiple attempts. Please retry."
726
- }
727
- };
834
+ return settlementFailureResponse(outcome);
728
835
  }
836
+ handlerResult.setResponseHeader(
837
+ "payment-response",
838
+ encodeBase64(outcome.confirmation)
839
+ );
840
+ handlerResult.setResponseHeader(
841
+ "access-control-expose-headers",
842
+ "payment-response"
843
+ );
729
844
  }
730
845
  handlerResult.sendResponse();
731
846
  return { pass: true, handlerRan: true };
732
847
  };
733
848
  }
734
849
 
735
- export { FacilitatorClient, HANDLER_TIMEOUT, PARALLEL_TOKENS, SETTLE_AMOUNT_HEADER, createPaymentGate, createPaymentMiddleware, encodeBase64, getDefaultAcceptedTokens, invalidSettleAmountResponse, isJsonContentType, jsonFromResponse, matchRoute, parseJsonBody, parsePrice, raceTimeout, resolveCharge, resolveSettleAmount, toEip155Network, validateAddress };
850
+ export { FacilitatorClient, HANDLER_TIMEOUT, PARALLEL_TOKENS, SETTLE_AMOUNT_HEADER, createPaymentGate, createPaymentMiddleware, encodeBase64, getDefaultAcceptedTokens, invalidSettleAmountResponse, isJsonContentType, jsonFromResponse, matchRoute, parseJsonBody, parsePrice, raceTimeout, resolveCharge, resolveSettleAmount, settleVerified, settlementFailureResponse, toEip155Network, validateAddress };
package/dist/express.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkPOU7EH3E_cjs = require('./chunk-POU7EH3E.cjs');
4
- require('./chunk-JPMK5YBU.cjs');
3
+ var chunkNA7245L5_cjs = require('./chunk-NA7245L5.cjs');
4
+ require('./chunk-KZZY5LS6.cjs');
5
5
 
6
6
  // src/express/adapter.ts
7
7
  function chunkBytes(args) {
@@ -108,8 +108,8 @@ var ExpressAdapter = class {
108
108
  return originalEnd(...args);
109
109
  });
110
110
  next();
111
- const outcome = await chunkPOU7EH3E_cjs.raceTimeout(endPromise, options?.timeoutMs);
112
- if (outcome === chunkPOU7EH3E_cjs.HANDLER_TIMEOUT) {
111
+ const outcome = await chunkNA7245L5_cjs.raceTimeout(endPromise, options?.timeoutMs);
112
+ if (outcome === chunkNA7245L5_cjs.HANDLER_TIMEOUT) {
113
113
  discarded = true;
114
114
  return {
115
115
  status: 504,
@@ -146,7 +146,7 @@ var ExpressAdapter = class {
146
146
  const chunk = chunkBytes(args);
147
147
  if (chunk) bytes.push(chunk);
148
148
  }
149
- return chunkPOU7EH3E_cjs.parseJsonBody(
149
+ return chunkNA7245L5_cjs.parseJsonBody(
150
150
  contentType,
151
151
  Buffer.concat(bytes).toString("utf8")
152
152
  );
@@ -170,7 +170,7 @@ var ExpressAdapter = class {
170
170
 
171
171
  // src/express/index.ts
172
172
  function paymentMiddleware(config) {
173
- const middleware = chunkPOU7EH3E_cjs.createPaymentMiddleware(config);
173
+ const middleware = chunkNA7245L5_cjs.createPaymentMiddleware(config);
174
174
  return async (req, res, next) => {
175
175
  const result = await middleware(new ExpressAdapter(req, res, next));
176
176
  if ("pass" in result) {
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-q_gu5wjz.cjs';
2
+ import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-BkGQ2lPA.cjs';
3
3
  import 'viem';
4
4
 
5
5
  declare class ExpressAdapter implements HTTPAdapter {
package/dist/express.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-q_gu5wjz.js';
2
+ import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-BkGQ2lPA.js';
3
3
  import 'viem';
4
4
 
5
5
  declare class ExpressAdapter implements HTTPAdapter {
package/dist/express.js CHANGED
@@ -1,5 +1,5 @@
1
- import { raceTimeout, HANDLER_TIMEOUT, parseJsonBody, createPaymentMiddleware } from './chunk-AENXD4OC.js';
2
- import './chunk-AFRDHGDT.js';
1
+ import { raceTimeout, HANDLER_TIMEOUT, parseJsonBody, createPaymentMiddleware } from './chunk-SA5ULSG2.js';
2
+ import './chunk-QAECRSWF.js';
3
3
 
4
4
  // src/express/adapter.ts
5
5
  function chunkBytes(args) {
package/dist/fastify.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkPOU7EH3E_cjs = require('./chunk-POU7EH3E.cjs');
4
- require('./chunk-JPMK5YBU.cjs');
3
+ var chunkNA7245L5_cjs = require('./chunk-NA7245L5.cjs');
4
+ require('./chunk-KZZY5LS6.cjs');
5
5
  var fp = require('fastify-plugin');
6
6
 
7
7
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -37,12 +37,14 @@ var FastifyAdapter = class {
37
37
  // src/fastify/index.ts
38
38
  function payloadJson(contentType, payload) {
39
39
  const text = typeof payload === "string" ? payload : payload instanceof Uint8Array ? Buffer.from(payload).toString("utf8") : void 0;
40
- return chunkPOU7EH3E_cjs.parseJsonBody(contentType, text);
40
+ return chunkNA7245L5_cjs.parseJsonBody(contentType, text);
41
41
  }
42
- function refuse(reply, body) {
43
- const json = JSON.stringify(body);
44
- reply.status(402);
45
- reply.header("Content-Type", "application/json");
42
+ function refuse(reply, result) {
43
+ const json = JSON.stringify(result.body);
44
+ reply.status(result.status);
45
+ for (const [name, value] of Object.entries(result.headers)) {
46
+ reply.header(name, value);
47
+ }
46
48
  reply.header("Content-Length", Buffer.byteLength(json));
47
49
  return json;
48
50
  }
@@ -52,11 +54,11 @@ function headerValue(value) {
52
54
  }
53
55
  function paymentMiddleware(config) {
54
56
  return fp__default.default(async (fastify) => {
55
- const gate = chunkPOU7EH3E_cjs.createPaymentGate(config);
57
+ const gate = chunkNA7245L5_cjs.createPaymentGate(config);
56
58
  const verifiedMap = /* @__PURE__ */ new WeakMap();
57
59
  const rawWriters = /* @__PURE__ */ new WeakMap();
58
60
  const needsBody = (req) => {
59
- const route = chunkPOU7EH3E_cjs.matchRoute(
61
+ const route = chunkNA7245L5_cjs.matchRoute(
60
62
  new FastifyAdapter(req).getPath(),
61
63
  config.routes
62
64
  );
@@ -109,41 +111,30 @@ function paymentMiddleware(config) {
109
111
  if (!verified) return payload;
110
112
  let raw;
111
113
  if (verified.scheme === "upto") {
112
- raw = headerValue(reply.getHeader(chunkPOU7EH3E_cjs.SETTLE_AMOUNT_HEADER));
113
- reply.removeHeader(chunkPOU7EH3E_cjs.SETTLE_AMOUNT_HEADER);
114
+ raw = headerValue(reply.getHeader(chunkNA7245L5_cjs.SETTLE_AMOUNT_HEADER));
115
+ reply.removeHeader(chunkNA7245L5_cjs.SETTLE_AMOUNT_HEADER);
114
116
  }
115
117
  if (reply.statusCode < 200 || reply.statusCode >= 300) {
116
118
  return payload;
117
119
  }
118
120
  let settleOptions;
119
121
  if (verified.scheme === "upto") {
120
- const resolved = await chunkPOU7EH3E_cjs.resolveCharge(verified, raw, {
122
+ const resolved = await chunkNA7245L5_cjs.resolveCharge(verified, raw, {
121
123
  status: reply.statusCode,
122
124
  header: (name) => headerValue(reply.getHeader(name)),
123
125
  body: async () => payloadJson(headerValue(reply.getHeader("content-type")), payload)
124
126
  });
125
127
  if (resolved.kind === "invalid") {
126
- return refuse(reply, chunkPOU7EH3E_cjs.invalidSettleAmountResponse(resolved.reason));
128
+ return refuse(reply, chunkNA7245L5_cjs.invalidSettleAmountResponse(resolved.reason));
127
129
  }
128
130
  if (resolved.kind === "skip") return payload;
129
131
  settleOptions = { amount: resolved.amount };
130
132
  }
131
- let settled = false;
132
- try {
133
- const settleResponse = settleOptions ? await verified.settle(settleOptions) : await verified.settle();
134
- if (settleResponse.success) {
135
- reply.header("payment-response", chunkPOU7EH3E_cjs.encodeBase64(settleResponse));
136
- reply.header("access-control-expose-headers", "payment-response");
137
- settled = true;
138
- }
139
- } catch {
140
- }
141
- if (!settled) {
142
- return refuse(reply, {
143
- error: "PAYMENT_SETTLEMENT_FAILED",
144
- message: "Payment could not be confirmed after multiple attempts. Please retry."
145
- });
146
- }
133
+ const outcome = await chunkNA7245L5_cjs.settleVerified(verified, settleOptions);
134
+ if (!outcome.settled)
135
+ return refuse(reply, chunkNA7245L5_cjs.settlementFailureResponse(outcome));
136
+ reply.header("payment-response", chunkNA7245L5_cjs.encodeBase64(outcome.confirmation));
137
+ reply.header("access-control-expose-headers", "payment-response");
147
138
  return payload;
148
139
  });
149
140
  });
@@ -1,5 +1,5 @@
1
1
  import { FastifyRequest, FastifyPluginAsync } from 'fastify';
2
- import { H as HTTPAdapter, P as PaymentMiddlewareConfig } from './types-q_gu5wjz.cjs';
2
+ import { H as HTTPAdapter, P as PaymentMiddlewareConfig } from './types-BkGQ2lPA.cjs';
3
3
  import 'viem';
4
4
 
5
5
  declare class FastifyAdapter implements HTTPAdapter {
package/dist/fastify.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FastifyRequest, FastifyPluginAsync } from 'fastify';
2
- import { H as HTTPAdapter, P as PaymentMiddlewareConfig } from './types-q_gu5wjz.js';
2
+ import { H as HTTPAdapter, P as PaymentMiddlewareConfig } from './types-BkGQ2lPA.js';
3
3
  import 'viem';
4
4
 
5
5
  declare class FastifyAdapter implements HTTPAdapter {
package/dist/fastify.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createPaymentGate, SETTLE_AMOUNT_HEADER, resolveCharge, invalidSettleAmountResponse, encodeBase64, matchRoute, parseJsonBody } from './chunk-AENXD4OC.js';
2
- import './chunk-AFRDHGDT.js';
1
+ import { createPaymentGate, SETTLE_AMOUNT_HEADER, resolveCharge, invalidSettleAmountResponse, settleVerified, settlementFailureResponse, encodeBase64, matchRoute, parseJsonBody } from './chunk-SA5ULSG2.js';
2
+ import './chunk-QAECRSWF.js';
3
3
  import fp from 'fastify-plugin';
4
4
 
5
5
  // src/fastify/adapter.ts
@@ -33,10 +33,12 @@ function payloadJson(contentType, payload) {
33
33
  const text = typeof payload === "string" ? payload : payload instanceof Uint8Array ? Buffer.from(payload).toString("utf8") : void 0;
34
34
  return parseJsonBody(contentType, text);
35
35
  }
36
- function refuse(reply, body) {
37
- const json = JSON.stringify(body);
38
- reply.status(402);
39
- reply.header("Content-Type", "application/json");
36
+ function refuse(reply, result) {
37
+ const json = JSON.stringify(result.body);
38
+ reply.status(result.status);
39
+ for (const [name, value] of Object.entries(result.headers)) {
40
+ reply.header(name, value);
41
+ }
40
42
  reply.header("Content-Length", Buffer.byteLength(json));
41
43
  return json;
42
44
  }
@@ -122,22 +124,11 @@ function paymentMiddleware(config) {
122
124
  if (resolved.kind === "skip") return payload;
123
125
  settleOptions = { amount: resolved.amount };
124
126
  }
125
- let settled = false;
126
- try {
127
- const settleResponse = settleOptions ? await verified.settle(settleOptions) : await verified.settle();
128
- if (settleResponse.success) {
129
- reply.header("payment-response", encodeBase64(settleResponse));
130
- reply.header("access-control-expose-headers", "payment-response");
131
- settled = true;
132
- }
133
- } catch {
134
- }
135
- if (!settled) {
136
- return refuse(reply, {
137
- error: "PAYMENT_SETTLEMENT_FAILED",
138
- message: "Payment could not be confirmed after multiple attempts. Please retry."
139
- });
140
- }
127
+ const outcome = await settleVerified(verified, settleOptions);
128
+ if (!outcome.settled)
129
+ return refuse(reply, settlementFailureResponse(outcome));
130
+ reply.header("payment-response", encodeBase64(outcome.confirmation));
131
+ reply.header("access-control-expose-headers", "payment-response");
141
132
  return payload;
142
133
  });
143
134
  });
package/dist/hono.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkPOU7EH3E_cjs = require('./chunk-POU7EH3E.cjs');
4
- require('./chunk-JPMK5YBU.cjs');
3
+ var chunkNA7245L5_cjs = require('./chunk-NA7245L5.cjs');
4
+ require('./chunk-KZZY5LS6.cjs');
5
5
 
6
6
  // src/hono/adapter.ts
7
7
  var TIMEOUT_BODY = JSON.stringify({
@@ -30,7 +30,7 @@ var HonoAdapter = class {
30
30
  // Hono caches the parsed body on the request, so the handler can still read
31
31
  // it after the price function did.
32
32
  async getBody() {
33
- if (!chunkPOU7EH3E_cjs.isJsonContentType(this.c.req.header("content-type"))) return void 0;
33
+ if (!chunkNA7245L5_cjs.isJsonContentType(this.c.req.header("content-type"))) return void 0;
34
34
  try {
35
35
  return await this.c.req.json();
36
36
  } catch {
@@ -38,8 +38,8 @@ var HonoAdapter = class {
38
38
  }
39
39
  }
40
40
  async runHandler(options) {
41
- const outcome = await chunkPOU7EH3E_cjs.raceTimeout(this.next(), options?.timeoutMs);
42
- if (outcome === chunkPOU7EH3E_cjs.HANDLER_TIMEOUT) {
41
+ const outcome = await chunkNA7245L5_cjs.raceTimeout(this.next(), options?.timeoutMs);
42
+ if (outcome === chunkNA7245L5_cjs.HANDLER_TIMEOUT) {
43
43
  return {
44
44
  status: 504,
45
45
  setResponseHeader: () => {
@@ -64,7 +64,7 @@ var HonoAdapter = class {
64
64
  getResponseHeader: (name) => res.headers.get(name) ?? void 0,
65
65
  removeResponseHeader: (name) => res.headers.delete(name),
66
66
  // Read from a clone: the response body streams once, to the client.
67
- getResponseBody: () => chunkPOU7EH3E_cjs.jsonFromResponse(res),
67
+ getResponseBody: () => chunkNA7245L5_cjs.jsonFromResponse(res),
68
68
  sendResponse: () => {
69
69
  },
70
70
  // The response is replaced: release its body (and whatever upstream
@@ -79,7 +79,7 @@ var HonoAdapter = class {
79
79
 
80
80
  // src/hono/index.ts
81
81
  function paymentMiddleware(config) {
82
- const middleware = chunkPOU7EH3E_cjs.createPaymentMiddleware(config);
82
+ const middleware = chunkNA7245L5_cjs.createPaymentMiddleware(config);
83
83
  return async (c, next) => {
84
84
  const result = await middleware(new HonoAdapter(c, next));
85
85
  if ("pass" in result) {