PAYATHON 2026

iOS 调起支付宝后取消支付,返回应用无结果回调

支付阿杰

结论

用户取消支付宝支付后,应用通常会收到 resultStatus = 6001。如果返回应用后完全没有回调,先检查两个地方:

  1. 支付时传入的 fromScheme 是否与应用配置的 URL Scheme 完全一致。
  2. 支付宝返回应用时,AppDelegate 或 SceneDelegate 是否将回跳 URL 交给了 AlipaySDK。

客户端回调并不可靠。应用可能被系统终止,也可能发生进程重建或跳转链路异常。最终支付状态不能只看客户端回调,必须由服务端根据支付宝异步通知或主动查询结果确认。

常见原因

URL Scheme 配置错误

调用支付宝 SDK 时,通常需要传入应用的 URL Scheme:

AlipaySDK.defaultService().payOrder(
    orderString,
    fromScheme: "your_app_scheme"
) { result in
    handleAlipayResult(result)
}

这里的 your_app_scheme 必须与 Xcode 中配置的 Scheme 完全一致:

Target
→ Info
→ URL Types
→ URL Schemes

检查大小写、拼写,以及不同环境下是否使用了不同配置。Scheme 不匹配时,支付宝可能无法正常唤起原应用。

回跳 URL 没有交给支付宝 SDK

从支付宝返回应用时,iOS 会通过 URL 回调入口通知应用。应用需要把收到的 URL 传给:

AlipaySDK.defaultService().processOrder(
    withPaymentResult: url,
    standbyCallback: callback
)

如果项目同时接入了微信登录、第三方分享、地图等多个 URL Scheme,要注意 URL 分发逻辑。某个 SDK 提前处理并直接 return,可能导致支付宝 SDK 收不到 URL。

iOS 13 之后只处理了 AppDelegate

启用 Scene 生命周期后,应用可能通过 SceneDelegate 接收 URL。若项目只实现了 AppDelegate 回调,支付宝回跳可能不会进入支付结果处理逻辑。

实际使用哪个入口,取决于项目是否启用了 Scene,以及当前支付宝 SDK 的接入方式。

支付过程中应用进程被重建

支付请求中的闭包保存在当前应用进程内。如果跳转到支付宝期间应用被系统终止,返回时启动的就是新进程,原来的闭包已经不存在。

即使应用能够正常打开,原始 payOrder 回调也不一定会继续执行。应用需要保留订单号,并向业务服务端查询订单状态。

解决步骤

1. 检查 Scheme 是否一致

可以将 Scheme 定义为统一常量,避免调用代码与工程配置使用不同的值:

enum PaymentConfig {
    static let alipayScheme = "your_app_scheme"
}

发起支付时使用这个常量:

AlipaySDK.defaultService().payOrder(
    orderString,
    fromScheme: PaymentConfig.alipayScheme
) { result in
    handleAlipayResult(result)
}

还要确认 Info.plist 已注册该 Scheme。

2. 在 AppDelegate 中处理回跳

如果项目未使用 Scene 生命周期,或者需要兼容旧系统,可以在 AppDelegate 中处理:

import UIKit
import AlipaySDK

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(
        _ app: UIApplication,
        open url: URL,
        options: [UIApplication.OpenURLOptionsKey: Any] = [:]
    ) -> Bool {
        AlipaySDK.defaultService().processOrder(
            withPaymentResult: url
        ) { result in
            handleAlipayResult(result)
        }

        return true
    }
}

应用接入多个第三方 SDK 时,应根据 URL 的 Scheme 或 Host 进行分发,但过滤条件不要设得过严。调试时可以先记录实际收到的回跳 URL,再据此确定分发规则。

3. 在 SceneDelegate 中处理回跳

启用了 Scene 生命周期的项目还需要检查 SceneDelegate:

import UIKit
import AlipaySDK

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(
        _ scene: UIScene,
        openURLContexts URLContexts: Set<UIOpenURLContext>
    ) {
        guard let url = URLContexts.first?.url else {
            return
        }

        AlipaySDK.defaultService().processOrder(
            withPaymentResult: url
        ) { result in
            handleAlipayResult(result)
        }
    }
}

不要假设 AppDelegate 和 SceneDelegate 都会执行。应根据项目的生命周期配置,在真机上确认回跳实际进入了哪个方法。

4. 统一处理支付结果

发起支付的回调和 URL 回跳的备用回调,可以交给同一个方法处理:

func handleAlipayResult(_ result: [AnyHashable: Any]?) {
    guard let result else {
        // 未取得有效结果,进入订单查询流程
        queryPaymentStatusFromServer()
        return
    }

    let status = result["resultStatus"] as? String
    let memo = result["memo"] as? String

    switch status {
    case "9000":
        // 客户端显示支付完成,但最终状态仍应由服务端确认
        queryPaymentStatusFromServer()

    case "6001":
        // 用户取消支付
        showPaymentCancelledMessage()

    case "8000":
        // 结果处理中,不应直接判定成功或失败
        queryPaymentStatusFromServer()

    default:
        // 失败、网络异常或未知状态
        logPaymentResult(status: status, memo: memo)
        queryPaymentStatusFromServer()
    }
}

6001 通常表示用户取消。状态码和字段的具体含义,应以项目实际使用的支付宝 SDK 文档为准。

5. 增加生命周期和 URL 日志

排查时可以记录以下节点:

func scene(
    _ scene: UIScene,
    openURLContexts URLContexts: Set<UIOpenURLContext>
) {
    for context in URLContexts {
        print("Received callback URL:", context.url.absoluteString)
    }
}

同时记录:

  • 发起支付的时间和业务订单号
  • 调用 payOrder 时使用的 Scheme
  • 应用进入后台和恢复前台的时间
  • AppDelegate、SceneDelegate 是否收到 URL
  • 支付宝 SDK 返回的完整结果字典

不要在日志中输出签名、私钥、完整用户信息等敏感数据。

仍然没有回调时如何定位

可以沿着调用链逐层排查:

  1. 无法返回应用时,先检查 URL Scheme 和 fromScheme。
  2. 能返回应用,但 URL 回调方法没有执行时,检查 Scene 配置和生命周期入口。
  3. URL 回调方法已经执行,但支付宝处理结果没有触发时,确认 URL 是否来自支付宝,以及是否正确调用了 processOrder(withPaymentResult:standbyCallback:)。
  4. SDK 回调已经执行,但界面没有变化时,检查线程、页面是否已经释放,以及结果是否传到了当前可见页面。
  5. 返回应用时发生了重新启动,说明原闭包可能已经丢失。此时应使用本地保存的订单号向服务端查询状态。

服务端状态确认

客户端收到“成功”,不代表业务支付已经完成;没有收到回调,也不能直接判定支付失败。推荐使用以下流程:

创建业务订单
→ 调起支付宝
→ 收到客户端回调或应用恢复前台
→ 请求业务服务端查询订单状态
→ 服务端结合支付宝异步通知或主动查询结果
→ 更新订单并返回最终状态

订单更新需要具备幂等性,避免客户端查询和支付宝异步通知等多个请求造成重复发货或重复入账。

如果用户取消支付后客户端没有回调,可以在应用恢复前台时查询一次尚未完成的订单。只有服务端确认订单未支付,客户端才能将订单显示为取消或未完成。

备注:内容仅供参考。