@tanakayuto/intmax402-express 0.3.1 → 0.3.3

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.
@@ -18,9 +18,19 @@ function intmax402(config) {
18
18
  return async (req, res, next) => {
19
19
  try {
20
20
  // Wait for payment verifier initialization if in progress
21
- if (initPromise) {
22
- await initPromise;
23
- initPromise = null;
21
+ try {
22
+ if (initPromise) {
23
+ await initPromise;
24
+ initPromise = null;
25
+ }
26
+ }
27
+ catch (e) {
28
+ res.status(503).json({
29
+ error: 'Payment verifier temporarily unavailable',
30
+ error_code: intmax402_core_1.INTMAX402_ERROR_CODES.INTMAX_NETWORK_UNAVAILABLE,
31
+ protocol: 'INTMAX402',
32
+ });
33
+ return;
24
34
  }
25
35
  const authHeader = req.headers.authorization;
26
36
  if (!authHeader) {
@@ -30,6 +40,7 @@ function intmax402(config) {
30
40
  res.setHeader("WWW-Authenticate", (0, intmax402_core_1.buildWWWAuthenticate)(nonce, config));
31
41
  res.status(statusCode).json({
32
42
  error: config.mode === "payment" ? "Payment Required" : "Unauthorized",
43
+ error_code: intmax402_core_1.INTMAX402_ERROR_CODES.MISSING_AUTH_HEADER,
33
44
  protocol: "INTMAX402",
34
45
  mode: config.mode,
35
46
  });
@@ -66,9 +77,29 @@ function intmax402(config) {
66
77
  res.status(500).json({ error: "Server misconfigured: serverAddress and amount required for payment mode" });
67
78
  return;
68
79
  }
69
- const paymentResult = await (0, verify_payment_1.verifyPayment)(credential.txHash, config.amount, config.serverAddress);
80
+ let paymentResult;
81
+ try {
82
+ paymentResult = await (0, verify_payment_1.verifyPayment)(credential.txHash, config.amount, config.serverAddress);
83
+ }
84
+ catch (err) {
85
+ if (err instanceof intmax402_core_1.INTMAX402Error) {
86
+ const status = err.code === intmax402_core_1.INTMAX402_ERROR_CODES.INTMAX_NETWORK_UNAVAILABLE ? 503 : 402;
87
+ res.status(status).json({
88
+ error: err.message,
89
+ error_code: err.code,
90
+ protocol: 'INTMAX402',
91
+ });
92
+ }
93
+ else {
94
+ res.status(402).json({
95
+ error: 'Payment verification failed',
96
+ protocol: 'INTMAX402',
97
+ });
98
+ }
99
+ return;
100
+ }
70
101
  if (!paymentResult.valid) {
71
- res.status(402).json({ error: paymentResult.error || "Payment verification failed" });
102
+ res.status(402).json({ error: paymentResult.error || "Payment verification failed", protocol: "INTMAX402" });
72
103
  return;
73
104
  }
74
105
  }
@@ -5,6 +5,7 @@ exports.getPaymentVerifierAddress = getPaymentVerifierAddress;
5
5
  exports.verifyPayment = verifyPayment;
6
6
  exports._resetPaymentVerifier = _resetPaymentVerifier;
7
7
  const intmax2_server_sdk_1 = require("intmax2-server-sdk");
8
+ const intmax402_core_1 = require("@tanakayuto/intmax402-core");
8
9
  // Singleton IntMaxNodeClient
9
10
  let client = null;
10
11
  let loginPromise = null;
@@ -40,8 +41,16 @@ async function initPaymentVerifier(config) {
40
41
  l1_rpc_url: config.l1_rpc_url,
41
42
  loggerLevel: "warn",
42
43
  });
43
- loginPromise = client.login().then(() => {
44
+ loginPromise = client
45
+ .login()
46
+ .then(() => {
44
47
  loginPromise = null;
48
+ })
49
+ .catch((err) => {
50
+ console.warn("[intmax402] INTMAX network login failed — payment verifier unavailable:", err instanceof Error ? err.message : String(err));
51
+ client = null;
52
+ loginPromise = null;
53
+ throw err;
45
54
  });
46
55
  await loginPromise;
47
56
  }
@@ -52,7 +61,7 @@ function getPaymentVerifierAddress() {
52
61
  }
53
62
  async function verifyPayment(txHash, expectedAmount, serverAddress, tokenIndex) {
54
63
  if (!client || !client.isLoggedIn) {
55
- return { valid: false, error: "Payment verifier not initialized" };
64
+ throw new intmax402_core_1.INTMAX402Error(intmax402_core_1.INTMAX402_ERROR_CODES.INTMAX_NETWORK_UNAVAILABLE, "Payment verifier temporarily unavailable. INTMAX network may be down.");
56
65
  }
57
66
  // Replay prevention: check if txHash was already used (or pending)
58
67
  cleanupExpiredHashes();
@@ -71,23 +80,41 @@ async function verifyPayment(txHash, expectedAmount, serverAddress, tokenIndex)
71
80
  transfers = transfers.concat(response2.items);
72
81
  }
73
82
  // Find matching transaction by digest
74
- const match = transfers.find((tx) => tx.digest === txHash);
83
+ let match = transfers.find((tx) => tx.digest === txHash);
84
+ // Polling retry: if not found, retry up to 3 times with 5s delay
85
+ // (transfer may not be reflected immediately after submission)
86
+ if (!match) {
87
+ for (let retry = 0; retry < 3; retry++) {
88
+ await new Promise((r) => setTimeout(r, 5000));
89
+ const retryResponse = await client.fetchTransfers({ cursor: null, limit: 100 });
90
+ match = retryResponse.items.find((tx) => tx.digest === txHash);
91
+ if (match)
92
+ break;
93
+ // Also check next page on retry if needed
94
+ if (!match && retryResponse.pagination?.has_more && retryResponse.pagination?.next_cursor != null) {
95
+ const retryResponse2 = await client.fetchTransfers({ cursor: retryResponse.pagination.next_cursor, limit: 100 });
96
+ match = retryResponse2.items.find((tx) => tx.digest === txHash);
97
+ if (match)
98
+ break;
99
+ }
100
+ }
101
+ }
75
102
  if (!match) {
76
103
  // Fix 1: Rollback on validation failure
77
104
  usedTxHashes.delete(txHash);
78
- return { valid: false, error: "Transaction not found in recent transfers" };
105
+ throw new intmax402_core_1.INTMAX402Error(intmax402_core_1.INTMAX402_ERROR_CODES.PAYMENT_NOT_FOUND, "Transaction not found in recent transfers", { txHash });
79
106
  }
80
107
  // Verify recipient matches server address
81
108
  if (match.to?.toLowerCase() !== serverAddress.toLowerCase()) {
82
109
  // Fix 1: Rollback on validation failure
83
110
  usedTxHashes.delete(txHash);
84
- return { valid: false, error: "Recipient does not match server address" };
111
+ throw new intmax402_core_1.INTMAX402Error(intmax402_core_1.INTMAX402_ERROR_CODES.PAYMENT_RECIPIENT_MISMATCH, "Recipient does not match server address", { expected: serverAddress, got: match.to });
85
112
  }
86
113
  // Fix 2: Verify amount using BigInt comparison (allows >= expectedAmount)
87
114
  if (BigInt(match.amount) < BigInt(expectedAmount)) {
88
115
  // Fix 1: Rollback on validation failure
89
116
  usedTxHashes.delete(txHash);
90
- return { valid: false, error: `Amount mismatch: expected ${expectedAmount}, got ${match.amount}` };
117
+ throw new intmax402_core_1.INTMAX402Error(intmax402_core_1.INTMAX402_ERROR_CODES.PAYMENT_AMOUNT_MISMATCH, `Amount mismatch: expected ${expectedAmount}, got ${match.amount}`, { expected: expectedAmount, got: match.amount });
91
118
  }
92
119
  // Verify token if specified
93
120
  if (tokenIndex !== undefined && match.tokenIndex !== tokenIndex) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanakayuto/intmax402-express",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "exports": {
@@ -26,7 +26,7 @@
26
26
  "typecheck": "tsc --noEmit"
27
27
  },
28
28
  "dependencies": {
29
- "@tanakayuto/intmax402-core": "0.3.1",
29
+ "@tanakayuto/intmax402-core": "0.3.2",
30
30
  "ethers": "^6.16.0",
31
31
  "intmax2-server-sdk": "^1.5.2"
32
32
  },