@parallel-protocol/x402 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -58
- package/dist/{chunk-AENXD4OC.js → chunk-GXPUE3ES.js} +233 -77
- package/dist/{chunk-JPMK5YBU.cjs → chunk-KZZY5LS6.cjs} +13 -2
- package/dist/{chunk-AFRDHGDT.js → chunk-QAECRSWF.js} +13 -2
- package/dist/{chunk-POU7EH3E.cjs → chunk-SNW5Z5HB.cjs} +255 -97
- package/dist/express.cjs +6 -6
- package/dist/express.d.cts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/express.js +2 -2
- package/dist/fastify.cjs +20 -29
- package/dist/fastify.d.cts +1 -1
- package/dist/fastify.d.ts +1 -1
- package/dist/fastify.js +13 -22
- package/dist/hono.cjs +7 -7
- package/dist/hono.d.cts +1 -1
- package/dist/hono.d.ts +1 -1
- package/dist/hono.js +2 -2
- package/dist/index.cjs +27 -19
- package/dist/index.d.cts +56 -7
- package/dist/index.d.ts +56 -7
- package/dist/index.js +2 -2
- package/dist/llm.cjs +13 -13
- package/dist/llm.d.cts +1 -1
- package/dist/llm.d.ts +1 -1
- package/dist/llm.js +1 -1
- package/dist/next.cjs +28 -44
- package/dist/next.d.cts +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/next.js +20 -36
- package/dist/{types-q_gu5wjz.d.cts → types-BkGQ2lPA.d.cts} +30 -1
- package/dist/{types-q_gu5wjz.d.ts → types-BkGQ2lPA.d.ts} +30 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { X402_ERROR_CODES,
|
|
1
|
+
import { X402_ERROR_CODES, X402ConfigError, X402RuntimeError, isPriceRejection } from './chunk-QAECRSWF.js';
|
|
2
2
|
import { getTokenDecimals as getTokenDecimals$1 } from '@parallel-protocol/chains';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
@@ -127,8 +127,18 @@ var priceSchema = z.union([
|
|
|
127
127
|
"Price must be a string, a bigint or a function of the request"
|
|
128
128
|
)
|
|
129
129
|
]);
|
|
130
|
+
var LOCAL_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
131
|
+
function isFacilitatorUrl(value) {
|
|
132
|
+
if (!URL.canParse(value)) return false;
|
|
133
|
+
const { protocol, hostname } = new URL(value);
|
|
134
|
+
return protocol === "https:" || protocol === "http:" && LOCAL_HOSTNAMES.has(hostname);
|
|
135
|
+
}
|
|
136
|
+
var facilitatorUrlSchema = z.string().refine(
|
|
137
|
+
isFacilitatorUrl,
|
|
138
|
+
"The facilitator URL must use https (plain http only on localhost, 127.0.0.1 or [::1])"
|
|
139
|
+
);
|
|
130
140
|
var facilitatorConfigSchema = z.object({
|
|
131
|
-
url:
|
|
141
|
+
url: facilitatorUrlSchema,
|
|
132
142
|
apiKey: z.string().optional()
|
|
133
143
|
});
|
|
134
144
|
var UPTO_MAX_TIMEOUT_SECONDS = 3600;
|
|
@@ -170,26 +180,32 @@ var facilitatorResponseSchema = z.discriminatedUnion("success", [
|
|
|
170
180
|
chain: z.string(),
|
|
171
181
|
gasSponsored: z.boolean(),
|
|
172
182
|
networkId: z.string(),
|
|
173
|
-
amountSettled: z.string().optional()
|
|
183
|
+
amountSettled: z.string().optional(),
|
|
184
|
+
alreadySettled: z.boolean().optional()
|
|
174
185
|
}),
|
|
175
186
|
z.object({
|
|
176
187
|
success: z.literal(false),
|
|
177
188
|
error: z.object({
|
|
178
189
|
code: z.string(),
|
|
179
190
|
message: z.string(),
|
|
180
|
-
hint: z.string().optional()
|
|
191
|
+
hint: z.string().optional(),
|
|
192
|
+
status: z.number().optional(),
|
|
193
|
+
retryAfter: z.string().optional()
|
|
181
194
|
})
|
|
182
195
|
})
|
|
183
196
|
]);
|
|
184
197
|
var verifyResponseSchema = z.object({
|
|
185
|
-
isValid: z.literal(true)
|
|
198
|
+
isValid: z.literal(true),
|
|
199
|
+
settleToken: z.string().min(1)
|
|
186
200
|
});
|
|
187
201
|
var settleSuccessSchema = z.object({
|
|
188
202
|
txHash: txHashSchema,
|
|
189
203
|
route: z.string(),
|
|
190
204
|
gasSponsored: z.boolean(),
|
|
191
205
|
// Metered routes: the amount actually charged (smallest unit).
|
|
192
|
-
amountSettled: z.string().optional()
|
|
206
|
+
amountSettled: z.string().optional(),
|
|
207
|
+
// The facilitator answered from a recorded outcome, nothing was broadcast.
|
|
208
|
+
alreadySettled: z.boolean().optional()
|
|
193
209
|
});
|
|
194
210
|
var settleErrorSchema = z.object({
|
|
195
211
|
error: z.string(),
|
|
@@ -203,6 +219,29 @@ var settleResponseSchema = z.union([
|
|
|
203
219
|
// src/client.ts
|
|
204
220
|
var FACILITATOR_TIMEOUT_MS = 1e4;
|
|
205
221
|
var SETTLE_TIMEOUT_MS = 6e4;
|
|
222
|
+
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)$/;
|
|
223
|
+
async function readErrorBody(response) {
|
|
224
|
+
const json = await response.json().catch(() => null);
|
|
225
|
+
if (typeof json !== "object" || json === null) return {};
|
|
226
|
+
const { error, message } = json;
|
|
227
|
+
return {
|
|
228
|
+
...typeof error === "string" && { error },
|
|
229
|
+
...typeof message === "string" && { message }
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function retryAfterOf(response) {
|
|
233
|
+
const value = response.headers.get("retry-after");
|
|
234
|
+
return value !== null && RETRY_AFTER_SHAPE.test(value) ? value : void 0;
|
|
235
|
+
}
|
|
236
|
+
function throwIfRedirect(response) {
|
|
237
|
+
const redirected = response.type === "opaqueredirect" || response.status >= 300 && response.status < 400;
|
|
238
|
+
if (!redirected) return;
|
|
239
|
+
response.body?.cancel().catch(() => {
|
|
240
|
+
});
|
|
241
|
+
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE, {
|
|
242
|
+
status: response.status
|
|
243
|
+
});
|
|
244
|
+
}
|
|
206
245
|
function decodePaymentHeader(paymentHeader) {
|
|
207
246
|
try {
|
|
208
247
|
return decodeBase64(paymentHeader);
|
|
@@ -211,24 +250,43 @@ function decodePaymentHeader(paymentHeader) {
|
|
|
211
250
|
}
|
|
212
251
|
}
|
|
213
252
|
var FacilitatorClient = class {
|
|
253
|
+
// Checked and kept when the client is built: a config changed afterwards
|
|
254
|
+
// does not move the calls elsewhere.
|
|
255
|
+
url;
|
|
256
|
+
apiKey;
|
|
214
257
|
constructor(config) {
|
|
215
|
-
|
|
258
|
+
const url = facilitatorUrlSchema.safeParse(config.url);
|
|
259
|
+
if (!url.success) {
|
|
260
|
+
throw new X402ConfigError(
|
|
261
|
+
url.error.issues[0]?.message ?? "Invalid facilitator URL",
|
|
262
|
+
url.error
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
this.url = new URL(url.data).href.replace(/\/+$/, "");
|
|
266
|
+
this.apiKey = config.apiKey;
|
|
216
267
|
}
|
|
217
|
-
config;
|
|
218
268
|
async verify(paymentHeader, paymentRequirements) {
|
|
219
269
|
const paymentPayload = decodePaymentHeader(paymentHeader);
|
|
220
270
|
let json;
|
|
221
271
|
try {
|
|
222
|
-
const response = await fetch(`${this.
|
|
272
|
+
const response = await fetch(`${this.url}/x402/verify`, {
|
|
223
273
|
method: "POST",
|
|
224
274
|
headers: this.buildHeaders(),
|
|
225
275
|
body: JSON.stringify({ paymentPayload, paymentRequirements }),
|
|
276
|
+
redirect: "manual",
|
|
226
277
|
signal: AbortSignal.timeout(FACILITATOR_TIMEOUT_MS)
|
|
227
278
|
});
|
|
279
|
+
throwIfRedirect(response);
|
|
228
280
|
if (!response.ok) {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
281
|
+
const body = await readErrorBody(response);
|
|
282
|
+
throw new X402RuntimeError(
|
|
283
|
+
body.error ?? X402_ERROR_CODES.FACILITATOR_UNAVAILABLE,
|
|
284
|
+
{
|
|
285
|
+
detail: body.message,
|
|
286
|
+
status: response.status,
|
|
287
|
+
retryAfter: retryAfterOf(response)
|
|
288
|
+
}
|
|
289
|
+
);
|
|
232
290
|
}
|
|
233
291
|
json = await response.json();
|
|
234
292
|
} catch (e) {
|
|
@@ -236,7 +294,8 @@ var FacilitatorClient = class {
|
|
|
236
294
|
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
237
295
|
}
|
|
238
296
|
try {
|
|
239
|
-
verifyResponseSchema.parse(json);
|
|
297
|
+
const { settleToken } = verifyResponseSchema.parse(json);
|
|
298
|
+
return { settleToken };
|
|
240
299
|
} catch {
|
|
241
300
|
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
|
|
242
301
|
}
|
|
@@ -245,28 +304,47 @@ var FacilitatorClient = class {
|
|
|
245
304
|
const paymentPayload = decodePaymentHeader(paymentHeader);
|
|
246
305
|
let json;
|
|
247
306
|
try {
|
|
248
|
-
const response = await fetch(`${this.
|
|
307
|
+
const response = await fetch(`${this.url}/x402/settle`, {
|
|
249
308
|
method: "POST",
|
|
250
309
|
headers: this.buildHeaders(),
|
|
251
|
-
// settleAmount only travels for metered routes
|
|
310
|
+
// settleAmount only travels for metered routes; the settle token
|
|
311
|
+
// travels whenever verify issued one.
|
|
252
312
|
body: JSON.stringify({
|
|
253
313
|
paymentPayload,
|
|
254
314
|
paymentRequirements,
|
|
255
315
|
...options?.amount !== void 0 && {
|
|
256
316
|
settleAmount: options.amount
|
|
317
|
+
},
|
|
318
|
+
...options?.settleToken !== void 0 && {
|
|
319
|
+
settleToken: options.settleToken
|
|
257
320
|
}
|
|
258
321
|
}),
|
|
322
|
+
redirect: "manual",
|
|
259
323
|
signal: AbortSignal.timeout(SETTLE_TIMEOUT_MS)
|
|
260
324
|
});
|
|
325
|
+
throwIfRedirect(response);
|
|
261
326
|
if (!response.ok) {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
327
|
+
const body = await readErrorBody(response);
|
|
328
|
+
if (body.error === void 0 && response.status >= 500) {
|
|
329
|
+
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
330
|
+
}
|
|
331
|
+
const retryAfter = retryAfterOf(response);
|
|
332
|
+
return {
|
|
333
|
+
success: false,
|
|
334
|
+
error: {
|
|
335
|
+
code: body.error ?? "SUBMISSION_FAILED",
|
|
336
|
+
message: body.message ?? "Settlement failed",
|
|
337
|
+
status: response.status,
|
|
338
|
+
...retryAfter !== void 0 && { retryAfter }
|
|
339
|
+
}
|
|
340
|
+
};
|
|
266
341
|
}
|
|
267
342
|
json = await response.json();
|
|
268
343
|
} catch (e) {
|
|
269
344
|
if (e instanceof X402RuntimeError) throw e;
|
|
345
|
+
if (e instanceof Error && e.name === "TimeoutError") {
|
|
346
|
+
throw new X402RuntimeError(X402_ERROR_CODES.SETTLE_TIMEOUT);
|
|
347
|
+
}
|
|
270
348
|
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
271
349
|
}
|
|
272
350
|
try {
|
|
@@ -281,7 +359,8 @@ var FacilitatorClient = class {
|
|
|
281
359
|
networkId: toEip155Network(paymentRequirements.network),
|
|
282
360
|
...parsed.amountSettled !== void 0 && {
|
|
283
361
|
amountSettled: parsed.amountSettled
|
|
284
|
-
}
|
|
362
|
+
},
|
|
363
|
+
...parsed.alreadySettled && { alreadySettled: true }
|
|
285
364
|
};
|
|
286
365
|
}
|
|
287
366
|
return {
|
|
@@ -292,16 +371,22 @@ var FacilitatorClient = class {
|
|
|
292
371
|
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
|
|
293
372
|
}
|
|
294
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* @deprecated The Parallel facilitator no longer serves `/x402/pay`, so this
|
|
376
|
+
* call fails with `FACILITATOR_UNAVAILABLE`. Use `verify`, then `settle`.
|
|
377
|
+
*/
|
|
295
378
|
async pay(paymentHeader) {
|
|
296
379
|
const headers = this.buildHeaders();
|
|
297
380
|
headers["X-PAYMENT"] = paymentHeader;
|
|
298
381
|
let json;
|
|
299
382
|
try {
|
|
300
|
-
const response = await fetch(`${this.
|
|
383
|
+
const response = await fetch(`${this.url}/x402/pay`, {
|
|
301
384
|
method: "POST",
|
|
302
385
|
headers,
|
|
386
|
+
redirect: "manual",
|
|
303
387
|
signal: AbortSignal.timeout(FACILITATOR_TIMEOUT_MS)
|
|
304
388
|
});
|
|
389
|
+
throwIfRedirect(response);
|
|
305
390
|
if (!response.ok) {
|
|
306
391
|
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
307
392
|
}
|
|
@@ -320,8 +405,8 @@ var FacilitatorClient = class {
|
|
|
320
405
|
const headers = {
|
|
321
406
|
"Content-Type": "application/json"
|
|
322
407
|
};
|
|
323
|
-
if (this.
|
|
324
|
-
headers["X-API-Key"] = this.
|
|
408
|
+
if (this.apiKey) {
|
|
409
|
+
headers["X-API-Key"] = this.apiKey;
|
|
325
410
|
}
|
|
326
411
|
return headers;
|
|
327
412
|
}
|
|
@@ -379,10 +464,21 @@ async function resolvePrice(config, ctx) {
|
|
|
379
464
|
}
|
|
380
465
|
var SETTLE_AMOUNT_HEADER = "payment-settle-amount";
|
|
381
466
|
var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
|
|
467
|
+
var SETTLEMENT_RESERVE_SECONDS = 120;
|
|
382
468
|
function handlerTimeoutFor(config) {
|
|
383
469
|
if ((config.scheme ?? "exact") !== "upto") return HANDLER_TIMEOUT_MS;
|
|
384
470
|
const seconds = config.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
|
|
385
|
-
return Math.max(
|
|
471
|
+
return Math.max(
|
|
472
|
+
HANDLER_TIMEOUT_MS,
|
|
473
|
+
(seconds - SETTLEMENT_RESERVE_SECONDS) * 1e3
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
function jsonResult(status, body, headers = {}) {
|
|
477
|
+
return {
|
|
478
|
+
status,
|
|
479
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
480
|
+
body
|
|
481
|
+
};
|
|
386
482
|
}
|
|
387
483
|
function resolveSettleAmount(raw, maxAmount, decimals) {
|
|
388
484
|
if (raw === void 0)
|
|
@@ -410,7 +506,10 @@ var INVALID_METER_RESPONSE = {
|
|
|
410
506
|
message: "The route's meter function failed or returned something that is not an amount; the response was discarded and nothing was charged."
|
|
411
507
|
};
|
|
412
508
|
function invalidSettleAmountResponse(reason) {
|
|
413
|
-
return
|
|
509
|
+
return jsonResult(
|
|
510
|
+
402,
|
|
511
|
+
reason === "meter" ? INVALID_METER_RESPONSE : INVALID_SETTLE_AMOUNT_RESPONSE
|
|
512
|
+
);
|
|
414
513
|
}
|
|
415
514
|
var AMOUNT_FORMAT = /^\d+(?:\.\d+)?$/;
|
|
416
515
|
async function resolveCharge(verified, settleHeader, response) {
|
|
@@ -558,31 +657,46 @@ function buildFacilitatorRequirements(config, asset, price) {
|
|
|
558
657
|
}
|
|
559
658
|
};
|
|
560
659
|
}
|
|
561
|
-
|
|
660
|
+
var CODE_SHAPE = /^[A-Z][A-Z0-9_]{2,63}$/;
|
|
661
|
+
var MESSAGE_MAX_LENGTH = 300;
|
|
662
|
+
function shapedCode(code) {
|
|
663
|
+
return typeof code === "string" && CODE_SHAPE.test(code) ? code : void 0;
|
|
664
|
+
}
|
|
665
|
+
function boundedMessage(message) {
|
|
666
|
+
if (typeof message !== "string") return void 0;
|
|
667
|
+
const oneLine = message.replace(/[\p{Cc}\p{Cf}]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
668
|
+
if (!oneLine) return void 0;
|
|
669
|
+
const chars = Array.from(oneLine);
|
|
670
|
+
return chars.length > MESSAGE_MAX_LENGTH ? `${chars.slice(0, MESSAGE_MAX_LENGTH).join("")}\u2026` : oneLine;
|
|
671
|
+
}
|
|
672
|
+
function paymentRequiredResponse(url, config, resolvedTokens, price, refusal) {
|
|
562
673
|
const paymentRequired = buildPaymentRequired(
|
|
563
674
|
url,
|
|
564
675
|
config,
|
|
565
676
|
resolvedTokens,
|
|
566
677
|
price
|
|
567
678
|
);
|
|
568
|
-
if (
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
679
|
+
if (refusal) {
|
|
680
|
+
paymentRequired.error = shapedCode(refusal.code) ?? X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE;
|
|
681
|
+
const message = boundedMessage(refusal.detail);
|
|
682
|
+
if (message) paymentRequired.message = message;
|
|
683
|
+
}
|
|
684
|
+
return jsonResult(402, paymentRequired, {
|
|
685
|
+
"payment-required": encodeBase64(paymentRequired),
|
|
686
|
+
"access-control-expose-headers": "payment-required",
|
|
687
|
+
...refusal?.retryAfter !== void 0 && {
|
|
688
|
+
"Retry-After": refusal.retryAfter
|
|
689
|
+
}
|
|
690
|
+
});
|
|
578
691
|
}
|
|
579
692
|
function createPaymentGate(config) {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
693
|
+
const checked = paymentMiddlewareConfigSchema.safeParse(config);
|
|
694
|
+
if (!checked.success) {
|
|
695
|
+
const issue = checked.error.issues[0];
|
|
696
|
+
const where = issue?.path.length ? ` at ${issue.path.join(".")}` : "";
|
|
583
697
|
throw new X402ConfigError(
|
|
584
|
-
|
|
585
|
-
|
|
698
|
+
`Invalid paymentMiddleware config${where}: ${issue?.message ?? "check your routes, facilitator URL and payTo addresses"}`,
|
|
699
|
+
checked.error
|
|
586
700
|
);
|
|
587
701
|
}
|
|
588
702
|
const client = new FacilitatorClient(config.facilitator);
|
|
@@ -630,10 +744,11 @@ function createPaymentGate(config) {
|
|
|
630
744
|
asset,
|
|
631
745
|
price
|
|
632
746
|
);
|
|
747
|
+
let settleToken;
|
|
633
748
|
try {
|
|
634
|
-
await client.verify(paymentHeader, requirements);
|
|
749
|
+
({ settleToken } = await client.verify(paymentHeader, requirements));
|
|
635
750
|
} catch (e) {
|
|
636
|
-
const
|
|
751
|
+
const refusal = e instanceof X402RuntimeError ? { code: e.code, detail: e.detail, retryAfter: e.retryAfter } : { code: X402_ERROR_CODES.FACILITATOR_UNAVAILABLE };
|
|
637
752
|
return {
|
|
638
753
|
type: "error",
|
|
639
754
|
result: paymentRequiredResponse(
|
|
@@ -641,7 +756,7 @@ function createPaymentGate(config) {
|
|
|
641
756
|
routeConfig,
|
|
642
757
|
resolvedTokens,
|
|
643
758
|
price,
|
|
644
|
-
|
|
759
|
+
refusal
|
|
645
760
|
)
|
|
646
761
|
};
|
|
647
762
|
}
|
|
@@ -652,12 +767,71 @@ function createPaymentGate(config) {
|
|
|
652
767
|
maxAmount: BigInt(requirements.maxAmountRequired),
|
|
653
768
|
decimals: resolveDecimals(routeConfig, asset),
|
|
654
769
|
handlerTimeoutMs: handlerTimeoutFor(routeConfig),
|
|
655
|
-
|
|
656
|
-
settle: (options) => options ? client.settle(paymentHeader, requirements, options) : client.settle(paymentHeader, requirements),
|
|
770
|
+
settle: (options) => client.settle(paymentHeader, requirements, { ...options, settleToken }),
|
|
657
771
|
...routeConfig.meter && { meter: routeConfig.meter, request }
|
|
658
772
|
};
|
|
659
773
|
};
|
|
660
774
|
}
|
|
775
|
+
var UNKNOWN_SETTLEMENT_MESSAGE = "The payment could not be settled.";
|
|
776
|
+
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.";
|
|
777
|
+
var AGENT_MESSAGES = {
|
|
778
|
+
[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.",
|
|
779
|
+
[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.",
|
|
780
|
+
[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.",
|
|
781
|
+
SETTLE_IN_PROGRESS: "The facilitator is still settling this payment. Do not sign another payment for this request before checking with the merchant.",
|
|
782
|
+
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.",
|
|
783
|
+
SETTLE_TOKEN_INVALID: MERCHANT_SETTLEMENT_MESSAGE,
|
|
784
|
+
SETTLE_TOKEN_REQUIRED: MERCHANT_SETTLEMENT_MESSAGE,
|
|
785
|
+
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.",
|
|
786
|
+
UNKNOWN: UNKNOWN_SETTLEMENT_MESSAGE
|
|
787
|
+
};
|
|
788
|
+
function settlementFailure(code, failure) {
|
|
789
|
+
const reason = shapedCode(code) ?? "UNKNOWN";
|
|
790
|
+
return {
|
|
791
|
+
settled: false,
|
|
792
|
+
reason,
|
|
793
|
+
message: AGENT_MESSAGES[reason] ?? boundedMessage(failure?.message) ?? UNKNOWN_SETTLEMENT_MESSAGE,
|
|
794
|
+
...failure?.retryAfter !== void 0 && {
|
|
795
|
+
retryAfter: failure.retryAfter
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
var RETRY_ONCE_CODES = /* @__PURE__ */ new Set([
|
|
800
|
+
X402_ERROR_CODES.SETTLE_TIMEOUT,
|
|
801
|
+
X402_ERROR_CODES.FACILITATOR_UNAVAILABLE
|
|
802
|
+
]);
|
|
803
|
+
async function settleVerified(verified, options) {
|
|
804
|
+
let response;
|
|
805
|
+
for (let attempt = 0; response === void 0; attempt++) {
|
|
806
|
+
try {
|
|
807
|
+
response = await verified.settle(options);
|
|
808
|
+
} catch (e) {
|
|
809
|
+
const code = e instanceof X402RuntimeError ? e.code : "UNKNOWN";
|
|
810
|
+
if (attempt > 0 || !RETRY_ONCE_CODES.has(code)) {
|
|
811
|
+
return settlementFailure(code);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (!response.success) {
|
|
816
|
+
return settlementFailure(response.error.code, response.error);
|
|
817
|
+
}
|
|
818
|
+
if (options?.amount !== void 0 && response.amountSettled !== options.amount) {
|
|
819
|
+
return settlementFailure("AMOUNT_MISMATCH");
|
|
820
|
+
}
|
|
821
|
+
return { settled: true, confirmation: response };
|
|
822
|
+
}
|
|
823
|
+
function settlementFailureResponse(failure) {
|
|
824
|
+
const body = {
|
|
825
|
+
error: "PAYMENT_SETTLEMENT_FAILED",
|
|
826
|
+
reason: failure.reason,
|
|
827
|
+
message: failure.message
|
|
828
|
+
};
|
|
829
|
+
return jsonResult(402, body, {
|
|
830
|
+
...failure.retryAfter !== void 0 && {
|
|
831
|
+
"Retry-After": failure.retryAfter
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
}
|
|
661
835
|
function createPaymentMiddleware(config) {
|
|
662
836
|
const gate = createPaymentGate(config);
|
|
663
837
|
return async (adapter) => {
|
|
@@ -687,11 +861,7 @@ function createPaymentMiddleware(config) {
|
|
|
687
861
|
});
|
|
688
862
|
if (resolved.kind === "invalid") {
|
|
689
863
|
handlerResult.discardResponse();
|
|
690
|
-
return
|
|
691
|
-
status: 402,
|
|
692
|
-
headers: { "Content-Type": "application/json" },
|
|
693
|
-
body: invalidSettleAmountResponse(resolved.reason)
|
|
694
|
-
};
|
|
864
|
+
return invalidSettleAmountResponse(resolved.reason);
|
|
695
865
|
}
|
|
696
866
|
if (resolved.kind === "skip") {
|
|
697
867
|
handlerResult.sendResponse();
|
|
@@ -699,37 +869,23 @@ function createPaymentMiddleware(config) {
|
|
|
699
869
|
}
|
|
700
870
|
settleOptions = { amount: resolved.amount };
|
|
701
871
|
}
|
|
702
|
-
|
|
703
|
-
|
|
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) {
|
|
872
|
+
const outcome = await settleVerified(gateResult, settleOptions);
|
|
873
|
+
if (!outcome.settled) {
|
|
719
874
|
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
|
-
};
|
|
875
|
+
return settlementFailureResponse(outcome);
|
|
728
876
|
}
|
|
877
|
+
handlerResult.setResponseHeader(
|
|
878
|
+
"payment-response",
|
|
879
|
+
encodeBase64(outcome.confirmation)
|
|
880
|
+
);
|
|
881
|
+
handlerResult.setResponseHeader(
|
|
882
|
+
"access-control-expose-headers",
|
|
883
|
+
"payment-response"
|
|
884
|
+
);
|
|
729
885
|
}
|
|
730
886
|
handlerResult.sendResponse();
|
|
731
887
|
return { pass: true, handlerRan: true };
|
|
732
888
|
};
|
|
733
889
|
}
|
|
734
890
|
|
|
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 };
|
|
891
|
+
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 };
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
var X402_ERROR_CODES = {
|
|
5
5
|
FACILITATOR_UNAVAILABLE: "FACILITATOR_UNAVAILABLE",
|
|
6
6
|
FACILITATOR_INVALID_RESPONSE: "FACILITATOR_INVALID_RESPONSE",
|
|
7
|
-
INVALID_PAYMENT: "INVALID_PAYMENT"
|
|
7
|
+
INVALID_PAYMENT: "INVALID_PAYMENT",
|
|
8
|
+
/** The facilitator did not answer a settle within the client's timeout: the payment may still settle. */
|
|
9
|
+
SETTLE_TIMEOUT: "SETTLE_TIMEOUT"
|
|
8
10
|
};
|
|
9
11
|
var X402ConfigError = class extends Error {
|
|
10
12
|
constructor(message, cause) {
|
|
@@ -14,12 +16,21 @@ var X402ConfigError = class extends Error {
|
|
|
14
16
|
}
|
|
15
17
|
};
|
|
16
18
|
var X402RuntimeError = class extends Error {
|
|
17
|
-
constructor(code) {
|
|
19
|
+
constructor(code, extra) {
|
|
18
20
|
super(code);
|
|
19
21
|
this.code = code;
|
|
20
22
|
this.name = "X402RuntimeError";
|
|
23
|
+
if (extra?.detail !== void 0) this.detail = extra.detail;
|
|
24
|
+
if (extra?.status !== void 0) this.status = extra.status;
|
|
25
|
+
if (extra?.retryAfter !== void 0) this.retryAfter = extra.retryAfter;
|
|
21
26
|
}
|
|
22
27
|
code;
|
|
28
|
+
/** The facilitator's message for this code, when it answered with one. */
|
|
29
|
+
detail;
|
|
30
|
+
/** The facilitator's HTTP status, when the error comes from its answer. */
|
|
31
|
+
status;
|
|
32
|
+
/** The facilitator's `Retry-After` header, when it sent one. */
|
|
33
|
+
retryAfter;
|
|
23
34
|
};
|
|
24
35
|
var X402PriceRejectedError = class extends Error {
|
|
25
36
|
constructor(code, message, status = 400) {
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
var X402_ERROR_CODES = {
|
|
3
3
|
FACILITATOR_UNAVAILABLE: "FACILITATOR_UNAVAILABLE",
|
|
4
4
|
FACILITATOR_INVALID_RESPONSE: "FACILITATOR_INVALID_RESPONSE",
|
|
5
|
-
INVALID_PAYMENT: "INVALID_PAYMENT"
|
|
5
|
+
INVALID_PAYMENT: "INVALID_PAYMENT",
|
|
6
|
+
/** The facilitator did not answer a settle within the client's timeout: the payment may still settle. */
|
|
7
|
+
SETTLE_TIMEOUT: "SETTLE_TIMEOUT"
|
|
6
8
|
};
|
|
7
9
|
var X402ConfigError = class extends Error {
|
|
8
10
|
constructor(message, cause) {
|
|
@@ -12,12 +14,21 @@ var X402ConfigError = class extends Error {
|
|
|
12
14
|
}
|
|
13
15
|
};
|
|
14
16
|
var X402RuntimeError = class extends Error {
|
|
15
|
-
constructor(code) {
|
|
17
|
+
constructor(code, extra) {
|
|
16
18
|
super(code);
|
|
17
19
|
this.code = code;
|
|
18
20
|
this.name = "X402RuntimeError";
|
|
21
|
+
if (extra?.detail !== void 0) this.detail = extra.detail;
|
|
22
|
+
if (extra?.status !== void 0) this.status = extra.status;
|
|
23
|
+
if (extra?.retryAfter !== void 0) this.retryAfter = extra.retryAfter;
|
|
19
24
|
}
|
|
20
25
|
code;
|
|
26
|
+
/** The facilitator's message for this code, when it answered with one. */
|
|
27
|
+
detail;
|
|
28
|
+
/** The facilitator's HTTP status, when the error comes from its answer. */
|
|
29
|
+
status;
|
|
30
|
+
/** The facilitator's `Retry-After` header, when it sent one. */
|
|
31
|
+
retryAfter;
|
|
21
32
|
};
|
|
22
33
|
var X402PriceRejectedError = class extends Error {
|
|
23
34
|
constructor(code, message, status = 400) {
|