Next.js 14集成PayHere支付网关,支付状态无法写入数据库
问题背景
开发Next.js 14项目时集成PayHere支付网关,按照官方文档通过notify_url接收支付状态通知,将官方提供的PHP验证逻辑转为TypeScript并实现webhook接口,但支付完成后数据库中的订单状态无法更新。
核心问题排查与修复方案
1. Prisma异步操作未等待(最常见原因)
原webhook代码中,prisma.order.update调用未添加await,导致数据库更新操作在未完成时就返回了响应,异步任务被丢弃,数据库不会执行更新。
修复:
在Prisma操作前添加await,并捕获可能的错误:
// 原代码 prisma.order.update({ where: { id: order_id }, data: { status: "complete" }, }); // 修复后 try { await prisma.order.update({ where: { id: order_id }, data: { status: "complete" }, }); console.log("订单状态更新成功"); } catch (dbError) { console.error("数据库更新失败:", dbError); }
2. MD5签名验证的大小写不匹配
PayHere官方验证逻辑要求对merchant_secret的MD5结果先转大写,再参与最终签名计算。原TS代码中仅对最终MD5结果转大写,但内部的merchant_secret MD5结果是小写,导致签名验证失败,无法进入更新逻辑。
修复:
对merchant_secret的MD5结果也执行toUpperCase():
// 原代码 crypto.createHash("md5").update(merchant_secret).digest("hex") // 修复后 crypto.createHash("md5").update(merchant_secret).digest("hex").toUpperCase()
完整的签名计算代码:
const local_md5sig = crypto .createHash("md5") .update( merchant_id + order_id + payhere_amount + payhere_currency + status_code + crypto.createHash("md5").update(merchant_secret).digest("hex").toUpperCase() ) .digest("hex") .toUpperCase();
3. PayHere通知的请求解析问题
PayHere默认通过x-www-form-urlencoded格式发送通知,而非JSON。如果是Next.js App Router的API路由,默认配置可能无法正确解析该格式,导致req.body参数缺失。
修复(App Router场景):
禁用默认JSON解析,使用第三方库手动解析表单数据:
import formidable from 'formidable'; export const config = { api: { bodyParser: false }, }; export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === 'POST') { const form = formidable({}); const [fields, files] = await form.parse(req); // 从fields中获取参数(PayHere的参数是数组格式,需取第一个元素) const merchant_id = fields.merchant_id?.[0]; const order_id = fields.order_id?.[0]; const payhere_amount = fields.payhere_amount?.[0]; // 后续验证与更新逻辑... } }
4. Order ID类型不匹配
如果数据库中order表的id是数字类型,而PayHere通知中的order_id是字符串,会导致Prisma的where条件匹配失败,无法找到对应订单。
修复:
根据数据库中id的类型转换order_id:
// 若id为数字类型 where: { id: Number(order_id) } // 若id为UUID或字符串类型,直接使用 where: { id: order_id }
调试建议
- 在webhook中添加详细日志,打印
req.body的所有参数,确认是否正确接收PayHere的通知数据; - 打印
local_md5sig和md5sig的值,对比是否一致,排查签名验证问题; - 捕获Prisma操作的错误,打印错误信息,确认是否存在数据库连接或权限问题;
- 使用PayHere的测试工具模拟通知请求,验证webhook的响应是否符合要求。
内容的提问来源于stack exchange,提问作者Nuwan Chamikara

