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

SwiftUI集成Stripe支付时@EnvironmentObject运行时报错问题

SwiftUI集成Stripe支付时的运行时错误解决方法

运行SwiftUI集成Stripe支付的代码时,触发如下运行时错误:

Thread 16: Fatal error: No ObservableObject of type Order found. A View.environmentObject(_:) for Order may be missing as an ancestor of this view.

报错位置在StripePaymentHandler类的configuration.defaultBillingDetails.email = order.email代码行。SecondView通过表单收集包含邮箱在内的订单信息,需要将订单信息传递给StripePaymentHandler以创建Stripe PaymentIntent。

相关代码

StripePaymentHandler类

import StripePaymentSheet
import SwiftUI

class StripePaymentHandler: ObservableObject {
    
    @EnvironmentObject var order: Order
    @Published var paymentSheet: PaymentSheet?
    @Published var showingAlert: Bool = false
    
    private let backendtUrl = URL(string: "http://xxxxxxxxx")!
   
    private var configuration = PaymentSheet.Configuration()
    private var clientSecret = ""
    var paymentIntentID: String = ""
    
    var alertText: String = ""
    var paymentAmount: Int = 0
    
    func preparePaymentSheet() {
        let url = backendtUrl.appendingPathComponent("prepare-payment-sheet")
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
            guard let data = data,
                  let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any],
                  let customerId = json["customer"] as? String,
                  let customerEphemeralKeySecret = json["ephemeralKey"] as? String,
                  let clientSecret = json["clientSecret"] as? String,
                  let paymentIntentID = json["paymentIntentID"] as? String,
                  let publishableKey = json["publishableKey"] as? String,
                  let self = self else {
                return
            }
            
            self.clientSecret = clientSecret
            self.paymentIntentID = paymentIntentID
            print("I got the paymentIntentID from prepare-payment-sheet = ", self.paymentIntentID)
            STPAPIClient.shared.publishableKey = publishableKey
            
            configuration.merchantDisplayName = "Gee-Ware"
            configuration.customer = .init(id: customerId, ephemeralKeySecret: customerEphemeralKeySecret)
           
            configuration.defaultBillingDetails.address.country = "US"
            configuration.defaultBillingDetails.email = order.email
            configuration.billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod = true
            configuration.allowsDelayedPaymentMethods = true
            configuration.primaryButtonColor = UIColor(red: 0.70, green: 0.54, blue: 0.93, alpha: 1.0)
            configuration.applePay = .init(
              merchantId: "merchant.com.your_app_name",
              merchantCountryCode: "US"
            )
            configuration.returnURL = "your-app://stripe-redirect"
        })
        task.resume()
    }
}

Order类

import Foundation

class Order: ObservableObject {
    
    @Published var name: String = ""
    @Published var email: String = ""
    @Published var streetAddress: String = ""
    @Published var city: String = ""
    @Published var state: String = ""
    @Published var zip: String = ""
}

SecondView结构体

import SwiftUI
import StripePaymentSheet

struct SecondView: View {
    @EnvironmentObject var order: Order
    @State private var isLocked = true
    @State private var isLoading = false
    let layoutProperties:LayoutProperties
    @State var readyForPayment: Bool = false
    @FocusState var textFieldFocused: Bool
    @StateObject var model = StripePaymentHandler()
    @EnvironmentObject var gloVars: GlobalVars
    @State private var enteredNumber = ""
    var enteredNumberFormatted: Double {
        return (Double(enteredNumber) ?? 0) / 100
    }
    
    var body: some View {
        ResponsiveView { properties in
            VStack {
                Text("Slide to start payment process, hit button to confirm payment amount, then enter your payment details and your book is on its way to you")
                    
                Form {
                    Section {
                        TextField("Name", text: $order.name)
                        TextField("Email Address", text: $order.email)
                        
                    }

                    Section {
                        TextField("Street Address", text: $order.streetAddress)
                        TextField("City", text: $order.city)
                        TextField("State", text: $order.state)
                        TextField("Zip", text: $order.zip)
                    }
                }
                GeometryReader { geometry in
                    ZStack(alignment: .leading) {
                        BackgroundComponent()
                        DraggingComponent(isLocked: $isLocked, isLoading: isLoading, maxWidth: geometry.size.width)
                    }
                }
                .frame(height: 50)
                .padding()
                .padding(.bottom, 20)
                .onChange(of: isLocked) { isLocked in
                    guard !isLocked else { return }
                    readyForPayment = true
                }
                Spacer()
               
                if let paymentSheet = model.paymentSheet {
          
                    PaymentSheet.PaymentButton(
                        paymentSheet: paymentSheet,
                        onCompletion: model.onPaymentCompletion
                    ) {
                        payButton
                    }
                }
                
            }
        }
        .alert(model.alertText, isPresented: $model.showingAlert) {
            Button("OK", role: .cancel) { }
        }
        .onChange(of: readyForPayment) {
                DispatchQueue.global(qos: .background).sync {
                    model.updatePaymentSheet()
                }
        }
        .onAppear {
            let priceAfterProfit = gloVars.bkAmount*100*gloVars.profitFactor
            enteredNumber = "\(priceAfterProfit)"
            model.paymentAmount = Int(enteredNumberFormatted * 100)
            model.preparePaymentSheet()
            textFieldFocused = true
        }
        .toolbar {
            ToolbarItem(placement: .keyboard) {
                Button("Done") {
                    textFieldFocused = false
                }
            }
        }
    }
}

问题原因

@EnvironmentObject仅能在View结构体或被View通过环境注入的ObservableObject中使用,直接在自定义的ObservableObject类(如StripePaymentHandler)里声明@EnvironmentObject var order: Order是无效的——环境对象的注入依赖View层级,普通ObservableObject不会自动获取环境中的对象。

解决方法

方法1:初始化时传入Order实例

修改StripePaymentHandler,移除@EnvironmentObject,改为初始化时接收Order对象:

class StripePaymentHandler: ObservableObject {
    let order: Order
    @Published var paymentSheet: PaymentSheet?
    @Published var showingAlert: Bool = false
    
    private let backendtUrl = URL(string: "http://xxxxxxxxx")!
   
    private var configuration = PaymentSheet.Configuration()
    private var clientSecret = ""
    var paymentIntentID: String = ""
    
    var alertText: String = ""
    var paymentAmount: Int = 0
    
    init(order: Order) {
        self.order = order
    }
    
    func preparePaymentSheet() {
        // 原有网络请求代码不变
        configuration.defaultBillingDetails.email = self.order.email
        // 其他代码不变
    }
}

在SecondView中创建StripePaymentHandler时传入已有的order环境对象:

struct SecondView: View {
    @EnvironmentObject var order: Order
    @StateObject var model: StripePaymentHandler
    
    init(layoutProperties: LayoutProperties) {
        self.layoutProperties = layoutProperties
        self._model = StateObject(wrappedValue: StripePaymentHandler(order: Order()))
    }
    
    // 原有代码不变
}

方法2:调用方法时传递Order信息

修改StripePaymentHandler的preparePaymentSheet方法,添加Order参数:

func preparePaymentSheet(with order: Order) {
    // 原有网络请求代码不变
    configuration.defaultBillingDetails.email = order.email
    // 其他代码不变
}

在SecondView的onAppear中调用时传入order:

.onAppear {
    let priceAfterProfit = gloVars.bkAmount*100*gloVars.profitFactor
    enteredNumber = "\(priceAfterProfit)"
    model.paymentAmount = Int(enteredNumberFormatted * 100)
    model.preparePaymentSheet(with: order)
    textFieldFocused = true
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 08:51:04