I’ve an app utilizing Stripe Funds that makes use of a view known as StripePaymentView that permits customers to create a Stripe Cost on the app and it really works nice so long as StripePaymentView is named from the preliminary struct MySwiftUIApp: App file within the WindowGroup as proven under
import SwiftUI
import SwiftData
import Stripe
@principal
struct MySwiftUIApp: App {
var physique: some Scene {
WindowGroup {
StripePaymentView()
//ContentView()
/*
ResponsiveView {properties in
MainTabbedView(layoutProperties: properties)
.modelContainer(for: SavedFavBooksFromISBNDB.self)
}
.onOpenURL { incomingURL in
let stripeHandled = StripeAPI.handleURLCallback(with: incomingURL)
if (!stripeHandled) {
// This was not a Stripe url – deal with the URL usually as you'd
}
}*/
}
}
}
The code works as I can see the fee intent get created for the right amount when trying on the debug console on the backend server working my specific.js script see under
{paymentIntentID: 'pi_3PxEmnJ15oFjjfNB10B4Cm8o', quantity: 7744}
Nevertheless when I attempt to push the view “StripePaymentView” from some other subview within the app the place it must be positioned I get an error when speaking to the backend server.
Right here is the debug console error on backend server
Under is the code for StripePaymentView
import SwiftUI
import StripePaymentSheet
struct StripePaymentView: View {
@FocusState var textFieldFocused: Bool
@ObservedObject var mannequin = StripePaymentHandler()
@State non-public var enteredNumber = ""
var enteredNumberFormatted: Double {
return (Double(enteredNumber) ?? 0) / 100
}
var physique: some View {
VStack {
Textual content("Enter the quantity")
ZStack(alignment: .heart) {
Textual content("$(enteredNumberFormatted, specifier: "%.2f")").font(Font.system(dimension: 30))
TextField("", textual content: $enteredNumber, onEditingChanged: { _ in
mannequin.paymentAmount = Int(enteredNumberFormatted * 100)
}, onCommit: {
textFieldFocused = false
}).targeted($textFieldFocused)
.keyboardType(.numberPad)
.foregroundColor(.clear)
.disableAutocorrection(true)
.accentColor(.clear)
}
Spacer()
if let paymentSheet = mannequin.paymentSheet, !textFieldFocused {
PaymentSheet.PaymentButton(
paymentSheet: paymentSheet,
onCompletion: mannequin.onPaymentCompletion
) {
payButton
}
}
}
.alert(mannequin.alertText, isPresented: $mannequin.showingAlert) {
Button("OK", position: .cancel) { }
}
.onChange(of: textFieldFocused) {
if !textFieldFocused {
DispatchQueue.world(qos: .background).sync {
mannequin.updatePaymentSheet()
}
}
}
.onAppear {
mannequin.preparePaymentSheet()
}
.padding(.horizontal)
.padding(.high, 50)
.padding(.backside)
.toolbar {
ToolbarItem(placement: .keyboard) {
Button("Carried out") {
textFieldFocused = false
}
}
}
}
@ViewBuilder
var payButton: some View {
HStack {
Spacer()
Textual content("Pay $(enteredNumberFormatted, specifier: "%.2f")")
Spacer()
}
.padding()
.foregroundColor(.white)
.background(
RoundedRectangle(cornerRadius: 10, type: .steady)
.fill(.indigo)
)
}
}
And the code for StripePaymentHandler which communicates with backend
import StripePaymentSheet
import SwiftUI
class StripePaymentHandler: ObservableObject {
@Printed var paymentSheet: PaymentSheet?
@Printed var showingAlert: Bool = false
non-public let backendtUrl = URL(string: "http://18.XXX.XX.XX:3000")!
non-public var configuration = PaymentSheet.Configuration()
non-public var clientSecret = ""
non-public var paymentIntentID: String = ""
var alertText: String = ""
var paymentAmount: Int = 0
func preparePaymentSheet() {
// MARK: Fetch the PaymentIntent and Buyer data from the backend
let url = backendtUrl.appendingPathComponent("prepare-payment-sheet")
var request = URLRequest(url: url)
request.httpMethod = "POST"
let job = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (knowledge, response, error) in
guard let knowledge = knowledge,
let json = strive? JSONSerialization.jsonObject(with: knowledge, choices: []) 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 {
// Deal with error
return
}
self.clientSecret = clientSecret
self.paymentIntentID = paymentIntentID
STPAPIClient.shared.publishableKey = publishableKey
// MARK: Create a PaymentSheet occasion
configuration.merchantDisplayName = "Instance, Inc."
configuration.buyer = .init(id: customerId, ephemeralKeySecret: customerEphemeralKeySecret)
configuration.allowsDelayedPaymentMethods = true
configuration.applePay = .init(
merchantId: "service provider.com.your_app_name",
merchantCountryCode: "US"
)
configuration.returnURL = "your-app://stripe-redirect"
})
job.resume()
}
func updatePaymentSheet() {
DispatchQueue.principal.async {
self.paymentSheet = nil
}
let bodyProperties: [String: Any] = [
"paymentIntentID": paymentIntentID,
"amount": paymentAmount
]
let url = backendtUrl.appendingPathComponent("update-payment-sheet")
var request = URLRequest(url: url)
request.setValue("utility/json", forHTTPHeaderField: "Content material-Kind")
request.httpBody = strive? JSONSerialization.knowledge(withJSONObject: bodyProperties)
request.httpMethod = "POST"
let job = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (knowledge, response, error) in
guard let self = self else {
// Deal with error
return
}
DispatchQueue.principal.async {
self.paymentSheet = PaymentSheet(paymentIntentClientSecret: self.clientSecret, configuration: self.configuration)
}
})
job.resume()
}
func onPaymentCompletion(consequence: PaymentSheetResult) {
swap consequence {
case .accomplished:
self.alertText = "Cost full!"
case .canceled:
self.alertText = "Cost canceled!"
case .failed(let error):
self.alertText = "Cost failed (error.localizedDescription)"
}
showingAlert = true
}
}
And right here is the specific.js script working on the backend server
const stripe = require('stripe')('sk_test_XXXX');
const specific = require('specific');
const app = specific();
app.use(specific.json());
app.submit('/prepare-payment-sheet', async (req, res) => {
const buyer = await stripe.clients.create();
const ephemeralKey = await stripe.ephemeralKeys.create({buyer: buyer.id},
{apiVersion: '2024-04-10'});
const paymentIntent = await stripe.paymentIntents.create({
quantity: 1099,
foreign money: 'usd',
buyer: buyer.id,
automatic_payment_methods: {
enabled: true,
},
});
res.json({
paymentIntentID: paymentIntent.id,
clientSecret: paymentIntent.client_secret,
ephemeralKey: ephemeralKey.secret,
buyer: buyer.id,
publishableKey: 'pk_test_XXXXX'
});
});
app.submit('/update-payment-sheet', async (req, res) => {
const paymentIntent = await stripe.paymentIntents.replace(
req.physique.paymentIntentID,
{
quantity: req.physique.quantity,
}
);
console.log(req.physique)
console.log(res.physique)
res.json({});
});
app.hear(3000, () => console.log('Working on port 3000'));