@mixrpay/merchant-sdk 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,901 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ ChargesClient: () => ChargesClient,
34
+ DEFAULT_FACILITATOR: () => DEFAULT_FACILITATOR,
35
+ USDC_CONTRACTS: () => USDC_CONTRACTS,
36
+ clearJWKSCache: () => clearJWKSCache,
37
+ createPaymentRequired: () => createPaymentRequired,
38
+ expressX402: () => x402,
39
+ fastifyX402: () => x4022,
40
+ generateNonce: () => generateNonce,
41
+ getDefaultJWKSUrl: () => getDefaultJWKSUrl,
42
+ isReceiptExpired: () => isReceiptExpired,
43
+ isValidAddress: () => isValidAddress,
44
+ minorToUsd: () => minorToUsd,
45
+ normalizeAddress: () => normalizeAddress,
46
+ parsePaymentReceipt: () => parsePaymentReceipt,
47
+ parseSessionGrant: () => parseSessionGrant,
48
+ parseX402Payment: () => parseX402Payment,
49
+ usdToMinor: () => usdToMinor,
50
+ verifyPaymentReceipt: () => verifyPaymentReceipt,
51
+ verifySessionWebhook: () => verifySessionWebhook,
52
+ verifyX402Payment: () => verifyX402Payment,
53
+ withX402: () => withX402,
54
+ x402Plugin: () => x402Plugin
55
+ });
56
+ module.exports = __toCommonJS(src_exports);
57
+
58
+ // src/verify.ts
59
+ var import_viem = require("viem");
60
+
61
+ // src/utils.ts
62
+ var USDC_CONTRACTS = {
63
+ 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
64
+ // Base Mainnet
65
+ 84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
66
+ // Base Sepolia
67
+ };
68
+ var DEFAULT_FACILITATOR = "https://x402.org/facilitator";
69
+ function getUSDCDomain(chainId) {
70
+ const verifyingContract = USDC_CONTRACTS[chainId];
71
+ if (!verifyingContract) {
72
+ throw new Error(`Unsupported chain ID: ${chainId}. Supported: ${Object.keys(USDC_CONTRACTS).join(", ")}`);
73
+ }
74
+ return {
75
+ name: "USD Coin",
76
+ version: "2",
77
+ chainId,
78
+ verifyingContract
79
+ };
80
+ }
81
+ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
82
+ TransferWithAuthorization: [
83
+ { name: "from", type: "address" },
84
+ { name: "to", type: "address" },
85
+ { name: "value", type: "uint256" },
86
+ { name: "validAfter", type: "uint256" },
87
+ { name: "validBefore", type: "uint256" },
88
+ { name: "nonce", type: "bytes32" }
89
+ ]
90
+ };
91
+ function usdToMinor(usd) {
92
+ return BigInt(Math.round(usd * 1e6));
93
+ }
94
+ function minorToUsd(minor) {
95
+ return Number(minor) / 1e6;
96
+ }
97
+ function generateNonce() {
98
+ const bytes = new Uint8Array(32);
99
+ crypto.getRandomValues(bytes);
100
+ return `0x${Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
101
+ }
102
+ function isValidAddress(address) {
103
+ return /^0x[a-fA-F0-9]{40}$/.test(address);
104
+ }
105
+ function normalizeAddress(address) {
106
+ if (!isValidAddress(address)) {
107
+ throw new Error(`Invalid address: ${address}`);
108
+ }
109
+ return address.toLowerCase();
110
+ }
111
+ function base64Decode(str) {
112
+ if (typeof Buffer !== "undefined") {
113
+ return Buffer.from(str, "base64").toString("utf-8");
114
+ }
115
+ return atob(str);
116
+ }
117
+
118
+ // src/verify.ts
119
+ async function verifyX402Payment(paymentHeader, options) {
120
+ try {
121
+ let decoded;
122
+ try {
123
+ const jsonStr = base64Decode(paymentHeader);
124
+ decoded = JSON.parse(jsonStr);
125
+ } catch (e) {
126
+ return { valid: false, error: "Invalid payment header encoding" };
127
+ }
128
+ if (!decoded.payload?.authorization || !decoded.payload?.signature) {
129
+ return { valid: false, error: "Missing authorization or signature in payment" };
130
+ }
131
+ const { authorization, signature } = decoded.payload;
132
+ const chainId = options.chainId || 8453;
133
+ const message = {
134
+ from: authorization.from,
135
+ to: authorization.to,
136
+ value: BigInt(authorization.value),
137
+ validAfter: BigInt(authorization.validAfter),
138
+ validBefore: BigInt(authorization.validBefore),
139
+ nonce: authorization.nonce
140
+ };
141
+ const domain = getUSDCDomain(chainId);
142
+ let signerAddress;
143
+ try {
144
+ signerAddress = await (0, import_viem.recoverTypedDataAddress)({
145
+ domain,
146
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
147
+ primaryType: "TransferWithAuthorization",
148
+ message,
149
+ signature
150
+ });
151
+ } catch (e) {
152
+ return { valid: false, error: "Failed to recover signer from signature" };
153
+ }
154
+ if (signerAddress.toLowerCase() !== authorization.from.toLowerCase()) {
155
+ return {
156
+ valid: false,
157
+ error: `Signature mismatch: expected ${authorization.from}, got ${signerAddress}`
158
+ };
159
+ }
160
+ const paymentAmount = BigInt(authorization.value);
161
+ if (paymentAmount < options.expectedAmount) {
162
+ return {
163
+ valid: false,
164
+ error: `Insufficient payment: expected ${options.expectedAmount}, got ${paymentAmount}`
165
+ };
166
+ }
167
+ if (authorization.to.toLowerCase() !== options.expectedRecipient.toLowerCase()) {
168
+ return {
169
+ valid: false,
170
+ error: `Wrong recipient: expected ${options.expectedRecipient}, got ${authorization.to}`
171
+ };
172
+ }
173
+ const now = Math.floor(Date.now() / 1e3);
174
+ if (Number(authorization.validBefore) < now) {
175
+ return { valid: false, error: "Payment authorization has expired" };
176
+ }
177
+ if (Number(authorization.validAfter) > now) {
178
+ return { valid: false, error: "Payment authorization is not yet valid" };
179
+ }
180
+ let txHash;
181
+ let settledAt;
182
+ if (!options.skipSettlement) {
183
+ const facilitatorUrl = options.facilitator || DEFAULT_FACILITATOR;
184
+ try {
185
+ const settlementResponse = await fetch(`${facilitatorUrl}/settle`, {
186
+ method: "POST",
187
+ headers: { "Content-Type": "application/json" },
188
+ body: JSON.stringify({
189
+ authorization,
190
+ signature,
191
+ chainId
192
+ })
193
+ });
194
+ if (!settlementResponse.ok) {
195
+ const errorBody = await settlementResponse.text();
196
+ let errorMessage = "Settlement failed";
197
+ try {
198
+ const errorJson = JSON.parse(errorBody);
199
+ errorMessage = errorJson.message || errorJson.error || errorMessage;
200
+ } catch {
201
+ errorMessage = errorBody || errorMessage;
202
+ }
203
+ return { valid: false, error: `Settlement failed: ${errorMessage}` };
204
+ }
205
+ const settlement = await settlementResponse.json();
206
+ txHash = settlement.txHash || settlement.tx_hash;
207
+ settledAt = /* @__PURE__ */ new Date();
208
+ } catch (e) {
209
+ return {
210
+ valid: false,
211
+ error: `Settlement request failed: ${e.message}`
212
+ };
213
+ }
214
+ }
215
+ return {
216
+ valid: true,
217
+ payer: authorization.from,
218
+ amount: minorToUsd(paymentAmount),
219
+ amountMinor: paymentAmount,
220
+ txHash,
221
+ settledAt: settledAt || /* @__PURE__ */ new Date(),
222
+ nonce: authorization.nonce
223
+ };
224
+ } catch (error) {
225
+ return {
226
+ valid: false,
227
+ error: `Verification error: ${error.message}`
228
+ };
229
+ }
230
+ }
231
+ async function parseX402Payment(paymentHeader, chainId = 8453) {
232
+ try {
233
+ const jsonStr = base64Decode(paymentHeader);
234
+ const decoded = JSON.parse(jsonStr);
235
+ if (!decoded.payload?.authorization) {
236
+ return { valid: false, error: "Missing authorization in payment" };
237
+ }
238
+ const { authorization, signature } = decoded.payload;
239
+ const domain = getUSDCDomain(chainId);
240
+ const message = {
241
+ from: authorization.from,
242
+ to: authorization.to,
243
+ value: BigInt(authorization.value),
244
+ validAfter: BigInt(authorization.validAfter),
245
+ validBefore: BigInt(authorization.validBefore),
246
+ nonce: authorization.nonce
247
+ };
248
+ const signerAddress = await (0, import_viem.recoverTypedDataAddress)({
249
+ domain,
250
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
251
+ primaryType: "TransferWithAuthorization",
252
+ message,
253
+ signature
254
+ });
255
+ if (signerAddress.toLowerCase() !== authorization.from.toLowerCase()) {
256
+ return { valid: false, error: "Signature mismatch" };
257
+ }
258
+ const amountMinor = BigInt(authorization.value);
259
+ return {
260
+ valid: true,
261
+ payer: authorization.from,
262
+ recipient: authorization.to,
263
+ amount: minorToUsd(amountMinor),
264
+ amountMinor,
265
+ expiresAt: new Date(Number(authorization.validBefore) * 1e3)
266
+ };
267
+ } catch (error) {
268
+ return { valid: false, error: `Parse error: ${error.message}` };
269
+ }
270
+ }
271
+
272
+ // src/receipt.ts
273
+ var jose = __toESM(require("jose"));
274
+ var DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || "http://localhost:3000/.well-known/jwks";
275
+ var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
276
+ var jwksCache = /* @__PURE__ */ new Map();
277
+ async function getJWKS(jwksUrl) {
278
+ const cached = jwksCache.get(jwksUrl);
279
+ const now = Date.now();
280
+ if (cached && now - cached.fetchedAt < JWKS_CACHE_TTL_MS) {
281
+ return cached.jwks;
282
+ }
283
+ const response = await fetch(jwksUrl);
284
+ if (!response.ok) {
285
+ throw new Error(`Failed to fetch JWKS from ${jwksUrl}: ${response.status} ${response.statusText}`);
286
+ }
287
+ const jwks = await response.json();
288
+ if (!jwks.keys || !Array.isArray(jwks.keys) || jwks.keys.length === 0) {
289
+ throw new Error("Invalid JWKS: missing or empty keys array");
290
+ }
291
+ jwksCache.set(jwksUrl, { jwks, fetchedAt: now });
292
+ return jwks;
293
+ }
294
+ async function createKeySet(jwksUrl) {
295
+ const jwks = await getJWKS(jwksUrl);
296
+ return jose.createLocalJWKSet(jwks);
297
+ }
298
+ async function verifyPaymentReceipt(receipt, options) {
299
+ const jwksUrl = options?.jwksUrl || DEFAULT_JWKS_URL;
300
+ try {
301
+ const keySet = await createKeySet(jwksUrl);
302
+ const verifyOptions = {
303
+ algorithms: ["RS256"]
304
+ };
305
+ if (options?.issuer) {
306
+ verifyOptions.issuer = options.issuer;
307
+ }
308
+ const { payload } = await jose.jwtVerify(receipt, keySet, verifyOptions);
309
+ const requiredFields = ["paymentId", "amount", "amountUsd", "payer", "recipient", "chainId", "txHash", "settledAt"];
310
+ for (const field of requiredFields) {
311
+ if (!(field in payload)) {
312
+ throw new Error(`Missing required field in receipt: ${field}`);
313
+ }
314
+ }
315
+ return {
316
+ paymentId: payload.paymentId,
317
+ amount: payload.amount,
318
+ amountUsd: payload.amountUsd,
319
+ payer: payload.payer,
320
+ recipient: payload.recipient,
321
+ chainId: payload.chainId,
322
+ txHash: payload.txHash,
323
+ settledAt: payload.settledAt,
324
+ issuedAt: new Date(payload.iat * 1e3),
325
+ expiresAt: new Date(payload.exp * 1e3)
326
+ };
327
+ } catch (error) {
328
+ if (error instanceof jose.errors.JWTExpired) {
329
+ throw new Error("Payment receipt has expired");
330
+ }
331
+ if (error instanceof jose.errors.JWTClaimValidationFailed) {
332
+ throw new Error(`Receipt validation failed: ${error.message}`);
333
+ }
334
+ if (error instanceof jose.errors.JWSSignatureVerificationFailed) {
335
+ throw new Error("Invalid receipt signature");
336
+ }
337
+ throw error;
338
+ }
339
+ }
340
+ function parsePaymentReceipt(receipt) {
341
+ const decoded = jose.decodeJwt(receipt);
342
+ return {
343
+ paymentId: decoded.paymentId,
344
+ amount: decoded.amount,
345
+ amountUsd: decoded.amountUsd,
346
+ payer: decoded.payer,
347
+ recipient: decoded.recipient,
348
+ chainId: decoded.chainId,
349
+ txHash: decoded.txHash,
350
+ settledAt: decoded.settledAt,
351
+ issuedAt: decoded.iat ? new Date(decoded.iat * 1e3) : void 0,
352
+ expiresAt: decoded.exp ? new Date(decoded.exp * 1e3) : void 0
353
+ };
354
+ }
355
+ function isReceiptExpired(receipt) {
356
+ const parsed = typeof receipt === "string" ? parsePaymentReceipt(receipt) : receipt;
357
+ if (!parsed.expiresAt) {
358
+ return false;
359
+ }
360
+ return parsed.expiresAt.getTime() < Date.now();
361
+ }
362
+ function clearJWKSCache() {
363
+ jwksCache.clear();
364
+ }
365
+ function getDefaultJWKSUrl() {
366
+ return DEFAULT_JWKS_URL;
367
+ }
368
+
369
+ // src/charges.ts
370
+ var import_crypto = __toESM(require("crypto"));
371
+ var ChargesClient = class {
372
+ constructor(options) {
373
+ this.apiKey = options.apiKey;
374
+ this.baseUrl = options.baseUrl || process.env.MIXRPAY_BASE_URL || "http://localhost:3000";
375
+ }
376
+ /**
377
+ * Create a new charge.
378
+ *
379
+ * @example
380
+ * ```typescript
381
+ * const result = await charges.create({
382
+ * sessionId: 'sk_xxx',
383
+ * amountUsd: 0.05,
384
+ * reference: 'image_gen_123',
385
+ * metadata: { feature: 'image_generation' }
386
+ * });
387
+ *
388
+ * if (result.success) {
389
+ * console.log(`Charged ${result.charge.amountUsdc}`);
390
+ * console.log(`TX: ${result.charge.explorerUrl}`);
391
+ * }
392
+ * ```
393
+ */
394
+ async create(params) {
395
+ const response = await fetch(`${this.baseUrl}/api/v1/charges`, {
396
+ method: "POST",
397
+ headers: {
398
+ "Content-Type": "application/json",
399
+ "Authorization": `Bearer ${this.apiKey}`
400
+ },
401
+ body: JSON.stringify({
402
+ session_id: params.sessionId,
403
+ amount_usd: params.amountUsd,
404
+ reference: params.reference,
405
+ metadata: params.metadata
406
+ })
407
+ });
408
+ const data = await response.json();
409
+ if (!response.ok) {
410
+ return {
411
+ success: false,
412
+ error: data.error,
413
+ details: data.details
414
+ };
415
+ }
416
+ return {
417
+ success: data.success,
418
+ charge: data.charge_id ? {
419
+ id: data.charge_id,
420
+ status: data.status,
421
+ amountUsd: data.amount_usd ?? 0,
422
+ amountUsdc: data.amount_usdc ?? "0",
423
+ txHash: data.tx_hash,
424
+ explorerUrl: data.explorer_url,
425
+ fromAddress: "",
426
+ toAddress: "",
427
+ reference: params.reference,
428
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
429
+ } : void 0,
430
+ idempotentReplay: data.idempotent_replay,
431
+ error: data.error
432
+ };
433
+ }
434
+ /**
435
+ * Get a charge by ID.
436
+ *
437
+ * @example
438
+ * ```typescript
439
+ * const charge = await charges.get('chg_xxx');
440
+ * console.log(charge.status); // 'confirmed'
441
+ * ```
442
+ */
443
+ async get(chargeId) {
444
+ const response = await fetch(`${this.baseUrl}/api/v1/charges?id=${chargeId}`, {
445
+ headers: {
446
+ "Authorization": `Bearer ${this.apiKey}`
447
+ }
448
+ });
449
+ if (!response.ok) {
450
+ if (response.status === 404) {
451
+ return null;
452
+ }
453
+ throw new Error(`Failed to get charge: ${response.statusText}`);
454
+ }
455
+ const data = await response.json();
456
+ return {
457
+ id: data.id,
458
+ status: data.status,
459
+ amountUsd: data.amount_usd,
460
+ amountUsdc: data.amount_usdc,
461
+ txHash: data.tx_hash,
462
+ explorerUrl: data.explorer_url,
463
+ fromAddress: data.from_address,
464
+ toAddress: data.to_address,
465
+ reference: data.reference,
466
+ createdAt: data.created_at,
467
+ confirmedAt: data.confirmed_at,
468
+ sessionName: data.session_name,
469
+ userWallet: data.user_wallet
470
+ };
471
+ }
472
+ /**
473
+ * Get a charge by transaction hash.
474
+ */
475
+ async getByTxHash(txHash) {
476
+ const response = await fetch(`${this.baseUrl}/api/v1/charges?tx_hash=${txHash}`, {
477
+ headers: {
478
+ "Authorization": `Bearer ${this.apiKey}`
479
+ }
480
+ });
481
+ if (!response.ok) {
482
+ if (response.status === 404) {
483
+ return null;
484
+ }
485
+ throw new Error(`Failed to get charge: ${response.statusText}`);
486
+ }
487
+ const data = await response.json();
488
+ return {
489
+ id: data.id,
490
+ status: data.status,
491
+ amountUsd: data.amount_usd,
492
+ amountUsdc: data.amount_usdc,
493
+ txHash: data.tx_hash,
494
+ explorerUrl: data.explorer_url,
495
+ fromAddress: data.from_address,
496
+ toAddress: data.to_address,
497
+ reference: data.reference,
498
+ createdAt: data.created_at,
499
+ confirmedAt: data.confirmed_at,
500
+ sessionName: data.session_name,
501
+ userWallet: data.user_wallet
502
+ };
503
+ }
504
+ };
505
+ function verifySessionWebhook(payload, signature, secret) {
506
+ const expectedSignature = import_crypto.default.createHmac("sha256", secret).update(payload).digest("hex");
507
+ return import_crypto.default.timingSafeEqual(
508
+ Buffer.from(signature),
509
+ Buffer.from(expectedSignature)
510
+ );
511
+ }
512
+ function parseSessionGrant(payload) {
513
+ if (payload.event !== "session.granted") {
514
+ throw new Error(`Unexpected event type: ${payload.event}`);
515
+ }
516
+ return {
517
+ sessionId: payload.session_id,
518
+ userWallet: payload.user_wallet,
519
+ merchantWallet: payload.merchant_wallet,
520
+ limits: {
521
+ maxTotalUsd: payload.limits.max_total_usd,
522
+ maxPerTxUsd: payload.limits.max_per_tx_usd,
523
+ expiresAt: payload.limits.expires_at
524
+ },
525
+ grantedAt: payload.granted_at
526
+ };
527
+ }
528
+
529
+ // src/receipt-fetcher.ts
530
+ var DEFAULT_MIXRPAY_API_URL = process.env.MIXRPAY_BASE_URL || "http://localhost:3000";
531
+ async function fetchPaymentReceipt(params) {
532
+ const apiUrl = params.apiUrl || DEFAULT_MIXRPAY_API_URL;
533
+ const endpoint = `${apiUrl}/api/v1/receipts`;
534
+ try {
535
+ const response = await fetch(endpoint, {
536
+ method: "POST",
537
+ headers: {
538
+ "Content-Type": "application/json"
539
+ },
540
+ body: JSON.stringify({
541
+ tx_hash: params.txHash,
542
+ payer: params.payer,
543
+ recipient: params.recipient,
544
+ amount: params.amount.toString(),
545
+ chain_id: params.chainId
546
+ })
547
+ });
548
+ if (!response.ok) {
549
+ console.warn(`[x402] Receipt fetch failed: ${response.status} ${response.statusText}`);
550
+ return null;
551
+ }
552
+ const data = await response.json();
553
+ return data.receipt || null;
554
+ } catch (error) {
555
+ console.warn("[x402] Receipt fetch error:", error.message);
556
+ return null;
557
+ }
558
+ }
559
+
560
+ // src/middleware/express.ts
561
+ function x402(options) {
562
+ return async (req, res, next) => {
563
+ try {
564
+ const context = {
565
+ path: req.path,
566
+ method: req.method,
567
+ headers: req.headers,
568
+ body: req.body
569
+ };
570
+ if (options.skip) {
571
+ const shouldSkip = await options.skip(context);
572
+ if (shouldSkip) {
573
+ return next();
574
+ }
575
+ }
576
+ const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
577
+ if (!recipient) {
578
+ console.error("[x402] No recipient address configured. Set MIXRPAY_MERCHANT_ADDRESS env var or pass recipient option.");
579
+ return res.status(500).json({
580
+ error: "Payment configuration error",
581
+ message: "Merchant wallet address not configured"
582
+ });
583
+ }
584
+ const price = options.getPrice ? await options.getPrice(context) : options.price;
585
+ const priceMinor = usdToMinor(price);
586
+ const chainId = options.chainId || 8453;
587
+ const facilitator = options.facilitator || DEFAULT_FACILITATOR;
588
+ const paymentHeader = req.headers["x-payment"];
589
+ if (!paymentHeader) {
590
+ const nonce = generateNonce();
591
+ const expiresAt = Math.floor(Date.now() / 1e3) + 300;
592
+ const paymentRequired = {
593
+ recipient,
594
+ amount: priceMinor.toString(),
595
+ currency: "USDC",
596
+ chainId,
597
+ facilitator,
598
+ nonce,
599
+ expiresAt,
600
+ description: options.description
601
+ };
602
+ res.setHeader("X-Payment-Required", JSON.stringify(paymentRequired));
603
+ res.setHeader("WWW-Authenticate", `X-402 ${Buffer.from(JSON.stringify(paymentRequired)).toString("base64")}`);
604
+ return res.status(402).json({
605
+ error: "Payment required",
606
+ payment: paymentRequired
607
+ });
608
+ }
609
+ const result = await verifyX402Payment(paymentHeader, {
610
+ expectedAmount: priceMinor,
611
+ expectedRecipient: recipient,
612
+ chainId,
613
+ facilitator,
614
+ skipSettlement: options.testMode
615
+ });
616
+ if (!result.valid) {
617
+ return res.status(402).json({
618
+ error: "Invalid payment",
619
+ reason: result.error
620
+ });
621
+ }
622
+ req.x402Payment = result;
623
+ if (result.txHash) {
624
+ res.setHeader("X-Payment-TxHash", result.txHash);
625
+ }
626
+ const receiptMode = options.receiptMode || "webhook";
627
+ if ((receiptMode === "jwt" || receiptMode === "both") && result.txHash && result.payer) {
628
+ try {
629
+ const receipt = await fetchPaymentReceipt({
630
+ txHash: result.txHash,
631
+ payer: result.payer,
632
+ recipient,
633
+ amount: priceMinor,
634
+ chainId,
635
+ apiUrl: options.mixrpayApiUrl
636
+ });
637
+ if (receipt) {
638
+ result.receipt = receipt;
639
+ res.setHeader("X-Payment-Receipt", receipt);
640
+ }
641
+ } catch (receiptError) {
642
+ console.warn("[x402] Failed to fetch JWT receipt:", receiptError);
643
+ }
644
+ }
645
+ if (options.onPayment) {
646
+ await options.onPayment(result);
647
+ }
648
+ next();
649
+ } catch (error) {
650
+ console.error("[x402] Middleware error:", error);
651
+ return res.status(500).json({
652
+ error: "Payment processing error",
653
+ message: error.message
654
+ });
655
+ }
656
+ };
657
+ }
658
+
659
+ // src/middleware/nextjs.ts
660
+ var import_server = require("next/server");
661
+ function withX402(config, handler) {
662
+ return async (req) => {
663
+ try {
664
+ const recipient = config.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
665
+ if (!recipient) {
666
+ console.error("[x402] No recipient address configured");
667
+ return import_server.NextResponse.json(
668
+ { error: "Payment configuration error" },
669
+ { status: 500 }
670
+ );
671
+ }
672
+ const price = config.getPrice ? await config.getPrice(req) : config.price;
673
+ const priceMinor = usdToMinor(price);
674
+ const chainId = config.chainId || 8453;
675
+ const facilitator = config.facilitator || DEFAULT_FACILITATOR;
676
+ const paymentHeader = req.headers.get("x-payment");
677
+ if (!paymentHeader) {
678
+ const nonce = generateNonce();
679
+ const expiresAt = Math.floor(Date.now() / 1e3) + 300;
680
+ const paymentRequired = {
681
+ recipient,
682
+ amount: priceMinor.toString(),
683
+ currency: "USDC",
684
+ chainId,
685
+ facilitator,
686
+ nonce,
687
+ expiresAt,
688
+ description: config.description
689
+ };
690
+ return import_server.NextResponse.json(
691
+ { error: "Payment required", payment: paymentRequired },
692
+ {
693
+ status: 402,
694
+ headers: {
695
+ "X-Payment-Required": JSON.stringify(paymentRequired),
696
+ "WWW-Authenticate": `X-402 ${Buffer.from(JSON.stringify(paymentRequired)).toString("base64")}`
697
+ }
698
+ }
699
+ );
700
+ }
701
+ const result = await verifyX402Payment(paymentHeader, {
702
+ expectedAmount: priceMinor,
703
+ expectedRecipient: recipient,
704
+ chainId,
705
+ facilitator,
706
+ skipSettlement: config.testMode
707
+ });
708
+ if (!result.valid) {
709
+ return import_server.NextResponse.json(
710
+ { error: "Invalid payment", reason: result.error },
711
+ { status: 402 }
712
+ );
713
+ }
714
+ const receiptMode = config.receiptMode || "webhook";
715
+ if ((receiptMode === "jwt" || receiptMode === "both") && result.txHash && result.payer) {
716
+ try {
717
+ const receipt = await fetchPaymentReceipt({
718
+ txHash: result.txHash,
719
+ payer: result.payer,
720
+ recipient,
721
+ amount: priceMinor,
722
+ chainId,
723
+ apiUrl: config.mixrpayApiUrl
724
+ });
725
+ if (receipt) {
726
+ result.receipt = receipt;
727
+ }
728
+ } catch (receiptError) {
729
+ console.warn("[x402] Failed to fetch JWT receipt:", receiptError);
730
+ }
731
+ }
732
+ if (config.onPayment) {
733
+ await config.onPayment(result, req);
734
+ }
735
+ const response = await handler(req, result);
736
+ if (result.txHash) {
737
+ response.headers.set("X-Payment-TxHash", result.txHash);
738
+ }
739
+ if (result.receipt) {
740
+ response.headers.set("X-Payment-Receipt", result.receipt);
741
+ }
742
+ return response;
743
+ } catch (error) {
744
+ console.error("[x402] Handler error:", error);
745
+ return import_server.NextResponse.json(
746
+ { error: "Payment processing error" },
747
+ { status: 500 }
748
+ );
749
+ }
750
+ };
751
+ }
752
+ function createPaymentRequired(options) {
753
+ const priceMinor = usdToMinor(options.amount);
754
+ const nonce = generateNonce();
755
+ const expiresAt = Math.floor(Date.now() / 1e3) + 300;
756
+ const paymentRequired = {
757
+ recipient: options.recipient,
758
+ amount: priceMinor.toString(),
759
+ currency: "USDC",
760
+ chainId: options.chainId || 8453,
761
+ facilitator: options.facilitator || DEFAULT_FACILITATOR,
762
+ nonce,
763
+ expiresAt,
764
+ description: options.description
765
+ };
766
+ return import_server.NextResponse.json(
767
+ { error: "Payment required", payment: paymentRequired },
768
+ {
769
+ status: 402,
770
+ headers: {
771
+ "X-Payment-Required": JSON.stringify(paymentRequired)
772
+ }
773
+ }
774
+ );
775
+ }
776
+
777
+ // src/middleware/fastify.ts
778
+ var x402Plugin = (fastify, options, done) => {
779
+ fastify.decorateRequest("x402Payment", null);
780
+ fastify.decorate("x402Defaults", {
781
+ recipient: options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS,
782
+ chainId: options.chainId || 8453,
783
+ facilitator: options.facilitator || DEFAULT_FACILITATOR
784
+ });
785
+ done();
786
+ };
787
+ function x4022(options) {
788
+ return async (request, reply) => {
789
+ const context = {
790
+ path: request.url,
791
+ method: request.method,
792
+ headers: request.headers,
793
+ body: request.body
794
+ };
795
+ if (options.skip) {
796
+ const shouldSkip = await options.skip(context);
797
+ if (shouldSkip) {
798
+ return;
799
+ }
800
+ }
801
+ const defaults = request.server.x402Defaults || {};
802
+ const recipient = options.recipient || defaults.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
803
+ if (!recipient) {
804
+ request.log.error("[x402] No recipient address configured");
805
+ return reply.status(500).send({
806
+ error: "Payment configuration error",
807
+ message: "Merchant wallet address not configured"
808
+ });
809
+ }
810
+ const price = options.getPrice ? await options.getPrice(context) : options.price;
811
+ const priceMinor = usdToMinor(price);
812
+ const chainId = options.chainId || defaults.chainId || 8453;
813
+ const facilitator = options.facilitator || defaults.facilitator || DEFAULT_FACILITATOR;
814
+ const paymentHeader = request.headers["x-payment"];
815
+ if (!paymentHeader) {
816
+ const nonce = generateNonce();
817
+ const expiresAt = Math.floor(Date.now() / 1e3) + 300;
818
+ const paymentRequired = {
819
+ recipient,
820
+ amount: priceMinor.toString(),
821
+ currency: "USDC",
822
+ chainId,
823
+ facilitator,
824
+ nonce,
825
+ expiresAt,
826
+ description: options.description
827
+ };
828
+ reply.header("X-Payment-Required", JSON.stringify(paymentRequired));
829
+ reply.header("WWW-Authenticate", `X-402 ${Buffer.from(JSON.stringify(paymentRequired)).toString("base64")}`);
830
+ return reply.status(402).send({
831
+ error: "Payment required",
832
+ payment: paymentRequired
833
+ });
834
+ }
835
+ const result = await verifyX402Payment(paymentHeader, {
836
+ expectedAmount: priceMinor,
837
+ expectedRecipient: recipient,
838
+ chainId,
839
+ facilitator,
840
+ skipSettlement: options.testMode
841
+ });
842
+ if (!result.valid) {
843
+ return reply.status(402).send({
844
+ error: "Invalid payment",
845
+ reason: result.error
846
+ });
847
+ }
848
+ request.x402Payment = result;
849
+ if (result.txHash) {
850
+ reply.header("X-Payment-TxHash", result.txHash);
851
+ }
852
+ const receiptMode = options.receiptMode || "webhook";
853
+ if ((receiptMode === "jwt" || receiptMode === "both") && result.txHash && result.payer) {
854
+ try {
855
+ const receipt = await fetchPaymentReceipt({
856
+ txHash: result.txHash,
857
+ payer: result.payer,
858
+ recipient,
859
+ amount: priceMinor,
860
+ chainId,
861
+ apiUrl: options.mixrpayApiUrl
862
+ });
863
+ if (receipt) {
864
+ result.receipt = receipt;
865
+ reply.header("X-Payment-Receipt", receipt);
866
+ }
867
+ } catch (receiptError) {
868
+ request.log.warn({ err: receiptError }, "[x402] Failed to fetch JWT receipt");
869
+ }
870
+ }
871
+ if (options.onPayment) {
872
+ await options.onPayment(result);
873
+ }
874
+ };
875
+ }
876
+ // Annotate the CommonJS export names for ESM import in node:
877
+ 0 && (module.exports = {
878
+ ChargesClient,
879
+ DEFAULT_FACILITATOR,
880
+ USDC_CONTRACTS,
881
+ clearJWKSCache,
882
+ createPaymentRequired,
883
+ expressX402,
884
+ fastifyX402,
885
+ generateNonce,
886
+ getDefaultJWKSUrl,
887
+ isReceiptExpired,
888
+ isValidAddress,
889
+ minorToUsd,
890
+ normalizeAddress,
891
+ parsePaymentReceipt,
892
+ parseSessionGrant,
893
+ parseX402Payment,
894
+ usdToMinor,
895
+ verifyPaymentReceipt,
896
+ verifySessionWebhook,
897
+ verifyX402Payment,
898
+ withX402,
899
+ x402Plugin
900
+ });
901
+ //# sourceMappingURL=index.js.map