@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.js
CHANGED
|
@@ -35,6 +35,7 @@ __export(src_exports, {
|
|
|
35
35
|
USDC_CONTRACTS: () => USDC_CONTRACTS,
|
|
36
36
|
clearJWKSCache: () => clearJWKSCache,
|
|
37
37
|
createPaymentRequired: () => createPaymentRequired,
|
|
38
|
+
expressMixrPay: () => mixrpay,
|
|
38
39
|
expressX402: () => x402,
|
|
39
40
|
fastifyX402: () => x4022,
|
|
40
41
|
generateNonce: () => generateNonce,
|
|
@@ -50,6 +51,7 @@ __export(src_exports, {
|
|
|
50
51
|
verifyPaymentReceipt: () => verifyPaymentReceipt,
|
|
51
52
|
verifySessionWebhook: () => verifySessionWebhook,
|
|
52
53
|
verifyX402Payment: () => verifyX402Payment,
|
|
54
|
+
withMixrPay: () => withMixrPay,
|
|
53
55
|
withX402: () => withX402,
|
|
54
56
|
x402Plugin: () => x402Plugin
|
|
55
57
|
});
|
|
@@ -271,7 +273,7 @@ async function parseX402Payment(paymentHeader, chainId = 8453) {
|
|
|
271
273
|
|
|
272
274
|
// src/receipt.ts
|
|
273
275
|
var jose = __toESM(require("jose"));
|
|
274
|
-
var DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || "
|
|
276
|
+
var DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || "https://mixrpay.com/.well-known/jwks";
|
|
275
277
|
var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
276
278
|
var jwksCache = /* @__PURE__ */ new Map();
|
|
277
279
|
async function getJWKS(jwksUrl) {
|
|
@@ -371,7 +373,7 @@ var import_crypto = __toESM(require("crypto"));
|
|
|
371
373
|
var ChargesClient = class {
|
|
372
374
|
constructor(options) {
|
|
373
375
|
this.apiKey = options.apiKey;
|
|
374
|
-
this.baseUrl = options.baseUrl || process.env.MIXRPAY_BASE_URL || "
|
|
376
|
+
this.baseUrl = options.baseUrl || process.env.MIXRPAY_BASE_URL || "https://mixrpay.com";
|
|
375
377
|
}
|
|
376
378
|
/**
|
|
377
379
|
* Create a new charge.
|
|
@@ -527,7 +529,7 @@ function parseSessionGrant(payload) {
|
|
|
527
529
|
}
|
|
528
530
|
|
|
529
531
|
// src/receipt-fetcher.ts
|
|
530
|
-
var DEFAULT_MIXRPAY_API_URL = process.env.MIXRPAY_BASE_URL || "
|
|
532
|
+
var DEFAULT_MIXRPAY_API_URL = process.env.MIXRPAY_BASE_URL || "https://mixrpay.com";
|
|
531
533
|
async function fetchPaymentReceipt(params) {
|
|
532
534
|
const apiUrl = params.apiUrl || DEFAULT_MIXRPAY_API_URL;
|
|
533
535
|
const endpoint = `${apiUrl}/api/v1/receipts`;
|
|
@@ -655,6 +657,262 @@ function x402(options) {
|
|
|
655
657
|
}
|
|
656
658
|
};
|
|
657
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
|
+
}
|
|
658
916
|
|
|
659
917
|
// src/middleware/nextjs.ts
|
|
660
918
|
var import_server = require("next/server");
|
|
@@ -773,6 +1031,241 @@ function createPaymentRequired(options) {
|
|
|
773
1031
|
}
|
|
774
1032
|
);
|
|
775
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
|
+
}
|
|
776
1269
|
|
|
777
1270
|
// src/middleware/fastify.ts
|
|
778
1271
|
var x402Plugin = (fastify, options, done) => {
|
|
@@ -880,6 +1373,7 @@ function x4022(options) {
|
|
|
880
1373
|
USDC_CONTRACTS,
|
|
881
1374
|
clearJWKSCache,
|
|
882
1375
|
createPaymentRequired,
|
|
1376
|
+
expressMixrPay,
|
|
883
1377
|
expressX402,
|
|
884
1378
|
fastifyX402,
|
|
885
1379
|
generateNonce,
|
|
@@ -895,6 +1389,7 @@ function x4022(options) {
|
|
|
895
1389
|
verifyPaymentReceipt,
|
|
896
1390
|
verifySessionWebhook,
|
|
897
1391
|
verifyX402Payment,
|
|
1392
|
+
withMixrPay,
|
|
898
1393
|
withX402,
|
|
899
1394
|
x402Plugin
|
|
900
1395
|
});
|