stripe / stripe-ios

Stripe iOS SDK
https://stripe.com
MIT License
2.09k stars 976 forks source link

Swift UI Integration #1204

Closed djshumpert closed 3 years ago

djshumpert commented 5 years ago

Hi, I recently started a Swift UI project and was trying to integrate creating customer tokens within the app. Has anyone tried integrating Stripe with Swift UI? I know it is still in beta ๐Ÿ™ˆ

yuki-stripe commented 5 years ago

Hey @djshumpert, we have not - interested to hear what you find!

patchthecode commented 5 years ago

@djshumpert did you make any progress? I am trying the same thing

amuresia commented 5 years ago

Hi all, I've given it a go in the last couple of days but now with the SCA in place, I get stuck when trying to support 3D Secure Authentication on iOS

I can't seem to be able to implement the STPAuthenticationContext protocol as my application doesn't use any UIViewControllers. Is anybody aware of any workarounds?

@yuki-stripe could we please re-open this issue as no solution was provided.

Gujci commented 5 years ago

@amuresia I have just ran into the same issue. It's sad, that STPAuthenticationContext needs to be an NSObject. Sadly, I have to go back to the old ViewController mess, and maybe wrap it into a UIViewControllerRepresentable. Right now I see no other option to provide the context.

davidme-stripe commented 5 years ago

Thanks for the feedback! We'll see if we can make this less messy.

davidme-stripe commented 5 years ago

EDIT Feb 18 2021: This comment originally contained a hacky SwiftUI code sample, but it's not what we'd recommend anymore. If you'd like SwiftUI support, please try the beta of our new payments UI.

amuresia commented 5 years ago

Hey @davidme-stripe! Thanks for the solution - it's definitely a step forward at least for now. Could you please also add a snippet of the way you implement and share your MyAPIClient.sharedClient? What type does it have because I am trying to follow step 2 from this tutorial https://stripe.com/docs/mobile/ios/standard#ephemeral-key but I'm a bit confused because it suggests I should cast my JSON response to [String: AnyObject] however it always fails to cast the response.

I tried the following approaches:

# Compiler error: Cast from 'String' to unrelated type '[String : AnyObject]' always fails
                let result = task.result!
                let responseString = String(data: result.responseData!, encoding: .utf8)
                completion(responseString! as? [String: AnyObject], nil)
# Compiler error: Cast from 'Data?' to unrelated type '[String : AnyObject]' always fails
                let result = task.result!
                completion(task.result!.responseData as? [String: AnyObject], nil)

And in both cases the View complains:

Argument type '[AnyHashable : Any]?' does not conform to expected type 'STPCustomerEphemeralKeyProvider'

unless I cast it again ...

let customerContext = STPCustomerContext(keyProvider: key as! STPCustomerEphemeralKeyProvider )
davidme-stripe commented 5 years ago

Try looking at MyAPIClient.swift in this repository, which was built to communicate with our example-ios-backend. Your API Client should conform to STPCustomerEphemeralKeyProvider, which involves implementing createCustomerKey(apiVersion:, completion:). If you then pass in your API Client (MyAPIClient.sharedClient() in our example) into STPCustomerContext as the keyProvider, we'll call createCustomerKey() on it when we need to create the key.

amuresia commented 5 years ago

Thank you very much kind sir! I've now got a working PoC which I'll continue to build on top of. I'll make sure to update this issue if some ๐Ÿ› crops up!

Gujci commented 5 years ago

@dackerman-stripe excuse me for falsely using the word "mess", Monday was quite exhausting for me.

The problem in may case was the following:

One solution I can imagine is having a UIHostingController subclass / make UIHostingController conform to STPAuthenticationContext, which may wrap my SwiftUI view. Then, this UIHostingController can be further wrapped into a UIViewControllerRepresentable to make it usable from SwiftUI. This wrapper would take the view model in as a parameter, add the UIHostingController reference to the view model (which is the main low in this idea) and pass the viewModel further to my custom SwiftUI view.

Your solution is somewhat similar at the point, where we need some reference to a UIViewController. In your case it's through a singleton.

To achieve a cleaner solution, right now I can only imagine having to modify the requirements of STPAuthenticationContext, so a non UIKit type can adopt it. It might flip all the internals of the framework, so, I don't think this solution could work. Of course it's your framework, so I'm in no position to make assumptions.

Gujci commented 4 years ago

Ended up subclassing UIHostingController, since generic extensions are not quite good with objc protocols.

class CardRegisterHostingController: UIHostingController<MySwiftUIView>, STPAuthenticationContext { ... }
amuresia commented 4 years ago

HI @davidme-stripe,

Not sure if it's related to the integration with SwiftUI but when I call paymentContext.presentShippingViewController() as suggested in step 5 of the docs, the view that is pushed on top doesn't have any input fields for the shipping address and looks like this

image

If I press 'Next' then things look better and I get the logic included in

paymentContext(_ paymentContext: STPPaymentContext, didUpdateShippingAddress address: STPAddress, completion: @escaping STPShippingMethodsCompletionBlock) {
image

Would you be so kind to tell me if I am missing some configuration or if there's a bug/ limitation that I stumbled across?

Thanks, Andrei

lukeharkin1993 commented 4 years ago

HI @davidme-stripe,

Not sure if it's related to the integration with SwiftUI but when I call paymentContext.presentShippingViewController() as suggested in step 5 of the docs, the view that is pushed on top doesn't have any input fields for the shipping address and looks like this

image

If I press 'Next' then things look better and I get the logic included in

paymentContext(_ paymentContext: STPPaymentContext, didUpdateShippingAddress address: STPAddress, completion: @escaping STPShippingMethodsCompletionBlock) {
image

Would you be so kind to tell me if I am missing some configuration or if there's a bug/ limitation that I stumbled across?

Thanks, Andrei

Hey Andrei,

Did you ever solve this? I am having the same issue too.

Thanks, Luke

amuresia commented 4 years ago

Hey @lukeharkin1993, I am afraid not ... I had to implement my own shipping logic/ views :(

lukeharkin1993 commented 4 years ago

Hey @lukeharkin1993, I am afraid not ... I had to implement my own shipping logic/ views :(

Ah no worries @amuresia, there is a - containsRequiredShippingAddressFields, but it doesn't seem to make a difference :/ thanks anyway. I think I will follow the same route you did.

davidme-stripe commented 4 years ago

Hi @amuresia and @lukeharkin1993, very sorry that I missed this!

It looks like you may not have set requiredShippingAddressFields in your STPPaymentConfiguration, so the STPShippingViewController has nothing to collect. Try something like this: paymentContext.configuration.requiredShippingAddressFields = [.name, .postalAddress, .phoneNumber, .emailAddress]. We'll look into making this more obvious in a future update.

If you have any problems in the future, please open a new GitHub issue or reach out to https://support.stripe.com โ€” it'll help us track your request and get you a quicker response!

lukeharkin1993 commented 4 years ago

Hi @davidme-stripe , I have implemented what you suggested. It works great thanks. Unfortunately when I call presentShippingViewController() after adding a card by calling presentPaymentOptionsViewController(), didCreatePaymentResult() never gets called. If I bypass presentShippingViewController() and just call requestPayment() straight after adding a card then the payment goes through fine. Have you any idea what might be happening? I can't find anything in the log and paymentContext doesn't seem to be nil. Thanks. I know above you said to open a new Github issue, but as it is related to the above question I have posted here.

davidme-stripe commented 4 years ago

Hey @lukeharkin1993, just to clarify: Are you calling presentShippingViewController(), then requestPayment() once the user has entered their shipping information, and not seeing didCreatePaymentResult() get called? It's expected that if you're not calling requestPayment(), it will never create the payment result.

If you're just trying to show the shipping view controller as part of your payment flow, it should work to just set requiredShippingAddressFields then call requestPayment() โ€” STPPaymentContext will display the shipping view controller if it needs to.

lukeharkin1993 commented 4 years ago

So initially what I was doing was calling presentPaymentOptionsViewController() then setting requiredShippingAddressFields and then calling-> presentShippingViewController() then -> requestPayment().

What I have gathered from your comment is I should be setting requiredShippingAddressFields then calling->presentPaymentOptionsViewController() then-> requestPayment() which then automatically presents the shipping view controller. Once I enter the details and hit next...it doesn't seem to reach didCreatePaymentResult

davidme-stripe commented 4 years ago

Ah, both of those should work correctly and reach didCreatePaymentResult. This sounds like a different bug, I'm going to move it out to a new issue so we can track it better. #1511

btian commented 4 years ago

What's the right way to present STPPaymentOptionsViewController in SwiftUI?

I'm doing something like

let keyWindow = UIApplication.shared.connectedScenes
            .filter({$0.activationState == .foregroundActive})
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?.windows
            .filter({$0.isKeyWindow}).first
        self.paymentContext.hostViewController = keyWindow?.rootViewController

Button(action: {
                self.paymentContext.presentPaymentOptionsViewController()
            }) {
                Text(self.paymentContextDelegate.paymentMethodButtonTitle)

But selecting a card or clicking the cancel button don't dismiss the view.

amuresia commented 4 years ago

Hey everyone! It's been a while since this issue was first brought up. I was able to successfully integrate stripe last year, back when I was using the early beta version of SwiftUI. Let's just say that the app didn't make the cut.

I started working on a new app in my free time and I am using the latest tool available, I think Apple refers to it as SwiftUI version 2 ... the only relevant change for the stripe sdk is that the app no longer uses an AppDelegate and SceneDelegate.

I have been fighting with it for 2 days now and I am still blocked. I raised a ticket with the support team and I am waiting to hear back from them via email.

Essentially, I can allow the user to select a payment method by calling the method presentPaymentOptionsViewController(). So far so good. I then proceed with my workflow and call requestPayment() and this is when the app crashes complaining that the customer context is nil.

After debugging for hours in a row I noticed that when the screen from presentPaymentOptionsViewController() is dismissed, all of the screens are reinitialised I am using the same approach as @btian to get to the root view controller

let keyWindow = UIApplication.shared.connectedScenes
            .filter({$0.activationState == .foregroundActive})
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?.windows
            .filter({$0.isKeyWindow}).first
        self.paymentContext.hostViewController = keyWindow?.rootViewController

I tried with .filter({$0.isKeyWindow}).last which doesn't crash the app but I am unable to type anything in the form.

What is interesting is that if I open the app again (re-build in XCode for example) and call requestPayment() without previously calling presentPaymentOptionsViewController() then I see a fulfilled payment intent in the dashboard.

Bottom line is that I can call presentPaymentOptionsViewController or requestPayment but not sequentially like I am supposed to (as indicated by the docs as well) because when one screen is dismissed, everything in the app reloads, causing the delegate and the customer context to be reinitialised.

I tried everything that I could think of to get over this and bypass the reinitialisation that occurs but I can't because there is no AppDelegate or SceneDelegate in SwiftUI 2.

Any help would be appreciated. Thank you!

mrkvans commented 4 years ago

I'm surprised Stripe has ignored this issue for over a year now. The solution for iOS13 should be the same for iOS14 - so where is the iOS13 support for SwiftUI? Where are Stripe's plans/roadmap to support Apple's multi-platform (Apple platforms) App development framework? With all the time and resources that Stripe Puts into its documentation, the massive gap in support here is baffling to me.

bat111 commented 4 years ago

Hey everyone! It's been a while since this issue was first brought up. I was able to successfully integrate stripe last year, back when I was using the early beta version of SwiftUI. Let's just say that the app didn't make the cut.

I started working on a new app in my free time and I am using the latest tool available, I think Apple refers to it as SwiftUI version 2 ... the only relevant change for the stripe sdk is that the app no longer uses an AppDelegate and SceneDelegate.

I have been fighting with it for 2 days now and I am still blocked. I raised a ticket with the support team and I am waiting to hear back from them via email.

Essentially, I can allow the user to select a payment method by calling the method presentPaymentOptionsViewController(). So far so good. I then proceed with my workflow and call requestPayment() and this is when the app crashes complaining that the customer context is nil.

After debugging for hours in a row I noticed that when the screen from presentPaymentOptionsViewController() is dismissed, all of the screens are reinitialised I am using the same approach as @btian to get to the root view controller

let keyWindow = UIApplication.shared.connectedScenes
            .filter({$0.activationState == .foregroundActive})
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?.windows
            .filter({$0.isKeyWindow}).first
        self.paymentContext.hostViewController = keyWindow?.rootViewController

I tried with .filter({$0.isKeyWindow}).last which doesn't crash the app but I am unable to type anything in the form.

What is interesting is that if I open the app again (re-build in XCode for example) and call requestPayment() without previously calling presentPaymentOptionsViewController() then I see a fulfilled payment intent in the dashboard.

Bottom line is that I can call presentPaymentOptionsViewController or requestPayment but not sequentially like I am supposed to (as indicated by the docs as well) because when one screen is dismissed, everything in the app reloads, causing the delegate and the customer context to be reinitialised.

I tried everything that I could think of to get over this and bypass the reinitialisation that occurs but I can't because there is no AppDelegate or SceneDelegate in SwiftUI 2.

Any help would be appreciated. Thank you!

hi amuresia

i am working to build my app on swiftui 1.0. i am glad you figured out to manage integrate Stripe into SwiftUi.

Hey everyone! It's been a while since this issue was first brought up. I was able to successfully integrate stripe last year, back when I was using the early beta version of SwiftUI. Let's just say that the app didn't make the cut.

I started working on a new app in my free time and I am using the latest tool available, I think Apple refers to it as SwiftUI version 2 ... the only relevant change for the stripe sdk is that the app no longer uses an AppDelegate and SceneDelegate.

I have been fighting with it for 2 days now and I am still blocked. I raised a ticket with the support team and I am waiting to hear back from them via email.

Essentially, I can allow the user to select a payment method by calling the method presentPaymentOptionsViewController(). So far so good. I then proceed with my workflow and call requestPayment() and this is when the app crashes complaining that the customer context is nil.

After debugging for hours in a row I noticed that when the screen from presentPaymentOptionsViewController() is dismissed, all of the screens are reinitialised I am using the same approach as @btian to get to the root view controller

let keyWindow = UIApplication.shared.connectedScenes
            .filter({$0.activationState == .foregroundActive})
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?.windows
            .filter({$0.isKeyWindow}).first
        self.paymentContext.hostViewController = keyWindow?.rootViewController

I tried with .filter({$0.isKeyWindow}).last which doesn't crash the app but I am unable to type anything in the form.

What is interesting is that if I open the app again (re-build in XCode for example) and call requestPayment() without previously calling presentPaymentOptionsViewController() then I see a fulfilled payment intent in the dashboard.

Bottom line is that I can call presentPaymentOptionsViewController or requestPayment but not sequentially like I am supposed to (as indicated by the docs as well) because when one screen is dismissed, everything in the app reloads, causing the delegate and the customer context to be reinitialised.

I tried everything that I could think of to get over this and bypass the reinitialisation that occurs but I can't because there is no AppDelegate or SceneDelegate in SwiftUI 2.

Any help would be appreciated. Thank you!

Can you put up your ready version on github helping other developer to look at it.

Bat

mrkvans commented 4 years ago

@david-stripe Has your team looked at this recently? It would be nice to have native SwiftUI support, especially since all the Stipe UIViewControllers are called and managed by the Stripe Frameworks.

At the very least, could you give us some samples of UIViewControllerRepresentable code that could be exposed in SwiftUI views?

Or, extra credit if you provide sample code for 100% ApplePay UI using Stripe processing.

daihovey commented 4 years ago

I ended up making a fake SwiftUi StripeView and setting the frame to zero and then using the stripe calls from inside the FakeStripeViewController. Need to play around with it bit more, but seems to work; and 3DS modal works too.

import SwiftUI
import Stripe
import UIKit

struct StripeBuyView: View {
    @EnvironmentObject var viewModel: ViewModel
    @State var isPressed = false
    var body: some View {
        VStack {
            Button(action: {
                isPressed = !isPressed
            }, label: {
                Text("Buy")
            })

            FakeStripeView(isPressed: $isPressed)
                .frame(width: 0, height: 0)
        }
    }
}

struct FakeStripeView: UIViewControllerRepresentable {
    @EnvironmentObject var viewModel: ViewModel
    @Binding var isPressed: Bool

    public typealias UIViewControllerType = FakeStripeViewController

    func makeUIViewController(context: UIViewControllerRepresentableContext<FakeStripeView>) -> FakeStripeViewController {
        let viewController = FakeStripeViewController(viewModel: viewModel)
        return viewController
    }

    func updateUIViewController(_ uiViewController: FakeStripeViewController, context _: UIViewControllerRepresentableContext<FakeStripeView>) {
        if isPressed {
            uiViewController.sendBuyOrder()
        }
    }
}

class FakeStripeViewController: UIViewController {

    var viewModel: ViewModel?

    convenience init() {
        self.init(viewModel: nil)
    }

    init(viewModel: ViewModel?) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    func sendBuyOrder() {
        // Perform Stripe calls with ViewModel data
    }
}

extension FakeStripeViewController: STPAuthenticationContext {
    func authenticationPresentingViewController() -> UIViewController {
        self
    }
}
madhavSoni commented 4 years ago
struct ContentView: View {
    @State var cardParams: STPPaymentMethodCardParams = STPPaymentMethodCardParams()

    var body: some View{
        VStack {
//            StripeCardTextField(cardParams: self.$cardParams).frame(width: 325, height: 70, alignment: .center).padding()
            Button(action: {

                self.checkoutViewController.pay(secret: self.paymentIntentClientSecret, cardParams: self.cardParams) { (status, paymentIntent, error) in
                    switch (status) {
                        case .failed:

                            break
                        case .canceled:

                            break
                        case .succeeded:

                            break
                        @unknown default:
                            fatalError()
                            break
                    }
                }
            }) {
                PayWithCardButton()
            }
    }

}

struct StripeCardTextField: UIViewRepresentable {
    @Binding var cardParams: STPPaymentMethodCardParams

    func makeUIView(context: Context) -> STPPaymentCardTextField {
        let cardField = STPPaymentCardTextField()
        return cardField
    }

    func updateUIView(_ uiView: STPPaymentCardTextField, context: Context) {
        DispatchQueue.main.async {
            self.cardParams = uiView.cardParams
        }
    }
}

I had something like this going and it was functional. The problem is that it caused that particular view, which is a modal view, to become incredibly buggy. Looking for alternative solutions.

Edit: Not included but in the onAppear you make a call to your backend for the client secret

Second Update: A quick google search yielded this, it works without theUI bugs. Likely as a result of using the Coordinator class. https://gist.github.com/Gujci/71eba08ec067e904f6e4b2a00f2af43d

macshodan commented 3 years ago

Hey everyone! It's been a while since this issue was first brought up. I was able to successfully integrate stripe last year, back when I was using the early beta version of SwiftUI. Let's just say that the app didn't make the cut. I started working on a new app in my free time and I am using the latest tool available, I think Apple refers to it as SwiftUI version 2 ... the only relevant change for the stripe sdk is that the app no longer uses an AppDelegate and SceneDelegate. I have been fighting with it for 2 days now and I am still blocked. I raised a ticket with the support team and I am waiting to hear back from them via email. Essentially, I can allow the user to select a payment method by calling the method presentPaymentOptionsViewController(). So far so good. I then proceed with my workflow and call requestPayment() and this is when the app crashes complaining that the customer context is nil. After debugging for hours in a row I noticed that when the screen from presentPaymentOptionsViewController() is dismissed, all of the screens are reinitialised I am using the same approach as @btian to get to the root view controller

let keyWindow = UIApplication.shared.connectedScenes
            .filter({$0.activationState == .foregroundActive})
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?.windows
            .filter({$0.isKeyWindow}).first
        self.paymentContext.hostViewController = keyWindow?.rootViewController

I tried with .filter({$0.isKeyWindow}).last which doesn't crash the app but I am unable to type anything in the form. What is interesting is that if I open the app again (re-build in XCode for example) and call requestPayment() without previously calling presentPaymentOptionsViewController() then I see a fulfilled payment intent in the dashboard. Bottom line is that I can call presentPaymentOptionsViewController or requestPayment but not sequentially like I am supposed to (as indicated by the docs as well) because when one screen is dismissed, everything in the app reloads, causing the delegate and the customer context to be reinitialised. I tried everything that I could think of to get over this and bypass the reinitialisation that occurs but I can't because there is no AppDelegate or SceneDelegate in SwiftUI 2. Any help would be appreciated. Thank you!

hi amuresia

i am working to build my app on swiftui 1.0. i am glad you figured out to manage integrate Stripe into SwiftUi.

Hey everyone! It's been a while since this issue was first brought up. I was able to successfully integrate stripe last year, back when I was using the early beta version of SwiftUI. Let's just say that the app didn't make the cut. I started working on a new app in my free time and I am using the latest tool available, I think Apple refers to it as SwiftUI version 2 ... the only relevant change for the stripe sdk is that the app no longer uses an AppDelegate and SceneDelegate. I have been fighting with it for 2 days now and I am still blocked. I raised a ticket with the support team and I am waiting to hear back from them via email. Essentially, I can allow the user to select a payment method by calling the method presentPaymentOptionsViewController(). So far so good. I then proceed with my workflow and call requestPayment() and this is when the app crashes complaining that the customer context is nil. After debugging for hours in a row I noticed that when the screen from presentPaymentOptionsViewController() is dismissed, all of the screens are reinitialised I am using the same approach as @btian to get to the root view controller

let keyWindow = UIApplication.shared.connectedScenes
            .filter({$0.activationState == .foregroundActive})
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?.windows
            .filter({$0.isKeyWindow}).first
        self.paymentContext.hostViewController = keyWindow?.rootViewController

I tried with .filter({$0.isKeyWindow}).last which doesn't crash the app but I am unable to type anything in the form. What is interesting is that if I open the app again (re-build in XCode for example) and call requestPayment() without previously calling presentPaymentOptionsViewController() then I see a fulfilled payment intent in the dashboard. Bottom line is that I can call presentPaymentOptionsViewController or requestPayment but not sequentially like I am supposed to (as indicated by the docs as well) because when one screen is dismissed, everything in the app reloads, causing the delegate and the customer context to be reinitialised. I tried everything that I could think of to get over this and bypass the reinitialisation that occurs but I can't because there is no AppDelegate or SceneDelegate in SwiftUI 2. Any help would be appreciated. Thank you!

Can you put up your ready version on github helping other developer to look at it.

Bat

Hi Bat,

I assuming you're calling presentPaymentOptionsViewController() from the paymentContext object, right? I've initialized mine in the View properties by setting it to the shared instance, so even if the view is re-initialized it will be consistent. Maybe that the issue you're having.

Here some snippets of my View:

struct PlaceOrderView: View { @ObservedObject var cartModel: CartModel let config = STPPaymentConfiguration.shared() .....

then later in the struct I have this method implemented:

private func paymentContextConfiguration() {
        self.config.appleMerchantIdentifier = "your.merchant.id"
        self.config.shippingType = .delivery
        self.config.requiredBillingAddressFields = .full

        self.config.companyName = "yourCompanyName"

        self.paymentContext = STPPaymentContext(customerContext: customerContext, configuration: self.config, theme: .default())
        self.paymentContext.paymentAmount = Int(self.cartModel.cart!.totalToPay*100)
        self.paymentContext.delegate = self.cartModel

        let keyWindow = UIApplication.shared.connectedScenes
            .filter({$0.activationState == .foregroundActive})
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?.windows
            .filter({$0.isKeyWindow}).first
        self.paymentContext.hostViewController = keyWindow?.rootViewController

        self.paymentContext.paymentCountry = "IT"
        self.paymentContext.paymentCurrency = "EUR"
    }

I have it tested and working under SwiftUI 2.0 with Xcode 12 against iOS 13 and iOS 14. The only downside is that I cannot use if from a modal view (eg. presenting from a .sheet) because it complains that it cannot present the PaymentOptionsViewController from another modal view.

mrkvans commented 3 years ago

Recently got Stripe working with SwiftUI for iOS 14... I opted to do Apple Pay UI exclusively, without any StripeUI Views.

Here's a presentation I gave about it. https://youtu.be/74JgD6HKg2o

I'd love to see official documentation from Stripe about this exact thing. SwiftUI is very wide-spread - iOS Devs are all integrating it... a simple Property Wrapper would make Stripe Adoption almost instantaneous!

Fingers crossed ๐Ÿคž for good things to come!!

davidme-stripe commented 3 years ago

Hey all! We are actively watching this thread, and I understand everyone's frustration with the current state of our SwiftUI support. I have some news on the Swift front:

One of the biggest blockers of SwiftUI support is the difficulty of shipping Swift in our SDK: The Objective-C/Swift bridge has some surprising quirks, and mixed-language frameworks aren't well-supported by Xcode or our various package managers. To fix this, we're converting the entire SDK to Swift. Here's a preview.

We're hoping this will give us a foundation on which to build Swift-first APIs and a great SwiftUI experience. Please try it out and let us know what you think!

james-william-r commented 3 years ago

@davidme-stripe - Just wanted the chime in here and say that we're also very much looking for a SwiftUI implementation of Stripe, it'll be a vital part of our app. I believe the release of SwiftUI 2 has brought a lot of first time app builders to iOS development, and thus can only see the demand for this growing! ๐Ÿ˜Š Looking forward to this! Keep up the great work!

amuresia commented 3 years ago

I assuming you're calling presentPaymentOptionsViewController() from the paymentContext object, right? I've initialized mine in the View properties by setting it to the shared instance, so even if the view is re-initialized it will be consistent. Maybe that the issue you're having.

Thank you for chipping in @macshodan. I tried out this approach as well but I am still seeing the app crash the second time the modal to add/ edit a card is presented with

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'hostViewController must not be nil on STPPaymentContext when calling pushPaymentOptionsViewController on it. Next time, set the hostViewController property first!'

In my view, I tried to have the following

let paymentConfiguration = STPPaymentConfiguration.shared()
@State var paymentContext: STPPaymentContext?

Then further down I called

    func paymentContextConfiguration() {
       ...
        let customerContext = STPCustomerContext(keyProvider: StripeApi())

        paymentContext = STPPaymentContext(customerContext: customerContext, configuration: paymentConfiguration, theme: .default())
        paymentContext!.delegate = PaymentContextDelegate()

        let keyWindow = UIApplication.shared.connectedScenes
            .filter({$0.activationState == .foregroundActive})
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?.windows
            .filter({$0.isKeyWindow}).first

        paymentContext!.hostViewController = keyWindow?.rootViewController
    }

Please let me know if I misunderstood your suggestion, I'm really keen on getting this to work properly.

macshodan commented 3 years ago

Hi @amuresia, I believe the problem is that you're initializing the paymentContext @state object with nullable. In my code I did the following for the view properties:

let config = STPPaymentConfiguration.shared()
@State private var paymentContext: STPPaymentContext!
let customerContext = STPCustomerContext(keyProvider: NetworkAPI.shared)

where the NetworkAPI class implements the STPCustomerEphemeralKeyProvider delegate. Then in the paymentContextConfiguration I initialize my paymentContext with view customerContex. Apart from that I don't see any other differences in our code.

Hope this helps.

madhavSoni commented 3 years ago

For some reason, I'm having an issue making direct charges in Stripe connect with a Swift UI integration. I formerly used transfers and they worked successfully, but switching to direct charges seemed to cause failures in my integration.

I'm getting the following error whenever I try doing a direct charge.

The PaymentIntent requires a payment method.

Anyone run into this issue or know what's going on? The only difference in my code is in my backend.

I formerly used this code because it worked:

  intent = stripe.PaymentIntent.create(
      amount=amount,
      currency='usd',
      on_behalf_of=account_id,
      receipt_email=email,
      transfer_data={
          'destination': account_id,
      },
      application_fee_amount=app_fee
  )

However, now my integration requires me to use direct charges, so now it looks something more like this:

   intent = stripe.PaymentIntent.create(
        amount=amount,
        currency='usd',
         payment_method_types=['card'],
        receipt_email=email,
        stripe_account= account_id,
        application_fee_amount=app_fee
    )

Anyone have any idea why this change is no longer attaching the payment method? I saw in the Stripe docs that it is necessary to clone a payment method for connect:

https://stripe.com/docs/payments/payment-methods/connect#creating-paymentmethods-directly-on-the-connected-account

However, there isn't an explanation for how to do this in iOS, yet alone SwiftUI.

Any help would be appreciated as this is the last part of my integration!

A big thank you to everyone in this thread and the folx at Stripe!

EDIT: Attaching my iOS code too in case it is relevant:

class CheckoutViewController: UIViewController {

    @objc
    func pay(secret: String?, cardParams: STPPaymentMethodCardParams, completion: @escaping (STPPaymentHandler.ActionStatus, STPPaymentIntent?, Error?) -> Void ) {
        guard let paymentIntentClientSecret = secret else {
            return;
        }
        // Collect card details

        let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: nil, metadata: nil)
        let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntentClientSecret)
        paymentIntentParams.paymentMethodParams = paymentMethodParams

        // Submit the payment
        let paymentHandler = STPPaymentHandler.shared()
        paymentHandler.confirmPayment(withParams: paymentIntentParams, authenticationContext: self) { (status, paymentIntent, error) in
            completion(status, paymentIntent, error)
        }
    }
}

I mention above my approach to entering the info into the CheckOutViewController. Cheers

aliriaz-stripe commented 3 years ago

@madhavSoni hello!

We should take this to a different channel and keep this thread for SwiftUI specific questions/comments, I'd recommend writing in to Support at https://support.stripe.com/contact with your code snippets and we can help you out there!

Could you include in your message, how you're initializing stripe-ios in your mobile app? For Direct Charges, your client-app needs to be initialized with both your Platform publishable key, and the Stripe account ID of the Connect account you are creating the Direct Charge on.

Please include that code snippet showing how you're initializing Stripe in your iOS app, the above code snippet and any request IDs for the PaymentIntent creation and confirmation when you write in to Support and we can help investigate!

dqii commented 3 years ago

It looks like converting the SDK to Swift was completed. Has there been progress on supporting SwiftUI 2?

ShahinBorujeni commented 3 years ago

Any update on support for SwiftUI? Our app uses SwiftUI and we need to implement Stripe to finish it up! if anyone has any solutions I'd appreciate it if you share your workaround! this is vital for our business. Thanks

mrkvans commented 3 years ago

Hi @ShahinBorujeni I recently launched a 100% SwiftUI app that leveraged Stripe. I didn't use any Stripe UI, I used Apple Pay UI with the Stripe payment processing.

What type of payment UI are you trying to implement?

ShahinBorujeni commented 3 years ago

We wanted to use the prebuilt UI for adding credit cards and making payment. But since thereโ€™s no official support for prebuilt on SwiftUI I will watch your YouTube video to at least implement Apple Pay for now. Thank you for the response

fassko commented 3 years ago

@ShahinBorujeni I'm using Stripe UI with SwiftUI. I'm stitching together and what I do is to replace the main view controller with the Stripe UI one. Check this one https://github.com/nelglez/stripe-swiftui I used this as an example, maybe it helps.

davidme-stripe commented 3 years ago

Hi all! We plan to launch SwiftUI support as part of our new Mobile Payments UI, which is currently in beta.

For folks on this thread, I've added a previewย to the mc-beta-2 branch. We haven't updated the beta docs for SwiftUI yet, but we have a few SwiftUI examples to serve as a reference:

You can run the PaymentSheet Example app from Stripe.xcworkspace to try each flow.

Please also register for our beta program โ€” we'll use that mailing list to collect feedback and notify you of new beta releases. You can also share feedback with us by emailing mobile-payments-ui-beta@stripe.com.

james-william-r commented 3 years ago

@davidme-stripe This is brilliant news! Thanks for your dedication on this! ๐ŸŽ‰๐Ÿ‘ P.S. We just signed up!

davidme-stripe commented 3 years ago

Update: We've released beta 2 and added some initial SwiftUI docs.

davidme-stripe commented 3 years ago

In 21.3.0, we added a SwiftUI STPPaymentCardTextField.Representable and ViewModifiers for confirming a PaymentIntent or SetupIntent. An example is available in the IntegrationTester app.

We still recommend using the Mobile Payments UI beta for new apps, but these new wrappers could be useful if you're trying to convert an existing integration to native SwiftUI.

ilazakis commented 3 years ago

Is there an STPAddCardViewController equivalent in the Mobile Payments UI beta? Iโ€™m new to the SDK, but it looks like itโ€™s the only way if one only wants to add a Payment Method for later use?

davidme-stripe commented 3 years ago

@ilazakis, it sounds like you're looking for SetupIntent support. This isn't included yet, but we intend to add it before the new UI is out of beta.

ilazakis commented 3 years ago

Thank you @davidme-stripe!

Would working with STPPaymentCardTextField.Representable directly still keep us safely within PCI compliance or is one required to use STPAddCardViewController? Happy to keep using a navigation bar hack others have used recently with STPAddCardViewController to create payment methods until the newer version lands.

Phrased differently, is using STPPaymentCardTextField.Representable and apiClient.createPaymentMethod ok as far as PCI compliance is concerned? Or is use of STPAddCardViewController mandatory? Trying to avoid SetupIntent if possible.

davidme-stripe commented 3 years ago

The Mobile SDK section of our security guide is the best source for information on this. I would treat the STPPaymentCardTextField -> STPAPIClient.shared.createPaymentMethod() flow as equivalent to STPAddCardViewController, as long as you pass an STPPaymentMethodCardParams and avoid handling or introspecting the raw card information.

ilazakis commented 3 years ago

Thanks David!

That's exactly the setup I'm currently using as an alternative, following STPAddCardViewController's implementation really closely, more or less copying it as-is.

davidme-stripe commented 3 years ago

We've released 21.5.0 with a public beta of PaymentSheet, including support for PaymentIntents and SetupIntents. Between PaymentSheet, STPPaymentCardTextField, and .paymentConfirmationSheet, I think we've covered everyone's SwiftUI needs. We intend to provide native SwiftUI support or SwiftUI wrappers for all future UI surfaces.

Thanks to everyone in this thread for your feedback! If you have any other questions or suggestions, feel free to file a new issue.