Android 端通过 URL 跳转支付宝后如何获取支付回调
结论
通过 alipays://platformapi/startapp?... 启动支付宝,只能把用户带到支付宝页面。支付宝不会通过 Activity Result、Intent 或应用重新进入前台等方式,向发起跳转的 Android 应用可靠地返回支付结果。
要准确判断支付是否成功,需要使用支付宝开放平台提供的支付产品,并建立完整的服务端处理流程:
- 服务端创建订单并完成签名。
- Android 端调用支付宝 App 支付 SDK。
- 客户端接收 SDK 返回的结果,用于即时更新页面提示。
- 服务端接收支付宝异步通知,验签后更新订单。
- 必要时,服务端调用订单查询接口确认最终状态。
发货、充值或开通权益时,应以服务端验签后的异步通知或主动查询结果为准。
为什么当前跳转方式拿不到回调
示例代码本质上只是一次普通的外部应用跳转:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(jumpUrl));
activity.startActivity(intent);
Android 只负责找到能够处理 alipays:// Scheme 的应用并打开该地址。这个跳转过程并未约定支付宝需要:
- 调用当前应用的某个回调;
- 返回支付状态;
- 调用
setResult(); - 携带商户订单号返回;
- 在用户付款后自动唤醒原应用。
所以,即使改用 startActivityForResult(),通常也拿不到支付结果。用户返回应用、支付宝页面关闭或应用重新进入前台,都不能说明付款已经成功。
固定二维码链接也可能只是一个收款入口,未必包含商户订单所需的订单号、签名和异步通知地址等信息,因此无法形成可靠的订单状态闭环。
推荐方案:接入支付宝 App 支付
1. 服务端创建订单
订单必须由商户服务端生成。服务端至少需要完成以下工作:
- 创建唯一的商户订单号
out_trade_no; - 设置金额、标题等业务参数;
- 配置支付宝异步通知地址
notify_url; - 使用商户私钥为请求参数签名;
- 将生成的订单字符串返回给 Android 客户端。
商户私钥不能放在 Android 应用中,否则可能因应用被反编译而泄露,并被用于伪造订单。
2. Android 调用支付 SDK
下面的代码只展示客户端调用结构。orderInfo 必须由服务端生成,客户端不能自行拼接或签名:
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
PayTask payTask = new PayTask(activity);
// orderInfo 由商户服务端生成并签名
Map<String, String> result = payTask.payV2(orderInfo, true);
activity.runOnUiThread(() -> {
String resultStatus = result.get("resultStatus");
String resultInfo = result.get("result");
// 这里只更新页面提示,不在此处直接发货或开通权益
if ("9000".equals(resultStatus)) {
showMessage("支付请求已完成,正在确认订单状态");
} else if ("8000".equals(resultStatus)) {
showMessage("支付结果确认中");
} else if ("6001".equals(resultStatus)) {
showMessage("用户取消支付");
} else {
showMessage("支付未完成");
}
queryOrderFromMerchantServer();
});
});
PayTask.payV2() 是阻塞调用,不能放在主线程中执行。SDK 版本和依赖配置应以接入时使用的支付宝开放平台文档为准。
常见的 resultStatus 如下:
| 状态码 | 常见含义 |
|---|---|
9000 | 客户端认为支付成功 |
8000 | 结果确认中 |
6001 | 用户取消 |
6002 | 网络连接异常 |
这些状态码只适合用来更新客户端提示。即使收到 9000,也不能只凭客户端返回的结果发货或充值。
3. 服务端处理异步通知
支付宝向 notify_url 发送通知后,服务端需要:
- 读取通知参数;
- 按照支付宝的规则验证签名;
- 检查
app_id和商户身份等信息; - 根据
out_trade_no查找本地订单; - 核对订单金额和币种;
- 判断交易状态;
- 以幂等方式更新订单;
- 按协议返回成功响应。
处理流程可以概括为:
public String handleAlipayNotify(Map<String, String> params) {
boolean valid = verifyAlipaySignature(params, alipayPublicKey);
if (!valid) {
return "failure";
}
String outTradeNo = params.get("out_trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
String appId = params.get("app_id");
Order order = orderService.findByOrderNo(outTradeNo);
if (order == null) {
return "failure";
}
if (!expectedAppId.equals(appId)) {
return "failure";
}
if (!order.getAmount().toPlainString().equals(totalAmount)) {
return "failure";
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// 必须幂等,防止重复通知导致重复发货
orderService.markPaidIfNecessary(order);
return "success";
}
return "success";
}
这段代码只用于说明处理流程。验签参数转换、金额比较、通知响应格式和交易状态处理方式,需要按照实际使用的支付宝服务端 SDK 及支付产品要求实现。
4. 客户端查询商户服务端
客户端支付流程结束后,应查询商户自己的服务端,不能直接把 SDK 返回值当作最终支付结果:
private void queryOrderFromMerchantServer() {
merchantApi.queryOrder(outTradeNo, order -> {
if ("PAID".equals(order.getStatus())) {
showPaymentSuccess();
} else if ("PAYING".equals(order.getStatus())) {
showPaymentProcessing();
} else {
showPaymentPending();
}
});
}
如果服务端暂时还没有收到异步通知,可以由服务端调用支付宝订单查询接口补充确认。支付宝私钥、验签公钥和服务端接口凭证都不能交给客户端。
如果必须保留 URL 跳转
用户返回应用后,可以提示”正在查询支付结果”,再通过业务订单号向商户服务端查询。不过,这要求二维码支付流程能够关联商户系统创建的唯一订单,并且服务端可以通过支付宝通知或查询接口获取该订单的状态。
不能通过以下方式判断付款成功:
@Override
protected void onResume() {
super.onResume();
// 错误:返回应用不代表支付成功
markOrderAsPaid();
}
也不能依赖:
startActivityForResult(intent, REQUEST_PAY);
外部 Scheme 跳转没有约定支付宝必须返回有效的 resultCode 和支付数据。
注意事项
- 不能根据应用重新进入前台、支付宝页面关闭或用户截图判断支付成功。
- 不能只依赖客户端的
resultStatus。客户端返回值可能丢失、延迟或被篡改。 - 不要在 Android 端保存商户私钥或完成订单签名。
- 服务端必须校验订单号、金额、商户身份和签名,不能只检查
trade_status。 - 异步通知可能重复发送,订单更新和发货逻辑必须具备幂等性。
- URL 参数需要正确编码,但编码只能解决链接格式问题,不能让普通 Scheme 跳转获得支付回调能力。
- 如果使用个人收款码、固定收款码或非开放平台商户支付产品,通常无法获得完整且可靠的订单回调。需要改用支持订单创建、异步通知和订单查询的商户支付产品。
备注:内容仅供参考。
