@mixrpay/merchant-sdk 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -30,247 +30,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
- ChargesClient: () => ChargesClient,
34
- DEFAULT_FACILITATOR: () => DEFAULT_FACILITATOR,
35
- USDC_CONTRACTS: () => USDC_CONTRACTS,
36
- clearJWKSCache: () => clearJWKSCache,
37
- createPaymentRequired: () => createPaymentRequired,
38
- expressMixrPay: () => mixrpay,
39
- expressX402: () => x402,
40
- fastifyX402: () => x4022,
41
- generateNonce: () => generateNonce,
42
- getDefaultJWKSUrl: () => getDefaultJWKSUrl,
43
- isReceiptExpired: () => isReceiptExpired,
44
- isValidAddress: () => isValidAddress,
45
- minorToUsd: () => minorToUsd,
46
- normalizeAddress: () => normalizeAddress,
47
- parsePaymentReceipt: () => parsePaymentReceipt,
48
- parseSessionGrant: () => parseSessionGrant,
49
- parseX402Payment: () => parseX402Payment,
50
- usdToMinor: () => usdToMinor,
51
33
  verifyPaymentReceipt: () => verifyPaymentReceipt,
52
- verifySessionWebhook: () => verifySessionWebhook,
53
- verifyX402Payment: () => verifyX402Payment,
54
- withMixrPay: () => withMixrPay,
55
- withX402: () => withX402,
56
- x402Plugin: () => x402Plugin
34
+ verifySessionWebhook: () => verifySessionWebhook
57
35
  });
58
36
  module.exports = __toCommonJS(src_exports);
59
37
 
60
- // src/verify.ts
61
- var import_viem = require("viem");
62
-
63
- // src/utils.ts
64
- var USDC_CONTRACTS = {
65
- 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
66
- // Base Mainnet
67
- 84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
68
- // Base Sepolia
69
- };
70
- var DEFAULT_FACILITATOR = "https://x402.org/facilitator";
71
- function getUSDCDomain(chainId) {
72
- const verifyingContract = USDC_CONTRACTS[chainId];
73
- if (!verifyingContract) {
74
- throw new Error(`Unsupported chain ID: ${chainId}. Supported: ${Object.keys(USDC_CONTRACTS).join(", ")}`);
75
- }
76
- return {
77
- name: "USD Coin",
78
- version: "2",
79
- chainId,
80
- verifyingContract
81
- };
82
- }
83
- var TRANSFER_WITH_AUTHORIZATION_TYPES = {
84
- TransferWithAuthorization: [
85
- { name: "from", type: "address" },
86
- { name: "to", type: "address" },
87
- { name: "value", type: "uint256" },
88
- { name: "validAfter", type: "uint256" },
89
- { name: "validBefore", type: "uint256" },
90
- { name: "nonce", type: "bytes32" }
91
- ]
92
- };
93
- function usdToMinor(usd) {
94
- return BigInt(Math.round(usd * 1e6));
95
- }
96
- function minorToUsd(minor) {
97
- return Number(minor) / 1e6;
98
- }
99
- function generateNonce() {
100
- const bytes = new Uint8Array(32);
101
- crypto.getRandomValues(bytes);
102
- return `0x${Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
103
- }
104
- function isValidAddress(address) {
105
- return /^0x[a-fA-F0-9]{40}$/.test(address);
106
- }
107
- function normalizeAddress(address) {
108
- if (!isValidAddress(address)) {
109
- throw new Error(`Invalid address: ${address}`);
110
- }
111
- return address.toLowerCase();
112
- }
113
- function base64Decode(str) {
114
- if (typeof Buffer !== "undefined") {
115
- return Buffer.from(str, "base64").toString("utf-8");
116
- }
117
- return atob(str);
118
- }
119
-
120
- // src/verify.ts
121
- async function verifyX402Payment(paymentHeader, options) {
122
- try {
123
- let decoded;
124
- try {
125
- const jsonStr = base64Decode(paymentHeader);
126
- decoded = JSON.parse(jsonStr);
127
- } catch (e) {
128
- return { valid: false, error: "Invalid payment header encoding" };
129
- }
130
- if (!decoded.payload?.authorization || !decoded.payload?.signature) {
131
- return { valid: false, error: "Missing authorization or signature in payment" };
132
- }
133
- const { authorization, signature } = decoded.payload;
134
- const chainId = options.chainId || 8453;
135
- const message = {
136
- from: authorization.from,
137
- to: authorization.to,
138
- value: BigInt(authorization.value),
139
- validAfter: BigInt(authorization.validAfter),
140
- validBefore: BigInt(authorization.validBefore),
141
- nonce: authorization.nonce
142
- };
143
- const domain = getUSDCDomain(chainId);
144
- let signerAddress;
145
- try {
146
- signerAddress = await (0, import_viem.recoverTypedDataAddress)({
147
- domain,
148
- types: TRANSFER_WITH_AUTHORIZATION_TYPES,
149
- primaryType: "TransferWithAuthorization",
150
- message,
151
- signature
152
- });
153
- } catch (e) {
154
- return { valid: false, error: "Failed to recover signer from signature" };
155
- }
156
- if (signerAddress.toLowerCase() !== authorization.from.toLowerCase()) {
157
- return {
158
- valid: false,
159
- error: `Signature mismatch: expected ${authorization.from}, got ${signerAddress}`
160
- };
161
- }
162
- const paymentAmount = BigInt(authorization.value);
163
- if (paymentAmount < options.expectedAmount) {
164
- return {
165
- valid: false,
166
- error: `Insufficient payment: expected ${options.expectedAmount}, got ${paymentAmount}`
167
- };
168
- }
169
- if (authorization.to.toLowerCase() !== options.expectedRecipient.toLowerCase()) {
170
- return {
171
- valid: false,
172
- error: `Wrong recipient: expected ${options.expectedRecipient}, got ${authorization.to}`
173
- };
174
- }
175
- const now = Math.floor(Date.now() / 1e3);
176
- if (Number(authorization.validBefore) < now) {
177
- return { valid: false, error: "Payment authorization has expired" };
178
- }
179
- if (Number(authorization.validAfter) > now) {
180
- return { valid: false, error: "Payment authorization is not yet valid" };
181
- }
182
- let txHash;
183
- let settledAt;
184
- if (!options.skipSettlement) {
185
- const facilitatorUrl = options.facilitator || DEFAULT_FACILITATOR;
186
- try {
187
- const settlementResponse = await fetch(`${facilitatorUrl}/settle`, {
188
- method: "POST",
189
- headers: { "Content-Type": "application/json" },
190
- body: JSON.stringify({
191
- authorization,
192
- signature,
193
- chainId
194
- })
195
- });
196
- if (!settlementResponse.ok) {
197
- const errorBody = await settlementResponse.text();
198
- let errorMessage = "Settlement failed";
199
- try {
200
- const errorJson = JSON.parse(errorBody);
201
- errorMessage = errorJson.message || errorJson.error || errorMessage;
202
- } catch {
203
- errorMessage = errorBody || errorMessage;
204
- }
205
- return { valid: false, error: `Settlement failed: ${errorMessage}` };
206
- }
207
- const settlement = await settlementResponse.json();
208
- txHash = settlement.txHash || settlement.tx_hash;
209
- settledAt = /* @__PURE__ */ new Date();
210
- } catch (e) {
211
- return {
212
- valid: false,
213
- error: `Settlement request failed: ${e.message}`
214
- };
215
- }
216
- }
217
- return {
218
- valid: true,
219
- payer: authorization.from,
220
- amount: minorToUsd(paymentAmount),
221
- amountMinor: paymentAmount,
222
- txHash,
223
- settledAt: settledAt || /* @__PURE__ */ new Date(),
224
- nonce: authorization.nonce
225
- };
226
- } catch (error) {
227
- return {
228
- valid: false,
229
- error: `Verification error: ${error.message}`
230
- };
231
- }
232
- }
233
- async function parseX402Payment(paymentHeader, chainId = 8453) {
234
- try {
235
- const jsonStr = base64Decode(paymentHeader);
236
- const decoded = JSON.parse(jsonStr);
237
- if (!decoded.payload?.authorization) {
238
- return { valid: false, error: "Missing authorization in payment" };
239
- }
240
- const { authorization, signature } = decoded.payload;
241
- const domain = getUSDCDomain(chainId);
242
- const message = {
243
- from: authorization.from,
244
- to: authorization.to,
245
- value: BigInt(authorization.value),
246
- validAfter: BigInt(authorization.validAfter),
247
- validBefore: BigInt(authorization.validBefore),
248
- nonce: authorization.nonce
249
- };
250
- const signerAddress = await (0, import_viem.recoverTypedDataAddress)({
251
- domain,
252
- types: TRANSFER_WITH_AUTHORIZATION_TYPES,
253
- primaryType: "TransferWithAuthorization",
254
- message,
255
- signature
256
- });
257
- if (signerAddress.toLowerCase() !== authorization.from.toLowerCase()) {
258
- return { valid: false, error: "Signature mismatch" };
259
- }
260
- const amountMinor = BigInt(authorization.value);
261
- return {
262
- valid: true,
263
- payer: authorization.from,
264
- recipient: authorization.to,
265
- amount: minorToUsd(amountMinor),
266
- amountMinor,
267
- expiresAt: new Date(Number(authorization.validBefore) * 1e3)
268
- };
269
- } catch (error) {
270
- return { valid: false, error: `Parse error: ${error.message}` };
271
- }
272
- }
273
-
274
38
  // src/receipt.ts
275
39
  var jose = __toESM(require("jose"));
276
40
  var DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || "https://mixrpay.com/.well-known/jwks";
@@ -339,171 +103,9 @@ async function verifyPaymentReceipt(receipt, options) {
339
103
  throw error;
340
104
  }
341
105
  }
342
- function parsePaymentReceipt(receipt) {
343
- const decoded = jose.decodeJwt(receipt);
344
- return {
345
- paymentId: decoded.paymentId,
346
- amount: decoded.amount,
347
- amountUsd: decoded.amountUsd,
348
- payer: decoded.payer,
349
- recipient: decoded.recipient,
350
- chainId: decoded.chainId,
351
- txHash: decoded.txHash,
352
- settledAt: decoded.settledAt,
353
- issuedAt: decoded.iat ? new Date(decoded.iat * 1e3) : void 0,
354
- expiresAt: decoded.exp ? new Date(decoded.exp * 1e3) : void 0
355
- };
356
- }
357
- function isReceiptExpired(receipt) {
358
- const parsed = typeof receipt === "string" ? parsePaymentReceipt(receipt) : receipt;
359
- if (!parsed.expiresAt) {
360
- return false;
361
- }
362
- return parsed.expiresAt.getTime() < Date.now();
363
- }
364
- function clearJWKSCache() {
365
- jwksCache.clear();
366
- }
367
- function getDefaultJWKSUrl() {
368
- return DEFAULT_JWKS_URL;
369
- }
370
106
 
371
107
  // src/charges.ts
372
108
  var import_crypto = __toESM(require("crypto"));
373
- var ChargesClient = class {
374
- constructor(options) {
375
- this.apiKey = options.apiKey;
376
- this.baseUrl = options.baseUrl || process.env.MIXRPAY_BASE_URL || "https://mixrpay.com";
377
- }
378
- /**
379
- * Create a new charge.
380
- *
381
- * @example
382
- * ```typescript
383
- * const result = await charges.create({
384
- * sessionId: 'sk_xxx',
385
- * amountUsd: 0.05,
386
- * reference: 'image_gen_123',
387
- * metadata: { feature: 'image_generation' }
388
- * });
389
- *
390
- * if (result.success) {
391
- * console.log(`Charged ${result.charge.amountUsdc}`);
392
- * console.log(`TX: ${result.charge.explorerUrl}`);
393
- * }
394
- * ```
395
- */
396
- async create(params) {
397
- const response = await fetch(`${this.baseUrl}/api/v1/charges`, {
398
- method: "POST",
399
- headers: {
400
- "Content-Type": "application/json",
401
- "Authorization": `Bearer ${this.apiKey}`
402
- },
403
- body: JSON.stringify({
404
- session_id: params.sessionId,
405
- amount_usd: params.amountUsd,
406
- reference: params.reference,
407
- metadata: params.metadata
408
- })
409
- });
410
- const data = await response.json();
411
- if (!response.ok) {
412
- return {
413
- success: false,
414
- error: data.error,
415
- details: data.details
416
- };
417
- }
418
- return {
419
- success: data.success,
420
- charge: data.charge_id ? {
421
- id: data.charge_id,
422
- status: data.status,
423
- amountUsd: data.amount_usd ?? 0,
424
- amountUsdc: data.amount_usdc ?? "0",
425
- txHash: data.tx_hash,
426
- explorerUrl: data.explorer_url,
427
- fromAddress: "",
428
- toAddress: "",
429
- reference: params.reference,
430
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
431
- } : void 0,
432
- idempotentReplay: data.idempotent_replay,
433
- error: data.error
434
- };
435
- }
436
- /**
437
- * Get a charge by ID.
438
- *
439
- * @example
440
- * ```typescript
441
- * const charge = await charges.get('chg_xxx');
442
- * console.log(charge.status); // 'confirmed'
443
- * ```
444
- */
445
- async get(chargeId) {
446
- const response = await fetch(`${this.baseUrl}/api/v1/charges?id=${chargeId}`, {
447
- headers: {
448
- "Authorization": `Bearer ${this.apiKey}`
449
- }
450
- });
451
- if (!response.ok) {
452
- if (response.status === 404) {
453
- return null;
454
- }
455
- throw new Error(`Failed to get charge: ${response.statusText}`);
456
- }
457
- const data = await response.json();
458
- return {
459
- id: data.id,
460
- status: data.status,
461
- amountUsd: data.amount_usd,
462
- amountUsdc: data.amount_usdc,
463
- txHash: data.tx_hash,
464
- explorerUrl: data.explorer_url,
465
- fromAddress: data.from_address,
466
- toAddress: data.to_address,
467
- reference: data.reference,
468
- createdAt: data.created_at,
469
- confirmedAt: data.confirmed_at,
470
- sessionName: data.session_name,
471
- userWallet: data.user_wallet
472
- };
473
- }
474
- /**
475
- * Get a charge by transaction hash.
476
- */
477
- async getByTxHash(txHash) {
478
- const response = await fetch(`${this.baseUrl}/api/v1/charges?tx_hash=${txHash}`, {
479
- headers: {
480
- "Authorization": `Bearer ${this.apiKey}`
481
- }
482
- });
483
- if (!response.ok) {
484
- if (response.status === 404) {
485
- return null;
486
- }
487
- throw new Error(`Failed to get charge: ${response.statusText}`);
488
- }
489
- const data = await response.json();
490
- return {
491
- id: data.id,
492
- status: data.status,
493
- amountUsd: data.amount_usd,
494
- amountUsdc: data.amount_usdc,
495
- txHash: data.tx_hash,
496
- explorerUrl: data.explorer_url,
497
- fromAddress: data.from_address,
498
- toAddress: data.to_address,
499
- reference: data.reference,
500
- createdAt: data.created_at,
501
- confirmedAt: data.confirmed_at,
502
- sessionName: data.session_name,
503
- userWallet: data.user_wallet
504
- };
505
- }
506
- };
507
109
  function verifySessionWebhook(payload, signature, secret) {
508
110
  const expectedSignature = import_crypto.default.createHmac("sha256", secret).update(payload).digest("hex");
509
111
  return import_crypto.default.timingSafeEqual(
@@ -511,886 +113,9 @@ function verifySessionWebhook(payload, signature, secret) {
511
113
  Buffer.from(expectedSignature)
512
114
  );
513
115
  }
514
- function parseSessionGrant(payload) {
515
- if (payload.event !== "session.granted") {
516
- throw new Error(`Unexpected event type: ${payload.event}`);
517
- }
518
- return {
519
- sessionId: payload.session_id,
520
- userWallet: payload.user_wallet,
521
- merchantWallet: payload.merchant_wallet,
522
- limits: {
523
- maxTotalUsd: payload.limits.max_total_usd,
524
- maxPerTxUsd: payload.limits.max_per_tx_usd,
525
- expiresAt: payload.limits.expires_at
526
- },
527
- grantedAt: payload.granted_at
528
- };
529
- }
530
-
531
- // src/receipt-fetcher.ts
532
- var DEFAULT_MIXRPAY_API_URL = process.env.MIXRPAY_BASE_URL || "https://mixrpay.com";
533
- async function fetchPaymentReceipt(params) {
534
- const apiUrl = params.apiUrl || DEFAULT_MIXRPAY_API_URL;
535
- const endpoint = `${apiUrl}/api/v1/receipts`;
536
- try {
537
- const response = await fetch(endpoint, {
538
- method: "POST",
539
- headers: {
540
- "Content-Type": "application/json"
541
- },
542
- body: JSON.stringify({
543
- tx_hash: params.txHash,
544
- payer: params.payer,
545
- recipient: params.recipient,
546
- amount: params.amount.toString(),
547
- chain_id: params.chainId
548
- })
549
- });
550
- if (!response.ok) {
551
- console.warn(`[x402] Receipt fetch failed: ${response.status} ${response.statusText}`);
552
- return null;
553
- }
554
- const data = await response.json();
555
- return data.receipt || null;
556
- } catch (error) {
557
- console.warn("[x402] Receipt fetch error:", error.message);
558
- return null;
559
- }
560
- }
561
-
562
- // src/middleware/express.ts
563
- function x402(options) {
564
- return async (req, res, next) => {
565
- try {
566
- const context = {
567
- path: req.path,
568
- method: req.method,
569
- headers: req.headers,
570
- body: req.body
571
- };
572
- if (options.skip) {
573
- const shouldSkip = await options.skip(context);
574
- if (shouldSkip) {
575
- return next();
576
- }
577
- }
578
- const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
579
- if (!recipient) {
580
- console.error("[x402] No recipient address configured. Set MIXRPAY_MERCHANT_ADDRESS env var or pass recipient option.");
581
- return res.status(500).json({
582
- error: "Payment configuration error",
583
- message: "Merchant wallet address not configured"
584
- });
585
- }
586
- const price = options.getPrice ? await options.getPrice(context) : options.price;
587
- const priceMinor = usdToMinor(price);
588
- const chainId = options.chainId || 8453;
589
- const facilitator = options.facilitator || DEFAULT_FACILITATOR;
590
- const paymentHeader = req.headers["x-payment"];
591
- if (!paymentHeader) {
592
- const nonce = generateNonce();
593
- const expiresAt = Math.floor(Date.now() / 1e3) + 300;
594
- const paymentRequired = {
595
- recipient,
596
- amount: priceMinor.toString(),
597
- currency: "USDC",
598
- chainId,
599
- facilitator,
600
- nonce,
601
- expiresAt,
602
- description: options.description
603
- };
604
- res.setHeader("X-Payment-Required", JSON.stringify(paymentRequired));
605
- res.setHeader("WWW-Authenticate", `X-402 ${Buffer.from(JSON.stringify(paymentRequired)).toString("base64")}`);
606
- return res.status(402).json({
607
- error: "Payment required",
608
- payment: paymentRequired
609
- });
610
- }
611
- const result = await verifyX402Payment(paymentHeader, {
612
- expectedAmount: priceMinor,
613
- expectedRecipient: recipient,
614
- chainId,
615
- facilitator,
616
- skipSettlement: options.testMode
617
- });
618
- if (!result.valid) {
619
- return res.status(402).json({
620
- error: "Invalid payment",
621
- reason: result.error
622
- });
623
- }
624
- req.x402Payment = result;
625
- if (result.txHash) {
626
- res.setHeader("X-Payment-TxHash", result.txHash);
627
- }
628
- const receiptMode = options.receiptMode || "webhook";
629
- if ((receiptMode === "jwt" || receiptMode === "both") && result.txHash && result.payer) {
630
- try {
631
- const receipt = await fetchPaymentReceipt({
632
- txHash: result.txHash,
633
- payer: result.payer,
634
- recipient,
635
- amount: priceMinor,
636
- chainId,
637
- apiUrl: options.mixrpayApiUrl
638
- });
639
- if (receipt) {
640
- result.receipt = receipt;
641
- res.setHeader("X-Payment-Receipt", receipt);
642
- }
643
- } catch (receiptError) {
644
- console.warn("[x402] Failed to fetch JWT receipt:", receiptError);
645
- }
646
- }
647
- if (options.onPayment) {
648
- await options.onPayment(result);
649
- }
650
- next();
651
- } catch (error) {
652
- console.error("[x402] Middleware error:", error);
653
- return res.status(500).json({
654
- error: "Payment processing error",
655
- message: error.message
656
- });
657
- }
658
- };
659
- }
660
- function mixrpay(options) {
661
- return async (req, res, next) => {
662
- try {
663
- const context = {
664
- path: req.path,
665
- method: req.method,
666
- headers: req.headers,
667
- body: req.body
668
- };
669
- if (options.skip) {
670
- const shouldSkip = await options.skip(context);
671
- if (shouldSkip) {
672
- return next();
673
- }
674
- }
675
- const priceUsd = options.getPrice ? await options.getPrice(context) : options.priceUsd;
676
- const sessionHeader = req.headers["x-mixr-session"];
677
- const widgetHeader = req.headers["x-mixr-payment"];
678
- const x402Header = req.headers["x-payment"];
679
- let result;
680
- if (sessionHeader) {
681
- result = await verifySessionPayment(sessionHeader, priceUsd, options, req);
682
- } else if (widgetHeader) {
683
- result = await verifyWidgetPayment(widgetHeader, priceUsd, options, req);
684
- } else if (x402Header) {
685
- result = await verifyX402PaymentUnified(x402Header, priceUsd, options, res);
686
- } else {
687
- return returnPaymentRequired(priceUsd, options, res);
688
- }
689
- if (!result.valid) {
690
- return res.status(402).json({
691
- error: "Invalid payment",
692
- reason: result.error,
693
- method: result.method
694
- });
695
- }
696
- req.mixrPayment = result;
697
- if (result.method === "x402" && result.x402Result) {
698
- req.x402Payment = result.x402Result;
699
- }
700
- if (result.txHash) {
701
- res.setHeader("X-Payment-TxHash", result.txHash);
702
- }
703
- if (result.chargeId) {
704
- res.setHeader("X-Mixr-Charge-Id", result.chargeId);
705
- }
706
- if (result.amountUsd !== void 0) {
707
- res.setHeader("X-Mixr-Charged", result.amountUsd.toString());
708
- }
709
- if (options.onPayment) {
710
- await options.onPayment(result);
711
- }
712
- next();
713
- } catch (error) {
714
- console.error("[mixrpay] Middleware error:", error);
715
- return res.status(500).json({
716
- error: "Payment processing error",
717
- message: error.message
718
- });
719
- }
720
- };
721
- }
722
- async function verifySessionPayment(sessionId, priceUsd, options, req) {
723
- const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://mixrpay.com";
724
- const secretKey = process.env.MIXRPAY_SECRET_KEY;
725
- if (!secretKey) {
726
- return {
727
- valid: false,
728
- method: "session",
729
- error: "MIXRPAY_SECRET_KEY not configured"
730
- };
731
- }
732
- try {
733
- const response = await fetch(`${baseUrl}/api/v2/charge`, {
734
- method: "POST",
735
- headers: {
736
- "Content-Type": "application/json",
737
- "Authorization": `Bearer ${secretKey}`
738
- },
739
- body: JSON.stringify({
740
- sessionId,
741
- amountUsd: priceUsd,
742
- feature: options.feature || req.headers["x-mixr-feature"],
743
- idempotencyKey: req.headers["x-idempotency-key"]
744
- })
745
- });
746
- if (!response.ok) {
747
- const errorData = await response.json().catch(() => ({}));
748
- return {
749
- valid: false,
750
- method: "session",
751
- error: errorData.message || errorData.error || `Charge failed: ${response.status}`,
752
- sessionId
753
- };
754
- }
755
- const data = await response.json();
756
- return {
757
- valid: true,
758
- method: "session",
759
- payer: data.payer || data.walletAddress,
760
- amountUsd: data.amountUsd || priceUsd,
761
- txHash: data.txHash,
762
- chargeId: data.chargeId,
763
- sessionId,
764
- feature: options.feature,
765
- settledAt: data.settledAt ? new Date(data.settledAt) : /* @__PURE__ */ new Date()
766
- };
767
- } catch (error) {
768
- return {
769
- valid: false,
770
- method: "session",
771
- error: `Session verification failed: ${error.message}`,
772
- sessionId
773
- };
774
- }
775
- }
776
- async function verifyWidgetPayment(paymentJwt, priceUsd, options, req) {
777
- const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://mixrpay.com";
778
- const secretKey = process.env.MIXRPAY_SECRET_KEY;
779
- if (!secretKey) {
780
- return {
781
- valid: false,
782
- method: "widget",
783
- error: "MIXRPAY_SECRET_KEY not configured"
784
- };
785
- }
786
- try {
787
- const response = await fetch(`${baseUrl}/api/widget/verify`, {
788
- method: "POST",
789
- headers: {
790
- "Content-Type": "application/json",
791
- "Authorization": `Bearer ${secretKey}`
792
- },
793
- body: JSON.stringify({
794
- paymentJwt,
795
- expectedAmountUsd: priceUsd,
796
- feature: options.feature || req.headers["x-mixr-feature"]
797
- })
798
- });
799
- if (!response.ok) {
800
- const errorData = await response.json().catch(() => ({}));
801
- return {
802
- valid: false,
803
- method: "widget",
804
- error: errorData.message || errorData.error || `Widget verification failed: ${response.status}`
805
- };
806
- }
807
- const data = await response.json();
808
- return {
809
- valid: true,
810
- method: "widget",
811
- payer: data.payer || data.walletAddress,
812
- amountUsd: data.amountUsd || priceUsd,
813
- txHash: data.txHash,
814
- chargeId: data.chargeId,
815
- feature: options.feature,
816
- settledAt: data.settledAt ? new Date(data.settledAt) : /* @__PURE__ */ new Date()
817
- };
818
- } catch (error) {
819
- return {
820
- valid: false,
821
- method: "widget",
822
- error: `Widget verification failed: ${error.message}`
823
- };
824
- }
825
- }
826
- async function verifyX402PaymentUnified(paymentHeader, priceUsd, options, res) {
827
- const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
828
- if (!recipient) {
829
- return {
830
- valid: false,
831
- method: "x402",
832
- error: "MIXRPAY_MERCHANT_ADDRESS not configured for x402 payments"
833
- };
834
- }
835
- const priceMinor = usdToMinor(priceUsd);
836
- const chainId = options.chainId || 8453;
837
- const facilitator = options.facilitator || DEFAULT_FACILITATOR;
838
- const x402Result = await verifyX402Payment(paymentHeader, {
839
- expectedAmount: priceMinor,
840
- expectedRecipient: recipient,
841
- chainId,
842
- facilitator,
843
- skipSettlement: options.testMode
844
- });
845
- if (!x402Result.valid) {
846
- return {
847
- valid: false,
848
- method: "x402",
849
- error: x402Result.error,
850
- x402Result
851
- };
852
- }
853
- const receiptMode = options.receiptMode || "webhook";
854
- if ((receiptMode === "jwt" || receiptMode === "both") && x402Result.txHash && x402Result.payer) {
855
- try {
856
- const receipt = await fetchPaymentReceipt({
857
- txHash: x402Result.txHash,
858
- payer: x402Result.payer,
859
- recipient,
860
- amount: priceMinor,
861
- chainId,
862
- apiUrl: options.mixrpayApiUrl
863
- });
864
- if (receipt) {
865
- x402Result.receipt = receipt;
866
- res.setHeader("X-Payment-Receipt", receipt);
867
- }
868
- } catch (receiptError) {
869
- console.warn("[mixrpay] Failed to fetch JWT receipt:", receiptError);
870
- }
871
- }
872
- return {
873
- valid: true,
874
- method: "x402",
875
- payer: x402Result.payer,
876
- amountUsd: x402Result.amount,
877
- txHash: x402Result.txHash,
878
- receipt: x402Result.receipt,
879
- settledAt: x402Result.settledAt,
880
- x402Result
881
- };
882
- }
883
- function returnPaymentRequired(priceUsd, options, res) {
884
- const priceMinor = usdToMinor(priceUsd);
885
- const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
886
- const chainId = options.chainId || 8453;
887
- const facilitator = options.facilitator || DEFAULT_FACILITATOR;
888
- const nonce = generateNonce();
889
- const expiresAt = Math.floor(Date.now() / 1e3) + 300;
890
- const x402PaymentRequired = recipient ? {
891
- recipient,
892
- amount: priceMinor.toString(),
893
- currency: "USDC",
894
- chainId,
895
- facilitator,
896
- nonce,
897
- expiresAt,
898
- description: options.description
899
- } : null;
900
- if (x402PaymentRequired) {
901
- res.setHeader("X-Payment-Required", JSON.stringify(x402PaymentRequired));
902
- res.setHeader("WWW-Authenticate", `X-402 ${Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64")}`);
903
- }
904
- return res.status(402).json({
905
- error: "Payment required",
906
- priceUsd,
907
- acceptedMethods: [
908
- { method: "session", header: "X-Mixr-Session", description: "Session authorization ID" },
909
- { method: "widget", header: "X-Mixr-Payment", description: "Widget payment JWT" },
910
- ...recipient ? [{ method: "x402", header: "X-PAYMENT", description: "x402 protocol payment" }] : []
911
- ],
912
- x402: x402PaymentRequired,
913
- description: options.description
914
- });
915
- }
916
-
917
- // src/middleware/nextjs.ts
918
- var import_server = require("next/server");
919
- function withX402(config, handler) {
920
- return async (req) => {
921
- try {
922
- const recipient = config.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
923
- if (!recipient) {
924
- console.error("[x402] No recipient address configured");
925
- return import_server.NextResponse.json(
926
- { error: "Payment configuration error" },
927
- { status: 500 }
928
- );
929
- }
930
- const price = config.getPrice ? await config.getPrice(req) : config.price;
931
- const priceMinor = usdToMinor(price);
932
- const chainId = config.chainId || 8453;
933
- const facilitator = config.facilitator || DEFAULT_FACILITATOR;
934
- const paymentHeader = req.headers.get("x-payment");
935
- if (!paymentHeader) {
936
- const nonce = generateNonce();
937
- const expiresAt = Math.floor(Date.now() / 1e3) + 300;
938
- const paymentRequired = {
939
- recipient,
940
- amount: priceMinor.toString(),
941
- currency: "USDC",
942
- chainId,
943
- facilitator,
944
- nonce,
945
- expiresAt,
946
- description: config.description
947
- };
948
- return import_server.NextResponse.json(
949
- { error: "Payment required", payment: paymentRequired },
950
- {
951
- status: 402,
952
- headers: {
953
- "X-Payment-Required": JSON.stringify(paymentRequired),
954
- "WWW-Authenticate": `X-402 ${Buffer.from(JSON.stringify(paymentRequired)).toString("base64")}`
955
- }
956
- }
957
- );
958
- }
959
- const result = await verifyX402Payment(paymentHeader, {
960
- expectedAmount: priceMinor,
961
- expectedRecipient: recipient,
962
- chainId,
963
- facilitator,
964
- skipSettlement: config.testMode
965
- });
966
- if (!result.valid) {
967
- return import_server.NextResponse.json(
968
- { error: "Invalid payment", reason: result.error },
969
- { status: 402 }
970
- );
971
- }
972
- const receiptMode = config.receiptMode || "webhook";
973
- if ((receiptMode === "jwt" || receiptMode === "both") && result.txHash && result.payer) {
974
- try {
975
- const receipt = await fetchPaymentReceipt({
976
- txHash: result.txHash,
977
- payer: result.payer,
978
- recipient,
979
- amount: priceMinor,
980
- chainId,
981
- apiUrl: config.mixrpayApiUrl
982
- });
983
- if (receipt) {
984
- result.receipt = receipt;
985
- }
986
- } catch (receiptError) {
987
- console.warn("[x402] Failed to fetch JWT receipt:", receiptError);
988
- }
989
- }
990
- if (config.onPayment) {
991
- await config.onPayment(result, req);
992
- }
993
- const response = await handler(req, result);
994
- if (result.txHash) {
995
- response.headers.set("X-Payment-TxHash", result.txHash);
996
- }
997
- if (result.receipt) {
998
- response.headers.set("X-Payment-Receipt", result.receipt);
999
- }
1000
- return response;
1001
- } catch (error) {
1002
- console.error("[x402] Handler error:", error);
1003
- return import_server.NextResponse.json(
1004
- { error: "Payment processing error" },
1005
- { status: 500 }
1006
- );
1007
- }
1008
- };
1009
- }
1010
- function createPaymentRequired(options) {
1011
- const priceMinor = usdToMinor(options.amount);
1012
- const nonce = generateNonce();
1013
- const expiresAt = Math.floor(Date.now() / 1e3) + 300;
1014
- const paymentRequired = {
1015
- recipient: options.recipient,
1016
- amount: priceMinor.toString(),
1017
- currency: "USDC",
1018
- chainId: options.chainId || 8453,
1019
- facilitator: options.facilitator || DEFAULT_FACILITATOR,
1020
- nonce,
1021
- expiresAt,
1022
- description: options.description
1023
- };
1024
- return import_server.NextResponse.json(
1025
- { error: "Payment required", payment: paymentRequired },
1026
- {
1027
- status: 402,
1028
- headers: {
1029
- "X-Payment-Required": JSON.stringify(paymentRequired)
1030
- }
1031
- }
1032
- );
1033
- }
1034
- function withMixrPay(config, handler) {
1035
- return async (req) => {
1036
- try {
1037
- const priceUsd = config.getPrice ? await config.getPrice(req) : config.priceUsd;
1038
- const sessionHeader = req.headers.get("x-mixr-session");
1039
- const widgetHeader = req.headers.get("x-mixr-payment");
1040
- const x402Header = req.headers.get("x-payment");
1041
- let result;
1042
- if (sessionHeader) {
1043
- result = await verifySessionPaymentNext(sessionHeader, priceUsd, config, req);
1044
- } else if (widgetHeader) {
1045
- result = await verifyWidgetPaymentNext(widgetHeader, priceUsd, config, req);
1046
- } else if (x402Header) {
1047
- result = await verifyX402PaymentNext(x402Header, priceUsd, config);
1048
- } else {
1049
- return returnPaymentRequiredNext(priceUsd, config);
1050
- }
1051
- if (!result.valid) {
1052
- return import_server.NextResponse.json(
1053
- { error: "Invalid payment", reason: result.error, method: result.method },
1054
- { status: 402 }
1055
- );
1056
- }
1057
- if (config.onPayment) {
1058
- await config.onPayment(result, req);
1059
- }
1060
- const response = await handler(req, result);
1061
- if (result.txHash) {
1062
- response.headers.set("X-Payment-TxHash", result.txHash);
1063
- }
1064
- if (result.chargeId) {
1065
- response.headers.set("X-Mixr-Charge-Id", result.chargeId);
1066
- }
1067
- if (result.amountUsd !== void 0) {
1068
- response.headers.set("X-Mixr-Charged", result.amountUsd.toString());
1069
- }
1070
- return response;
1071
- } catch (error) {
1072
- console.error("[withMixrPay] Handler error:", error);
1073
- return import_server.NextResponse.json(
1074
- { error: "Payment processing error" },
1075
- { status: 500 }
1076
- );
1077
- }
1078
- };
1079
- }
1080
- async function verifySessionPaymentNext(sessionId, priceUsd, config, req) {
1081
- const baseUrl = config.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://mixrpay.com";
1082
- const secretKey = process.env.MIXRPAY_SECRET_KEY;
1083
- if (!secretKey) {
1084
- return { valid: false, method: "session", error: "MIXRPAY_SECRET_KEY not configured" };
1085
- }
1086
- try {
1087
- const response = await fetch(`${baseUrl}/api/v2/charge`, {
1088
- method: "POST",
1089
- headers: {
1090
- "Content-Type": "application/json",
1091
- "Authorization": `Bearer ${secretKey}`
1092
- },
1093
- body: JSON.stringify({
1094
- sessionId,
1095
- amountUsd: priceUsd,
1096
- feature: config.feature || req.headers.get("x-mixr-feature"),
1097
- idempotencyKey: req.headers.get("x-idempotency-key")
1098
- })
1099
- });
1100
- if (!response.ok) {
1101
- const errorData = await response.json().catch(() => ({}));
1102
- return {
1103
- valid: false,
1104
- method: "session",
1105
- error: errorData.message || errorData.error || `Charge failed: ${response.status}`,
1106
- sessionId
1107
- };
1108
- }
1109
- const data = await response.json();
1110
- return {
1111
- valid: true,
1112
- method: "session",
1113
- payer: data.payer || data.walletAddress,
1114
- amountUsd: data.amountUsd || priceUsd,
1115
- txHash: data.txHash,
1116
- chargeId: data.chargeId,
1117
- sessionId,
1118
- feature: config.feature,
1119
- settledAt: data.settledAt ? new Date(data.settledAt) : /* @__PURE__ */ new Date()
1120
- };
1121
- } catch (error) {
1122
- return {
1123
- valid: false,
1124
- method: "session",
1125
- error: `Session verification failed: ${error.message}`,
1126
- sessionId
1127
- };
1128
- }
1129
- }
1130
- async function verifyWidgetPaymentNext(paymentJwt, priceUsd, config, req) {
1131
- const baseUrl = config.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://mixrpay.com";
1132
- const secretKey = process.env.MIXRPAY_SECRET_KEY;
1133
- if (!secretKey) {
1134
- return { valid: false, method: "widget", error: "MIXRPAY_SECRET_KEY not configured" };
1135
- }
1136
- try {
1137
- const response = await fetch(`${baseUrl}/api/widget/verify`, {
1138
- method: "POST",
1139
- headers: {
1140
- "Content-Type": "application/json",
1141
- "Authorization": `Bearer ${secretKey}`
1142
- },
1143
- body: JSON.stringify({
1144
- paymentJwt,
1145
- expectedAmountUsd: priceUsd,
1146
- feature: config.feature || req.headers.get("x-mixr-feature")
1147
- })
1148
- });
1149
- if (!response.ok) {
1150
- const errorData = await response.json().catch(() => ({}));
1151
- return {
1152
- valid: false,
1153
- method: "widget",
1154
- error: errorData.message || errorData.error || `Widget verification failed: ${response.status}`
1155
- };
1156
- }
1157
- const data = await response.json();
1158
- return {
1159
- valid: true,
1160
- method: "widget",
1161
- payer: data.payer || data.walletAddress,
1162
- amountUsd: data.amountUsd || priceUsd,
1163
- txHash: data.txHash,
1164
- chargeId: data.chargeId,
1165
- feature: config.feature,
1166
- settledAt: data.settledAt ? new Date(data.settledAt) : /* @__PURE__ */ new Date()
1167
- };
1168
- } catch (error) {
1169
- return {
1170
- valid: false,
1171
- method: "widget",
1172
- error: `Widget verification failed: ${error.message}`
1173
- };
1174
- }
1175
- }
1176
- async function verifyX402PaymentNext(paymentHeader, priceUsd, config) {
1177
- const recipient = config.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
1178
- if (!recipient) {
1179
- return {
1180
- valid: false,
1181
- method: "x402",
1182
- error: "MIXRPAY_MERCHANT_ADDRESS not configured for x402 payments"
1183
- };
1184
- }
1185
- const priceMinor = usdToMinor(priceUsd);
1186
- const chainId = config.chainId || 8453;
1187
- const facilitator = config.facilitator || DEFAULT_FACILITATOR;
1188
- const x402Result = await verifyX402Payment(paymentHeader, {
1189
- expectedAmount: priceMinor,
1190
- expectedRecipient: recipient,
1191
- chainId,
1192
- facilitator,
1193
- skipSettlement: config.testMode
1194
- });
1195
- if (!x402Result.valid) {
1196
- return {
1197
- valid: false,
1198
- method: "x402",
1199
- error: x402Result.error,
1200
- x402Result
1201
- };
1202
- }
1203
- const receiptMode = config.receiptMode || "webhook";
1204
- if ((receiptMode === "jwt" || receiptMode === "both") && x402Result.txHash && x402Result.payer) {
1205
- try {
1206
- const receipt = await fetchPaymentReceipt({
1207
- txHash: x402Result.txHash,
1208
- payer: x402Result.payer,
1209
- recipient,
1210
- amount: priceMinor,
1211
- chainId,
1212
- apiUrl: config.mixrpayApiUrl
1213
- });
1214
- if (receipt) {
1215
- x402Result.receipt = receipt;
1216
- }
1217
- } catch (receiptError) {
1218
- console.warn("[withMixrPay] Failed to fetch JWT receipt:", receiptError);
1219
- }
1220
- }
1221
- return {
1222
- valid: true,
1223
- method: "x402",
1224
- payer: x402Result.payer,
1225
- amountUsd: x402Result.amount,
1226
- txHash: x402Result.txHash,
1227
- receipt: x402Result.receipt,
1228
- settledAt: x402Result.settledAt,
1229
- x402Result
1230
- };
1231
- }
1232
- function returnPaymentRequiredNext(priceUsd, config) {
1233
- const priceMinor = usdToMinor(priceUsd);
1234
- const recipient = config.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
1235
- const chainId = config.chainId || 8453;
1236
- const facilitator = config.facilitator || DEFAULT_FACILITATOR;
1237
- const nonce = generateNonce();
1238
- const expiresAt = Math.floor(Date.now() / 1e3) + 300;
1239
- const x402PaymentRequired = recipient ? {
1240
- recipient,
1241
- amount: priceMinor.toString(),
1242
- currency: "USDC",
1243
- chainId,
1244
- facilitator,
1245
- nonce,
1246
- expiresAt,
1247
- description: config.description
1248
- } : null;
1249
- const headers = {};
1250
- if (x402PaymentRequired) {
1251
- headers["X-Payment-Required"] = JSON.stringify(x402PaymentRequired);
1252
- headers["WWW-Authenticate"] = `X-402 ${Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64")}`;
1253
- }
1254
- return import_server.NextResponse.json(
1255
- {
1256
- error: "Payment required",
1257
- priceUsd,
1258
- acceptedMethods: [
1259
- { method: "session", header: "X-Mixr-Session", description: "Session authorization ID" },
1260
- { method: "widget", header: "X-Mixr-Payment", description: "Widget payment JWT" },
1261
- ...recipient ? [{ method: "x402", header: "X-PAYMENT", description: "x402 protocol payment" }] : []
1262
- ],
1263
- x402: x402PaymentRequired,
1264
- description: config.description
1265
- },
1266
- { status: 402, headers }
1267
- );
1268
- }
1269
-
1270
- // src/middleware/fastify.ts
1271
- var x402Plugin = (fastify, options, done) => {
1272
- fastify.decorateRequest("x402Payment", null);
1273
- fastify.decorate("x402Defaults", {
1274
- recipient: options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS,
1275
- chainId: options.chainId || 8453,
1276
- facilitator: options.facilitator || DEFAULT_FACILITATOR
1277
- });
1278
- done();
1279
- };
1280
- function x4022(options) {
1281
- return async (request, reply) => {
1282
- const context = {
1283
- path: request.url,
1284
- method: request.method,
1285
- headers: request.headers,
1286
- body: request.body
1287
- };
1288
- if (options.skip) {
1289
- const shouldSkip = await options.skip(context);
1290
- if (shouldSkip) {
1291
- return;
1292
- }
1293
- }
1294
- const defaults = request.server.x402Defaults || {};
1295
- const recipient = options.recipient || defaults.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
1296
- if (!recipient) {
1297
- request.log.error("[x402] No recipient address configured");
1298
- return reply.status(500).send({
1299
- error: "Payment configuration error",
1300
- message: "Merchant wallet address not configured"
1301
- });
1302
- }
1303
- const price = options.getPrice ? await options.getPrice(context) : options.price;
1304
- const priceMinor = usdToMinor(price);
1305
- const chainId = options.chainId || defaults.chainId || 8453;
1306
- const facilitator = options.facilitator || defaults.facilitator || DEFAULT_FACILITATOR;
1307
- const paymentHeader = request.headers["x-payment"];
1308
- if (!paymentHeader) {
1309
- const nonce = generateNonce();
1310
- const expiresAt = Math.floor(Date.now() / 1e3) + 300;
1311
- const paymentRequired = {
1312
- recipient,
1313
- amount: priceMinor.toString(),
1314
- currency: "USDC",
1315
- chainId,
1316
- facilitator,
1317
- nonce,
1318
- expiresAt,
1319
- description: options.description
1320
- };
1321
- reply.header("X-Payment-Required", JSON.stringify(paymentRequired));
1322
- reply.header("WWW-Authenticate", `X-402 ${Buffer.from(JSON.stringify(paymentRequired)).toString("base64")}`);
1323
- return reply.status(402).send({
1324
- error: "Payment required",
1325
- payment: paymentRequired
1326
- });
1327
- }
1328
- const result = await verifyX402Payment(paymentHeader, {
1329
- expectedAmount: priceMinor,
1330
- expectedRecipient: recipient,
1331
- chainId,
1332
- facilitator,
1333
- skipSettlement: options.testMode
1334
- });
1335
- if (!result.valid) {
1336
- return reply.status(402).send({
1337
- error: "Invalid payment",
1338
- reason: result.error
1339
- });
1340
- }
1341
- request.x402Payment = result;
1342
- if (result.txHash) {
1343
- reply.header("X-Payment-TxHash", result.txHash);
1344
- }
1345
- const receiptMode = options.receiptMode || "webhook";
1346
- if ((receiptMode === "jwt" || receiptMode === "both") && result.txHash && result.payer) {
1347
- try {
1348
- const receipt = await fetchPaymentReceipt({
1349
- txHash: result.txHash,
1350
- payer: result.payer,
1351
- recipient,
1352
- amount: priceMinor,
1353
- chainId,
1354
- apiUrl: options.mixrpayApiUrl
1355
- });
1356
- if (receipt) {
1357
- result.receipt = receipt;
1358
- reply.header("X-Payment-Receipt", receipt);
1359
- }
1360
- } catch (receiptError) {
1361
- request.log.warn({ err: receiptError }, "[x402] Failed to fetch JWT receipt");
1362
- }
1363
- }
1364
- if (options.onPayment) {
1365
- await options.onPayment(result);
1366
- }
1367
- };
1368
- }
1369
116
  // Annotate the CommonJS export names for ESM import in node:
1370
117
  0 && (module.exports = {
1371
- ChargesClient,
1372
- DEFAULT_FACILITATOR,
1373
- USDC_CONTRACTS,
1374
- clearJWKSCache,
1375
- createPaymentRequired,
1376
- expressMixrPay,
1377
- expressX402,
1378
- fastifyX402,
1379
- generateNonce,
1380
- getDefaultJWKSUrl,
1381
- isReceiptExpired,
1382
- isValidAddress,
1383
- minorToUsd,
1384
- normalizeAddress,
1385
- parsePaymentReceipt,
1386
- parseSessionGrant,
1387
- parseX402Payment,
1388
- usdToMinor,
1389
118
  verifyPaymentReceipt,
1390
- verifySessionWebhook,
1391
- verifyX402Payment,
1392
- withMixrPay,
1393
- withX402,
1394
- x402Plugin
119
+ verifySessionWebhook
1395
120
  });
1396
121
  //# sourceMappingURL=index.js.map