アプリにApple Payを導入する - 商品購入のハードルを下げる -

Apple Payといえば、駅や実店舗での決済が注目されがちですが、iOSアプリやSafariから利用することもできます。

NFCを搭載していないiPhone 6/6sやiPhone SEも、アプリ・Safariでの決済には対応しています。[^1]

本稿では開発者の視点でアプリにApple Payを導入する方法を説明します。

![demo_n.gif](https://qiita-image-store.s3.amazonaws.com/0/50559/258af1b8-3ad3-e8bd-41af-dc0a8c2b5204.gif "demo_n.gif")


## アプリにApple Payを導入することのメリット
Apple Payは一度設定してしまえば、利用する各アプリではクレジットカード番号、配送先・連絡先などを入力せずに簡単に決済できます。
利用者がアプリ上から商品を購入する際のハードルを下げることにつながるでしょう。


## In-App Purchaseとの違い
Apple Payは現実の商品やサービスの販売する際に使用し、In-App Purchaseはアプリ内で利用する電子的な商品・サービスを販売する際に使用します。

* Apple Pay
    - 食料品、衣類、家電
    - クラブ会員、ホテル予約、イベントチケット

* In-App Purchase
    - ゲームの課金アイテム
    - アプリのプレミアム機能の開放


## 導入方法
それでは具体的な導入方法について説明していきます。


### 準備
実装する前に、まず以下の準備が必要です。

- マーチャントIDの登録
- マーチャントIDに紐づく証明書の作成
- 決済プラットフォームのアカウントを作成
- 証明書の作成・決済プラットフォームにアップロード
- テストカードの作成

#### マーチャントIDの登録
Apple Payによる決済を提供するにはマーチャントIDを登録する必要があります。
マーチャントとは商品・サービスの販売者のことです。

AppleのDeveloperサイトの
[Account]メニュー → [Certificates, IDs & Profiles] → Identifiersカテゴリの[Merchant IDs]
から登録できます。

https://developer.apple.com/account/ios/identifier/merchant

![merchant1.png](https://qiita-image-store.s3.amazonaws.com/0/50559/2f86a3c2-52f0-e9bf-9805-db726c1c6627.png "merchant1.png")

マーチャントIDはmerchantから始まるリバースドメイン形式が推奨されます。

```例：
merchant.com.example.merchantName
```

画面に従って進めば、特に迷うところはないはずです。


#### 決済プラットフォームのアカウントを作成
Apple Payでは決済処理を決済プラットフォーム（オンライン決済代行業者）を通して行います。したがって利用する決済プラットフォームのアカウントを作成する必要があります（厳密にいうと自分たちが決済インフラを保有する場合は別です）。

日本では以下の決済プラットフォームに対応しています（2017年3月現在 https://developer.apple.com/apple-pay/ ）。  

- GMO Payment Gateway
- PAY.JP
- SoftBank Payment Service
- Sony Payment Services
- Stripe
- VeriTrans

StripeやPAY.JPは初期費用無料で利用することができます。
2017年3月現在、残念ながらVISAブランドがアプリからのApple Pay決済に対応していないということもあり、今回はMastercardやAMEXにくわえJCBも利用可能なPAY.JPを選択してみました。

PAY.JP (https://pay.jp/)


#### 証明書の作成・決済プラットフォームにアップロード
マーチャントIDに紐づく証明書を作成し、決済プラットフォームにアップロードします。

詳しくは利用する決済プラットフォームのマニュアルに従っていただきたいのですが、大まかな流れは次のようになるかと思います。

1. 決済プラットフォームのサイトから証明書署名要求（CSR）をダウンロード
2. Apple Developerサイトで証明書を作成
3. 作成した証明書を決済プラットフォームのサイトでアップロード


#### テストカードの作成
テストのための決済カードで取引をテストするために、Apple Payサンドボックス環境が用意されています。

テストカードを作成するには、まずiTunes Connect（https://itunesconnect.apple.com/ ）にログインし、
[ユーザと役割] → [Sandboxテスター]でテストユーザーを登録します。

![TestUser.png](https://qiita-image-store.s3.amazonaws.com/0/50559/b117ba63-4fca-c077-109e-52500e843e08.png "TestUser.png")

つづいて、テストに使うiOS端末でiCloudの設定（[設定] → [iCloud]）を開き、先程登録したテストユーザーでサインインします。

サインインできたらWalletアプリを開き、以下のページの「Test Cards for Apps and the Web」のセクションにあるカード番号の一覧から任意の番号を登録します。

Apple Pay Sandbox Testing
https://developer.apple.com/support/apple-pay-sandbox/

![TestCard.png](https://qiita-image-store.s3.amazonaws.com/0/50559/b5c1ed40-9c13-f2f5-db23-030e829fd4a2.png "TestCard.png")


### 実装

ここからはXcodeを立ち上げて実装していきます。

#### Capabilitiesの設定
TARGETSのCapablilitiesでApple Payを有効にします。
Apple Payのトグルをオンにすると事前に作成したマーチャントIDが表示されるので、チェックボックスをオンにしてください。

![Capabilities.png](https://qiita-image-store.s3.amazonaws.com/0/50559/36fb404c-2471-b1e0-4314-84a8a225aa53.png "Capabilities.png")


#### PassKitをインポートする
Apple PayはPassKitの機能で構成されます。

```ViewController.swift
import PassKit
```


#### Apple Payボタンを配置する
![Button.png](https://qiita-image-store.s3.amazonaws.com/0/50559/e2546b19-bb6f-0d00-06de-29463af08d41.png "Button.png")

上のようなApple Payボタンを配置します。IBは対応していないので、コードで配置する必要があります。

```ViewController.swift
    // Apple Payボタン
    private lazy var paymentButton: PKPaymentButton = self.createPaymentButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Apple Payボタンをビューに追加
        view.addSubview(paymentButton)
        
        // 〜 略 〜
    }

    // Apple Payボタンを作成
    private func createPaymentButton() -> PKPaymentButton {
        // ボタン作成
        let button = PKPaymentButton(type: .plain, style: .black)
        // (Auto Layoutのため)
        button.translatesAutoresizingMaskIntoConstraints = false
        // アクション設定
        button.addTarget(self, action: #selector(ViewController.paymentBUttonTapped), for: .touchUpInside)
        
        return button
    }
    
```

PKPaymentButtonのイニシャライザでは、目的に応じてボタンのテキストを選択したり、色を指定することができます。

- type:
	+ plain → Apple Payのロゴのみ
	+ buy → “Buy with”のテキストとロゴ
	+ set​Up → “Set up”のテキストとロゴ
	+ in​Store → “Pay with”のテキストとロゴ
	+ donate → “Donate with”のテキストとロゴ
- style:
	+ white → 白
	+ white​Outline → 黒い枠線付きの白
	+ black → 黒

これらの指定はAppleのガイドラインに従う必要があります。[^2]

#### Apple Payを利用可能か確認する
ユーザーの端末がApple Payを利用できない状況では、Apple Payボタンを表示すべきではありません。

引数なしの`PKPaymentAuthorizationViewController.canMakePayments`メソッドで、Apple Payの利用可否を判定できます。
ここでの利用できない場合というのは、デバイスがApple Payに対応していない、または、ペアレンタルコントロールによって利用が制限されている場合が該当します。

さらにPKPaymentNetworkの配列を引数に取る`canMakePayments`メソッドで、対象のカードをユーザーが登録しているかどうかを確認することができます。
対象のカードが登録されていない場合、ユーザーに登録する意思があれば、`PKPassLibrary().openPaymentSetup()`でカード登録画面を開くこともできます。

```ViewController.swift
    // サポートするカードの種類
    private var paymentNetworksToSupport: [PKPaymentNetwork] {
        get {
            if #available(iOS 10.0, *) {
                return PKPaymentRequest.availableNetworks()
            } else {
                return [ .masterCard, .amex ]
            }
        }
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        if !isApplePayAvailable() {
            // Apple Payに対応してなければApple Payボタンを表示してはいけない
            paymentButton.isHidden = true
            
        } else if !isPaymentNetworksAvailable() {
            // サポート対象のカードをユーザーが登録していない
            paymentButton.isHidden = true
            // カードの設定を促す
            showSetupPrompt()
            
        } else {
            paymentButton.isHidden = false
        }
    }

    // デバイスがApple Payをサポートしているかどうか
    private func isApplePayAvailable() -> Bool {
        // 引数なしのcanMakePaymentsメソッドで、デバイスがApple Payをサポートしているか確認できる
        return PKPaymentAuthorizationViewController.canMakePayments()
    }
    
    // サポート対象のカードが登録されているかどうか
    private func isPaymentNetworksAvailable() -> Bool {
        // PKPaymentNetworkの配列を引数に取るcanMakePaymentsメソッドで、引数の種類のカードをユーザーが登録しているか確認できる
        return PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: paymentNetworksToSupport)
    }
    
    // カードの設定を促す
    private func showSetupPrompt() {
        let alert = UIAlertController(
            title: "利用できるカードが登録されていません",
            message: "カードを登録しますか？",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "はい", style: UIAlertActionStyle.default, handler: { action in
            // カードの設定画面（Walletアプリ）を開く
            PKPassLibrary().openPaymentSetup()
        }))
        alert.addAction(UIAlertAction(title: "いいえ", style: UIAlertActionStyle.cancel, handler: nil))
        present(alert, animated: true, completion: nil)
    }
    
```


#### ペイメントリクエストを作成する
Apple Payボタンがタップされた場合の処理を記述していきます。

Apple Payボタンがタップされたときは、決済に必要な要素をまとめたペイメントリクエストを作成し、それをもとに速やかにペイメントシートを表示します。
ペイメントリクエストは`PKPaymentRequest`クラスのインスタンスです。
詳細は以下のコードのコメントをご覧ください。

```ViewController.swift
    // Apple Payボタンタップ
    func paymentBUttonTapped() {
        let merchantIdentifier = "Appleのサイトで登録したマーチャントID"
        
        didPaymentSucceed = false
        
        // 決済の要求を作成
        let paymentRequest = PKPaymentRequest()
        paymentRequest.currencyCode = "JPY" // 通貨
        paymentRequest.countryCode = "JP"   // 国コード
        paymentRequest.merchantIdentifier = merchantIdentifier
        // サポートするカードの種類
        paymentRequest.supportedNetworks = paymentNetworksToSupport
        // プロトコル（3-D Secure必須）
        paymentRequest.merchantCapabilities = PKMerchantCapability.capability3DS
        // 支払いの内訳・合計を設定
        paymentRequest.paymentSummaryItems = getpaymentSummaryItems()
        
        // 要求する請求先の項目（オプション）
        paymentRequest.requiredBillingAddressFields = .postalAddress
        // 要求する配送先の項目（オプション）
        paymentRequest.requiredShippingAddressFields = [.postalAddress, .email]
        // 配送方法（オプション）
        paymentRequest.shippingMethods = getShipingMethods()
        
       // 〜 略 〜        
    }
    
    private func getpaymentSummaryItems() -> [PKPaymentSummaryItem] {
        // 商品価格・送料・割引額など、表示する支払いの内容を設定
        let item = PKPaymentSummaryItem(label: "バナナ", amount: NSDecimalNumber(string: "50"))
        let deliveryCharge = PKPaymentSummaryItem(label: "配送料", amount: 5)
        
        // 総額には会社名をセット（ref. https://developer.apple.com/reference/passkit/pkpaymentrequest/1619231-paymentsummaryitems）
        let total = PKPaymentSummaryItem(label: "(株)ゴリラのバナナ屋", amount: getAmount())
        
        // ※配列の最後のアイテムが総額として設定される
        return [item, deliveryCharge, total]
    }
    
    private func getShipingMethods() -> [PKShippingMethod] {
        // 配送方法の配列
        let shippingMethods = [PKShippingMethod(label: "黒い猫", amount: 50)]
        shippingMethods[0].identifier = "BlackCat"
        shippingMethods[0].detail = "詳細"
        
        return shippingMethods
    }

```

#### ペイメントシートを表示する
ペイメントシートを表示します。

```ViewController.swift
        // ペイメントシートを表示
        let paymentController = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest)
        paymentController.delegate = self
        present(paymentController, animated: true, completion: nil)
        
```

このとき、ペイメントシートにおけるユーザーの操作をハンドリングするためのデリゲートを指定します。
デリゲートは`PKPaymentAuthorizationViewControllerDelegate`プロトコルを実装したクラスです。
実装すべきデリゲートメソッドは後述します。

```ViewController.swift
class ViewController: UIViewController, PKPaymentAuthorizationViewControllerDelegate {
```

![paymentsheet.png](https://qiita-image-store.s3.amazonaws.com/0/50559/83645f17-db1b-a89d-0d0a-64a28b3f6416.png "paymentsheet.png")


#### 支払い承認時のデリゲートメソッドを実装する
PKPaymentAuthorizationControllerDelegateのなかでもっとも重要なメソッドは、ユーザーが支払いを承認したときに呼ばれる`​Authorization​View​Controller(_:​did​Authorize​Payment:​completion:​)`です。実装必須のデリゲートメソッドです。


PKPayment型の第2引数の`token`プロパティの値を使って決済プラットフォームと連携します。
この第2引数には、他にも請求先・配送先・配送方法と行った重要な情報が含まれています。

デリゲートメソッドの処理を終了するときには、第3引数のcompletion関数をステータスを引数として呼び出す必要があります。PKPaymentAuthorizationControllerDelegateの主要なメソッドは同様にcompletionを引数に持ちますが、PKPaymentAuthorizationControllerはこのcompletionが呼ばれるまで次のデリゲートメソッドの呼び出しを待機します。

以下はpayment​Authorization​Controller(_:​did​Authorize​Payment:​completion:​)の実装例です。決済プラットフォームと連携しつつ、アプリのサーバーとも通信しています。
実際の決済はバックエンド側で決済プラットフォームと連携します。

決済プラットフォームのSDKに依存した処理ですので参考程度にご覧ください。

```ViewController.swift
    // ユーザーが支払いを承認した（Touch IDまたはパスコードの入力）ときに呼ばれるデリゲートメソッド
    func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, completion: @escaping (PKPaymentAuthorizationStatus) -> Void) {
        
        // 受け取ったトークンを使って決済プラットフォームと連携し、決済処理を行う
        // 原則として決済プラットフォームが提供するSDKを用いる
        // 実際の決済はバックエンド側で決済プラットフォームと連携する
        // ここでは参考までにPAY.JPを利用する場合の処理を記述する
        // ↓↓↓
        let PAYJPPublicKey = "PAY.JPの設定画面で確認した公開鍵"
        
        let apiClient = PAYJP.APIClient(publicKey: PAYJPPublicKey)
        // Apple PayのペイメントトークンからPAY.JPのトークンを作成
        apiClient.createToken(with: payment.token) { (result) in
            switch result {
            case .success(let token):
                // PAY.JPのトークン作成成功
                
                // アプリのバックエンドと通信する
                var request = URLRequest(url: URL(string: "https://paymentBackEnd.Example.com/bananaapplepay/api/orders/")!)
                
                request.httpMethod = "POST"
                request.httpBody = "token=\(token.identifer)&amount=\(self.getAmount())&email=\(payment.shippingContact?.emailAddress ?? "")".data(using: .utf8)
                
                let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
                    if let error = error {
                        print("error: \(error.localizedDescription)")
                        completion(.failure)
                        return
                    }
                    
                    guard let httpResponse = response as? HTTPURLResponse  else {
                        completion(.failure)
                        return
                    }
                    
                    if 200...299 ~= httpResponse.statusCode {
                        // 決済処理が正常に完了
                        self.didPaymentSucceed = true
                        completion(.success)
                        
                    } else {
                        print("error: \(data!)")
                        completion(.failure)
                    }
                })
                
                task.resume()
                
            case .failure(let error):
                // PAY.JPのトークン作成失敗
                print("error: \(error.localizedDescription)")
                completion(.failure)
            }
        }
        // ↑↑↑
        // 決済プラットフォームとの連携処理ここまで
    }
```

支払いの承認処理が終了したときには`paymentAuthorizationViewControllerDidFinish`メソッドが呼ばれます。
実装必須のデリゲートメソッドです。

決済処理が成功した場合だけでなく、失敗時やキャンセル時も最終的にこのメソッドで処理します。

```ViewController.swift
        // ペイメントシートを閉じる
        controller.dismiss(animated: true,completion: nil)
        
        if didPaymentSucceed {
            performSegue(withIdentifier:"ThankYou", sender: self)
        }

```

順番が前後しますが、PKPaymentAuthorizationControllerDelegateのデリゲートメソッドには、
他にも支払い方法の変更時や配送先・配送方法の変更時に呼ばれるものもあります。実装はオプションです。

これらのデリゲートメソッドでは住所などの入力チェックや金額の更新を行うとよいでしょう。

なお、引数completionに渡すのステータス（`PKPaymentAuthorizationStatus`）には、バリデーションエラーを表す`invalidBillingPostalAddress`といったものもあります。

```ViewController.swift
    // ユーザーが配送方法を変更したときに呼ばれるデリゲートメソッド
    func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didSelect shippingMethod: PKShippingMethod, completion: @escaping (PKPaymentAuthorizationStatus, [PKPaymentSummaryItem]) -> Void) {
        // 必要に応じて配送料の更新などを行う
        // updateDeliveryCharge(shippingMethod)
        completion(.success, getpaymentSummaryItems())
    }
    
    // ユーザーが配送先を変更したときに呼ばれるデリゲートメソッド
    func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didSelectShippingContact contact: PKContact, completion: @escaping (PKPaymentAuthorizationStatus, [PKShippingMethod], [PKPaymentSummaryItem]) -> Void) {
        // 必要に応じて配送先に対する入力チェックなどを行う
        // if isValidContact(contact) {
        //     completion(.success, getShipingMethods(), getpaymentSummaryItems())
        // } else {
        //    completion(.invalidShippingContact, getShipingMethods(), getpaymentSummaryItems())
        // }
        completion(.success, getShipingMethods(), getpaymentSummaryItems())
    }

```

API Reference 「PKPaymentAuthorizationControllerDelegate」
(https://developer.apple.com/reference/passkit/pkpaymentauthorizationcontrollerdelegate)


### バックエンドの決済処理
決済プラットフォームと連携した実際の決済処理はバックエンドで行います。各決済プラットフォームのドキュメントを参照して実装してください。

ここでは参考までに、Java言語でPAY.JPのライブラリを使用した簡単な例を掲載します。
（JAX-RSでWebサービスを作成しています）

```OrderService.java
@Path("orders")
public class OrderService {

    @Path("/")
    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.TEXT_PLAIN)
    public Response create(@FormParam("token") String token, @FormParam("amount") Integer amount, @FormParam("email") String email) {
        // ※サンプルのためアプリ独自の処理は省略し、PAY.JPとの連携のみ記述する
        
        Payjp.apiKey = "PAY.JPの設定画面で確認した秘密鍵";
        Map<String, Object> chargeParams = new HashMap<>();
        chargeParams.put("card", token);
        chargeParams.put("amount", amount);
        chargeParams.put("currency", "jpy");

        try {
            Charge.create(chargeParams);
            return Response.ok("Complete!.").build();

        } catch (InvalidRequestException | CardException ex) {
            throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(ex.getMessage()).build());

        } catch (AuthenticationException | APIConnectionException | APIException ex) {
            throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(ex.getMessage()).build());
        }
    }
}

```


## 最後に
アプリにApple Payを導入するとユーザーは商品購入時の決済を簡単に行う事ができるようになります。

これは商品の購入される可能性が高まるという意味で、アプリの提供側にとってもチャンスです。
これから実装する機会が増えるのではないでしょうか。


## 作成したサンプル
https://github.com/shindooo/BananaApplePay
https://github.com/shindooo/BananaApplePayServer

[^1]: https://support.apple.com/ja-jp/HT207150 
[^2]: https://developer.apple.com/ios/human-interface-guidelines/technologies/apple-pay/
  https://developer.apple.com/apple-pay/Apple-Pay-Identity-Guidelines.pdf