0

I am using Stripe API to accept card payment in SwiftUI.

I managed to make successful payment using STPPaymentCardTextField wrapped in UIViewRepresentable and confirm it using the provided sheet modifier .paymentConfirmationSheet(....).

The problem appeared when I adopted the new NavigationStack in iOS 16. It appears that the .paymentConfirmationSheet(....) doesn't work properly if it is presented inside the new NavigationStack.

Is there any other way I can confirm card payment in SwiftUI? How can I fix this?

If I switch back to NavigationView it works as expected but I would want to use the new features of NavigationStack.

My demo checkout view and its viewmodel

struct TestCheckout: View {
    
    @ObservedObject var model = TestCheckoutViewModel()
    
    var body: some View {
        
        VStack {
            CardField(paymentMethodParams: $model.paymentMethodParams)
            
            Button("Pay") {
                model.pay()
            }
            .buttonStyle(LargeButtonStyle())
            .paymentConfirmationSheet(isConfirmingPayment: $model.confirmPayment,
                                      paymentIntentParams: model.paymentIntentParams,
                                      onCompletion: model.onPaymentComplete)
        }
    }
}

class TestCheckoutViewModel: ObservableObject {
    @Published var paymentMethodParams: STPPaymentMethodParams?
    @Published var confirmPayment = false
    @Published var paymentIntentParams = STPPaymentIntentParams(clientSecret: "")
    
    func pay() {
        
        Task {
            
            do {
                // Create dummy payment intent
                let paymentIntent = try await StripeManager.shared.getPaymentIntent(orderId: "", amount: 1000)
                
                // Collect card details
                let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntent.secret)
                paymentIntentParams.paymentMethodParams = paymentMethodParams
                
                // Submit the payment
                DispatchQueue.main.async {
                    self.paymentIntentParams = paymentIntentParams
                    self.confirmPayment = true
                }
            } catch {
                print(error)
            }
        }
    }

    func onPaymentComplete(status: STPPaymentHandlerActionStatus, paymentIntent: STPPaymentIntent?, error: Error?) {
        print("Payment completed: \(error)")
    }
}

Now this doesn't work

struct TestView: View {
    var body: some View {
        NavigationStack {
            NavigationLink("Checkout", value: "checkout")
                .navigationDestination(for: String.self) { string in
                    if string == "checkout" {
                        TestCheckout()
                    }
                }
        }
    }
}

But this does

struct TestView: View {
    var body: some View {
        TestCheckout()
    }
}

The error I get is:

Error Domain=com.stripe.lib Code=50 "There was an unexpected error -- try again in a few seconds" UserInfo={NSLocalizedDescription=There was an unexpected error -- try again in a few seconds, com.stripe.lib:StripeErrorTypeKey=invalid_request_error, com.stripe.lib:StripeErrorCodeKey=payment_intent_unexpected_state, com.stripe.lib:ErrorMessageKey=Nemůžete potvrdit tento Platební záměr, protože v něm chybí platební metoda. Můžete buď aktualizovat Platební záměr s platební metodou a pak jej znovu potvrdit, nebo jej znovu potvrďte přímo s platební metodou.}
  • 1
    Consider adding how exactly `.paymentConfirmationSheet` were used in both cases. – Ranoiaetep Jan 10 '23 at 17:54
  • +1 to the previous comment. Examples of the code that works when using `NavigationView` but does not work when using `NavigationStack` will be very helpful in answering your question. – RyanM Jan 10 '23 at 17:59

1 Answers1

0

Finally I found the problem.

The problem is the @ObservedObject var model = TestCheckoutViewModel(). For some reason it doesn't work with @ObservedObject anymore and it has to be @StateObject.