Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions _posts/2020-03-05-apple-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Apple Pay

Setting up Apple Pay in a new project while being able to properly test it during development is a long process, especially if you've not done it before.

If you're just getting started and want to test the basics of Apple Pay like showing the payment sheet and confirming a test transaction you can simply test it on the simulator. It just works out of the box. The payment token won't be valid, but you can test the whole UI layer almost effortlessly.

Unfortunately in some Xcode releases this breaks (it did in Xcode 12.1 for example), but usually it works fairly reliably.

If you want to take the next step though, you need to set up the whole environment and use a sandbox account on a real device.

The last step would be testing a real transaction with a real card to be able to test if your server can handle a real token from Apple Pay correctly.

## Basic Apple Pay Usage

The [Apple Pay Programming Guide][0] from Apple is great. It goes step by step on how to set up everything. We are going to cover the environment setup in the [Sandbox Testing][1] section while here we are going to cover the code.

To use Apple Pay you always need to 'import PassKit'. This step will be a given and won't be repeated in every section.

The first step is to check if the device supports Apple Pay. This can be done by using PKPaymentAuthorizationViewController if you can import UIKit or PKPaymentAuthorizationController if you can't. For the purpose of this article we are always going to use PKPaymentAuthorizationViewController since it's what most people use, but you can substitute it to PKPaymentAuthorizationController if you need.

To check if Apple Pay is supported:

`PKPaymentAuthorizationViewController.canMakePayments()`

After confirming that Apple Pay is supported the next step is to check if the user has a card supported by the merchant in the wallet:

`PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: network, capabilities: capabilities)`

Otherwise we need to prompt the user to add a card by opening the wallet app:

`PKPassLibrary().openPaymentSetup()`

We can now set up the payment request with all the necessary fields. For simplicity we are going to assume that this is a service transaction, if it's not you would need to ask for a delivery address by setting it as a mandatory field in the payment request and also implement additional delegate methods.
```
let transaction = PKPaymentRequest()
transaction.currencyCode = //3 digits code
transaction.countryCode = //2 digits code
transaction.merchantIdentifier = //This identifier needs to match the identifier selected in Capabilities > Apple Pay

transaction.merchantCapabilities = //What kind of capabilities (for example 3D secure) the merchant supports
transaction.supportedNetworks = //What kind of cards the merchant supports

let subtotal = NSDecimalNumber(string: PRICE, locale: locale)

let total = PKPaymentSummaryItem(label: ITEM, amount: subtotal, type: .final)

transaction.paymentSummaryItems = [total]
```

After setting up the PKPaymentRequest we can now show the payment sheet using a PKPaymentAuthorizationViewController. Be mindful that the initializer for it returns an optional value. If the paymentRequest is not valid or can't be loaded correctly it will return nil.
```
guard let vc = PKPaymentAuthorizationViewController(paymentRequest: request) else {
//Handle error
return
}
```
You should now keep a weak reference to the PKPaymentAuthorizationViewController somewhere to be able to dismiss it since it won't get automatically dismissed.

You should also add yourself as the delegate to be able to respond to user events. Implement the following delegate methods:
```
extension MyClass: PKPaymentAuthorizationViewControllerDelegate, PKPaymentAuthorizationControllerDelegate {

public func paymentAuthorizationControllerDidFinish(_ controller: PKPaymentAuthorizationController) {
//Called in any case - Either Cancelled or Authorized
}


public func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
//Called in any case - Either Cancelled or Authorized
}

public func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController,
didAuthorizePayment payment: PKPayment,
completion: @escaping (PKPaymentAuthorizationStatus) -> Void) {
//success
}

@available(iOS 11.0, *)
public func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController,
didAuthorizePayment payment: PKPayment,
handler: @escaping (PKPaymentAuthorizationResult) -> Void) {
//success
}
}
```
You are now ready to test your application on the simulator.

## Simulator Testing

Testing on the simulator should just work. The only caveats is that the payment token returned in this case will be empty so be mindful to code for this case if you're going to test on a simulator.

Testing on a simulator should be done only in the initial prototyping phase and you should always test on a real device later on.

## Sandbox Testing

In this section we will focus on step 2: testing on a real device using a Sandbox account.

### Account Setup

Before getting started implementing Apple Pay your developer account needs to be setup to support it.

1. Log in to the Developer Account and select [Identifiers][5]. Add a new merchant ID by choosing an identifier and a description.
1. Now select [Certificates][6]. Create a new decryption certificate and select Create. Select the merchant Id that you just created and upload a new certificate signing request generated by your mac's keychain.

You will need this even if you're only testing since the payment will not go through without having one. Be mindful that you need to select a custom key pair information by selecting "Let me specify key pair information" and selecting ECC, key size 256 bits otherwise your certsigningrequest will be rejected.

If you are a merchant you should be aware that letting your developer generate your merchant decryption certificate will allow him to be able to read all of the receipts coming from Apple Pay so you might want to do that in house even if you trust the developer (GDPR and such).


1. Open your project and add a new capability -> Apple Pay selecting your newly created apple pay merchant id
1. Create a new testing user on [Apple app store connect website][5]. If you're using an old test account go to that page anyway and make sure that apple pay is enabled since it was not for me. If it is not enabled you will need to create a new account. Set the account region to US to make sure it will work.

After creating it you will have to set up 2 factor auth and make sure you're actually signed in. Go to Settings > iCloud > sign it. Force quit and reopen settings and see if you're actually signed it, if not sign it again. Repeat this process as many times as necessary.

The whole section on [Sandbox Testing][2] from Apple Website is worth a read.
1. Log out from BOTH iCloud and AppStore and login to only iCloud with your test user

[Apple][2]:
>If you mistakenly use a sandbox tester account to sign in to a production environment, like iTunes, on your test device instead of your test environment, the sandbox account becomes invalid and can’t be used again. If this happens, create a new sandbox tester account with a new email address.

1. Open Wallet.app and add test credit cards found on [apple pay sandbox website][2]
If the credit cards do not work here are some workarounds:
- Use a US testing account and change region to US [This was the one that worked for me]
- Try a different card from the Apple provided list. Be aware that the selected card will need to match the merchant's capabilities
- Logout and login again on both iCloud and AppStore
- Force quit Wallet and restart iPhone
- Use a real credit card instead of a test one (!? Yes this was a suggested solution)
1. Inside Wallet.app change the shipping address to a real address

### Workarounds

I've run into a lot of issues while initially setting up the environment and while the documentation from Apple website in this case is good it does not cover any troubleshooting. Also StackOverflow and other siilar websites do not have enough people interested in the topic so the solutions there often work in some cases and not in others. This is a list of the various solutions I found and a few more that worked for me. I've been told that the solutions I found do not work for toher people so be aware of that.

[From Stack Overflow][3]:
> "on iOS 11 and sandbox tester account, if the app forgot to call completion once, it will never be called again. until I logout the tester account from iCloud and log back in.."

I personally did not have this problem, but it might be worth relogging if you see the delegate methods not being fired.

After all of this you need to implement the delegate methods:

Make sure you always call the completion or handler or iOS will fail the payment after 15/20 secs

## Real Transaction Testing

From your staging/qa environment you can test payment tokens towards cards. For the iOS application perspective nothing should change apart from the fact that you're no longer using the sandbox account during transactions.

## Conclusion

You've now learned how to set up Apple Pay and how to test it effectively.

[0]:
[1]: #sandbox-testing
[2]: https://developer.apple.com/apple-pay/sandbox-testing/
[3]: https://stackoverflow.com/questions/50176397/error-apple-pay-not-completed
[4]: https://developer.apple.com/account/resources/identifiers/add/bundleId
[5]: https://appstoreconnect.apple.com/
[6]: https://developer.apple.com/account/resources/certificates/add