---
# System prepended metadata

title: Northstar iOS - Cards API Proposal

---

# Northstar iOS - Cards API Proposal

## Summary

**Problem**:
A merchant accepting cards (without 3DS) is required to implement 3 non-applicable methods: `cardDidCancel`, `cardThreeDSecureWillLaunch`, and `cardThreeDSecureDidFinish` methods. This is confusing as these methods will never be called for card-only integrations, yet are required for their app to compile.

**Solution**:
* Have `approveOrder()` return a callback of (`CardResult`, `Error`).
* Move 3DS-related methods to an *optional* `ThreeDSecureChallengePresentationDelegate`.
    * Allows merchants to hook into UI related events regarding the 3DS WebView presentation and dismissal.

**Rationale**:
* It is common in Apple's APIs to use delegate methods for process-oriented tasks, whereas closures are more common for result oriented tasks ([reference](https://stablekernel.com/article/use-blocks-closures-delegates-callbacks/)).
* Less lines of code for card integration.
* Avoid confusing `NOT APPLICABLE`, yet required function conformance.

## Card Integration

### Current

```swift
class ViewController: UIViewController {
    
    ...
    
    func launchCardFlow() {
        let config = CoreConfig(accessToken: "ACCESS_TOKEN", environment: .sandbox)
        let cardClient = CardClient(config: config)

        let card = Card(...)
        let cardRequest = CardRequest(orderID: "ORDER_ID", card: card)

        cardClient.delegate = self
        cardClient.approveOrder(request: cardRequest, context: self)
    }
}

extension ViewController: CardDelegate {

    func card(_ cardClient: CardClient, didFinishWithResult result: CardResult) {
        print(result)
        authorizeOrder(result) // complete payment flow
    }

    func card(_ cardClient: CardClient, didFinishWithError error: CoreSDKError) {
        print(error.localizedDescription)
    }

    func cardDidCancel(_ cardClient: CardClient) {
        // NOT APPLICABLE
    }

    func cardThreeDSecureWillLaunch(_ cardClient: CardClient) {
        // NOT APPLICABLE
    }

    func cardThreeDSecureDidFinish(_ cardClient: CardClient) {
        // NOT APPLICABLE
    }
}
```

### Proposed

```swift
class ViewController: UIViewController {
    
    ...
    func launchCardFlow() async {
        let config = CoreConfig(accessToken: "ACCESS_TOKEN", environment: .sandbox)
        let cardClient = CardClient(config: config)

        let card = Card(...)
        let cardRequest = CardRequest(orderID: "ORDER_ID", card: card)
        
        result, error = await approveOrder(request: cardRequest)
        if error {
            // handle error
        } else {
            // authorize or capture
        }
    }
    
}

```

## 3DS

### Proposed

```swift
class ViewController: UIViewController {
    
    ...
    let config = CoreConfig(accessToken: "ACCESS_TOKEN", environment: .sandbox)

    func launchCardFlow() async {
        let cardClient = CardClient(config: config)

        let card = Card(...)
        let cardRequest = CardRequest(orderID: "ORDER_ID", card: card)
        
        // Comment to disable 3DS
        let threeDSecureRequest = ThreeDSecureRequest(...)
        cardRequest.threeDSecureRequest = threeDSecureRequest
        cardClient.threeDSecureChallengeDelegate = self
        
        result, error = await approveOrder(request: cardRequest)
        if error {
            // handle error
        } else {
            // authorize or capture
        }
    }
    
}

extension ViewController: ThreeDSecureChallengePresentationDelegate {
    
    func threeDSecureDidCancel(_ cardClient: CardClient) {
        // show error onscreen 
    }

    func threeDSecureWillLaunch(_ cardClient: CardClient) {
        // show spinner
    }

    func threeDSecureDidFinish(_ cardClient: CardClient) {
        // clean-up UI
    }
    
}

```

### Current

```swift
class ViewController: UIViewController {
    
    ...
    
    func launchCardFlow() {
        let config = CoreConfig(accessToken: "ACCESS_TOKEN", environment: .sandbox)
        let cardClient = CardClient(config: config)

        let card = Card(...)
        let cardRequest = CardRequest(orderID: "ORDER_ID", card: card)
        
        let threeDSecureRequest = ThreeDSecureRequest(...)
        cardRequest.threeDSecureRequest = threeDSecureRequest
        
        cardClient.delegate = self
        cardClient.approveOrder(request: cardRequest, context: self)
    }
}

extension ViewController: CardDelegate {

    func card(_ cardClient: CardClient, didFinishWithResult result: CardResult) {
        print(result)
        authorizeOrder(result) // complete payment flow
    }

    func card(_ cardClient: CardClient, didFinishWithError error: CoreSDKError) {
        print(error.localizedDescription)
    }

    func cardDidCancel(_ cardClient: CardClient) {
        print("cancelled")
    }

    func cardThreeDSecureWillLaunch(_ cardClient: CardClient) {
        print("will launch")
    }

    func cardThreeDSecureDidFinish(_ cardClient: CardClient) {
        print("did finish")
    }
}
```

## PayPal (Web)

### Current

```swift
class ViewController: UIViewController {
    
    ...
    
    func launchPayPalFlow() {
        let config = CoreConfig(accessToken: "ACCESS_TOKEN", environment: .sandbox)
        let payPalClient = PayPalWebCheckoutClient(config: config)

        let payPalRequest = PayPalWebCheckoutRequest(orderID: "ORDER_ID")

        payPalClient.delegate = self
        payPalClient.start(request: payPalRequest, context: self)
    }
}

extension ViewController: PayPalWebCheckoutDelegate {

    func payPal(_ payPalClient: PayPalWebCheckoutClient, didFinishWithResult result: PayPalWebCheckoutResult) {
        print(result)
        authorizeOrder(result) // complete payment flow
    }

    func payPal(_ payPalClient: PayPalWebCheckoutClient, didFinishWithError error: CoreSDKError) {
        print(error.localizedDescription)
    }

    func payPalDidCancel(_ payPalClient: PayPalWebCheckoutClient) {
        print("cancelled")
    }
}
```

## PayPal (Native)

```swift
class ViewController: UIViewController {
    
    ...
    
    func launchPayPalFlow() {
        let config = CoreConfig(accessToken: "ACCESS_TOKEN", environment: .sandbox)
        let payPalClient = PayPalClient(config: config)

        payPalClient.delegate = self
        payPalClient.start { createOrderAction in
            createOrderAction.set(orderId: orderID)
        }
    }
}

extension ViewController: PayPalWebCheckoutDelegate {

    func paypal(_ payPalClient: PayPalClient, didFinishWithResult approvalResult: Approval) {
        print(result)
        authorizeOrder(result) // complete payment flow
    }

    func paypal(_ payPalClient: PayPalClient, didFinishWithError error: CoreSDKError) {
        print(error.localizedDescription)
    }

    func paypalDidCancel(_ payPalClient: PayPalClient) {
        print("cancelled")
    }

    func paypalWillStart(_ payPalClient: PayPalClient) {
        // the paypal pay sheet is about to appear. Handle loading views, spinners, etc.
    }

    func paypalDidShippingAddressChange(
        _ payPalClient: PayPalClient,
        shippingChange: ShippingChange,
        shippingChangeAction: ShippingChangeAction
    ) {
        // called when the user decides to change the address or the shipping method of the order.
    }
}