iOSエンジニアの@kitasukeです。 USのアプリでApple Payに対応したので、その実装方法やその中で得た知見について紹介します。
背景
USではApple Payに対応しているアプリが徐々に増えています。また実店舗でも使えるところがあり、日常生活でApple Payを目にする機会が多くなっています。USのメルカリアプリでは決済方法としてPayPalとクレジットカードをサポートしていますが、ユーザーの購入体験をより向上するためにApple Payに対応しました。
Apple Payとは
Apple Payとは、Appleが提供する簡単で安全な決済サービスです。
サポートされている国は、アメリカ、イギリス、オーストラリア、中国、シンガポールです。サポートされているカードは、指定の銀行やストアから発行されているVisa, MasterCard, American Express, Discover, China Union Payのクレジットカードとデビットカードです。サポートされているデバイスは、iPhone SE, iPhone 6, iPhone 6 Plusかそれ以降のiPhoneや、iPad Pro, iPad Air 2, iPad mini 3かそれ以降のiPadや、該当するiPhoneとペアリングしているApple Watchです。詳しくはこちらを参照ください。*1
Apple Payの仕組み
Apple Payで使用されるカード情報はWalletアプリで安全に管理されています。カード情報に紐付けて、請求先や配送先や連絡先を保存できます。
実際に支払う際は、PassKit
フレームワークを使って支払いリクエストを生成します。ユーザーがそのリクエストをアプリ内で許可すると、Apple Pay用の証明書を使って暗号化された支払い情報やトークンがアプリケーションサーバー経由でAppleサーバーに送信され、その結果がアプリに返ってきます。 Appleはペイメントプロバイダーが提供しているSDKの利用を推奨しています。メルカリではBraintreeのSDKを利用して実装しています。
Using a payment provider that supports Apple Pay with a SDK is highly recommended. The alternative is to provide your own server-side solution to receive payments from your app, decrypt payment tokens and interface with the payment provider. Handling credit and debit card payments can be complicated. If you do not have the expertise and systems in place, an SDK from a payment provider is the quickest and most reliable way to support Apple Pay in your app.
Apple Payの決済フロー
基本的に以下の通りになります。
- Apple Payのセットアップ(セットアップ済みでない場合)
- ユーザーがApple Pay購入ボタンを押す
- ペイメントシートが表示される
- ユーザーがペイメントシートに情報を入力する
- ユーザーが支払いを許可する
- トークンを受け取る
- 証明書を使ってトークンを暗号化する
- トークンをアプリケーションサーバーに送信する
- アプリケーションサーバーで決済処理を完了する
- 購入完了画面の表示
アプリ内で行うことは、主にペイメントシートの操作とトークンの送信です。
Apple Payの実装について
ここからは実装する上で必要な情報を記載します。
ヒューマンインターフェースガイド
まず最初に、ヒューマンインターフェースガイドから大事なポイントを紹介します。アプリの審査に関係するところなので、ドキュメントの全てに目を通すことをお勧めします。
リソースについて
Appleはいくつかのリソースを提供しています。Apple PayボタンとApple Payマークです。
Apple Payボタン
Apple Payボタンとは、Apple Payで購入する際やセットアップする際に使用されるボタンです。
購入ボタンは、iOS 8.3以上の場合はPKPaymentButton
クラスからボタンを生成して、iOS 8.3より前の場合は以下のリンクからリソースをダウンロードして使用します。
セットアップボタンは、iOS 9以上の場合はPKPaymentButton
クラスを利用して、それ以前の場合は購入ボタン同様に下記のリソースを使用します。
https://developer.apple.com/apple-pay/
Apple Payでの購入ボタンのサイズは、通常の購入ボタンと同じサイズかそれ以上にする必要があります。
色は黒と白の2色あり、画像上に表示されるテキストに細かい違いもあります。
メルカリでは以下のように表示しています。
購入画面
Apple Payマーク
Apple Payマークは、Apple Payでの決済だと示すマークです。主に決済方法選択画面で使用されます。
また、Apple Payのプロモーションにもこのマークを使用できます。
画像のリソースは上記のApple Payボタンと同じ場所に用意されています。
メルカリでは以下のように表示しています。
決済方法選択画面
リソースについての詳細は、こちらのドキュメントを参照してください。
https://developer.apple.com/apple-pay/Apple-Pay-Identity-Guidelines.pdf
ペイメントシート
ペイメントシートには購入するために必要な情報を表示します。
メルカリでは、カード情報、商品の配送先、配送方法、値段を表示しています。
他には連絡先や請求先を表示することも可能です。
値段について、総額のみ表示するのではなく配送料や割引額など全て表示する必要があります。
また、マーチャント名をPAYの文字の後ろに表示する必要があります。
購入が完了した後に購入完了画面を表示して、配送状況などを説明することが推奨されています。
Display an order confirmation or thank you page. To provide a straightforward user experience, after the payment is complete, use an order confirmation page to display details about when the merchandise will ship and how users can check the status of their order.
詳細はこちらのドキュメントを参照してください。
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/ApplePay.htmldeveloper.apple.com
証明書の準備
Apple Payに関する情報の暗号化のために、下記の準備が必要です。
- デベロッパーセンターでMerchant IDの作成
- Merchant IDから証明書の作成
- CapabilitiesタブのApple Payを有効にして、適切なMarchant IDの選択
Braintreeを利用する場合は、上記の証明書をBraintreeの管理画面にアップロードする必要があります。
セットアップ
Apple Payボタンを表示する際は、以下の2点を必ず確認する必要があります。
- 端末がApple Payに対応しているか
- Apple Payがセットアップされているか
上記の点を確認するには、PKPaymentAuthorizationViewController
が提供しているメソッドを利用します。
class func canMakePayments() -> Bool
canMakePayments()
は、端末がApple Payに対応していればtrue
を返します。
class func canMakePaymentsUsingNetworks(_ supportedNetworks: [String]) -> Bool
canMakePaymentsUsingNetworks(_:)
は、引数で渡したカードのどれかが使える状態、つまりセットアップ済みであればtrue
を返します。
下記が用意されているカード一覧です。OSによってサポートされている種類が違うので注意してください。
@available(iOS 8.0, *) public let PKPaymentNetworkAmex: String @available(iOS 9.2, *) public let PKPaymentNetworkChinaUnionPay: String @available(iOS 9.0, *) public let PKPaymentNetworkDiscover: String @available(iOS 9.2, *) public let PKPaymentNetworkInterac: String @available(iOS 8.0, *) public let PKPaymentNetworkMasterCard: String @available(iOS 9.0, *) public let PKPaymentNetworkPrivateLabel: String @available(iOS 8.0, *) public let PKPaymentNetworkVisa: String
この2つのメソッドを利用して、セットアップボタン・購入ボタンの表示をハンドリングします。
セットアップボタンをタップすると、Walletアプリに遷移させる必要があります。
PKPassLibrary
が提供するopenPaymentSetup()
を呼ぶと遷移できます。しかし、このメソッドはiOS 8.3以上でしか使えないので注意してください。
購入ボタンをタップすると、ペイメントシートを表示します。
ペイメントシート
ここからは、ペイメントシートの操作方法を説明します。
PKPaymentRequest
から生成したリクエストをPKPaymentAuthorizationViewController
に渡してペイメントシートを表示します。
まずはリクエストに必要な情報をセットします。
paymentRequest.currencyCode = "USD" // 通貨 paymentRequest.countryCode = "US" // 国 paymentRequest.merchantIdentifier = "merchant.com.example" // Marchant ID paymentRequest.supportedNetworks = [PKPaymentNetworkVisa, PKPaymentNetworkMasterCard] // 対応カード paymentRequest.merchantCapabilities = [.Capability3DS] // プロトコル
次にシートに表示する項目をセットします。
請求先を必要とする場合はrequiredBillingAddressFields
、配送先を必要とする場合はrequiredShippingAddressFields
に以下の中から適切な値をセットしてください。
paymentRequest.requiredShippingAddressFields = [.PostalAddress] struct PKAddressField : OptionSetType { init(rawValue rawValue: UInt) static var None: PKAddressField { get } static var PostalAddress: PKAddressField { get } static var Phone: PKAddressField { get } static var Email: PKAddressField { get } static var Name: PKAddressField { get } static var All: PKAddressField { get } }
メルカリのように、商品を配送する場合はshippingMethods
とshippingType
も必要です。
let shippingMethod = PKShippingMethod(label: "FedEx", amount: .zero()) shippingMethod.identifier = "FedEx" shippingMethod.detail = "June 13 - 18" paymentRequest.shippingMethods = [shippingMethod]
ピックアップなどにも対応しているのであれば、該当するshippingType
をセットします。
enum PKShippingType : UInt { case Shipping case Delivery case StorePickup case ServicePickup }
最後に値段を表示します。今回の例では、商品の金額、配送料、割引額、合計の4つを表示します。
let itemPrice = PKPaymentSummaryItem(label: "Item Price", amount: NSDecimalNumber(int: 35)) // 商品の金額 let shippingFee = PKPaymentSummaryItem(label: "Shipping", amount: .zero()) // 配送料 let discountAmount = PKPaymentSummaryItem(label: "Balance", amount: NSDecimalNumber(int: 2)) // 割引額 let totalAmount = PKPaymentSummaryItem(label: "VIA Mercari", amount: NSDecimalNumber(int: 33)) // 合計 paymentRequest.paymentSummaryItems = [itemPrice, shippingFee, discountAmount, totalAmount]
あとはペイメントシートを表示する処理です。
let viewController = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest) viewController.delegate = self navigationController.presentViewController(viewController, animated: true, completion: nil)
これで以下のようにペイメントシートが表示されます。
ここまでがペイメントシートに表示する内容の例です。
次に、ペイメントシート上で入力された情報の処理について説明します。
PKPaymentAuthorizationViewControllerDelegate
には色々なデリゲートメソッドが用意されています。
今回紹介するのは、メルカリで使用しているものだけ抜粋します。
まずは、配送先の取得についてです。
配送先の入力は、ペイメントシート上か連絡先アプリから追加するかの2通りです。アプリ内で保持している配送先情報のプリセットはできないようです。
入力画面は以下の通りです。どちらの方法にせよ、Apple側では住所のバリデーションは一切行われないので、厳密なバリデーションはアプリケーションサーバー上でやるとしても簡単なバリデーションはアプリ内で行う方が良いと思います。
配送先の入力値は、ペイメントシートに戻る際に呼ばれるデリゲートメソッドで取得できます。
@available(iOS, introduced=8.0, deprecated=9.0, message="Use the CNContact backed delegate method instead") optional public func paymentAuthorizationViewController(controller: PKPaymentAuthorizationViewController, didSelectShippingAddress address: ABRecord, completion: (PKPaymentAuthorizationStatus, [PKShippingMethod], [PKPaymentSummaryItem]) -> Void) @available(iOS 9.0, *) optional public func paymentAuthorizationViewController(controller: PKPaymentAuthorizationViewController, didSelectShippingContact contact: PKContact, completion: (PKPaymentAuthorizationStatus, [PKShippingMethod], [PKPaymentSummaryItem]) -> Void)
プライバシーの観点で、この段階では全ての住所を取得できません。
To maintain privacy, the shipping information provided in paymentAuthorizationViewController:didSelectShippingContact:completion: is anonymized. The returned contact contains enough information to calculate shipping costs, without revealing sensitive information about the user. You do not get the user’s full shipping information until after the user approves the payment. Additionally, the data available in the contact changes from country to country, and can change from release to release. Be sure to test your app appropriately.
ここでは空文字、郵便番号の桁数、国名などをチェックしています。
バリデーションエラーの場合はcompletion
の第一引数にInvalidShippingPostalAddress
を渡して、そうでなければSuccess
を渡します。
エラーの場合はペイメントシートの表示が以下のようになります。
次に、ユーザーが支払いを許可した際に呼ばれるデリゲートメソッドです。
public func paymentAuthorizationViewController(controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, completion: (PKPaymentAuthorizationStatus) -> Void)
このPKPayment
オブジェクトには全ての住所が含まれているので、もう一度住所のバリデーションをし直します。
その後は、アプリケーションサーバー上での住所バリデーションチェックと、Apple Payの決済処理を行います。
メルカリでは住所のバリデーションが通らない場合は以下のようにエラー画面を表示して、決済処理を進めないようにしています。
最後に、Apple Payの処理が終わった際に呼ばれるデリゲートメソッドです。
全ての処理が完了したので、ペイメントシートを閉じましょう。
public func paymentAuthorizationViewControllerDidFinish(controller: PKPaymentAuthorizationViewController)
気をつけたポイント
対応OSの選定
メルカリではiOS 9以上の端末のみApple Payをサポートしています。工数をかける割に効果が低いと判断して、iOS 9以上の端末のみにしました。
- ボタンの画像をプロジェクトに追加するとアプリサイズが増える
- アプリからApple Payのセットアップページに遷移させたいケースがある
- 住所オブジェクトを扱う場合に
Contacts
とAddressBook
のフレームワークをOSによって使い分ける必要がある - USではiOS 8端末の使用率が極端に低い
購入フロー
メルカリでは大まかに3パターン用意しています。ユーザーによって適切な内容を表示することが求められます。
Apple Payセットアップ済みのユーザー
決済方法選択画面にApple Payを選択肢として表示します。メルカリでの購入が初めてのユーザーはApple Payをデフォルトの決済方法にして、それ以外は最後に使用した方法をデフォルトにしています。
Apple Payの購入ボタンは、セットアップ済みかつ決済方法がApple Payの場合のみ表示しています。
Apple Payセットアップ済みでは無いユーザー
決済方法選択画面にApple Payのセットアップボタンを表示します。WalletアプリでApple Payのセットアップをした後は、セットアップボタンを非表示にします。
Apple Payに対応している端末では無いユーザー
Apple Payのセットアップボタンを表示しません。もちろんApple Payの購入ボタンも表示されることはありません。
決済機能以外の対応
Apple Payはあくまで決済処理しか面倒を見てくれません。メルカリのように商品を発送する場合は、住所のバリデーションが必須となります。決済が簡単に出来るようになっても商品が届かなければ意味がありません。
アプリにとって購入に必要な処理を適切に行いましょう。
まとめ
いくつか気をつけるポイントはありますが、思ったより楽に実装できました。シミュレーターでApple Payの決済もテスト出来るので、実機検証もそこまで苦労しないと思います。
この機能をローンチして、徐々にApple Payで決済するユーザーも増えていますし、他のアプリでApple Payを使っているユーザーにとっては相性が良いと思います。逆にApple Payの決済に対応していないと、ユーザーが離脱するという可能性もあるかもしれません。
日本でもApple Payが年内に使えるようになるという噂もあるので、多くのアプリで対応されるとユーザーも幸せになると思います。
*1:2016年5月時点