The Journey of Apple Pay at JUST EAT
The original post is published on the JUST EAT tech blog at the following URL http://tech.just-eat.com/2015/07/14/the-journey-of-apple-pay-at-just-eat/
Apple Pay has recently been released in UK and at JUST EAT we worked on the integration in the iOS app to better support all of our customers and to ease the experience to both existing and new users. Until version 10 of our iOS UK app, the checkout for completing an order was wrapped into a webview and the flow was as follows:
Since Apple pushes developers to implement Apple Pay in a way that the checkout doesn’t force the user to log in, the checkout flow had to be reworked, and we took the opportunity to make the majority of the checkout flow native. This enabled us to support both checkout flows:
- standard checkout (now with a more native flavour)
- Apple Pay checkout
The latter is clearly a fantastic solution for completing the checkout in very few steps with a great and simple UX. Thanks to the information provided by Apple Pay (inserted by the user when registering a debit/credit card) the user details native screen is no longer necessary and more importantly for the user, there is no need to log in to the platform.
A further detail on the checkout is that we support two different so-called “service types” for the orders: delivery and collection. Defined as so:
1 2 3 4 5 6
On a side note, these changes soon became a challenge during the development as JUST EAT need to treat Apple Pay users (guest users) in a similar manner to users that have registered previously to our service.
How we designed around Apple Pay
At the time of writing there are already a few very good articles about a basic integration with Apple Pay. Probably the best reference worth mentioning is the NSHipster post.
Rather than discussing the basic concepts (creating the merchant ID, configuring the
PKPaymentRequest object, handling the presentation of the PKPaymentAuthorizationViewController, sending the token to the Payment Service Provider, etc.), we think it’d be more useful to walk you through the architectural aspects we considered when designing the solution on iOS using Objective-C.
In the architecture we are proposing, the relevant components for handling an Apple Pay payment are the following:
Some additional components are also present in the big picture:
N.B. We haven’t used the iOS SDK provided by our PSP (Payment Service Provider) to communicate directly to it from the iOS app, but rather we rely on a payment API to complete this communication.
At JUST EAT we like dependency injection and composition when possible. Developing this new feature with these concepts in mind helped to develop components that are isolated, easily pluggable, easy to test and (sometimes) reusable.
The above components have well-defined responsibilities. We’ll provide simplified code for the interfaces. Let’s go through them in a constructive order:
N.B. Don’t be alarmed if you see the usage of the
JEFuture or the
JEProgress symbols. Lots of parts in our codebase rely on JustPromises (the library about Future and Promises we open sourced on GitHub). You’ll also see the usage of some DTOs and the
- CheckoutService: responsible for handling the basic flow for the checkout. This is very much platform dependant. It could include the logic to perform the necessary actions in the backend to prepare the order to be completed with Apple Pay. In our case we need to store the user delivery notes (things like “The door bell doesn’t work please call me when you arrive.”) and the preferred time for the delivery.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
- ApplePayPaymentRequestFactory: responsible for creating the
PKPaymentRequestobjects representing a transaction. In our case objects of this kind are initialised with a delivery method and a card fee. The input parameter for the method returning a
PKPaymentRequestis a representation of the basket.
1 2 3 4 5 6 7 8 9 10
You might wonder why
summaryItemsForBasket: is public rather than keep it private. The reason is related to the fact that the block parameter of
paymentAuthorizationViewController:didSelectShippingAddress:completion: has the following signature:
1 2 3 4
It might very well be that some items are not available to be shipped to a specific address, and therefore we need a way to provide the updated list of summary items for the new shipping address the user selected.
- ApplePayPaymentHandler: objects of this class are responsible for handling the payment from beginning to end. It’s initialised with a CheckoutService that covers the initial part of the flow (i.e. the steps that happen with the standard checkout). A
PKPaymentRequestand a basket representation are provided (along with some other minor details) to objects of this class when asked to actually process a payment.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
- ApplePayService: this service is responsible for implementing the
PKPaymentAuthorizationViewControllerDelegateprotocol, for checking if Apple Pay is enabled on the device and for cancelling the payment (if it’s not too late). It’s initialised with a view controller (used to display the
JEApplePayPaymentHandler. The logic for handling the selection of shipping address or the shipping method is here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
- ABRecordRefConverter: just a bunch of class methods for isolating the logic for transforming the
ABRecordRefto easy-to-use DTOs. Until iOS 8.4, the delegate method
ABRecordRef. Starting with iOS 9.0, this method is deprecated and a similar one using a wrapper object (
PKContact) on top of the Contacts framework is used.
ABRecordRefConverterbasically does the work that Apple did for us in iOS 9.
1 2 3 4 5 6 7 8 9
Similar to what happened in the
ApplePayPaymentRequestFactory, you might wonder why the
addressForABRecordRef: method is necessary. The reason is that the second parameter of
paymentAuthorizationViewController:didSelectShippingAddress:completion: is an
ABRecordRef populated with only the address information for privacy reasons.
- PaymentFlowController: it is responsible for creating the necessary stack and interactions between of all the above components. It acts as the delegate of the
ApplePayServiceto handle the navigation flow and it should be intended to be the starting point and glue around our standard checkout flow and the Apple Pay one.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
We strongly believe in unit testing (and automation and integration testing as well) and we strive to cover our code with the necessary tests for every new feature we develop. Payments are clearly a hot topic and a crucial part of our business, therefore structuring this feature in small and separated components allowed us to easily keep the code coverage for the above components close to 100%.
The way the payment request is populated dictates what the Apple Pay sheet will display. How to populate the shipping and billing address properties is not completely straightforward. An excerpt of code we have in production is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Using the above configuration the Apple Pay sheets for delivery and collection orders appear like so:
Note that, in the case of collection orders, even if we set the
requiredShippingAddressFields property to something meaningful (which isn’t
PKAddressFieldNone), the associated cell in the sheet is not displayed.
At JUST EAT we need to know upfront if the order is for delivery or collection in order to let the user fill the basket accordingly (e.g. some items might not be available for delivery) and for this reason we couldn’t leverage the built-in capabilities of Apple Pay to handle different shipping methods. Moreover, since Apple defines the shipping type like so:
1 2 3 4 5 6
for collection orders the correct value to use would be
PKShippingTypeStorePickup but the address of the store must be present in the list of addresses the user has entered on the device. This isn’t practical.
Going back to how the payment request is configured, it’s critical for JUST EAT to have the phone number and the email of the customer in order for customer services to contact them in case something goes wrong with the order. This applies to delivery orders as well as collection orders. At first we thought that, since for collection orders the shipping address was not necessary, the property
PKPaymentRequestcould be set to
PKAddressFieldNone and we could grab the phone number and email from the
Unfortunately, even setting the
PKAddressFieldAll when fetching the info from the
billingAddress property of the
PKPayment the phone number and email values are not there. Crucially, to grab the necessary order details info we had to merge the information provided by the two properties (
shippingAddress) as so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
From our journey into the Apple Pay world we learned that Apple pushes a lot for the Apple Pay button to be prominent in your app’s UI. Unless one starts an iOS application from scratch and provides only Apple Pay as a payment method, other methods are usually already available (cards, PayPal, etc.): showing the Apple Pay button as big as the other buttons leading to different payments is almost a mandatory requirement from Apple. Showing the button as a first option is equally important.
What is not so mandatory, but still very nice to have, is to provide the user with the ability to complete the checkout without logging in. This is a good thing for a few reasons:
- remove the friction and enable happy paths to help your customers pay quicker
- no need to collect data from the user any more, leveraging what’s already provided by Apple Pay
- Apple and customer happiness
As in our case, this is far from being trivial and logic in the backend usually needs to be tweaked accordingly to support what we call “guest” or “anonymous” users.
On a side note, it wasn’t completely clear from the beginning that the
ABRecordRef object provided in the
paymentAuthorizationViewController:didSelectShippingAddress:completion: delegate method does not contain the full user data but just the address, but that is sufficient information to calculate the shipping cost and the availability of the items to a specific address. The entire user information is provided in the
PKPayment object via the
billingAddressproperties once the user authorised the payment.
In iOS 9 the API slightly changed mainly due to introduction of the Contacts framework and the deprecation of the AddressBook one. PassKit provides a new class
PKContact that is a wrapper around objects from the Contacts framework. This means that the following properties of
are deprecated in favour of the following new ones:
This will help a lot since the old
ABRecordRef is defined as a
CFTypeRef and the related APIs are not easy to consume in an object-oriented world.