1

I'm trying to implement Stripe in my SwiftUI app using the STPApplePayContextDelegate.

I have created a class that conforms to this delegate according to this documentation but no luck. I get this error: // Type of expression is ambiguous without more context in this line let applePayContext = STPApplePayContext(paymentRequest: paymentRequest, delegate: self)

What am I doing wrong here?

struct PaymentButtonController : UIViewControllerRepresentable {
    
    class Coordinator : NSObject, STPApplePayContextDelegate {
        var vc : UIViewController?
        
        @objc func buttonPressed() {
            let merchantIdentifier = "merchant.com.your_app_name"
            let paymentRequest = StripeAPI.paymentRequest(withMerchantIdentifier: merchantIdentifier, country: "US", currency: "USD")

            // Configure the line items on the payment request
            paymentRequest.paymentSummaryItems = [
                // The final line should represent your company;
                // it'll be prepended with the word "Pay" (i.e. "Pay iHats, Inc $50")
                PKPaymentSummaryItem(label: "iHats, Inc", amount: 50.00),
            ]
            
            // Initialize an STPApplePayContext instance
                if let applePayContext = STPApplePayContext(paymentRequest: paymentRequest, delegate: self) {
                    // Present Apple Pay payment sheet
                    if let vc = vc {
                        applePayContext.presentApplePay(on: vc)
                    }
                    
                } else {
                    // There is a problem with your Apple Pay configuration
                }
        }
        
        func applePayContext(_ context: STPApplePayContext, didCreatePaymentMethod paymentMethod: STPPaymentMethod, paymentInformation: PKPayment, completion: @escaping STPIntentClientSecretCompletionBlock) {
            let clientSecret = "..."
            print("ENDLICH")
            // Retrieve the PaymentIntent client secret from your backend (see Server-side step above)
            // Call the completion block with the client secret or an error
            completion(clientSecret, nil);
        }
        
        func applePayContext(_ context: STPApplePayContext, didCompleteWith status: STPPaymentStatus, error: Error?) {
            print("ENDLICH")
            switch status {
            case .success:
                // Payment succeeded, show a receipt view
                break
            case .error:
                // Payment failed, show the error
                break
            case .userCancellation:
                // User cancelled the payment
                break
            @unknown default:
                fatalError()
            }
        }
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }
    
    func makeUIViewController(context: Context) -> UIViewController {
        let button = PKPaymentButton(paymentButtonType: .plain, paymentButtonStyle: .automatic)
        button.addTarget(context.coordinator, action: #selector(context.coordinator.buttonPressed), for: .touchUpInside)
        let vc = UIViewController()
        context.coordinator.vc = vc
        vc.view.addSubview(button)
        return vc
    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        
    }
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209
M1X
  • 4,971
  • 10
  • 61
  • 123

1 Answers1

2

The function you're calling (presentApplePay) expects a UIViewController as its input, but you're passing self, which is your ApplePayContext, defined above as NSObject, ObservableObject, STPApplePayContextDelegate.

The challenge that you're going to face is getting a UIViewController context to pass to it, as you won't have any references to a UIViewController in pure SwiftUI.

You have a few possible solutions:

  1. In your SceneDelegate, pass a reference of your UIHostingController down to your views, then use it as the argument to presentApplePay
  2. Use UIViewControllerRepresentable to get a UIViewController you can embed into your SwiftUI view and past as the argument (https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable)
  3. Use a library like SwiftUI-Introspect to get the underlying UIViewController to your current SwiftUI views (https://github.com/siteline/SwiftUI-Introspect)

Update: In response your request for code, here's something to start with. Note that not everything is hooked up yet -- you'll need to connect the buttonPressed method, add layout constraints to the button, etc, but it gives you a way to figure out how to get a reference to a UIViewController

struct PaymentButtonController : UIViewControllerRepresentable {
    
    class Coordinator : NSObject {
        var vc : UIViewController?
        
        @objc func buttonPressed() {
            print("Button with VC: \(vc)")
        }
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }
    
    func makeUIViewController(context: Context) -> UIViewController {
        let button = PKPaymentButton()
        button.addTarget(context.coordinator, action: #selector(context.coordinator.buttonPressed), for: .touchUpInside)
        let vc = UIViewController()
        context.coordinator.vc = vc
        vc.view.addSubview(button)
        return vc
    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        
    }
}

Embed in your view by using this in your SwiftUI code:

PaymentButtonController()
jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • I choose option do and even though I feel like i'm almost at the end Im still not trying to figure it out completely. I did create a class that conforms to `UIViewControllerRepresentable` and Im trying to pass it as an argument but i'm getting: `Type of expression is ambiguous without more context`. I updated my answer.. @jn_pdx – M1X Jan 10 '21 at 19:29
  • 1
    I see what you did, but you need to actually embed the UIViewControllerRepresentable in your view hierarchy -- not just create it within your ObservableObject. It's hard to give you an example since you're not showing any of your actual view code (ie how you get to startPayment) but you can use something like this answer as an inspiration: https://stackoverflow.com/questions/62638416/how-to-properly-present-pkpaymentauthorizationviewcontroller-in-a-swiftui-only-p – jnpdx Jan 10 '21 at 19:39
  • I added in my answer how I show the Payment button. I did manage to show the screen shown in the link in the comment but the thing is that I can not use that since I do not get any success or error messages from Apple, only from Stripe – M1X Jan 10 '21 at 19:44
  • So you meant to put the button also in the UIViewControllerRepresentable? – M1X Jan 10 '21 at 19:45
  • 1
    That would be probably be the easiest, yes. – jnpdx Jan 10 '21 at 19:47
  • Can you please provide a code sample with the `UIViewControllerRepresentable` ? @jn_pdx – M1X Jan 10 '21 at 20:14
  • Its working fine. I made the Coordinator to conform to STPApplePayContextDelegate and I'm getting response from the Stripe callback methods. I think thats it right ? That was the last piece. (check my latest code in the question – M1X Jan 10 '21 at 20:56
  • 2
    Thanks for this, I like SwiftUI, but nobody has documentation on how to use their libraries with it, they all just assume you are using Storyboards, very frustrating! – JMK Jun 12 '21 at 19:13