iOS16 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下的视图状态传递时机或层级问题,可通过以下调整修复:
用
@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) } } }调整Button的action调用方式
Button("Pay") { Task { await model.pay() } }将
.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导航组件限制:
修改视图模型的
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) } } }移除结账视图中的
.paymentConfirmationSheet修饰符
由于已手动处理支付流程,无需再保留该修饰符。
说明
方案1通过适配NavigationStack的视图特性修复原有问题,方案2则完全绕过SwiftUI的Sheet修饰符限制,稳定性更高,推荐在复杂导航场景下使用。
内容的提问来源于stack exchange,提问作者MatuskaApp
相关产品推荐
相关产品推荐

