支付回调notify_url使用什么方式?如何获取参数?
支付阿杰
明确结论
notify_url 一般由支付平台服务器发起异步回调。大多数支付接口使用 HTTP POST 请求,但参数编码方式并不统一,常见的有:
application/x-www-form-urlencoded:从表单参数中读取application/json:从请求体中读取 JSONtext/xml或application/xml:从请求体中读取 XML
所以,不能只看回调是否使用 POST,还要根据支付平台的接口文档和请求头 Content-Type 判断参数的读取方式。
回调处理步骤
收到支付通知后,可以按以下顺序处理:
- 读取请求方法、请求头和原始请求体。
- 根据
Content-Type解析参数。 - 按照平台规定的规则验证签名。
- 核对订单号、商户号、支付金额、币种和支付状态。
- 使用数据库事务或唯一约束保证幂等,防止重复入账。
- 业务处理完成后,返回支付平台要求的成功响应。
如果发生网络超时,或者平台没有收到正确响应,同一条通知可能会重复发送。因此,回调接口必须能够安全地重复执行。
如何获取回调参数
表单格式
如果请求头类似:
Content-Type: application/x-www-form-urlencoded
参数通常在 POST 表单中,例如:
order_no=202609130001&amount=99.00&status=SUCCESS&sign=...
PHP 可以这样读取:
<?php
$params = $_POST;
$orderNo = $params['order_no'] ?? '';
$amount = $params['amount'] ?? '';
$status = $params['status']'] ?? '';
$sign = $params['sign'] ?? '';
Java Spring Boot 可以使用:
@PostMapping("/payment/notify")
public String notify(@RequestParam Map<String, String> params) {
String orderNo = params.get("order_no");
String amount = params.get("amount");
String status = params.get("status");
String sign = params.get("sign");
// 验签并处理订单
return "success";
}
JSON 格式
如果请求头类似:
Content-Type: application/json
需要读取并解析请求体,不能按普通表单参数处理。
Node.js Express 示例:
app.post("/payment/notify", express.json(), async (req, res) => {
const { order_no, amount, status, sign } = req.body;
// 验签并处理订单
res.send("success");
});
Spring Boot 示例:
@PostMapping("/payment/notify")
public String notify(@RequestBody Map<String, Object> body) {
String orderNo = String.valueOf(body.get("order_no"));
String status = String.valueOf(body.get("status"));
// 验签并处理订单
return "success";
}
XML 格式
部分支付接口会通过 POST 请求发送 XML:
<xml>
<order_no>202609130001</order_no>
<status>SUCCESS</status>
<sign>...</sign>
</xml>
这种情况下,需要先读取原始请求体,再使用经过安全配置的 XML 解析器处理,不能直接从查询字符串或表单参数中取值。
验签时为什么要保留原始请求体
有些支付平台会直接使用原始请求体参与签名验证。如果框架先将 JSON 或 XML 解析成对象,再重新序列化,字段顺序、空格或数字格式可能发生变化,最终导致验签失败。
Express 可以在解析 JSON 时保留原始数据:
app.use(express.json({
verify: (req, res, buffer) => {
req.rawBody = buffer;
}
}));
app.post("/payment/notify", async (req, res) => {
const signature = req.get("X-Signature");
const rawBody = req.rawBody;
const valid = verifySignature(rawBody, signature);
if (!valid) {
return res.status(400).send("invalid signature");
}
// 验签成功后处理业务
res.send("success");
});
签名对应的请求头、签名算法和参与验签的数据,都要严格按照支付平台的文档处理。
注意事项
- 不要只根据客户端跳转结果判断支付是否成功,应以服务端异步通知或主动查询结果为准。
- 验签通过前,不要更新订单状态。
- 除了检查
status,还必须核对回调金额与本地订单金额。 - 回调接口必须支持幂等处理。同一条支付通知即使执行多次,最终结果也要保持一致。
- 参数名称和大小写要以接口文档为准,不要自行猜测。
- 成功响应可能是
success、JSON 或 XML,具体格式由支付平台规定。 - 如果业务处理时间较长,应避免让回调请求长时间阻塞,否则平台可能认为通知失败并发起重试。
- 调试时可以记录请求方法、
Content-Type、请求头和原始请求体,但日志必须脱敏,防止签名、证书信息或用户隐私泄露。 notify_url通常需要允许支付平台从公网访问,并正确配置 HTTPS、域名和防火墙规则。具体网络要求以支付平台文档为准。
备注:内容仅供参考。
