开发App时,支付宝支付通知应对接哪个接口?
明确结论
应接入支付宝 App 支付接口 alipay.trade.app.pay,并配置用于接收支付结果异步通知的 notify_url。
支付成功通知不会直接发给 App 客户端。支付宝服务器会通过 HTTP POST 请求将结果发送到业务服务器。业务服务器完成验签并保存支付记录后,再通过接口查询、WebSocket 或消息推送等方式将结果告知 App。
典型流程如下:
App 发起支付
↓
业务服务器调用 alipay.trade.app.pay 生成支付参数
↓
App 调起支付宝客户端完成支付
↓
支付宝服务器 POST 支付结果到 notify_url
↓
业务服务器验签、核对订单并更新支付状态
↓
App 查询订单状态或接收业务服务器推送
为什么不能只处理 App 的支付回调
支付宝 SDK 会将客户端支付结果返回给 App,但这个结果只能用于界面展示,不能作为最终入账依据,原因包括:
- App 可能被关闭、断网或切到后台,因而收不到结果。
- 客户端返回的数据可能被篡改。
- 支付成功后,客户端回调可能因网络问题丢失。
- 用户完成支付,不代表业务订单一定处理成功。
订单是否支付成功,应以服务端收到的支付宝异步通知为准。如果通知暂时没有到达,可以调用 alipay.trade.query 主动查询订单状态。
接入步骤
1. 服务端创建 App 支付订单
服务端调用 alipay.trade.app.pay,填写商户订单号、金额和商品标题等信息,同时设置可通过公网访问的 notify_url。
下面的示例使用支付宝 Java SDK。具体依赖版本和客户端初始化方式,应以当前接入的支付宝开放平台 SDK 文档为准:
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo("ORDER_202609090001");
model.setTotalAmount("99.00");
model.setSubject("会员服务");
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl("https://api.example.com/payment/alipay/notify");
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
if (response.isSuccess()) {
// 将 response.getBody() 返回给 App,由 App 调起支付宝 SDK
String orderString = response.getBody();
}
notify_url 必须指向服务端地址,例如:
https://api.example.com/payment/alipay/notify
不能使用 App Scheme、Universal Link、localhost,或者只能在局域网内访问的地址。
2. App 调起支付宝支付
服务端返回 orderString 后,App 使用支付宝客户端 SDK 发起支付。客户端回调可以用来提示“支付处理中”,但不能仅凭这个回调将订单标记为已支付。
伪代码如下:
orderString = requestOrderFromBusinessServer()
result = alipaySdk.pay(orderString)
showPaymentProcessing()
queryOrderStatusFromBusinessServer()
App 应向自己的业务服务器查询订单状态,不应直接信任支付宝客户端的返回值。
3. 服务端接收异步通知
支付宝会以表单参数的形式向 notify_url 发起 POST 请求。服务端收到通知后,应先验签,再核对订单信息。
Spring Boot 示例:
import com.alipay.api.internal.util.AlipaySignature;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
public class AlipayNotifyController {
private static final String ALIPAY_PUBLIC_KEY = "支付宝公钥";
private static final String CHARSET = "UTF-8";
private static final String SIGN_TYPE = "RSA2";
@PostMapping(
value = "/payment/alipay/notify",
produces = "text/plain;charset=UTF-8"
)
public String receiveNotify(HttpServletRequest request) {
try {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((name, values) -> {
if (values != null && values.length > 0) {
params.put(name, String.join(",", values));
}
});
boolean verified = AlipaySignature.rsaCheckV1(
params,
ALIPAY_PUBLIC_KEY,
CHARSET,
SIGN_TYPE
);
if (!verified) {
return "failure";
}
String appId = params.get("app_id");
String sellerId = params.get("seller_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");
// 还需要从数据库读取本地订单并核对:
// 1. app_id 是否属于当前应用
// 2. seller_id 是否属于当前收款账号
// 3. out_trade_no 是否存在
// 4. total_amount 是否与订单金额一致
// 5. 当前订单是否已经处理过
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// 在数据库事务中执行幂等更新:
// updateOrderPaid(outTradeNo, tradeNo, totalAmount);
return "success";
}
// 对已正确接收且无需进一步处理的合法通知,
// 通常也应按接入规范返回 success。
return "success";
} catch (Exception e) {
// 发生异常时不要确认成功,以便支付宝按机制重新通知。
return "failure";
}
}
}
如果使用支付宝公钥证书模式,验签初始化和调用方法可能与普通公钥模式不同。此时应使用对应的证书模式 SDK 配置,不能直接套用示例中的普通支付宝公钥配置。
4. 将支付状态同步给 App
支付宝异步通知到达业务服务器后,可以通过以下方式让 App 获取结果:
- App 定时调用订单查询接口。
- App 返回支付页面时立即查询一次,随后进行短时间轮询。
- 业务服务器通过 WebSocket 通知在线 App。
- 使用 APNs、厂商推送等方式发送支付成功消息。
实际项目中,“支付完成后立即查询 + 短时间轮询”通常更稳妥。即使推送丢失,App 仍可从业务服务器获取最终状态。
业务查询接口可以设计为:
GET /api/orders/ORDER_202609090001/payment-status
Authorization: Bearer <token>
返回示例:
{
"orderNo": "ORDER_202609090001",
"paymentStatus": "PAID",
"channel": "ALIPAY",
"tradeNo": "支付宝交易号"
}
异步通知必须检查的内容
验签成功后,不能直接发货或开通服务,还要核对以下内容:
app_id是否等于当前支付宝应用的 App ID。seller_id是否为预期的收款方。out_trade_no是否对应本地真实订单。total_amount是否与本地订单金额一致。trade_status是否为业务认可的成功状态。- 订单是否已经完成过支付处理。
金额应使用 BigDecimal 比较,不要使用 double:
BigDecimal notifiedAmount =
new BigDecimal(params.get("total_amount"));
BigDecimal orderAmount =
order.getPayAmount();
if (notifiedAmount.compareTo(orderAmount) != 0) {
return "failure";
}
注意事项
异步通知必须幂等
支付宝可能重复发送同一笔交易的通知。每次通知都要正常验签,但订单状态更新、发货、积分发放和会员开通等业务操作只能执行一次。
可以为 out_trade_no 或支付宝交易号 trade_no 建立唯一约束,并在数据库事务中完成状态更新。
正确返回响应内容
通知处理成功后,应按照支付宝接入规范返回以下纯文本:
success
不要返回 JSON、HTML 或其他提示文字。如果没有返回支付宝能够识别的成功响应,支付宝可能再次发送通知。
不要通过金额或订单号判断通知真实性
攻击者可以自行构造 POST 请求。服务端必须先使用支付宝公钥验证 sign,不能只检查订单号、交易状态或金额。
保存原始通知和处理记录
建议记录以下信息:
out_trade_notrade_notrade_status- 通知时间
- 验签结果
- 业务处理结果
日志中不要输出私钥、访问令牌或完整的敏感信息。
增加主动查询作为补偿
异步通知可能延迟,也可能因服务器暂时不可用而处理失败。可以调用 alipay.trade.query,通过 out_trade_no 或 trade_no 主动查询交易状态,并配合定时对账任务修复异常订单。
这个需求对应的不是一个“直接通知 App 的支付宝接口”。正确做法是接入 alipay.trade.app.pay,由业务服务器通过 notify_url 接收支付结果的异步通知,再将可靠的订单状态同步给 App。
备注:内容仅供参考。
