Проблема с @EnvironmentObject в приложении 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”. Код для 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:

  1. Не используйте @EnvironmentObject в классе StripePaymentHandler. Вместо этого передайте объект Order непосредственно в метод, который вызывает StripePaymentHandler.

  2. Передайте 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 в окружении, поскольку теперь вы явно передаете его в метод, который его использует.

Оцените материал
Добавить комментарий

Капча загружается...