In the ever-evolving landscape of iOS development, understanding and implementing payment solutions like Apple Pay is paramount. In the first part of this series, we delved into the intricacies of Apple Pay’s functionality in Swift. We explored its mechanisms, its integration into iOS applications, and the challenges developers face, particularly when handling payment-related interactions.

One significant challenge often encountered in such integrations is the reliance on traditional delegate patterns. While delegates have long been a staple in iOS development, they come with their limitations, especially in complex scenarios like payment processing. In this second part, we shift our focus to exploring the drawbacks of using delegates and propose a more robust alternative: the delegate proxy pattern.

Join us as we delve deeper into the intricacies of Apple Pay integration, uncover the limitations of delegates, and discover how embracing the delegate proxy pattern can enhance the efficiency, flexibility, and maintainability of our iOS applications.

RxSwift Delegate Proxy

To begin implementing DelegateProxy for PKPaymentAuthorizationViewControllerDelegate, we need to define a class that acts as the proxy. This involves implementing certain methods such as registerKnownImplementations, setCurrentDelegate, and currentDelegate.

Here’s a basic outline of how we can proceed:

// MARK: - PKPaymentAuthorizationViewControllerDelegateProxy

public class PKPaymentAuthorizationViewControllerDelegateProxy
: DelegateProxy<PKPaymentAuthorizationViewController, PKPaymentAuthorizationViewControllerDelegate>
, DelegateProxyType
, PKPaymentAuthorizationViewControllerDelegate {
// MARK: - DelegateProxyType methods

    public static func registerKnownImplementations() {

        register {

            PKPaymentAuthorizationViewControllerDelegateProxy(paymentAuthorizationViewController: $0)
            
        }

    }

    public static func currentDelegate(for object: PKPaymentAuthorizationViewController) -> PKPaymentAuthorizationViewControllerDelegate? {

        object.delegate

    }
    
    public static func setCurrentDelegate(
        _ delegate: PKPaymentAuthorizationViewControllerDelegate?,
        to object: PKPaymentAuthorizationViewController
    ) {
        
        object.delegate = delegate
        
    }

    // Implement delegate methods as necessary
}

This class serves as the DelegateProxy for PKPaymentAuthorizationViewControllerDelegate. It inherits from DelegateProxy, specifies the parent object type (PKPaymentAuthorizationViewController), and the delegate type (PKPaymentAuthorizationViewControllerDelegate).

The registerKnownImplementations method registers known implementations of the proxy, while setCurrentDelegate and currentDelegate methods manage setting and retrieving the current delegate, respectively.

Now, you can use this DelegateProxy to handle delegation for PKPaymentAuthorizationViewControllerDelegate in a reactive way using RxSwift. Long with creating the PKPaymentAuthorizationViewControllerDelegateProxy, we need to extend PKPaymentAuthorizationViewController to define a computed property of type ControlEvent<PKPaymentAuthorizationViewControllerDelegate>. This property will use the DelegateProxy we’ve created to handle delegation in a reactive way. We have also a special property that will be very helpful later when we get the transactionState from our proxy delegate.

Here’s a basic outline of how we can proceed:

import PassKit

import RxSwift
import RxCocoa

extension Reactive where Base: PKPaymentAuthorizationViewController {

    public var delegate: PKPaymentAuthorizationViewControllerDelegateProxy {

        .proxy(for: base)

    }

    public var transactionState: Observable<ApplePayTransactionState> {

        delegate.transactionStateObservable

    }

}

Now, we have to implement Apple Pay delegates methods. To do we have define a state variable transactionStateObservable to handle the different states of our payment. We have created an enum type called ApplePayTransactionState. This is the definition of out the type :

public enum ApplePayTransactionState {
    
    case cancelled
    
    case confirmingPayment(
        PKPayment,
        (PKPaymentAuthorizationStatus) -> Void
    )
    
    case completed
    
}

Now we focus to implement the delegates

// MARK: - PKPaymentAuthorizationViewControllerDelegate methods
    
    public func paymentAuthorizationViewController(
        _ controller: PKPaymentAuthorizationViewController,
        didAuthorizePayment payment: PKPayment,
        completion: @escaping (PKPaymentAuthorizationStatus) -> Void
    ) {
        
        authorizePaymentStatus = .authorized
        
        transactionStateSubject.onNext(
            .confirmingPayment(
                payment,
                completion
            )
        )
        
    }
    
    public func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {

        if case .authorized = authorizePaymentStatus {

            transactionStateSubject.onNext(.completed)
            
        } else {

            transactionStateSubject.onNext(.cancelled)
            
        }

    }

I think that you remember the delegates that we have used in the first article. But something new now is that I use transactionStateSubject which is a private variable in my proxy and observed by the public variable transactionStateObservable

// MARK: - Public properties

    public lazy var transactionStateObservable = transactionStateSubject.asObservable()

Also I added a private observable to handle the authorization from Apple Pay which is  authorizePaymentStatus

This is the declaration of the two private variables

// MARK: - Private properties

    private let transactionStateSubject = ReplaySubject<ApplePayTransactionState>.create(bufferSize: 1)

    private var authorizePaymentStatus: ApplePayAuthorizePaymentStatus?

Using the delegate proxy

After declaring the PKPaymentAuthorizationViewControllerDelegateProxy, implementing the delegates methods reactively and extend PKPaymentAuthorizationViewController to support the PKPaymentAuthorizationViewControllerDelegateProxy as delegate, we should now show you how you can use that.

Our team philosophy, was to create an ApplePayAPI class that check if we can make a payment with Apple first and observe the transactionStateObservable. This logic is the same for all our payments methods like PayPal, credit cards…

We have create first a protocol that we named ApplePayAPIProtocol which contain only one method processPayment. We need at least the amount of the order and the currency code.

Here’s a basic outline of how we can proceed:

// sourcery: AutoMockable
public protocol ApplePayAPIProtocol {

    var canMakePayments: Bool { get }

    func processPayment(
        _ amount: NSDecimalNumber,
        currencyCode: String
    ) -> Observable<ApplePayTransactionState>

}

Now we implement our ApplePayAPIProtocol responsable of creating the ApplePayRequest, communicate with the proxy to retrieve the state object.

We check first if we can make ApplePay Payment (check if we have one card at least in the wallet).

import PassKit

import RxSwift

struct ApplePayAPI: ApplePayAPIProtocol {

    // MARK: - ApplePayAPIProtocol methods
    
    var canMakePayments: Bool {
        
        PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: configuration.supportedNetworks)
    }

// MARK: - Private properties

    private let configuration: ApplePayAPIConfiguration

// MARK: - Lifecycle
    
    init(configuration: ApplePayAPIConfiguration) {

        self.configuration = configuration

 }

// MARK: - ApplePayAPIProtocol methods

}

canMakePayment is a static standard method used by the PKPaymentAuthorizationViewController to verify if we can make payment based on supported network wish is an array of PKPaymentNetwork. We have created a type that we called ApplePayAPIConfiguration that encapsulate all ApplePay configurations.

import PassKit

import Common

public struct ApplePayAPIConfiguration {

    // MARK: - Public Properties

    let summaryItemLabel: String

    let merchantIdentifier: String

    let supportedNetworks: [PKPaymentNetwork]

    let merchantCapabilities: PKMerchantCapability

    let countryCode: String

    init(environment: Environment) {

        self.summaryItemLabel = environment.applePaySummaryItemLabel

        self.merchantIdentifier = environment.applePayMerchantIdentifier

        self.supportedNetworks = environment.applePaySupportedNetworks

        self.merchantCapabilities = environment.applePayMerchantCapabilities

        self.countryCode = environment.appMarket

    }

}

We use our common framework to inject the Environment attribute which contains specific configuration depending of the country.

Now, we can implement our processPayment method in order to get the transactionState from the delegates proxy. You will see that it’s really very simple 🙂

func processPayment(
        _ amount: NSDecimalNumber,
        currencyCode: String
    ) -> Observable<ApplePayTransactionState> {

        let paymentRequest = createPaymentRequestAmount(
            amount,
            currencyCode: currencyCode
        )

        guard let authorizationViewController = createAuthorizationViewController(for: paymentRequest) else {

            return .just(.cancelled)

        }

        let transactionStateObservable = authorizationViewController.rx.transactionState
            .do { state in
                switch state {

                case .completed,
                     .cancelled:

                    UIApplication.topViewController()?.dismiss(animated:                     true)

                default:

                    break
                    
                }
            }

        UIApplication.topViewController()?.present(
            authorizationViewController,
            animated: true,
            completion: nil
        )

        return transactionStateObservable
        
    }
    

To create the PKPaymentAuthorizationViewController we need the PKPaymentRequest using the amount and the currency and also our ApplePayAPIConfirguration , we retrieve this information from our use case (we will speak about that in the next article).

The authorizationViewController is a PKPaymentAuthorizationViewController in RxSwift with a special variable our famous transactionState that we have declare above when we have extend the PKPaymentAuthorizationViewController to be reactive.

For navigation purpose we need only to handle the two states completed and cancelled to dismiss the PKPaymentAuthorizationViewController. Maybe you think about the confirm state! no worry 🙂

case confirmingPayment(
        PKPayment,
        (PKPaymentAuthorizationStatus) -> Void
 )

The third state is really very important later to validate the order or to cancel it. We return the three cases using the return transactionStateObservable statement. Of course, we need to open our authorizationViewController using this instruction.

UIApplication.topViewController()?.present(
            authorizationViewController,
            animated: true,
            completion: nil
        )

What’s else?

Now we have passed from a spaghetti one or two classes that handle delegates, navigation and business logic to at least five classes and struct encapsulated in Payment framework that we have created for that and this the structure

This is not sufficient, our business logic and how communicate with back end API is always in our viewModel. it’s time for clean architecture concept to come now to handle this issue. see you in the next part to speak about that.

References

https://github.com/ReactiveX/RxSwift/blob/main/RxCocoa/Common/DelegateProxyType.swift

https://blog.ippon.fr/2018/11/13/rxswift-how-to-make-your-favorite-delegate-based-apis-reactive/

https://samritchie.net/posts/rxswift-delegateproxy-with-required-methods/

Leave A Comment

Résoudre : *
20 × 26 =


fr_FRFrench