@mixrpay/merchant-sdk 0.1.0 → 0.2.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/README.md +186 -88
- package/dist/index.js +498 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +496 -3
- package/dist/index.mjs.map +1 -1
- package/dist/middleware/express.js +259 -1
- package/dist/middleware/express.js.map +1 -1
- package/dist/middleware/express.mjs +258 -1
- package/dist/middleware/express.mjs.map +1 -1
- package/dist/middleware/fastify.js +1 -1
- package/dist/middleware/fastify.js.map +1 -1
- package/dist/middleware/fastify.mjs +1 -1
- package/dist/middleware/fastify.mjs.map +1 -1
- package/dist/middleware/nextjs.js +238 -1
- package/dist/middleware/nextjs.js.map +1 -1
- package/dist/middleware/nextjs.mjs +237 -1
- package/dist/middleware/nextjs.mjs.map +1 -1
- package/package.json +4 -1
- package/dist/index.d.mts +0 -291
- package/dist/index.d.ts +0 -291
- package/dist/middleware/express.d.mts +0 -57
- package/dist/middleware/express.d.ts +0 -57
- package/dist/middleware/fastify.d.mts +0 -56
- package/dist/middleware/fastify.d.ts +0 -56
- package/dist/middleware/nextjs.d.mts +0 -78
- package/dist/middleware/nextjs.d.ts +0 -78
- package/dist/types-BJNy6Hhb.d.mts +0 -166
- package/dist/types-BJNy6Hhb.d.ts +0 -166
package/dist/index.mjs
CHANGED
|
@@ -214,7 +214,7 @@ async function parseX402Payment(paymentHeader, chainId = 8453) {
|
|
|
214
214
|
|
|
215
215
|
// src/receipt.ts
|
|
216
216
|
import * as jose from "jose";
|
|
217
|
-
var DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || "
|
|
217
|
+
var DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || "https://mixrpay.com/.well-known/jwks";
|
|
218
218
|
var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
219
219
|
var jwksCache = /* @__PURE__ */ new Map();
|
|
220
220
|
async function getJWKS(jwksUrl) {
|
|
@@ -314,7 +314,7 @@ import crypto2 from "crypto";
|
|
|
314
314
|
var ChargesClient = class {
|
|
315
315
|
constructor(options) {
|
|
316
316
|
this.apiKey = options.apiKey;
|
|
317
|
-
this.baseUrl = options.baseUrl || process.env.MIXRPAY_BASE_URL || "
|
|
317
|
+
this.baseUrl = options.baseUrl || process.env.MIXRPAY_BASE_URL || "https://mixrpay.com";
|
|
318
318
|
}
|
|
319
319
|
/**
|
|
320
320
|
* Create a new charge.
|
|
@@ -470,7 +470,7 @@ function parseSessionGrant(payload) {
|
|
|
470
470
|
}
|
|
471
471
|
|
|
472
472
|
// src/receipt-fetcher.ts
|
|
473
|
-
var DEFAULT_MIXRPAY_API_URL = process.env.MIXRPAY_BASE_URL || "
|
|
473
|
+
var DEFAULT_MIXRPAY_API_URL = process.env.MIXRPAY_BASE_URL || "https://mixrpay.com";
|
|
474
474
|
async function fetchPaymentReceipt(params) {
|
|
475
475
|
const apiUrl = params.apiUrl || DEFAULT_MIXRPAY_API_URL;
|
|
476
476
|
const endpoint = `${apiUrl}/api/v1/receipts`;
|
|
@@ -598,6 +598,262 @@ function x402(options) {
|
|
|
598
598
|
}
|
|
599
599
|
};
|
|
600
600
|
}
|
|
601
|
+
function mixrpay(options) {
|
|
602
|
+
return async (req, res, next) => {
|
|
603
|
+
try {
|
|
604
|
+
const context = {
|
|
605
|
+
path: req.path,
|
|
606
|
+
method: req.method,
|
|
607
|
+
headers: req.headers,
|
|
608
|
+
body: req.body
|
|
609
|
+
};
|
|
610
|
+
if (options.skip) {
|
|
611
|
+
const shouldSkip = await options.skip(context);
|
|
612
|
+
if (shouldSkip) {
|
|
613
|
+
return next();
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
const priceUsd = options.getPrice ? await options.getPrice(context) : options.priceUsd;
|
|
617
|
+
const sessionHeader = req.headers["x-mixr-session"];
|
|
618
|
+
const widgetHeader = req.headers["x-mixr-payment"];
|
|
619
|
+
const x402Header = req.headers["x-payment"];
|
|
620
|
+
let result;
|
|
621
|
+
if (sessionHeader) {
|
|
622
|
+
result = await verifySessionPayment(sessionHeader, priceUsd, options, req);
|
|
623
|
+
} else if (widgetHeader) {
|
|
624
|
+
result = await verifyWidgetPayment(widgetHeader, priceUsd, options, req);
|
|
625
|
+
} else if (x402Header) {
|
|
626
|
+
result = await verifyX402PaymentUnified(x402Header, priceUsd, options, res);
|
|
627
|
+
} else {
|
|
628
|
+
return returnPaymentRequired(priceUsd, options, res);
|
|
629
|
+
}
|
|
630
|
+
if (!result.valid) {
|
|
631
|
+
return res.status(402).json({
|
|
632
|
+
error: "Invalid payment",
|
|
633
|
+
reason: result.error,
|
|
634
|
+
method: result.method
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
req.mixrPayment = result;
|
|
638
|
+
if (result.method === "x402" && result.x402Result) {
|
|
639
|
+
req.x402Payment = result.x402Result;
|
|
640
|
+
}
|
|
641
|
+
if (result.txHash) {
|
|
642
|
+
res.setHeader("X-Payment-TxHash", result.txHash);
|
|
643
|
+
}
|
|
644
|
+
if (result.chargeId) {
|
|
645
|
+
res.setHeader("X-Mixr-Charge-Id", result.chargeId);
|
|
646
|
+
}
|
|
647
|
+
if (result.amountUsd !== void 0) {
|
|
648
|
+
res.setHeader("X-Mixr-Charged", result.amountUsd.toString());
|
|
649
|
+
}
|
|
650
|
+
if (options.onPayment) {
|
|
651
|
+
await options.onPayment(result);
|
|
652
|
+
}
|
|
653
|
+
next();
|
|
654
|
+
} catch (error) {
|
|
655
|
+
console.error("[mixrpay] Middleware error:", error);
|
|
656
|
+
return res.status(500).json({
|
|
657
|
+
error: "Payment processing error",
|
|
658
|
+
message: error.message
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
async function verifySessionPayment(sessionId, priceUsd, options, req) {
|
|
664
|
+
const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://mixrpay.com";
|
|
665
|
+
const secretKey = process.env.MIXRPAY_SECRET_KEY;
|
|
666
|
+
if (!secretKey) {
|
|
667
|
+
return {
|
|
668
|
+
valid: false,
|
|
669
|
+
method: "session",
|
|
670
|
+
error: "MIXRPAY_SECRET_KEY not configured"
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
try {
|
|
674
|
+
const response = await fetch(`${baseUrl}/api/v2/charge`, {
|
|
675
|
+
method: "POST",
|
|
676
|
+
headers: {
|
|
677
|
+
"Content-Type": "application/json",
|
|
678
|
+
"Authorization": `Bearer ${secretKey}`
|
|
679
|
+
},
|
|
680
|
+
body: JSON.stringify({
|
|
681
|
+
sessionId,
|
|
682
|
+
amountUsd: priceUsd,
|
|
683
|
+
feature: options.feature || req.headers["x-mixr-feature"],
|
|
684
|
+
idempotencyKey: req.headers["x-idempotency-key"]
|
|
685
|
+
})
|
|
686
|
+
});
|
|
687
|
+
if (!response.ok) {
|
|
688
|
+
const errorData = await response.json().catch(() => ({}));
|
|
689
|
+
return {
|
|
690
|
+
valid: false,
|
|
691
|
+
method: "session",
|
|
692
|
+
error: errorData.message || errorData.error || `Charge failed: ${response.status}`,
|
|
693
|
+
sessionId
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
const data = await response.json();
|
|
697
|
+
return {
|
|
698
|
+
valid: true,
|
|
699
|
+
method: "session",
|
|
700
|
+
payer: data.payer || data.walletAddress,
|
|
701
|
+
amountUsd: data.amountUsd || priceUsd,
|
|
702
|
+
txHash: data.txHash,
|
|
703
|
+
chargeId: data.chargeId,
|
|
704
|
+
sessionId,
|
|
705
|
+
feature: options.feature,
|
|
706
|
+
settledAt: data.settledAt ? new Date(data.settledAt) : /* @__PURE__ */ new Date()
|
|
707
|
+
};
|
|
708
|
+
} catch (error) {
|
|
709
|
+
return {
|
|
710
|
+
valid: false,
|
|
711
|
+
method: "session",
|
|
712
|
+
error: `Session verification failed: ${error.message}`,
|
|
713
|
+
sessionId
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
async function verifyWidgetPayment(paymentJwt, priceUsd, options, req) {
|
|
718
|
+
const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://mixrpay.com";
|
|
719
|
+
const secretKey = process.env.MIXRPAY_SECRET_KEY;
|
|
720
|
+
if (!secretKey) {
|
|
721
|
+
return {
|
|
722
|
+
valid: false,
|
|
723
|
+
method: "widget",
|
|
724
|
+
error: "MIXRPAY_SECRET_KEY not configured"
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
try {
|
|
728
|
+
const response = await fetch(`${baseUrl}/api/widget/verify`, {
|
|
729
|
+
method: "POST",
|
|
730
|
+
headers: {
|
|
731
|
+
"Content-Type": "application/json",
|
|
732
|
+
"Authorization": `Bearer ${secretKey}`
|
|
733
|
+
},
|
|
734
|
+
body: JSON.stringify({
|
|
735
|
+
paymentJwt,
|
|
736
|
+
expectedAmountUsd: priceUsd,
|
|
737
|
+
feature: options.feature || req.headers["x-mixr-feature"]
|
|
738
|
+
})
|
|
739
|
+
});
|
|
740
|
+
if (!response.ok) {
|
|
741
|
+
const errorData = await response.json().catch(() => ({}));
|
|
742
|
+
return {
|
|
743
|
+
valid: false,
|
|
744
|
+
method: "widget",
|
|
745
|
+
error: errorData.message || errorData.error || `Widget verification failed: ${response.status}`
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
const data = await response.json();
|
|
749
|
+
return {
|
|
750
|
+
valid: true,
|
|
751
|
+
method: "widget",
|
|
752
|
+
payer: data.payer || data.walletAddress,
|
|
753
|
+
amountUsd: data.amountUsd || priceUsd,
|
|
754
|
+
txHash: data.txHash,
|
|
755
|
+
chargeId: data.chargeId,
|
|
756
|
+
feature: options.feature,
|
|
757
|
+
settledAt: data.settledAt ? new Date(data.settledAt) : /* @__PURE__ */ new Date()
|
|
758
|
+
};
|
|
759
|
+
} catch (error) {
|
|
760
|
+
return {
|
|
761
|
+
valid: false,
|
|
762
|
+
method: "widget",
|
|
763
|
+
error: `Widget verification failed: ${error.message}`
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
async function verifyX402PaymentUnified(paymentHeader, priceUsd, options, res) {
|
|
768
|
+
const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
|
|
769
|
+
if (!recipient) {
|
|
770
|
+
return {
|
|
771
|
+
valid: false,
|
|
772
|
+
method: "x402",
|
|
773
|
+
error: "MIXRPAY_MERCHANT_ADDRESS not configured for x402 payments"
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
const priceMinor = usdToMinor(priceUsd);
|
|
777
|
+
const chainId = options.chainId || 8453;
|
|
778
|
+
const facilitator = options.facilitator || DEFAULT_FACILITATOR;
|
|
779
|
+
const x402Result = await verifyX402Payment(paymentHeader, {
|
|
780
|
+
expectedAmount: priceMinor,
|
|
781
|
+
expectedRecipient: recipient,
|
|
782
|
+
chainId,
|
|
783
|
+
facilitator,
|
|
784
|
+
skipSettlement: options.testMode
|
|
785
|
+
});
|
|
786
|
+
if (!x402Result.valid) {
|
|
787
|
+
return {
|
|
788
|
+
valid: false,
|
|
789
|
+
method: "x402",
|
|
790
|
+
error: x402Result.error,
|
|
791
|
+
x402Result
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
const receiptMode = options.receiptMode || "webhook";
|
|
795
|
+
if ((receiptMode === "jwt" || receiptMode === "both") && x402Result.txHash && x402Result.payer) {
|
|
796
|
+
try {
|
|
797
|
+
const receipt = await fetchPaymentReceipt({
|
|
798
|
+
txHash: x402Result.txHash,
|
|
799
|
+
payer: x402Result.payer,
|
|
800
|
+
recipient,
|
|
801
|
+
amount: priceMinor,
|
|
802
|
+
chainId,
|
|
803
|
+
apiUrl: options.mixrpayApiUrl
|
|
804
|
+
});
|
|
805
|
+
if (receipt) {
|
|
806
|
+
x402Result.receipt = receipt;
|
|
807
|
+
res.setHeader("X-Payment-Receipt", receipt);
|
|
808
|
+
}
|
|
809
|
+
} catch (receiptError) {
|
|
810
|
+
console.warn("[mixrpay] Failed to fetch JWT receipt:", receiptError);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return {
|
|
814
|
+
valid: true,
|
|
815
|
+
method: "x402",
|
|
816
|
+
payer: x402Result.payer,
|
|
817
|
+
amountUsd: x402Result.amount,
|
|
818
|
+
txHash: x402Result.txHash,
|
|
819
|
+
receipt: x402Result.receipt,
|
|
820
|
+
settledAt: x402Result.settledAt,
|
|
821
|
+
x402Result
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function returnPaymentRequired(priceUsd, options, res) {
|
|
825
|
+
const priceMinor = usdToMinor(priceUsd);
|
|
826
|
+
const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
|
|
827
|
+
const chainId = options.chainId || 8453;
|
|
828
|
+
const facilitator = options.facilitator || DEFAULT_FACILITATOR;
|
|
829
|
+
const nonce = generateNonce();
|
|
830
|
+
const expiresAt = Math.floor(Date.now() / 1e3) + 300;
|
|
831
|
+
const x402PaymentRequired = recipient ? {
|
|
832
|
+
recipient,
|
|
833
|
+
amount: priceMinor.toString(),
|
|
834
|
+
currency: "USDC",
|
|
835
|
+
chainId,
|
|
836
|
+
facilitator,
|
|
837
|
+
nonce,
|
|
838
|
+
expiresAt,
|
|
839
|
+
description: options.description
|
|
840
|
+
} : null;
|
|
841
|
+
if (x402PaymentRequired) {
|
|
842
|
+
res.setHeader("X-Payment-Required", JSON.stringify(x402PaymentRequired));
|
|
843
|
+
res.setHeader("WWW-Authenticate", `X-402 ${Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64")}`);
|
|
844
|
+
}
|
|
845
|
+
return res.status(402).json({
|
|
846
|
+
error: "Payment required",
|
|
847
|
+
priceUsd,
|
|
848
|
+
acceptedMethods: [
|
|
849
|
+
{ method: "session", header: "X-Mixr-Session", description: "Session authorization ID" },
|
|
850
|
+
{ method: "widget", header: "X-Mixr-Payment", description: "Widget payment JWT" },
|
|
851
|
+
...recipient ? [{ method: "x402", header: "X-PAYMENT", description: "x402 protocol payment" }] : []
|
|
852
|
+
],
|
|
853
|
+
x402: x402PaymentRequired,
|
|
854
|
+
description: options.description
|
|
855
|
+
});
|
|
856
|
+
}
|
|
601
857
|
|
|
602
858
|
// src/middleware/nextjs.ts
|
|
603
859
|
import { NextResponse } from "next/server";
|
|
@@ -716,6 +972,241 @@ function createPaymentRequired(options) {
|
|
|
716
972
|
}
|
|
717
973
|
);
|
|
718
974
|
}
|
|
975
|
+
function withMixrPay(config, handler) {
|
|
976
|
+
return async (req) => {
|
|
977
|
+
try {
|
|
978
|
+
const priceUsd = config.getPrice ? await config.getPrice(req) : config.priceUsd;
|
|
979
|
+
const sessionHeader = req.headers.get("x-mixr-session");
|
|
980
|
+
const widgetHeader = req.headers.get("x-mixr-payment");
|
|
981
|
+
const x402Header = req.headers.get("x-payment");
|
|
982
|
+
let result;
|
|
983
|
+
if (sessionHeader) {
|
|
984
|
+
result = await verifySessionPaymentNext(sessionHeader, priceUsd, config, req);
|
|
985
|
+
} else if (widgetHeader) {
|
|
986
|
+
result = await verifyWidgetPaymentNext(widgetHeader, priceUsd, config, req);
|
|
987
|
+
} else if (x402Header) {
|
|
988
|
+
result = await verifyX402PaymentNext(x402Header, priceUsd, config);
|
|
989
|
+
} else {
|
|
990
|
+
return returnPaymentRequiredNext(priceUsd, config);
|
|
991
|
+
}
|
|
992
|
+
if (!result.valid) {
|
|
993
|
+
return NextResponse.json(
|
|
994
|
+
{ error: "Invalid payment", reason: result.error, method: result.method },
|
|
995
|
+
{ status: 402 }
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
if (config.onPayment) {
|
|
999
|
+
await config.onPayment(result, req);
|
|
1000
|
+
}
|
|
1001
|
+
const response = await handler(req, result);
|
|
1002
|
+
if (result.txHash) {
|
|
1003
|
+
response.headers.set("X-Payment-TxHash", result.txHash);
|
|
1004
|
+
}
|
|
1005
|
+
if (result.chargeId) {
|
|
1006
|
+
response.headers.set("X-Mixr-Charge-Id", result.chargeId);
|
|
1007
|
+
}
|
|
1008
|
+
if (result.amountUsd !== void 0) {
|
|
1009
|
+
response.headers.set("X-Mixr-Charged", result.amountUsd.toString());
|
|
1010
|
+
}
|
|
1011
|
+
return response;
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
console.error("[withMixrPay] Handler error:", error);
|
|
1014
|
+
return NextResponse.json(
|
|
1015
|
+
{ error: "Payment processing error" },
|
|
1016
|
+
{ status: 500 }
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
async function verifySessionPaymentNext(sessionId, priceUsd, config, req) {
|
|
1022
|
+
const baseUrl = config.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://mixrpay.com";
|
|
1023
|
+
const secretKey = process.env.MIXRPAY_SECRET_KEY;
|
|
1024
|
+
if (!secretKey) {
|
|
1025
|
+
return { valid: false, method: "session", error: "MIXRPAY_SECRET_KEY not configured" };
|
|
1026
|
+
}
|
|
1027
|
+
try {
|
|
1028
|
+
const response = await fetch(`${baseUrl}/api/v2/charge`, {
|
|
1029
|
+
method: "POST",
|
|
1030
|
+
headers: {
|
|
1031
|
+
"Content-Type": "application/json",
|
|
1032
|
+
"Authorization": `Bearer ${secretKey}`
|
|
1033
|
+
},
|
|
1034
|
+
body: JSON.stringify({
|
|
1035
|
+
sessionId,
|
|
1036
|
+
amountUsd: priceUsd,
|
|
1037
|
+
feature: config.feature || req.headers.get("x-mixr-feature"),
|
|
1038
|
+
idempotencyKey: req.headers.get("x-idempotency-key")
|
|
1039
|
+
})
|
|
1040
|
+
});
|
|
1041
|
+
if (!response.ok) {
|
|
1042
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1043
|
+
return {
|
|
1044
|
+
valid: false,
|
|
1045
|
+
method: "session",
|
|
1046
|
+
error: errorData.message || errorData.error || `Charge failed: ${response.status}`,
|
|
1047
|
+
sessionId
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
const data = await response.json();
|
|
1051
|
+
return {
|
|
1052
|
+
valid: true,
|
|
1053
|
+
method: "session",
|
|
1054
|
+
payer: data.payer || data.walletAddress,
|
|
1055
|
+
amountUsd: data.amountUsd || priceUsd,
|
|
1056
|
+
txHash: data.txHash,
|
|
1057
|
+
chargeId: data.chargeId,
|
|
1058
|
+
sessionId,
|
|
1059
|
+
feature: config.feature,
|
|
1060
|
+
settledAt: data.settledAt ? new Date(data.settledAt) : /* @__PURE__ */ new Date()
|
|
1061
|
+
};
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
return {
|
|
1064
|
+
valid: false,
|
|
1065
|
+
method: "session",
|
|
1066
|
+
error: `Session verification failed: ${error.message}`,
|
|
1067
|
+
sessionId
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
async function verifyWidgetPaymentNext(paymentJwt, priceUsd, config, req) {
|
|
1072
|
+
const baseUrl = config.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://mixrpay.com";
|
|
1073
|
+
const secretKey = process.env.MIXRPAY_SECRET_KEY;
|
|
1074
|
+
if (!secretKey) {
|
|
1075
|
+
return { valid: false, method: "widget", error: "MIXRPAY_SECRET_KEY not configured" };
|
|
1076
|
+
}
|
|
1077
|
+
try {
|
|
1078
|
+
const response = await fetch(`${baseUrl}/api/widget/verify`, {
|
|
1079
|
+
method: "POST",
|
|
1080
|
+
headers: {
|
|
1081
|
+
"Content-Type": "application/json",
|
|
1082
|
+
"Authorization": `Bearer ${secretKey}`
|
|
1083
|
+
},
|
|
1084
|
+
body: JSON.stringify({
|
|
1085
|
+
paymentJwt,
|
|
1086
|
+
expectedAmountUsd: priceUsd,
|
|
1087
|
+
feature: config.feature || req.headers.get("x-mixr-feature")
|
|
1088
|
+
})
|
|
1089
|
+
});
|
|
1090
|
+
if (!response.ok) {
|
|
1091
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1092
|
+
return {
|
|
1093
|
+
valid: false,
|
|
1094
|
+
method: "widget",
|
|
1095
|
+
error: errorData.message || errorData.error || `Widget verification failed: ${response.status}`
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
const data = await response.json();
|
|
1099
|
+
return {
|
|
1100
|
+
valid: true,
|
|
1101
|
+
method: "widget",
|
|
1102
|
+
payer: data.payer || data.walletAddress,
|
|
1103
|
+
amountUsd: data.amountUsd || priceUsd,
|
|
1104
|
+
txHash: data.txHash,
|
|
1105
|
+
chargeId: data.chargeId,
|
|
1106
|
+
feature: config.feature,
|
|
1107
|
+
settledAt: data.settledAt ? new Date(data.settledAt) : /* @__PURE__ */ new Date()
|
|
1108
|
+
};
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
return {
|
|
1111
|
+
valid: false,
|
|
1112
|
+
method: "widget",
|
|
1113
|
+
error: `Widget verification failed: ${error.message}`
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
async function verifyX402PaymentNext(paymentHeader, priceUsd, config) {
|
|
1118
|
+
const recipient = config.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
|
|
1119
|
+
if (!recipient) {
|
|
1120
|
+
return {
|
|
1121
|
+
valid: false,
|
|
1122
|
+
method: "x402",
|
|
1123
|
+
error: "MIXRPAY_MERCHANT_ADDRESS not configured for x402 payments"
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
const priceMinor = usdToMinor(priceUsd);
|
|
1127
|
+
const chainId = config.chainId || 8453;
|
|
1128
|
+
const facilitator = config.facilitator || DEFAULT_FACILITATOR;
|
|
1129
|
+
const x402Result = await verifyX402Payment(paymentHeader, {
|
|
1130
|
+
expectedAmount: priceMinor,
|
|
1131
|
+
expectedRecipient: recipient,
|
|
1132
|
+
chainId,
|
|
1133
|
+
facilitator,
|
|
1134
|
+
skipSettlement: config.testMode
|
|
1135
|
+
});
|
|
1136
|
+
if (!x402Result.valid) {
|
|
1137
|
+
return {
|
|
1138
|
+
valid: false,
|
|
1139
|
+
method: "x402",
|
|
1140
|
+
error: x402Result.error,
|
|
1141
|
+
x402Result
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
const receiptMode = config.receiptMode || "webhook";
|
|
1145
|
+
if ((receiptMode === "jwt" || receiptMode === "both") && x402Result.txHash && x402Result.payer) {
|
|
1146
|
+
try {
|
|
1147
|
+
const receipt = await fetchPaymentReceipt({
|
|
1148
|
+
txHash: x402Result.txHash,
|
|
1149
|
+
payer: x402Result.payer,
|
|
1150
|
+
recipient,
|
|
1151
|
+
amount: priceMinor,
|
|
1152
|
+
chainId,
|
|
1153
|
+
apiUrl: config.mixrpayApiUrl
|
|
1154
|
+
});
|
|
1155
|
+
if (receipt) {
|
|
1156
|
+
x402Result.receipt = receipt;
|
|
1157
|
+
}
|
|
1158
|
+
} catch (receiptError) {
|
|
1159
|
+
console.warn("[withMixrPay] Failed to fetch JWT receipt:", receiptError);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return {
|
|
1163
|
+
valid: true,
|
|
1164
|
+
method: "x402",
|
|
1165
|
+
payer: x402Result.payer,
|
|
1166
|
+
amountUsd: x402Result.amount,
|
|
1167
|
+
txHash: x402Result.txHash,
|
|
1168
|
+
receipt: x402Result.receipt,
|
|
1169
|
+
settledAt: x402Result.settledAt,
|
|
1170
|
+
x402Result
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
function returnPaymentRequiredNext(priceUsd, config) {
|
|
1174
|
+
const priceMinor = usdToMinor(priceUsd);
|
|
1175
|
+
const recipient = config.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;
|
|
1176
|
+
const chainId = config.chainId || 8453;
|
|
1177
|
+
const facilitator = config.facilitator || DEFAULT_FACILITATOR;
|
|
1178
|
+
const nonce = generateNonce();
|
|
1179
|
+
const expiresAt = Math.floor(Date.now() / 1e3) + 300;
|
|
1180
|
+
const x402PaymentRequired = recipient ? {
|
|
1181
|
+
recipient,
|
|
1182
|
+
amount: priceMinor.toString(),
|
|
1183
|
+
currency: "USDC",
|
|
1184
|
+
chainId,
|
|
1185
|
+
facilitator,
|
|
1186
|
+
nonce,
|
|
1187
|
+
expiresAt,
|
|
1188
|
+
description: config.description
|
|
1189
|
+
} : null;
|
|
1190
|
+
const headers = {};
|
|
1191
|
+
if (x402PaymentRequired) {
|
|
1192
|
+
headers["X-Payment-Required"] = JSON.stringify(x402PaymentRequired);
|
|
1193
|
+
headers["WWW-Authenticate"] = `X-402 ${Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64")}`;
|
|
1194
|
+
}
|
|
1195
|
+
return NextResponse.json(
|
|
1196
|
+
{
|
|
1197
|
+
error: "Payment required",
|
|
1198
|
+
priceUsd,
|
|
1199
|
+
acceptedMethods: [
|
|
1200
|
+
{ method: "session", header: "X-Mixr-Session", description: "Session authorization ID" },
|
|
1201
|
+
{ method: "widget", header: "X-Mixr-Payment", description: "Widget payment JWT" },
|
|
1202
|
+
...recipient ? [{ method: "x402", header: "X-PAYMENT", description: "x402 protocol payment" }] : []
|
|
1203
|
+
],
|
|
1204
|
+
x402: x402PaymentRequired,
|
|
1205
|
+
description: config.description
|
|
1206
|
+
},
|
|
1207
|
+
{ status: 402, headers }
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
719
1210
|
|
|
720
1211
|
// src/middleware/fastify.ts
|
|
721
1212
|
var x402Plugin = (fastify, options, done) => {
|
|
@@ -822,6 +1313,7 @@ export {
|
|
|
822
1313
|
USDC_CONTRACTS,
|
|
823
1314
|
clearJWKSCache,
|
|
824
1315
|
createPaymentRequired,
|
|
1316
|
+
mixrpay as expressMixrPay,
|
|
825
1317
|
x402 as expressX402,
|
|
826
1318
|
x4022 as fastifyX402,
|
|
827
1319
|
generateNonce,
|
|
@@ -837,6 +1329,7 @@ export {
|
|
|
837
1329
|
verifyPaymentReceipt,
|
|
838
1330
|
verifySessionWebhook,
|
|
839
1331
|
verifyX402Payment,
|
|
1332
|
+
withMixrPay,
|
|
840
1333
|
withX402,
|
|
841
1334
|
x402Plugin
|
|
842
1335
|
};
|