There's a unique brand of modern angst that manifests the moment you decide to buy something online. While there's no English word for it, it translates roughly to "Where is my credit card? What is its number? How badly do I actually want this thing, anyway?".
When you're on an iOS device, this ennui intensifies: there's a good chance you don't have your card with you, and holding your credit card and typing on your phone simultaneously is a feat best left to gymnasts and astronauts. (I'm sort of joking, but also willing to bet Apple has measured this in a lab somewhere.)
And if you're a developer accepting credit card payments in your app, this unfortunate phenomenon is directly correlated to lost revenue.
Apple Pay changes all this. Though a lot of the attention around its launch has focused on physical payments in stores (where customers can use their iPhone to pay at NFC-enabled payment terminals), there's an equally large opportunity for iOS developers to improve the checkout experience in their apps.
Just a reminder: if you're selling digital goods or virtual currencies in your app, you should use In-App Purchases—not Apple Pay—to sell your content (See section 11.2 of the App Store Review Guidelines). You can use Apple Pay for selling physical goods and services.
Obtaining an Apple Merchant ID
You'll have to register an Apple Merchant ID before testing anything out. Before doing this, you should choose a payment provider to actually handle your credit card processing. Apple provides a list of recommended ones at their Apple Pay Developer Page (disclosure: I work for Stripe, one of the recommended companies, but the code in this article doesn't depend on you choosing any specific provider). While your provider should have a specific guide for how to get set up using Apple Pay with their platform, the process looks like this:
- Head to the
Certificates, Identifiers, and Profiles
section of the Apple Developer center and create a new merchant ID. - Next, head to the Certificates section and create a new Apple Pay Certificate. This involves uploading a Certificate Signing Request (CSR) to Apple. When you're signing up for a payment processor, they'll typically give you a CSR to use. You can use a CSR that you generate yourself to follow along with this guide, but your payment processor won't be able to decrypt payments made with it and you'll need to redo it later.
- In Xcode, go to the "Capabilities" section of your project settings and turn "Apple Pay" to on. You may have to select the merchant ID you created earlier from the list provided.
Making Your First Charge
Apple Pay will only work on an iOS device capable of using Apple Pay (e.g. iPhone 6/6+, iPad Mini 3, iPad Air 2). In addition, you have to have successfully added the Apple Pay entitlement (described in "Obtaining an Apple Merchant ID") in order to test it in your app. If you'd like to approximate its behavior on the simulator, you can find a testing library that mimics its functionality (with test credit card details) at https://github.com/stripe/ApplePayStubs.
Once you're up and running with a merchant account, getting started with Apple Pay is really straightforward. When it's time to check out, you'll first need to see if Apple Pay is supported on the device you're running and your customer has added any cards to Passbook:
letpaymentNetworks=[PKPaymentNetworkAmex,PKPaymentNetworkMasterCard,PKPaymentNetworkVisa]ifPKPaymentAuthorizationViewController.canMakePaymentsUsingNetworks(paymentNetworks){// Pay is available!}else{// Show your own credit card form.}
Assuming Apple Pay is available, the next step is to assemble a PKPaymentRequest
. This object describes the charge you're requesting from your customer. If you're requesting payment in the U.S. (reasonable, as Apple Pay is currently US-only), here's some default options you'll need to set that'll likely stay constant:
letrequest=PKPaymentRequest()request.supportedNetworks=[PKPaymentNetworkAmex,PKPaymentNetworkMasterCard,PKPaymentNetworkVisa]request.countryCode="US"request.currencyCode="USD"request.merchantIdentifier="<#Replace me with your Apple Merchant ID#>"request.merchantCapabilities=.Capability3DS
Next, describe the things the customer is actually buying with the paymentSummaryItems
property. This takes an array of PKPaymentSummaryItem
s, which have a label
and amount
. They're analogous to line items on a receipt (which we'll see momentarily).
letwax=PKPaymentSummaryItem(label:"Mustache Wax",amount:NSDecimalNumber(string:"10.00"))letdiscount=PKPaymentSummaryItem(label:"Discount",amount:NSDecimalNumber(string:"-1.00"))lettotalAmount=wax.amount.decimalNumberByAdding(discount.amount).decimalNumberByAdding(shipping.amount)lettotal=PKPaymentSummaryItem(label:"NSHipster",amount:totalAmount)request.paymentSummaryItems=[wax,discount,shipping,total]
Note that you can specify zero or negative amounts here to apply coupons or communicate other information. However, the total amount requested must be greater than zero. You'll note we use a PKShippingMethod
(inherits from PKPaymentSummaryItem
) to describe our shipping item. More on this later.
Next, to display the actual payment sheet to the customer, we create an instance of PKPaymentAuthorizationViewController
with our PKPaymentRequest
and present it. (Assume for this example that all this code is inside a UIViewController
that will sit behind the payment screen).
letviewController=PKPaymentAuthorizationViewController(paymentRequest:request)viewController.delegate=selfpresentViewController(viewController,animated:true,completion:nil)
A few style nits to be aware of:
- The view controller doesn't fully obscure the screen (in this case the blue background is part of our application). You can update the background view controller while the
PKPaymentAuthorizationViewController
is visible if you want. - All text is automatically capitalized.
- The final line item is separated from the rest, and is intended to display the total amount you're charging. The label will be prepended with the word "PAY", so it usually makes sense to put your company name for the payment summary item's
label
. - The entire UI is presented via a Remote View Controller. This means that outside the
PKPaymentRequest
you give it, it's impossible to otherwise style or modify the contents of this view.
PKPaymentAuthorizationViewControllerDelegate
In order to actually handle the payment information returned by the PKPaymentAuthorizationViewController
, you need to implement the PKPaymentAuthorizationViewControllerDelegate
protocol. This has 2 required methods, -(void)paymentAuthorizationViewController:didAuthorizePayment:completion:
and -(void)paymentAuthorizationViewControllerDidFinish:
.
To understand how each of these components work, let's check out a timeline of how an Apple Pay purchase works:
- You present a
PKPaymentAuthorizationViewController
as described above. - The customer approves the purchase using Touch ID (or, if that fails 3 times, by entering their passcode).
- The thumbprint icon turns into a spinner, with the label "Processing"
- Your delegate receives the
paymentAuthorizationViewController:didAuthorizePayment:completion:
callback. - Your application communicates asynchronously with your payment processor and website backend to actually make a charge with those payment details. Once this complete, you invoke the
completion
handler that you're given as a parameter with eitherPKPaymentAuthorizationStatus.Success
orPKPaymentAuthorizationStatus.Failure
depending on the result. - The
PKPaymentAuthorizationViewController
spinner animates into a success or failure icon. If successful, a notification will arrive from PassBook indicating a charge on the customer's credit card. - Your delegate receives the
paymentAuthorizationViewControllerDidFinish:
callback. It is then responsible for callingdismissViewControllerAnimated:completion
to dismiss the payment screen.
Concretely, this comes out looking like this:
// MARK: - PKPaymentAuthorizationViewControllerDelegatefuncpaymentAuthorizationViewController(controller:PKPaymentAuthorizationViewController!,didAuthorizePaymentpayment:PKPayment!,completion:((PKPaymentAuthorizationStatus)->Void)!){// Use your payment processor's SDK to finish charging your customer.// When this is done, call completion(PKPaymentAuthorizationStatus.Success)}funcpaymentAuthorizationViewControllerDidFinish(controller:PKPaymentAuthorizationViewController!){dismissViewControllerAnimated(true,completion:nil)}
Here, the processPayment:payment completion:
method is your own code, and would leverage your payment processor's SDK to finish the charge.
Dynamic Shipping Methods and Pricing
If you're using Apple Pay to let your customer buy physical goods, you might want to offer them different shipping options. You can do this by setting the shippingMethods
property on PKPaymentRequest
. Then, you can respond to your customer's selection by implementing the optional PKPaymentAuthorizationViewControllerDelegate
method, paymentAuthorizationViewController:didSelectShippingMethod:completion:
. This method follows a similar pattern to the didAuthorizePayment
method described above, where you're allowed to do asynchronous work and then call a callback with an updated array of PKPaymentSummaryItem
s that includes the customer's desired shipping method. (Remember from earlier that PKShippingMethod
inherits from PKPaymentSummaryItem
? This is really helpful here!)
Here's a modified version of our earlier example, implemented as a computed property on the view controller and helper function:
varpaymentRequest:PKPaymentRequest{letrequest=...// initialize as beforeletfreeShipping=PKShippingMethod(label:"Free Shipping",amount:NSDecimalNumber(string:"0"))freeShipping.identifier="freeshipping"freeShipping.detail="Arrives in 6-8 weeks"letexpressShipping=PKShippingMethod(label:"Express Shipping",amount:NSDecimalNumber(string:"10.00"))expressShipping.identifier="expressshipping"expressShipping.detail="Arrives in 2-3 days"request.shippingMethods=[freeShipping,expressShipping]request.paymentSummaryItems=paymentSummaryItemsForShippingMethod(freeShipping)returnrequest}funcpaymentSummaryItemsForShippingMethod(shipping:PKShippingMethod)->([PKPaymentSummaryItem]){letwax=PKPaymentSummaryItem(label:"Mustache Wax",amount:NSDecimalNumber(string:"10.00"))letdiscount=PKPaymentSummaryItem(label:"Discount",amount:NSDecimalNumber(string:"-1.00"))lettotalAmount=wax.amount.decimalNumberByAdding(discount.amount).decimalNumberByAdding(shipping.amount)lettotal=PKPaymentSummaryItem(label:"NSHipster",amount:totalAmount)return[wax,discount,shipping,total]}// MARK: - PKPaymentAuthorizationViewControllerDelegatefuncpaymentAuthorizationViewController(controller:PKPaymentAuthorizationViewController!,didSelectShippingMethodshippingMethod:PKShippingMethod!,completion:((PKPaymentAuthorizationStatus,[AnyObject]!)->Void)!){completion(PKPaymentAuthorizationStatus.Success,paymentSummaryItemsForShippingMethod(shippingMethod))}
In this example, the customer will get the option to choose either free or express shipping—and the price they're quoted will adjust accordingly as they change their selection.
But wait, there's more!
Instead of having to provide a bunch of flat-rate shipping options, you can let your customer choose their shipping address and then calculate shipping rates dynamically based on that. To do that, you'll first need to set the requiredShippingAddressFields
property on your PKPaymentRequest
. This can represent any combination of PKAddressField.Email
, .PhoneNumber
, and .PostalAddress
.
Alternatively, if you don't need the user's full mailing address but need to collect some contact information (like an email address to send receipts to), this is a good way to do it.
When this field is set, a new "Shipping Address" row appears in the payment UI that allows the customer to choose one of their saved addresses. Every time they choose one, the (aptly named) paymentAuthorizationViewController:didSelectShippingAddress:completion:
message will be sent to your PKPaymentAuthorizationViewControllerDelegate
.
Here, you should calculate the shipping rates for the selected address and then call the completion
callback with 3 arguments:
- The result of the call
PKPaymentAuthorizationStatus.Success
if successful- `
PKPaymentAuthorizationStatus.
Failure` if a connection error occurs .InvalidShippingPostalAddress
if the API returns an empty array (i.e. shipping to that address is impossible).
- An array of
PKShippingMethod
s representing the customer's available shipping options - A new array of
PKPaymentSummaryItem
s that contains one of the shipping methods
I've set up a really simple web backend that queries the EasyPost API for shipping rates to a given address. The source is available at https://github.com/jflinter/example-shipping-api.
Here's a function to query it, using Alamofire:
importAddressBookimportPassKitimportAlamofirefuncaddressesForRecord(record:ABRecord)->[[String:String]]{varaddresses:[[String:String]]=[]letvalues:ABMultiValue=ABRecordCopyValue(record,kABPersonAddressProperty).takeRetainedValue()forindexin0..<ABMultiValueGetCount(values){ifletaddress=ABMultiValueCopyValueAtIndex(values,index).takeRetainedValue()as?[String:String]{addresses.append(address)}}returnaddresses}funcfetchShippingMethodsForAddress(address:[String:String],completion:([PKShippingMethod]?)->Void){letparameters=["street":address[kABPersonAddressStreetKey]??"","city":address[kABPersonAddressCityKey]??"","state":address[kABPersonAddressStateKey]??"","zip":address[kABPersonAddressZIPKey]??"","country":address[kABPersonAddressCountryKey]??""]Alamofire.request(.GET,"http://example.com",parameters:parameters).responseJSON{(_,_,JSON,_)inifletrates=JSONas?[[String:String]]{letshippingMethods=map(rates){(rate)->PKShippingMethodinletidentifier=rate["id"]letcarrier=rate["carrier"]??"Unknown Carrier"letservice=rate["service"]??"Unknown Service"letamount=NSDecimalNumber(string:rate["amount"])letarrival=rate["formatted_arrival_date"]??"Unknown Arrival"letshippingMethod=PKShippingMethod(label:"\(carrier) \(service)",amount:amount)shippingMethod.identifier=identifiershippingMethod.detail=arrivalreturnshippingMethod}}}}
With this, it's simple to implement PKPaymentAuthorizationViewControllerDelegate
:
funcpaymentAuthorizationViewController(controller:PKPaymentAuthorizationViewController!,didSelectShippingAddressrecord:ABRecord!,completion:((PKPaymentAuthorizationStatus,[AnyObject]!,[AnyObject]!)->Void)!){ifletaddress=addressesForRecord(record).first{fetchShippingMethodsForAddress(address){(shippingMethods)inswitchshippingMethods?.count{case.None:completion(PKPaymentAuthorizationStatus.Failure,nil,nil)case.Some(0):completion(PKPaymentAuthorizationStatus.InvalidShippingPostalAddress,nil,nil)default:completion(PKPaymentAuthorizationStatus.Success,shippingMethods,self.paymentSummaryItemsForShippingMethod(shippingMethods!.first!))}}}else{completion(PKPaymentAuthorizationStatus.Failure,nil,nil)}}
Now, the customer can select an address and receive a different set of shipping options depending on where they live. Both the shippingAddress
and shippingMethod
they ultimately select will be available as properties on the PKPayment
that is given to your delegate in the paymentAuthorizationViewController:didAuthorizePayment:completion:
method.
You can find all of the source code in this article at https://github.com/jflinter/ApplePayExample.
Even though Apple Pay only exposes a small number of public APIs, its possible applications are wide-ranging and you can customize your checkout flow to fit your app. It even enables you to build new types of flows, such as letting your customers buy stuff without having to create an account first.
As more apps start using Apple Pay (and as more customers own devices that support it), it'll become a ubiquitous way of paying for things in iOS apps. I'm excited to hear what you build with Apple Pay— if you have any questions, or want to show anything off, please get in touch!