@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/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 || "http://localhost:3000/.well-known/jwks";
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 || "http://localhost:3000";
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 || "http://localhost:3000";
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
  };