使用当前checkOutRequestId查询Lipa Na MPESA交易状态报500错误
问题根源分析
- 全局Timestamp/Password过期:你的
server.js中timestamp和password是服务器启动时生成的全局变量,但Safaricom API要求每个请求必须使用当前请求时刻的时间戳生成密码。服务器运行一段时间后,旧时间戳会失效,导致查询请求因鉴权失败返回500。 - 查询时机过早:STK Push返回
CheckoutRequestID仅代表请求已被受理,Safaricom后台需要几秒完成交易记录落地,立即查询会因数据未同步触发500错误。
修复方案
1. 每次请求重新生成Timestamp和Password
将时间戳和密码的生成逻辑移到每个路由处理函数内部,确保每次请求都使用最新的有效鉴权信息:
// server.js 移除全局的timestamp和password定义 app.post("/stk", generateToken, async (req, res) => { // 每次请求生成新的timestamp和password const date = new Date(); const timestamp = date.getFullYear() + ("0" + (date.getMonth() + 1)).slice(-2) + ("0" + date.getDate()).slice(-2) + ("0" + date.getHours()).slice(-2) + ("0" + date.getMinutes()).slice(-2) + ("0" + date.getSeconds()).slice(-2); const password = Buffer.from( businessShortCode + passKey + timestamp ).toString("base64"); const phone = `254${req.body.phone.substring(1)}`; const amount = req.body.amount; await axios .post( "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest", { BusinessShortCode: businessShortCode, Password: password, Timestamp: timestamp, TransactionType: "CustomerPayBillOnline", Amount: amount, PartyA: phone, PartyB: businessShortCode, PhoneNumber: phone, CallBackURL: "https://011c-179-000-1312-63.ngrok.io/stk_callback", AccountReference: "Test", TransactionDesc: "Test", }, { headers: { authorization: `Bearer ${token}`, }, } ) .then((data) => { res.status(200).json(data.data); }) .catch((err) => { res.status(400).json(err.message); }); }); app.post("/transactionStatus", generateToken, async (req, res) => { // 查询请求同样生成新的timestamp和password const date = new Date(); const timestamp = date.getFullYear() + ("0" + (date.getMonth() + 1)).slice(-2) + ("0" + date.getDate()).slice(-2) + ("0" + date.getHours()).slice(-2) + ("0" + date.getMinutes()).slice(-2) + ("0" + date.getSeconds()).slice(-2); const password = Buffer.from( businessShortCode + passKey + timestamp ).toString("base64"); const checkoutReqId = req.body.checkoutReqId; const trnxUrl = "https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query"; await axios .post( trnxUrl, { BusinessShortCode: businessShortCode, Password: password, Timestamp: timestamp, CheckoutRequestID: checkoutReqId, }, { headers: { authorization: `Bearer ${token}`, }, } ) .then((data) => { res.status(200).json(data.data); console.log("check data response", data.data); }) .catch((err) => { console.log("trnx attempt fail", err.message); res.status(400).json(err.message); }); });
2. 调整查询时机,添加延迟重试
修改前端index.js,不要在收到STK响应后立即查询,设置延迟后发起请求并添加重试机制:
// index.js 修改trnxStatus函数为带延迟重试的版本 function trnxStatus(checkoutReqId, retryCount = 3) { if (retryCount <= 0) { console.log("查询次数用尽,仍未获取有效响应"); return; } // 延迟3秒后查询(可根据实际情况调整) setTimeout(() => { const body = { checkoutReqId: checkoutReqId, }; const options = { method: "POST", body: JSON.stringify(body), headers: { "Content-type": "application/json; charset=UTF-8" }, }; const trnxUrl = "http://localhost:port/transactionStatus/"; fetch(trnxUrl, options) .then((res) => { if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); return res.json(); }) .then((data) => { console.log("response data", data); }) .catch((err) => { console.log(`查询失败,剩余重试次数:${retryCount - 1}`, err.message); // 递归重试 trnxStatus(checkoutReqId, retryCount - 1); }); }, 3000); }
3. 优先依赖CallBackURL通知(最佳实践)
Safaricom会在用户完成/取消支付后主动调用你设置的CallBackURL,建议在/stk_callback路由中处理支付结果,而非主动轮询:
// server.js 添加回调路由处理 app.post("/stk_callback", (req, res) => { const callbackData = req.body; console.log("支付回调数据:", callbackData); // 解析回调数据,更新本地交易状态 const checkoutRequestID = callbackData.Body.stkCallback.CheckoutRequestID; const resultCode = callbackData.Body.stkCallback.ResultCode; const resultDesc = callbackData.Body.stkCallback.ResultDesc; if (resultCode === 0) { console.log(`支付成功:${checkoutRequestID}`); } else { console.log(`支付失败:${checkoutRequestID},原因:${resultDesc}`); } // 必须返回200响应给Safaricom,否则会重复回调 res.status(200).json({ ResultCode: 0, ResultDesc: "Success" }); });
额外注意事项
- 确保
generateToken中间件生成的token有效且未过期(Safaricom的access token有效期为1小时)。 - 沙箱环境响应较慢时,可适当延长延迟时间(比如5秒)。
内容的提问来源于stack exchange,提问作者The_Ogre
相关产品推荐
相关产品推荐

