PAYATHON 2026

支付宝移动 App 支付成功后如何通知后端

支付小周

明确结论

支付结果应以支付宝发送到后端 notify_url 的异步通知为准,不能由 App 直接向后端上报“支付成功”。

App 在支付完成后拿到的同步结果,只用于更新页面状态,例如显示“支付处理中”。后端验签并核对订单信息无误后,才能将订单改为已支付。如果异步通知暂时没有到达,后端可以调用支付宝交易查询接口补查。

为什么不能相信 App 上报的支付结果

App 运行在用户设备上,其请求和返回值都可能被篡改、伪造或重复提交。即使客户端支付 SDK 返回成功状态,也不能直接据此发货、充值或开通服务。

完整的支付流程如下:

  1. App 请求业务后端创建订单。
  2. 后端生成本地订单,并调用或组装支付宝 App 支付请求。
  3. 后端将支付宝支付参数返回给 App。
  4. App 调起支付宝支付。
  5. 支付宝通过 notify_url 向业务后端发送异步通知。
  6. 后端验签,并核对订单号、金额、商户身份和交易状态。
  7. 校验通过后,后端以幂等方式更新订单。
  8. 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 上传的“支付成功”字段。

异步通知没有及时到达怎么办

网络故障、服务发布或接口超时都可能导致通知延迟。可以建立主动补查机制:

  1. 用户返回支付结果页时,App 向业务后端查询订单状态。
  2. 如果订单仍为待支付,后端调用支付宝交易查询接口,例如 alipay.trade.query。
  3. 定时扫描长时间处于待支付状态的订单并补查。
  4. 查询确认支付成功后,仍以幂等方式更新订单。

主动查询用于补偿异步通知。无论通过哪条路径确认支付结果,最终都应进入同一套订单支付处理逻辑。

注意事项

  • notify_url 必须能从公网访问,不能依赖用户登录态,也不能要求支付宝携带业务系统的登录凭证。
  • 必须先验签,再根据通知内容执行业务操作。
  • 除签名外,还要校验 app_id、out_trade_no、total_amount、seller_id 和 trade_status。
  • 金额应使用 BigDecimal 或最小货币单位整数处理,避免直接使用浮点数比较。
  • 支付通知可能重复到达,因此订单更新、库存扣减、积分发放和发货操作都必须具备幂等性。
  • 本地订单号 out_trade_no 应保持唯一,并与支付宝交易号 trade_no 一起保存,便于对账和退款。
  • 不要直接根据通知中的金额决定商品价格,应使用本地订单记录反向核对。
  • 通知接口应尽快完成核心处理并返回结果。发货、短信或消息推送等耗时操作,可以在订单状态落库后异步执行。
  • 应记录必要的通知日志和验签失败原因,但不能在日志中泄露私钥、完整敏感参数或用户隐私信息。

备注:内容仅供参考。