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