iosswiftxcodeswiftuiapplepay

Unable to implement Apple Pay in SwiftUI


I'm trying to implement Apple Pay in my SwiftUI app and I'm stuck at showing the button.

I have done that by using UIViewRepresentable

import SwiftUI
import UIKit
import PassKit
import Foundation

struct ApplePayButton: UIViewRepresentable {
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    func updateUIView(_ uiView: PKPaymentButton, context: Context) {

    }
    
    func makeUIView(context: Context) -> PKPaymentButton {
        let paymentButton = PKPaymentButton(paymentButtonType: .plain, paymentButtonStyle: .black)
        return paymentButton
    }
    
    class Coordinator: NSObject, PKPaymentAuthorizationViewControllerDelegate  {
        func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
            //
        }

        func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) {
            print("did authorize payment")

        }

        func paymentAuthorizationViewControllerWillAuthorizePayment(_ controller: PKPaymentAuthorizationViewController) {
            print("Will authorize payment")
        }
    }
}



class ApplePayManager: NSObject {
    let currencyCode: String
    let countryCode: String
    let merchantID: String
    let paymentNetworks: [PKPaymentNetwork]
    let items: [PKPaymentSummaryItem]

    init(items: [PKPaymentSummaryItem],
           currencyCode: String = "EUR",
           countryCode: String = "AT",
           merchantID: String = "c.c.c",
           paymentNetworks: [PKPaymentNetwork] = [PKPaymentNetwork.masterCard, PKPaymentNetwork.visa]) {
        self.items = items
        self.currencyCode = currencyCode
        self.countryCode = countryCode
        self.merchantID = merchantID
        self.paymentNetworks = paymentNetworks
    }

    func paymentViewController() -> PKPaymentAuthorizationViewController? {
        if PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: paymentNetworks) {
            let request = PKPaymentRequest()
            request.currencyCode = self.currencyCode
            request.countryCode = self.countryCode
            request.supportedNetworks = paymentNetworks
            request.merchantIdentifier = self.merchantID
            request.paymentSummaryItems = items
            request.merchantCapabilities = [.capabilityCredit, .capabilityDebit]
            return PKPaymentAuthorizationViewController(paymentRequest: request)
        }
        return nil
    }
}

I do not want to use PKPaymentAuthorizationController because I want to use the native button.

When I click at the button I get this error:

[General] Payment request is invalid: Error Domain=PKPassKitErrorDomain Code=1 "Invalid in-app payment request" UserInfo={NSLocalizedDescription=Invalid in-app payment request, NSUnderlyingError=0x600003aeebb0 {Error Domain=PKPassKitErrorDomain Code=1 "PKPaymentRequest must contain an NSArray property 'paymentSummaryItems' of at least 1 valid objects of class PKPaymentSummaryItem" UserInfo={NSLocalizedDescription=PKPaymentRequest must contain an NSArray property 'paymentSummaryItems' of at least 1 valid objects of class PKPaymentSummaryItem}}}

View:

struct PaymentView: View {
    @Environment(\.presentationMode) private var presentationMode
    @ObservedObject var requestViewModel: RequestViewModel
    
    var applePayManager = ApplePayManager(items: [
        PKPaymentSummaryItem(label: "Some Product", amount: 9.99)
    ])
    
    var body: some View {
        NavigationView {
            VStack {
                Text("By paying you agree to give the package to transporter.")
                // requestViewModel.respondToRequest(status: button.status)
                
                ApplePayButton()
                    .frame(width: 228, height: 40, alignment: .center)
                    .onTapGesture {
                        applePayManager.paymentViewController()
                    }
                
            }
            .navigationBarTitle("Payment")
            .navigationBarItems(trailing: Button(action: {
                presentationMode.wrappedValue.dismiss()
            }) {
                Text("Done")
            })
        }
    }
}

What am I doing wrong here?


Solution

  • Just in case someone else is struggling like me: Here is the full code.

    import Foundation
    import PassKit
    
    class PaymentHandler: NSObject, ObservableObject {
        func startPayment(paymentSummaryItems: [PKPaymentSummaryItem]) {
            
            // Create our payment request
            let paymentRequest = PKPaymentRequest()
            paymentRequest.paymentSummaryItems = paymentSummaryItems
            paymentRequest.merchantIdentifier = "merchant.de.xxx"
            paymentRequest.merchantCapabilities = .capability3DS
            paymentRequest.countryCode = "AT"
            paymentRequest.currencyCode = "EUR"
            paymentRequest.requiredShippingContactFields = [.phoneNumber, .emailAddress]
            paymentRequest.supportedNetworks = [.masterCard, .visa]
            
            // Display our payment request
            let paymentController = PKPaymentAuthorizationController(paymentRequest: paymentRequest)
            paymentController.delegate = self
            paymentController.present(completion: { (presented: Bool) in })
        }
    }
    
    
    /**
     PKPaymentAuthorizationControllerDelegate conformance.
     */
    extension PaymentHandler: PKPaymentAuthorizationControllerDelegate {
    
        func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, completion: @escaping (PKPaymentAuthorizationStatus) -> Void) {
            completion(.success)
            print("paymentAuthorizationController completion(.success)")
        }
    
        func paymentAuthorizationControllerDidFinish(_ controller: PKPaymentAuthorizationController) {
            print("DidFinish")
        }
        
        func paymentAuthorizationControllerWillAuthorizePayment(_ controller: PKPaymentAuthorizationController) {
            print("WillAuthorizePayment")
        }
    
    }
    
    struct PaymentButton: UIViewRepresentable {
        func updateUIView(_ uiView: PKPaymentButton, context: Context) { }
        
        func makeUIView(context: Context) -> PKPaymentButton {
            return PKPaymentButton(paymentButtonType: .plain, paymentButtonStyle: .automatic)
        }
    }
    

    Use it in the view:

    PaymentButton()
        .frame(width: 228, height: 40, alignment: .center)
        .onTapGesture {
            paymentHandler.startPayment(paymentSummaryItems: paymentSummaryItems)
        }