支付宝移动 App 支付成功后如何通知后端
支付小周
明确结论
支付结果应以支付宝发送到后端 notify_url 的异步通知为准,不能由 App 直接向后端上报“支付成功”。
App 在支付完成后拿到的同步结果,只用于更新页面状态,例如显示“支付处理中”。后端验签并核对订单信息无误后,才能将订单改为已支付。如果异步通知暂时没有到达,后端可以调用支付宝交易查询接口补查。
为什么不能相信 App 上报的支付结果
App 运行在用户设备上,其请求和返回值都可能被篡改、伪造或重复提交。即使客户端支付 SDK 返回成功状态,也不能直接据此发货、充值或开通服务。
完整的支付流程如下:
- App 请求业务后端创建订单。
- 后端生成本地订单,并调用或组装支付宝 App 支付请求。
- 后端将支付宝支付参数返回给 App。
- App 调起支付宝支付。
- 支付宝通过
notify_url向业务后端发送异步通知。 - 后端验签,并核对订单号、金额、商户身份和交易状态。
- 校验通过后,后端以幂等方式更新订单。
- App 查询业务后端的订单状态并刷新页面。
后端创建支付请求
后端应在支付请求中指定 notify_url,该地址必须能被支付宝服务器访问。
下面的示例只展示关键字段,具体初始化方式取决于使用的支付宝 Java SDK 版本:
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo(orderNo);
model.setTotalAmount("99.00");
model.setSubject("商品订单");
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl("https://pay.example.com/api/alipay/notify");
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
String orderString = response.getBody();
后端将 orderString 返回给 App,由 App 用它调起支付宝客户端。商户私钥、支付宝公钥等敏感配置必须保存在后端,不能放入 App。
接收并处理支付宝异步通知
下面是一个简化的 Spring Boot 示例:
@PostMapping(
value = "/api/alipay/notify",
produces = "text/plain;charset=UTF-8"
)
public String handleAlipayNotify(HttpServletRequest request) throws Exception {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
params.put(key, String.join(",", values));
});
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
return "failure";
}
String appId = params.get("app_id");
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
String sellerId = params.get("seller_id");
Order order = orderService.findByOrderNo(outTradeNo);
if (order == null) {
return "failure";
}
if (!expectedAppId.equals(appId)) {
return "failure";
}
if (!expectedSellerId.equals(sellerId)) {
return "failure";
}
BigDecimal notifiedAmount = new BigDecimal(totalAmount);
if (order.getPayAmount().compareTo(notifiedAmount) != 0) {
return "failure";
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
orderService.markPaidIdempotently(
outTradeNo,
tradeNo,
notifiedAmount
);
}
return "success";
}
示例中的 alipayPublicKey 是支付宝公钥,不是应用公钥。实际使用时,需要根据应用采用的密钥或证书模式,选择对应的验签方法。
通知验签、业务校验和订单处理全部成功后,接口才能返回以下纯文本:
success
处理失败时,应返回:
failure
不要返回 JSON、HTML 页面或其他提示文字。
App 端应该怎样处理
App 可以读取支付 SDK 的同步返回值,以便及时更新页面,但不能直接将本地订单标记为最终支付成功。
val payResult = PayResult(rawResult)
when (payResult.resultStatus) {
"9000" -> {
// 仅表示客户端侧支付流程返回成功。
// 最终结果仍需向业务后端查询。
queryOrderStatus(orderNo)
}
"8000" -> {
// 支付结果处理中,稍后查询后端订单状态。
showPayProcessing()
queryOrderStatusLater(orderNo)
}
else -> {
// 取消、失败或其他状态。
showPayFailedOrCancelled()
}
}
App 应调用业务订单查询接口:
GET /api/orders/{orderNo}/status
后端返回的是自己数据库中记录的订单状态,不能简单相信 App 上传的“支付成功”字段。
异步通知没有及时到达怎么办
网络故障、服务发布或接口超时都可能导致通知延迟。可以建立主动补查机制:
- 用户返回支付结果页时,App 向业务后端查询订单状态。
- 如果订单仍为待支付,后端调用支付宝交易查询接口,例如
alipay.trade.query。 - 定时扫描长时间处于待支付状态的订单并补查。
- 查询确认支付成功后,仍以幂等方式更新订单。
主动查询用于补偿异步通知。无论通过哪条路径确认支付结果,最终都应进入同一套订单支付处理逻辑。
注意事项
notify_url必须能从公网访问,不能依赖用户登录态,也不能要求支付宝携带业务系统的登录凭证。- 必须先验签,再根据通知内容执行业务操作。
- 除签名外,还要校验
app_id、out_trade_no、total_amount、seller_id和trade_status。 - 金额应使用
BigDecimal或最小货币单位整数处理,避免直接使用浮点数比较。 - 支付通知可能重复到达,因此订单更新、库存扣减、积分发放和发货操作都必须具备幂等性。
- 本地订单号
out_trade_no应保持唯一,并与支付宝交易号trade_no一起保存,便于对账和退款。 - 不要直接根据通知中的金额决定商品价格,应使用本地订单记录反向核对。
- 通知接口应尽快完成核心处理并返回结果。发货、短信或消息推送等耗时操作,可以在订单状态落库后异步执行。
- 应记录必要的通知日志和验签失败原因,但不能在日志中泄露私钥、完整敏感参数或用户隐私信息。
备注:内容仅供参考。
