minne iOS

日本でのApple Payローンチパートナーに選ばれたminneアプリの開発秘話

minne iOS

こんにちは、minneの@nakajijapanと申すものです。 夏頃からすすめてきたプロジェクトであるminneのApple Pay対応がリリースされました。 すでにApple Payを使ってみた、という方もたくさんいるのではないでしょうか。今回の記事は、Apple Payをリリースするに当たって対応したときに色々と苦労したこと、minne特有の仕様に合わせたことがありましたので、そのことについてお話させていただきます。

Apple Payとは

http://www.apple.com/jp/apple-pay/

まず、Apple Payは安全で便利な決済方法の一つです! ユーザはApple Payを利用してAppleIDに紐づいた連絡先・配送先・クレジットカード情報を素早く安全に利用できます。 そのため、アプリ毎にユーザ登録・住所登録をする必要はなく、すぐに欲しいものを購入できるという、素晴らしい体験を提供できるようです。

詳細はApple Pay Guideに記述されていて、どういう仕組みになっているのか・導入方法などが順序立てて説明されていますのでご参照ください。

ちなみにminneではiOSアプリだけではなく、macOS SierraがインストールされたMacでもApple Payを利用して決済ができるようになっています。 Webでの決済は今年のWWDC2016で発表された新しい機能です。

仕組み

  • 登録したカード情報はSecure Elementと言われるチップ内で暗号化して保存されます。
  • 支払のやりとりは暗号化されたペイメントトークンで行います。トークンは一回きりの利用です。
  • 暗号化済みのペイメントトークンをMerchant SystemもしくはPayment Providerに送信し、支払い情報を復号化して処理することです。
  • アプリ内課金とは異なり、Appleは決済の安全性・匿名性を担保しているだけで決済の責任を負いません。

Payment Flow

一覧の流れはこちらを見るとざっくり確認できます。 minneでは決済の処理はPayment Providerに直接通信する訳ではなく、弊社のサーバを介して処理を行なっています。 利用するサービスによってはPayment Providerと直接やり取りを行うものもあるようです。

参照:https://developer.apple.com/apple-pay/get-started/

実装編

Payment Button

Apple Payを利用して購入する時はPKPaymentButtonクラスを利用することが推奨とされています。これを使うことで、Apple側で用意されている専用の画像を利用することなく、ボタンの縦横比のサイズを自動で考慮してボタンを配置できます。さらに内部処理で自動的に多言語化してくれます。 iOS8.3以降から利用可能です。しかし、OSがバージョンアップされるごとにPKPaymentButtonTypeの種類がiOS9以降でないと利用できない場合があるので注意が必要です。 以下がその値です。OSが上がるにつれて利用できるものが増えているのがわかります。

@available(iOS 8.3, *)
public enum PKPaymentButtonType : Int {
    case Plain
    case Buy
    @available(iOS 9.0, *)
    case SetUp
    @available(iOS 10.0, *)
    case InStore
}

minneでは「購入できる時」はPKPaymentButtonType.Buy「セットアップが必要な時」はPKPaymentButtonType.Setupを設定してWalletでクレジットカードを登録するように促しています。

実装上の注意

PKPaymentButtonは一度インスタンスにしてしまうとPKPaymentButtonType,PKPaymentButtonStyleが変更できません。 つまり、PKPaymentButtonType.SetupからWalletでクレジットカードを登録して画面から戻ったときにPKPaymentButtonType.Buyに変更しておきたい場合は、再度PKPaymentButtonクラスをインスタンス化しておく必要があります。 しかし、Walletからアプリに戻ってくるときに、view(Will|Did)Appearのイベントは発火しないのでUIApplicationDidBecomeActiveNotificationのイベントを監視も行い、UIを更新するという作業をしなければいけません。

日本語へのこだわり

開発し始めたときは.plainで設定したシンプルなデザインだとminneユーザ層にはわかりづらいのではないか、極力わかりやすものにしようという理由からPKPaymentButtonType.BuyPKPaymentButtonType.SetUpのような文言のあるものにしていました。 また、PKPaymentButtonを利用するに当たって多言語対応すると聞いていたのでそちらで実装していました。しかし、リリースギリギリまで翻訳のニュアンスが違う状態だったので一旦戻そうか、直るのを信じて進めるか定期的に議論しながら進めていたのは記憶に新しいです。

参照:https://developer.apple.com/reference/passkit/pkpaymentbuttontype

クレジットカード登録促進

iOS8.3以降であれば、Walletを開くAPIが用意されているのでそれを利用します。スキーマでWalletを開くことはできますが、Appleが推奨していないので利用は控えた方が良さそうです。iOS8.3以下の時はWalletを開くAPIがサポートされていないのでメッセージを出力してクレジットカードの登録を促進しています。

    if #available(iOS 8.3, *) {
        PKPassLibrary().openPaymentSetup()
    } else {
        MIToast.showWarningWithMessage("Apple Payをご利用になる場合は、Walletアプリでクレジットカードの登録を行ってください", completionBlock: nil)
    }

PaymentRequest

PKPaymentRequestはPaymentSheetに決済に関する情報を表示するために必要な設定を行います。 基本的な設定はPayment Requestの作成を見ればどのようにしてPaymentSheetを出力するのかが説明されています。

主に以下の設定ができます。

  • MerchantID
  • 国のリージョン
  • サポートするPaymentNetwork
    • iOS10からPKPaymentNetworkSuicaというものが追加されました。何らかの制限はあるかもしれませんが、今後利用できるようになるのでしょうね!
  • 配送先
  • 請求先
    • minneでは必要ではなかったので利用していません。

また、minneではゲスト購入と会員で情報の表示制御が行われています。会員であれば連絡先は登録済みですが、ゲスト購入の場合は新たに連絡先を記述してもらう必要があるので、PKAddressField.Phone PKAddressField.Emailの項目を追加しています。

request.requiredShippingAddressFields = MIPurchasesConfirm.isGuestCheckout() ? [.PostalAddress, .Phone, .Email] : [.PostalAddress, .Phone]

PaymentToken Format

支払いに利用されるトークン情報です。情報の復号化と検証・決済処理はPayment Platform側で行うのでminne側ではただただ生成された情報をそちらに送るだけで大丈夫です。 詳細は支払いの処理に記述されています。 minneでは取得したペイメントトークンを一旦minneのサーバを介してPayment Platform側に情報を送信しています。

public func paymentAuthorizationViewController(controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, completion: (PKPaymentAuthorizationStatus) -> Void) {

    if #available(iOS 9.0, *) {
        let applePaymentManager = ApplePaymentManager.sharedManager
        let shippingContact = AppleContact(contact: payment.shippingContact)
        let billingContact = AppleContact(contact: payment.billingContact)

        APIClient().request(PostApplePayPurchaseEndpoint(
            productID: nil,
            shippingID: nil,
            shippingContact: shippingContact,
            billingContact: billingContact,
            token: payment.token)) {(result: APIResult<PostApplePayPurchaseEndpoint.ResponseType>) in
                // something
        }
    }

}

個人情報の取り扱い

Apple側で用意されている配送先・請求先情報はかなり自由に入力できるので、バリデーションはアプリ側で行う必要がありました。 また、配送先や請求先情報を入力する際に、iOS標準の連絡先アプリからも情報を取り込むことが可能となっています。そのときに、注意が必要なのは氏名が入力されずに会社名が設定されているパターンです。 このときは会社名は氏名に入らず、住所情報に自動的に入ります。当初は住所だけではなく名前の簡易チェックも入れるべきなのではという話になったのですがいざ名前を取得しようとすると名前の取得ができませんでした。これは仕様であり、必要最低限の情報だけしか取得できない状態になっています。

contact A contact object representing the new shipping address. To maintain privacy, the shipping information is anonymized. For example, in the United States it only includes the city, state, and zip code. This provides enough information to calculate shipping costs, without revealing sensitive information until the user actually approves the purchase.

参照:https://developer.apple.com/reference/passkit/pkpaymentauthorizationviewcontrollerdelegate/1616198-paymentauthorizationviewcontroll

そのため、ここでは名前のバリデーションはせずに決済時に行うようにしました。 ちなみにAppleのサーバおよびSecure Elementとの調整を通じてペイメントトークンの生成が終了したときには情報が参照できるようになっています。

// AppleのサーバおよびSecure Elementとの調整を通じてペイメントトークンの生成が終了したとき呼び出される
- (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller
                       didAuthorizePayment:(PKPayment *)payment
                                completion:(void (^)(PKPaymentAuthorizationStatus status))completion;

フリガナの取り扱い

Apple Payでは購入するときに、配送先の情報を入力するには氏名のフリガナの入力が行えません。しかし、minneでは配送先設定時にはフリガナは必須です。 このままではゲスト購入後のユーザ登録の際、配送先設定情報がが初期化されてしまい、再度フリガナの入力をしなければいけません。 実は、iOS標準の連絡先アプリからから情報を取り込んだ場合は、連絡先にフリガナが設定されていればそれも同時に取得できます。 これがあることでユーザ登録時にフリガナを入力せずとも最小限の情報だけでユーザ登録が行うことが可能です。

日本特有の仕様

ドキュメントではiOS8からのサポートという記述があります。そのため、iOS8でもデバイスがApple Payを対応していればボタンが表示されてしまいます。しかし、日本のApple Payのサポート対象はiOS10.1からです。 そうなると、「実際の決済はできないのにボタンが表示されてしまう」というユーザ体験を損ねる状態になるのでApple PayがサポートされたデバイスかどうかはiOSのバージョンも一緒に条件式に追加してあげる必要がありました。

static var canMakePaymentsInJapan: Bool {

    if #available(iOS 10.1, *) {
        if PKPaymentAuthorizationController.canMakePayments() {
            return true
        }
    }
    return false
}

バージョンによるフレームワークの違い

minneのアプリはiOS8だけがサポート対象であったのでそれを考慮した実装をしていました。そのため、iOS8もサポートしているのなら連絡先を扱う場合に注意する必要があります。 連絡先アプリの情報を扱う(住所情報を取り込んだり、登録したりする)ときにはiOSのバージョンによってフレームワークを切り分ける必要がありました。

  • iOS8まで
    • AddressBook
  • iOS9以上
    • PassKit

開発当初は情報も少なかったことからドキュメントの通りApple PayのサポートはiOS8であり、アプリもiOS8をサポートしていましたのでそれを考慮して実装していました。 しかし、後半で日本はiOS10.1からと分かり、どのOSのバージョンでも利用できるように頑張って実装していたのですが、その必要はほぼなくなったのでした。。。

決済のテスト

外部通信することなく、iOS10以上のシミュレータからダミー決済ができます。 実機で行う場合にはクレジットカードを実際にWalletから登録してあげる必要があります。これもiOS10以上の端末であればApple Pay Sandboxで提供されているダミーのクレジットカード情報を利用します。 日本ではまだリリース前だったことからUSリージョンにする必要がありました。現状は既に日本でリリースされているので利用できるようになっているかと思います。

まとめ

今回Apple Payを対応するにあたって苦労したところ、minneならではの仕様に合わせて実装したところを赤裸々にお伝えしました。 これからPayment Platformsが公開されたことでより多くのアプリがApple Payを対応していくことになると思いますが、これらの情報が少しでもみなさんのお役に立てばと思います。 @nakajijapanでした。

ちなみにminneではエンジニアを積極採用中なので興味のある方はぜひ応募していただければと思います!!