M1X
M1X

Reputation: 5374

Stripe with SwiftUI

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) {
        
    }
}

Upvotes: 1

Views: 1579

Answers (1)

jnpdx
jnpdx

Reputation: 52565

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()

Upvotes: 2

Related Questions