@mixrpay/merchant-sdk 0.1.1 → 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 +495 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +493 -0
- package/dist/index.mjs.map +1 -1
- package/dist/middleware/express.js +258 -0
- package/dist/middleware/express.js.map +1 -1
- package/dist/middleware/express.mjs +257 -0
- package/dist/middleware/express.mjs.map +1 -1
- package/dist/middleware/nextjs.js +237 -0
- package/dist/middleware/nextjs.js.map +1 -1
- package/dist/middleware/nextjs.mjs +236 -0
- 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
|
@@ -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
|
};
|