ios – Downside speaking with backend server with Stripe Funds on SwiftUI App

0
28
ios – Downside speaking with backend server with Stripe Funds on SwiftUI App


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
enter image description here

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'));

LEAVE A REPLY

Please enter your comment!
Please enter your name here