I am getting a runtime error of “Thread 16: Fatal error: No ObservableObject of type Order found. A View.environmentObject(_:) for Order may be missing as an ancestor of this view.” in the code for StripePaymentHandler on this line “configuration.defaultBillingDetails.email = order.email”. The code for StripePaymentHandler is below. SecondView has a form that gathers order data including email address and then sends the order info to StripePaymentHandler that creates the Stripe paymentIntent
<code>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: 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, 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 {
// 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
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()
}
</code>
<code>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: 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, 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 {
// 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
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()
}
</code>
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: 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, 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 {
// 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
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()
}
Here is the Order class code
<code>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 = ""
}
</code>
<code>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 = ""
}
</code>
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 = ""
}
And the code for SecondView
<code>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("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)
}
}
//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
}
}
} //end vstack
}//end 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("Done") {
textFieldFocused = false
}
}
}
}
</code>
<code>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("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)
}
}
//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
}
}
} //end vstack
}//end 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("Done") {
textFieldFocused = false
}
}
}
}
</code>
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("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)
}
}
//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
}
}
} //end vstack
}//end 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("Done") {
textFieldFocused = false
}
}
}
}
5