Я получаю ошибку выполнения “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”. Код для StripePaymentHandler приведен ниже. SecondView содержит форму, которая собирает данные заказа, включая адрес электронной почты, и затем отправляет информацию о заказе в StripePaymentHandler, который создает paymentIntent Stripe
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() {
// MARK: Получить PaymentIntent и информацию о клиенте с бэкенда
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("Я получил paymentIntentID из prepare-payment-sheet = ", self.paymentIntentID)
STPAPIClient.shared.publishableKey = publishableKey
// MARK: Создать экземпляр PaymentSheet
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) //Color(red: 179 / 255, green: 140 / 255, blue: 237 / 255)
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 = "" //$99.66
var enteredNumberFormatted: Double {
return (Double(enteredNumber) ?? 0) / 100
}
var body: some View {
ResponsiveView { properties in
VStack {
Text("Сдвиньте, чтобы начать процесс оплаты, нажмите кнопку, чтобы подтвердить сумму платежа, затем введите свои платежные данные, и ваша книга будет отправлена к вам")
Form {
Section {
TextField("Имя", text: $order.name)
TextField("Адрес электронной почты", text: $order.email)
}
Section {
TextField("Улица", text: $order.streetAddress)
TextField("Город", text: $order.city)
TextField("Штат", text: $order.state)
TextField("Почтовый индекс", text: $order.zip)
}
}
//Spacer()
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
}
}
} //конец vstack
}//конец responsiveview
)
.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("Готово") {
textFieldFocused = false
}
}
}
}
Ответ
Проблема, с которой вы сталкиваетесь, связана с тем, что объект Order
, который вы пытаетесь использовать в StripePaymentHandler
, не был правильно передан через environmentObject
. В объекте StripePaymentHandler
вы пытаетесь использовать @EnvironmentObject
для доступа к вашему объекту Order
, однако сам класс StripePaymentHandler
не является представлением, и поэтому не может иметь доступ к окружению.
Вот как вы можете исправить вашу проблему и убедиться, что Order
доступен в StripePaymentHandler
:
-
Не используйте
@EnvironmentObject
в классеStripePaymentHandler
. Вместо этого передайте объектOrder
непосредственно в метод, который вызываетStripePaymentHandler
. - Передайте
Order
вStripePaymentHandler
как параметр метода. Например, вы можете создать функцию вStripePaymentHandler
, которая принимаетOrder
в качестве аргумента.
Вот как будет выглядеть ваша исправленная версия кода:
class StripePaymentHandler: ObservableObject {
@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(order: Order) { // передаем Order как параметр
// MARK: Fetch the PaymentIntent and Customer information from the backend
let url = backendtUrl.appendingPathComponent("prepare-payment-sheet")
var request = URLRequest(url: url)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { [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 {
// Handle error
return
}
self.clientSecret = clientSecret
self.paymentIntentID = paymentIntentID
print("I got the paymentIntentID from prepare-payment-sheet = ", self.paymentIntentID)
STPAPIClient.shared.publishableKey = publishableKey
// MARK: Create a PaymentSheet instance
self.configuration.merchantDisplayName = "Gee-Ware"
self.configuration.customer = .init(id: customerId, ephemeralKeySecret: customerEphemeralKeySecret)
self.configuration.defaultBillingDetails.address.country = "US"
self.configuration.defaultBillingDetails.email = order.email // используем переданный Order
self.configuration.billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod = true
self.configuration.allowsDelayedPaymentMethods = true
self.configuration.primaryButtonColor = UIColor(red: 0.70, green: 0.54, blue: 0.93, alpha: 1.0)
self.configuration.applePay = .init(
merchantId: "merchant.com.your_app_name",
merchantCountryCode: "US"
)
self.configuration.returnURL = "your-app://stripe-redirect"
}
task.resume()
}
}
Затем в вашем SecondView
можно будет вызвать этот метод следующим образом:
.onAppear {
let priceAfterProfit = gloVars.bkAmount * 100 * gloVars.profitFactor
enteredNumber = "\(priceAfterProfit)"
model.paymentAmount = Int(enteredNumberFormatted * 100)
model.preparePaymentSheet(order: order) // передаем объект Order
textFieldFocused = true
}
С этими изменениями ваш код должен работать без ошибок, связанных с отсутствием Order
в окружении, поскольку теперь вы явно передаете его в метод, который его использует.