How to use Stripe and Apple Pay in iOS
Tags: swift, ios, stripe, applepay
Published: 2019-05-14 21:43:22

---
title: How to use Stripe and Apple Pay in iOS
published: true
description: 
tags: swift,ios,stripe,applepay
---

- [iOS Integration](https://stripe.com/docs/mobile/ios)
- [Setting Up Apple Pay Requirements](https://developer.apple.com/documentation/passkit/apple_pay/setting_up_apple_pay_requirements)
- [Apple Pay](https://stripe.com/docs/apple-pay)

## Show basic add card in iOS

```swift
import UIKit
import Stripe

final class MainController: UIViewController {

    func showPayment() {
        let addCardViewController = STPAddCardViewController()
        addCardViewController.delegate = self
        let navigationController = UINavigationController(rootViewController: addCardViewController)

        present(navigationController, animated: true, completion: nil)
    }
}

extension MainController: STPAddCardViewControllerDelegate {
    func addCardViewControllerDidCancel(_ addCardViewController: STPAddCardViewController) {
        dismiss(animated: true, completion: nil)
    }

    func addCardViewController(_ addCardViewController: STPAddCardViewController, didCreateToken token: STPToken, completion: @escaping STPErrorBlock) {
        _ = token.tokenId
        completion(nil)
        dismiss(animated: true, completion: nil)
    }
}
```

## Generate ephemeral key
https://stripe.com/docs/mobile/ios/standard#ephemeral-key

> In order for our prebuilt UI elements to function, you'll need to provide them with an ephemeral key, a short-lived API key with restricted API access. You can think of an ephemeral key as a session, authorizing the SDK to retrieve and update a specific Customer object for the duration of the session.

### Backend in Go
https://github.com/stripe/stripe-go

Need a secret key by going to `Stripe dashboard -> Developers -> API keys -> Secret key`

```
stripe.Key = "sk_key"
```

Need customer id. We can manually create one in `Stripe dashboard -> Customers`

```go
package main

import (
	"net"
	"encoding/json"
	"fmt"
	"net/http"
	"github.com/stripe/stripe-go"
    "github.com/stripe/stripe-go/ephemeralkey"
)

func main() {
	stripe.Key = "sk_test_mM2MkqO61n7vvbVRfeYmBgWm00Si2PtWab"

	http.HandleFunc("/ephemeral_keys", generateEphemeralKey)
  	if err := http.ListenAndServe(":8080", nil); err != nil {
    	panic(err)
  	}
}

type EphemeralKeysRequest struct {
    ApiVersion string `json:"api_version"`
}

func generateEphemeralKey(w http.ResponseWriter, r *http.Request) {
	customerId := "cus_Eys6aeP5xR89ab"

	decoder := json.NewDecoder(r.Body)
    var t EphemeralKeysRequest
    err := decoder.Decode(&t)
    if err != nil {
        panic(err)
    }

    stripeVersion := t.ApiVersion
    if stripeVersion == "" {
        log.Printf("Stripe-Version not found\n")
        w.WriteHeader(400)
        return
    }
    params := &stripe.EphemeralKeyParams{
        Customer: stripe.String(customerId),
        StripeVersion: stripe.String(stripeVersion),
	}
	
    key, err := ephemeralkey.New(params)
    if err != nil {
        log.Printf("Stripe bindings call failed, %v\n", err)
        w.WriteHeader(500)
        return
    }
    w.Write(key.RawJSON)
}
```

### iOS client

Networking client uses [How to make simple networking client in Swift](https://github.com/onmyway133/blog/issues/222)

Need an object that conforms to `STPCustomerEphemeralKeyProvider`

```swift
final class EphemeralKeyClient: NSObject, STPCustomerEphemeralKeyProvider {
    let client = NetworkClient(baseUrl: URL(string: "http://localhost:8080")!)

    func createCustomerKey(withAPIVersion apiVersion: String, completion: @escaping STPJSONResponseCompletionBlock) {
        var options = Options()
        options.httpMethod = .post
        options.path = "ephemeral_keys"
        options.parameters = [
            "api_version": apiVersion
        ]

        client.makeJson(options: options, completion: { result in
            switch result {
            case .success(let json):
                completion(json, nil)
            case .failure(let error):
                completion(nil, error)
            }
        })
    }
}
```

Setting up `STPCustomerContext` and `STPPaymentContext`

```swift
final class MainController: UIViewController {
    let client = EphemeralKeyClient()
    let customerContext: STPCustomerContext
    let paymentContext: STPPaymentContext

    init() {
        self.customerContext = STPCustomerContext(keyProvider: client)
        self.paymentContext = STPPaymentContext(customerContext: customerContext)
        super.init(nibName: nil, bundle: nil)
        paymentContext.delegate = self
        paymentContext.hostViewController = self
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func start() {
        paymentContext.presentShippingViewController()
    }
}
```

## Handle charge
https://stripe.com/docs/charges

### Backend in Go

If we use `stripe_id` from card, which has the form of `card_xxx`, we need to include `customer` info

If we use `token`, which has the form `tok_xxx`, then no need for `customer` info

From `STPPaymentResult`

> When you're using `STPPaymentContext` to request your user's payment details, this is the object that will be returned to your application when they've successfully made a payment. It currently just contains a `source`, but in the future will include any relevant metadata as well. You should pass `source.stripeID` to your server, and call the charge creation endpoint. This assumes you are charging a Customer, so you should specify the `customer` parameter to be that customer's ID and the `source` parameter to the value returned here. For more information, see https://stripe.com/docs/api#create_charge

```go
package main

import (
	"net"
	"encoding/json"
	"fmt"
	"net/http"
	"log"
	"os"
	"github.com/stripe/stripe-go/charge"
)

func main() {
	stripe.Key = "sk_test_mM2MkqO61n7vvbVRfeYmBgWm00Si2PtWab"

	http.HandleFunc("/request_charge", handleCharge)
  	if err := http.ListenAndServe(":8080", nil); err != nil {
    	panic(err)
  	}
}

var customerId = "cus_Eys6aeP5xR89ab"

type PaymentResult struct {
    StripeId string `json:"stripe_id"`
}

func handleCharge(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
    var t PaymentResult
    err := decoder.Decode(&t)
    if err != nil {
        panic(err)
    }

    params := &stripe.ChargeParams{
        Amount:      stripe.Int64(150),
        Currency:    stripe.String(string(stripe.CurrencyUSD)),
		Description: stripe.String("Charge from my Go backend"),
		Customer: stripe.String(customerId),
    }
		
	params.SetSource(t.StripeId)
    ch, err := charge.New(params)
    if err != nil {
		fmt.Fprintf(w, "Could not process payment: %v", err)
		fmt.Println(ch)
		w.WriteHeader(400)
    }
	
	w.WriteHeader(200)
}

```

### iOS client

```swift
final class PaymentClient {
    let client = NetworkClient(baseUrl: URL(string: "http://192.168.210.219:8080")!)

    func requestCharge(source: STPSourceProtocol, completion: @escaping (Result<(), Error>) -> Void) {
        var options = Options()
        options.httpMethod = .post
        options.path = "request_charge"
        options.parameters = [
            "stripe_id": source.stripeID
        ]

        client.makeJson(options: options, completion: { result in
            completion(result.map({ _ in () }))
        })
    }
}

paymentContext.requestPayment()

extension MainController: STPPaymentContextDelegate {
    func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPErrorBlock) {
        client.requestCharge(source: paymentResult.source, completion: { result in
            switch result {
            case .success:
                completion(nil)
            case .failure(let error):
                completion(error)
            }
        })
    }
}
```

## Token from card

Use `STPAPIClient.shared().createToken` to get token from card https://stripe.com/docs/mobile/ios/custom#collecting-card-details

```swift
let cardParams = STPCardParams()
cardParams.number = "4242424242424242"
cardParams.expMonth = 10
cardParams.expYear = 2021
cardParams.cvc = "123"

STPAPIClient.shared().createToken(withCard: cardParams) { (token: STPToken?, error: Error?) in
    guard let token = token, error == nil else {
        // Present error to user...
        return
    }

    submitTokenToBackend(token, completion: { (error: Error?) in
        if let error = error {
            // Present error to user...
        }
        else {
            // Continue with payment...
        }
    })
}
```

## Payment options and shipping view controllers

Instead of using `paymentContext`

```swift
paymentContext.pushShippingViewController()
paymentContext.pushPaymentOptionsViewController()
```

We can use view controllers https://stripe.com/docs/mobile/ios/custom#stppaymentoptionsviewcontroller directly with `STPPaymentOptionsViewController` and `STPShippingAddressViewController`. Then implement `STPPaymentOptionsViewControllerDelegate` and `STPShippingAddressViewControllerDelegate`

## Register merchant Id and Apple Pay certificate
https://stripe.com/docs/apple-pay/apps

Get Certificate signing request file from Stripe https://dashboard.stripe.com/account/apple_pay

We can't register merchant id with Enterprise account

## Use Apple Pay

### Go backend

Use token

```go
type ApplePayRequest struct {
    Token string `json:"token"`
}

func handleChargeUsingApplePay(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
    var t ApplePayRequest
    err := decoder.Decode(&t)
    if err != nil {
        panic(err)
    }

    params := &stripe.ChargeParams{
        Amount:      stripe.Int64(150),
        Currency:    stripe.String(string(stripe.CurrencyUSD)),
		Description: stripe.String("Charge from my Go backend for Apple Pay"),
    }
		
	params.SetSource(t.Token)
    ch, err := charge.New(params)
    if err != nil {
		fmt.Fprintf(w, "Could not process payment: %v", err)
		fmt.Println(ch)
		w.WriteHeader(400)
    }
	
	w.WriteHeader(200)
}
```

### iOS client

Update client to send `STPToken`

```swift
final class PaymentClient: NSObject {
    let client = NetworkClient(baseUrl: URL(string: "localhost:8080")!)

    func requestCharge(token: STPToken, completion: @escaping (Result<(), Error>) -> Void) {
        var options = Options()
        options.httpMethod = .post
        options.path = "request_charge_apple_pay"
        options.parameters = [
            "token": token.tokenId
        ]

        client.make(options: options, completion: { result in
            completion(result.map({ _ in () }))
        })
    }

    func useApplePay(payment: PKPayment, completion: @escaping (Result<(), Error>) -> Void) {
        STPAPIClient.shared().createToken(with: payment, completion: { (token: STPToken?, error: Error?) in
            guard let token = token, error == nil else {
                return
            }

            self.requestCharge(token: token, completion: completion)
        })
    }
}
```

Use `PKPaymentAuthorizationViewController`, not `PKPaymentAuthorizationController`

https://developer.apple.com/documentation/passkit/pkpaymentauthorizationcontroller

> The PKPaymentAuthorizationController class performs the same role as the PKPaymentAuthorizationViewController class, but it does not depend on the UIKit framework. This means that the authorization controller can be used in places where a view controller cannot (for example, in watchOS apps or in SiriKit extensions).


```swift
extension MainController {
    func showApplePay() {
        let merchantId = "merchant.com.onmyway133.MyApp"
        let paymentRequest = Stripe.paymentRequest(withMerchantIdentifier: merchantId, country: "US", currency: "USD")
        paymentRequest.paymentSummaryItems = [
            PKPaymentSummaryItem(label: "Rubber duck", amount: 1.5)
        ]

        guard Stripe.canSubmitPaymentRequest(paymentRequest) else {
            assertionFailure()
            return
        }

        guard let authorizationViewController = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest) else {
            assertionFailure()
            return
        }

        authorizationViewController.delegate = self
        innerNavigationController.present(authorizationViewController, animated: true, completion: nil)
    }
}

extension MainController: PKPaymentAuthorizationViewControllerDelegate {
    func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
        controller.dismiss(animated: true, completion: nil)
    }

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

        client.useApplePay(payment: payment, completion: { result in
            switch result {
            case .success:
                completion(.init(status: .success, errors: nil))
            case .failure(let error):
                completion(.init(status: .failure, errors: [error]))
            }
        })
    }
}

```

## Showing Apple Pay option

From `appleMerchantIdentifier`

> The Apple Merchant Identifier to use during Apple Pay transactions. To create one of these, see our guide at https://stripe.com/docs/mobile/apple-pay . You must set this to a valid identifier in order to automatically enable Apple Pay.

```swift
if Stripe.deviceSupportsApplePay() {
    STPPaymentConfiguration.shared().appleMerchantIdentifier = "merchant.com.onmyway133.MyApp"
}

paymentContext.pushPaymentOptionsViewController()
```

## requestPayment not showing UI

From `requestPayment`

> Requests payment from the user. This may need to present some supplemental UI to the user, in which case it will be presented on the payment context's `hostViewController`. For instance, if they've selected Apple Pay as their payment method, calling this method will show the payment sheet. If the user has a card on file, this will use that without presenting any additional UI. After this is called, the `paymentContext:didCreatePaymentResult:completion:` and `paymentContext:didFinishWithStatus:error:` methods will be called on the context's `delegate`.

Use `STPPaymentOptionsViewController` to show cards and Apple Pay options

Code for `requestPayment`

```objc
- (void)requestPayment {
    WEAK(self);
    [[[self.didAppearPromise voidFlatMap:^STPPromise * _Nonnull{
        STRONG(self);
        return self.loadingPromise;
    }] onSuccess:^(__unused STPPaymentOptionTuple *tuple) {
        STRONG(self);
        if (!self) {
            return;
        }

        if (self.state != STPPaymentContextStateNone) {
            return;
        }

        if (!self.selectedPaymentOption) {
            [self presentPaymentOptionsViewControllerWithNewState:STPPaymentContextStateRequestingPayment];
        }
        else if ([self requestPaymentShouldPresentShippingViewController]) {
            [self presentShippingViewControllerWithNewState:STPPaymentContextStateRequestingPayment];
        }
        else if ([self.selectedPaymentOption isKindOfClass:[STPCard class]] ||
                 [self.selectedPaymentOption isKindOfClass:[STPSource class]]) {
            self.state = STPPaymentContextStateRequestingPayment;
            STPPaymentResult *result = [[STPPaymentResult alloc] initWithSource:(id<STPSourceProtocol>)self.selectedPaymentOption];
            [self.delegate paymentContext:self didCreatePaymentResult:result completion:^(NSError * _Nullable error) {
                stpDispatchToMainThreadIfNecessary(^{
                    if (error) {
                        [self didFinishWithStatus:STPPaymentStatusError error:error];
                    } else {
                        [self didFinishWithStatus:STPPaymentStatusSuccess error:nil];
                    }
                });
            }];
        }
        else if ([self.selectedPaymentOption isKindOfClass:[STPApplePayPaymentOption class]]) {
      // ....
```

## Payment options

```swift
func paymentOptionsViewController(_ paymentOptionsViewController: STPPaymentOptionsViewController, didSelect paymentOption: STPPaymentOption) {
	// No op
}
```

After user selects payment option, the change is saved in dashboard `https://dashboard.stripe.com/test/customers`, but for card only. Select Apple Pay does not reflect change in web dashboard.

Apple pay option is added manually locally, from `STPCustomer+SourceTuple.m` 😲

```objc
- (STPPaymentOptionTuple *)filteredSourceTupleForUIWithConfiguration:(STPPaymentConfiguration *)configuration {
    id<STPPaymentOption> _Nullable selectedMethod = nil;
    NSMutableArray<id<STPPaymentOption>> *methods = [NSMutableArray array];
    for (id<STPSourceProtocol> customerSource in self.sources) {
        if ([customerSource isKindOfClass:[STPCard class]]) {
            STPCard *card = (STPCard *)customerSource;
            [methods addObject:card];
            if ([card.stripeID isEqualToString:self.defaultSource.stripeID]) {
                selectedMethod = card;
            }
        }
        else if ([customerSource isKindOfClass:[STPSource class]]) {
            STPSource *source = (STPSource *)customerSource;
            if (source.type == STPSourceTypeCard
                && source.cardDetails != nil) {
                [methods addObject:source];
                if ([source.stripeID isEqualToString:self.defaultSource.stripeID]) {
                    selectedMethod = source;
                }
            }
        }
    }

    return [STPPaymentOptionTuple tupleWithPaymentOptions:methods
                                    selectedPaymentOption:selectedMethod
                                        addApplePayOption:configuration.applePayEnabled];
}
```

STPApplePayPaymentOption` is not available in `paymentContext.paymentOptions` immediately

## Change selected payment option

In `STPPaymentContext`

`setSelectedPaymentOption` is read only and trigger `paymentContextDidChange`, but it checks if `the new selected payment option` is equal to existing selected payment option

```objc
- (void)setSelectedPaymentOption:(id<STPPaymentOption>)selectedPaymentOption {
    if (selectedPaymentOption && ![self.paymentOptions containsObject:selectedPaymentOption]) {
        self.paymentOptions = [self.paymentOptions arrayByAddingObject:selectedPaymentOption];
    }
    if (![_selectedPaymentOption isEqual:selectedPaymentOption]) {
        _selectedPaymentOption = selectedPaymentOption;
        stpDispatchToMainThreadIfNecessary(^{
            [self.delegate paymentContextDidChange:self];
        });
    }
}
```

There is `retryLoading` which is called at `init`

```objc
- (void)retryLoading {
    // Clear any cached customer object before refetching
    if ([self.apiAdapter isKindOfClass:[STPCustomerContext class]]) {
        STPCustomerContext *customerContext = (STPCustomerContext *)self.apiAdapter;
        [customerContext clearCachedCustomer];
    }
    WEAK(self);
    self.loadingPromise = [[[STPPromise<STPPaymentOptionTuple *> new] onSuccess:^(STPPaymentOptionTuple *tuple) {
        STRONG(self);
        self.paymentOptions = tuple.paymentOptions;
        self.selectedPaymentOption = tuple.selectedPaymentOption;
    }] onFailure:^(NSError * _Nonnull error) {
        STRONG(self);
        if (self.hostViewController) {
            [self.didAppearPromise onSuccess:^(__unused id value) {
                if (self.paymentOptionsViewController) {
                    [self appropriatelyDismissPaymentOptionsViewController:self.paymentOptionsViewController completion:^{
                        [self.delegate paymentContext:self didFailToLoadWithError:error];
                    }];
                } else {
                    [self.delegate paymentContext:self didFailToLoadWithError:error];
                }
            }];
        }
    }];
    [self.apiAdapter retrieveCustomer:^(STPCustomer * _Nullable customer, NSError * _Nullable error) {
        stpDispatchToMainThreadIfNecessary(^{
            STRONG(self);
            if (!self) {
                return;
            }
            if (error) {
                [self.loadingPromise fail:error];
                return;
            }
            if (!self.shippingAddress && customer.shippingAddress) {
                self.shippingAddress = customer.shippingAddress;
                self.shippingAddressNeedsVerification = YES;
            }

            STPPaymentOptionTuple *paymentTuple = [customer filteredSourceTupleForUIWithConfiguration:self.configuration];

            [self.loadingPromise succeed:paymentTuple];
        });
    }];
}
```

Which in turns call `STPCustomerEphemeralKeyProvider`. As stripe does not save Apple Pay option in dashboard, this method return list of card payment options, together with the default card as selected payment option 😲

Although the new `STPCard` has a different address, it is the exact same card with the same info, and the `isEqual` method of `STPCard` is

```objc
- (BOOL)isEqualToCard:(nullable STPCard *)other {
    if (self == other) {
        return YES;
    }

    if (!other || ![other isKindOfClass:self.class]) {
        return NO;
    }
    
    return [self.stripeID isEqualToString:other.stripeID];
}
```

I raised an issue [How to change selected payment option?](https://github.com/stripe/stripe-ios/issues/1175) hope it gets resolved soon 😢

Original post https://github.com/onmyway133/blog/issues/219