示例代码均为匿名化改写,用于说明模式;交易状态必须以服务端核对结果为准。
发起支付不等于交易成功
用户切换到第三方支付应用后,原应用可能挂起;用户也可能取消、切换账号,或在服务端更新订单前先回到客户端。把 SDK 的“请求已接受”当作付款完成,会造成订单状态误报。
客户端收到回跳时,适合将它视为查询提示。真正的付款状态应通过服务端查询确认,只有服务端状态满足条件后才清理本地待处理记录或离开付款流程。
enum PaymentState: Sendable {
case pending, paid, cancelled, failed
}
struct PendingPayment: Sendable {
let reference: String
let accountID: UUID
}
protocol PaymentReconciliation: Sendable {
func state(for reference: String, accountID: UUID) async throws -> PaymentState
func removePending(reference: String, accountID: UUID) async
}
func reconcile(
_ payment: PendingPayment,
using service: some PaymentReconciliation
) async throws -> PaymentState {
let state = try await service.state(
for: payment.reference,
accountID: payment.accountID
)
switch state {
case .pending:
break // Keep the local reference for a later retry.
case .paid, .cancelled, .failed:
await service.removePending(
reference: payment.reference,
accountID: payment.accountID
)
}
return state
}
第三方回跳只表示可以开始核对,不足以证明付款成功;成功页面必须等待服务端确认。
为何先写待处理记录
发起第三方流程前,把恢复所需的支付引用和账号归属写入本地应用数据,再打开第三方 SDK。若支付页面无法启动,则撤销本次新建记录。这样即使 App 被系统回收,也可以在下次恢复时继续核对。
本地记录应只包含必要的引用标识,不保存可复用登录凭据。记录要绑定账号,避免当前用户误处理另一个账号之前留下的支付状态。
待处理引用应在切换到支付应用前持久化,并绑定发起它的账号。这样应用被系统回收后仍能恢复查询,同时不必把可复用凭据写入本地记录。
对账时机与失败处理
登录恢复、重新回到前台和进入相关订单页面,都可以触发一次对账。网络暂时失败时有限重试;仍失败就保留引用,等待下一次前台机会。若应用不安排后台任务,就不应承诺用户离开应用时持续更新支付状态。
凭据传递要格外谨慎
若第三方页面路由中需要登录凭据,应核对该凭据的权限、有效期、接收范围以及日志策略。URL 编码不会隐藏凭据;可行时使用服务端签发的短时、单次交换码。
