支付宝支付接口使用相同参数重复调用时返回什么?
结论
支付宝支付接口通常允许使用同一个商户订单号重试,但第二次请求的返回数据不一定与第一次完全相同。
支付宝一般通过 out_trade_no 等业务单号识别同一笔交易。参数相同的重复请求通常不会创建两笔支付订单,但返回内容会受交易状态、支付产品、具体 API 和请求时机影响。可能出现以下情况:
- 返回已经创建的交易信息;
- 返回仍可使用的支付参数,例如二维码;
- 提示订单已存在、已支付、已关闭或不允许重复创建;
- 第一次支付请求超时,第二次请求返回当前交易结果;
- 业务数据相同,但
sign、时间戳等字段发生变化,导致完整响应字符串不同。
因此,不能通过比较两次响应是否完全一致来判断支付是否成功。
为什么重复请求的返回值可能不同
支付接口处理的是有状态的交易。即使两次请求参数完全相同,请求到达支付宝时,订单状态也可能已经改变。
例如,同一个 out_trade_no 可能依次经历:
订单不存在
↓
WAIT_BUYER_PAY
↓
TRADE_SUCCESS
第一次请求可能创建订单并返回二维码。第二次请求到达时,用户或许已经完成付款。支付宝不会再次创建相同订单,而是根据已有交易返回对应结果。
常见情况如下:
| 第二次请求时的状态 | 可能的处理结果 |
|---|---|
| 第一次请求尚未成功落单 | 可能重新受理请求 |
| 订单等待付款 | 可能返回已有交易信息,也可能提示订单已经存在 |
| 订单已经支付成功 | 不会重复扣款,通常应查询并按成功订单处理 |
| 订单已经关闭 | 原商户订单号通常不能直接重新支付 |
| 两次请求并发到达 | 返回结果可能不同,需要通过查询接口确认最终状态 |
| 同一订单号对应的金额等信息不同 | 通常会被拒绝,具体错误码以对应 API 返回为准 |
不同支付方式的处理行为也有差异:
- 扫码付通常涉及
alipay.trade.precreate,需要关注二维码和支付宝交易状态。 - App 支付通常涉及
alipay.trade.app.pay,服务端返回供客户端调用支付宝 SDK 的支付参数。 - JS 支付可能先创建交易,再由前端通过支付宝 JSAPI 拉起收银台。
- 条码支付等场景可能属于付款码支付,其同步结果和处理中状态的处理方式也有所不同。
处理重复请求时,应以具体 API 文档中的幂等规则、错误码和交易状态为准,不能直接套用其他支付产品的处理方式。
正确的处理方式
1. 使用稳定且唯一的商户订单号
同一笔业务订单在每次重试时,都应使用相同的 out_trade_no:
out_trade_no = PAY202609090001
全新的支付应生成新的业务订单号。不要为了绕过超时或重复请求而更换订单号,否则可能产生多笔可支付订单。
2. 保证关键业务参数一致
使用同一个 out_trade_no 重试时,至少要保证以下信息不变:
- 支付金额;
- 商户身份;
- 商品或订单标识;
- 币种;
- 交易类型;
- 其他会影响交易创建的关键字段。
尤其不能保留原 out_trade_no,却修改支付金额。支付宝可能将这种情况判定为同一订单的数据冲突并拒绝请求。
3. 请求超时时先查询订单
支付请求发生网络超时,不代表支付宝没有收到请求。此时不要立即生成新订单再次发起支付,应先调用交易查询接口,例如:
alipay.trade.query
一般可以按以下流程处理:
发起支付
↓
是否获得明确结果
├─ 是:按返回结果处理
└─ 否:使用 out_trade_no 查询交易
↓
根据查询结果决定重试、等待或结束
4. 以交易状态判断结果
业务系统应根据支付宝返回的交易状态处理订单,不要比较两次返回报文是否一致。
常见状态包括:
WAIT_BUYER_PAY:交易已创建,等待买家付款;TRADE_SUCCESS:支付成功;TRADE_FINISHED:交易完成;TRADE_CLOSED:交易关闭。
部分支付产品还有处理中状态或专属字段,具体定义应以实际接口文档为准。
5. 异步通知也要做幂等处理
支付宝可能重复发送异步通知。接收通知时,应结合 out_trade_no、trade_no 和本地订单状态进行幂等处理。
即使收到两次成功通知,也不能重复发货、重复充值或重复增加账户余额。
Java 示例
下面的示例演示扫码付请求结果不明确时,如何使用同一个 out_trade_no 查询交易。SDK 类名和初始化参数可能因支付宝 Java SDK 版本不同而略有差异。
String outTradeNo = "PAY202609090001";
AlipayTradePrecreateRequest payRequest =
new AlipayTradePrecreateRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo);
bizContent.put("total_amount", "99.00");
bizContent.put("subject", "测试订单");
payRequest.setBizContent(bizContent.toJSONString());
try {
AlipayTradePrecreateResponse payResponse =
alipayClient.execute(payRequest);
if (payResponse.isSuccess()) {
String qrCode = payResponse.getQrCode();
// 保存二维码和订单状态,不要仅依赖当前响应判断最终支付结果
saveQrCode(outTradeNo, qrCode);
} else {
// 业务失败时记录 code、sub_code、msg 和 sub_msg
logPayFailure(
payResponse.getCode(),
payResponse.getSubCode(),
payResponse.getMsg(),
payResponse.getSubMsg()
);
queryTrade(alipayClient, outTradeNo);
}
} catch (AlipayApiException e) {
// 网络超时或无法确定支付宝是否已经受理时,先查询交易
queryTrade(alipayClient, outTradeNo);
}
查询交易的方法如下:
private void queryTrade(
AlipayClient alipayClient,
String outTradeNo
) throws AlipayApiException {
AlipayTradeQueryRequest queryRequest =
new AlipayTradeQueryRequest();
JSONObject queryContent = new JSONObject();
queryContent.put("out_trade_no", outTradeNo);
queryRequest.setBizContent(queryContent.toJSONString());
AlipayTradeQueryResponse queryResponse =
alipayClient.execute(queryRequest);
if (!queryResponse.isSuccess()) {
logQueryFailure(
queryResponse.getCode(),
queryResponse.getSubCode(),
queryResponse.getMsg(),
queryResponse.getSubMsg()
);
return;
}
String tradeStatus = queryResponse.getTradeStatus();
switch (tradeStatus) {
case "WAIT_BUYER_PAY":
markWaitingForPayment(outTradeNo);
break;
case "TRADE_SUCCESS":
case "TRADE_FINISHED":
markPaidIdempotently(
outTradeNo,
queryResponse.getTradeNo()
);
break;
case "TRADE_CLOSED":
markClosed(outTradeNo);
break;
default:
markUnknown(outTradeNo, tradeStatus);
break;
}
}
更新本地订单状态时同样要保证幂等。例如,只允许订单从”待支付”更新为”已支付”一次:
UPDATE payment_order
SET
status = 'PAID',
alipay_trade_no = ?
WHERE out_trade_no = ?
AND status = 'WAIT_PAY';
如果受影响行数为 0,应先检查订单是否已经处理,不要再次执行发货或充值操作。
注意事项
- “支持重试”不代表”两次返回内容完全一致”。
- 不要根据响应字符串、签名或时间戳是否相同来判断是不是同一笔交易。
- App 支付的业务内容即使相同,返回的支付参数也可能因签名时间等因素发生变化。
- 重复请求扫码支付时,不要假设二维码一定相同,也不要假设系统一定会重新生成二维码,应以接口响应和交易查询结果为准。
- 接口返回失败、网络超时和支付失败是三种不同情况。网络超时通常表示结果未知,需要主动查询。
- 同一个
out_trade_no不能对应不同金额或不同业务订单。 - 已关闭的交易如果需要重新支付,通常应按照业务规则创建新的支付订单,并使用新的
out_trade_no。 - 同步返回、主动查询和异步通知可能先后到达,三条处理链路都要具备幂等能力。
alipay.trade.precreate、alipay.trade.app.pay、alipay.trade.create等接口的错误码和重复调用行为可能不同,应以当前使用的 API 文档为准。
相同参数的重复调用可以用于处理结果未知的情况,但系统必须保证同一笔业务订单只处理一次。最终支付结果应结合交易状态、主动查询和验签后的异步通知确认,不能依赖第二次接口响应与第一次完全一致。
备注:内容仅供参考。
