You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS16 SwiftUI中NavigationStack下Stripe支付确认失败修复方案

在SwiftUI NavigationStack中修复Stripe支付确认问题

问题背景

此前通过UIViewRepresentable包装STPPaymentCardTextField,配合.paymentConfirmationSheet修饰符,在NavigationView中可正常完成Stripe卡片支付。但切换到iOS16的NavigationStack后,该修饰符在导航栈内部失效,触发支付时提示支付意向缺少支付方式,但直接展示结账视图时功能正常。

错误信息

Error Domain=com.stripe.lib Code=50 "There was an unexpected error -- try again in a few seconds" UserInfo={NSLocalizedDescription=There was an unexpected error -- try again in a few seconds, com.stripe.lib:StripeErrorTypeKey=invalid_request_error, com.stripe.lib:StripeErrorCodeKey=payment_intent_unexpected_state, com.stripe.lib:ErrorMessageKey=Nemůžete potvrdit tento Platební záměr, protože v něm chybí platební metoda. Můžete buď aktualizovat Platební záměr s platební metodou a pak jej znovu potvrdit, nebo jej znovu potvrďte přímo s platební metodou.}

相关代码

结账视图与视图模型

struct TestCheckout: View {
    
    @ObservedObject var model = TestCheckoutViewModel()
    
    var body: some View {
        
        VStack {
            CardField(paymentMethodParams: $model.paymentMethodParams)
            
            Button("Pay") {
                model.pay()
            }
            .buttonStyle(LargeButtonStyle())
            .paymentConfirmationSheet(isConfirmingPayment: $model.confirmPayment,
                                      paymentIntentParams: model.paymentIntentParams,
                                      onCompletion: model.onPaymentComplete)
        }
    }
}

class TestCheckoutViewModel: ObservableObject {
    @Published var paymentMethodParams: STPPaymentMethodParams?
    @Published var confirmPayment = false
    @Published var paymentIntentParams = STPPaymentIntentParams(clientSecret: "")
    
    func pay() {
        
        Task {
            
            do {
                // 创建测试支付意向
                let paymentIntent = try await StripeManager.shared.getPaymentIntent(orderId: "", amount: 1000)
                
                // 绑定卡片信息
                let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntent.secret)
                paymentIntentParams.paymentMethodParams = paymentMethodParams
                
                // 提交支付
                DispatchQueue.main.async {
                    self.paymentIntentParams = paymentIntentParams
                    self.confirmPayment = true
                }
            } catch {
                print(error)
            }
        }
    }

    func onPaymentComplete(status: STPPaymentHandlerActionStatus, paymentIntent: STPPaymentIntent?, error: Error?) {
        print("Payment completed: \(error)")
    }
}

无法工作的NavigationStack版本

struct TestView: View {
    var body: some View {
        NavigationStack {
            NavigationLink("Checkout", value: "checkout")
                .navigationDestination(for: String.self) { string in
                    if string == "checkout" {
                        TestCheckout()
                    }
                }
        }
    }
}

正常工作的直接展示版本

struct TestView: View {
    var body: some View {
        TestCheckout()
    }
}

解决方案

方案1:优化状态更新与视图层级适配NavigationStack

问题源于NavigationStack下的视图状态传递时机或层级问题,可通过以下调整修复:

  1. 用@MainActor简化主线程更新
    改造视图模型的pay方法,确保UI状态更新在主线程执行,替代手动DispatchQueue.main.async:

    class TestCheckoutViewModel: ObservableObject {
        // ... 原有属性 ...
        
        @MainActor
        func pay() async {
            do {
                let paymentIntent = try await StripeManager.shared.getPaymentIntent(orderId: "", amount: 1000)
                let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntent.secret)
                paymentIntentParams.paymentMethodParams = paymentMethodParams
                
                self.paymentIntentParams = paymentIntentParams
                self.confirmPayment = true
            } catch {
                print(error)
            }
        }
    }
    
  2. 调整Button的action调用方式

    Button("Pay") {
        Task {
            await model.pay()
        }
    }
    
  3. 将.paymentConfirmationSheet应用到父容器
    确保支付确认Sheet绑定到整个结账视图的层级,而非单个Button:

    VStack {
        CardField(paymentMethodParams: $model.paymentMethodParams)
        
        Button("Pay") {
            Task {
                await model.pay()
            }
        }
        .buttonStyle(LargeButtonStyle())
    }
    .paymentConfirmationSheet(isConfirmingPayment: $model.confirmPayment,
                              paymentIntentParams: model.paymentIntentParams,
                              onCompletion: model.onPaymentComplete)
    

方案2:手动使用STPPaymentHandler处理支付(推荐)

完全脱离.paymentConfirmationSheet修饰符,直接调用Stripe的原生支付处理API,不受SwiftUI导航组件限制:

  1. 修改视图模型的pay方法

    class TestCheckoutViewModel: ObservableObject {
        // ... 原有属性 ...
        
        func pay() async {
            do {
                let paymentIntent = try await StripeManager.shared.getPaymentIntent(orderId: "", amount: 1000)
                guard let paymentMethodParams = paymentMethodParams else {
                    print("未获取到卡片支付参数")
                    return
                }
                
                let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntent.secret)
                paymentIntentParams.paymentMethodParams = paymentMethodParams
                
                // 获取当前根视图控制器用于支付验证
                guard let rootVC = UIApplication.shared.connectedScenes
                        .filter({$0.activationState == .foregroundActive})
                        .compactMap({$0 as? UIWindowScene})
                        .first?.windows
                        .filter({$0.isKeyWindow})
                        .first?.rootViewController else {
                    print("无法获取根视图控制器")
                    return
                }
                
                // 手动触发支付确认
                STPPaymentHandler.shared().confirmPayment(withParams: paymentIntentParams, authenticationContext: rootVC) { [weak self] status, intent, error in
                    DispatchQueue.main.async {
                        self?.onPaymentComplete(status: status, paymentIntent: intent, error: error)
                    }
                }
            } catch {
                print(error)
            }
        }
    }
    
  2. 移除结账视图中的.paymentConfirmationSheet修饰符
    由于已手动处理支付流程,无需再保留该修饰符。

说明

方案1通过适配NavigationStack的视图特性修复原有问题,方案2则完全绕过SwiftUI的Sheet修饰符限制,稳定性更高,推荐在复杂导航场景下使用。

内容的提问来源于stack exchange,提问作者MatuskaApp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 12:35:21