今のところ導入予定はないですが、折角リリースされているので、Android Pay APIについて調べてみました。

そもそもAndroid Payとは

Android Payとは電子マネー・ポイントカード・クレジットカードをAndroidデバイスに登録し、NFCを経由して電子マネーを使用したり、ポイントを追加・使用したり、クレジットカード決済を利用することができるサービスとなります。
上記のサービスを利用するために、Googleが公開しているアプリ「Android Pay」をインストールする必要があります。ただし、未だ日本ではクレジットカード会社との提携が完了していないのか、クレジットカード決済をAndroid Payから直接使用することはできないようです(2017.06現在)

サービスとしては上記のアプリにより提供されているのですが、Android Payとしてのクレジットカード決済機能を3rdPartyアプリから利用できるようにAPIが公開されています。(電子マネーやポイントカードについては公開されていない様子: 2017.06現在)。このAPIが今回のブログのターゲットとなります。

Android PayとGoogle Play In-app Billing

アプリでの料金支払いは、これまで(そしてこれからも)Google Play In-app Billingがありますが、Android Payがサービスインされたことにより、支払い方法が2パターン用意されたことになります。
Googleは以下のように記載しています。
If you wish to:
Have Google process payments for you, or
Sell digital goods such as movies or games
within your app, please use Google Play In-app Billing instead.
"https://developers.google.com/android-pay/get-started"より
なので、既にGoogle Play In-app Billingを実装済みだったり、販売するものがデジタルコンテンツだったりする場合は、これまで通りGoogle Play In-app Billingを使用するのがよいとのことです。

ただし、Google Play In-app Billingは売り上げの30%を手数料としてGoogleに支払う必要があるので、クレジットカード会社と契約している企業においては、手数料の兼ね合いでどちらを使用すべきかを検討するのも面白いかと。
なお、Android Payも競合するApple PayもGoogle(出典)/Apple(出典)への手数料は発生しないようです。

アプリからのAPIコール

アプリからAndroid PayのAPIをコールして、料金を徴収する方法です。 ただし、残念ながら、日本ではクレジットカード会社との提携が完了していないのか、クレジットカード決済は使用できないので、以下に記載する内容は試せないです(2017.06現在)。
上記の点が、調査を進めている途中で分かり、記事にするのを止めようかと思いましたが、とりあえずGoogleが公開しているサンプルアプリとそれに対する説明からコード解析を行いたいと思います。

コードを見たところ、要点は以下の点となります。
  1. Android Pay APIを使用できる状態か否かの判定
  2. 支払いに"Android Pay"を使用することを選択するためのボタンを表示
  3. "Android Pay"で実際に支払いを行う

Android Pay APIを使用できる状態か否かの判定

必ず実施しないとならない機能ではない(使用できないときは、その先でエラーになる)です。
com.google.android.gms.wallet.Wallet.Payments.isReadyToPay()の結果をResultCallbackインスタンスで受けることができます。
コード: com.google.android.gms.samples.wallet.CheckoutActivity.onCreate
    @Override
    protected void onCreate(Bundle savedInstanceState) {
                      :省略
        // Android Payの使用可否を確認するためのGoogleApiClientインスタンスの生成と設定
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Wallet.API, new Wallet.WalletOptions.Builder()
                        .setEnvironment(Constants.WALLET_ENVIRONMENT)
                        .build())
                .enableAutoManage(this, this)
                .build();
                      :省略
        Wallet.Payments.isReadyToPay(mGoogleApiClient).setResultCallback(
                new ResultCallback<booleanresult>() {
                    @Override
                    public void onResult(@NonNull BooleanResult booleanResult) {
                        hideProgressDialog();

                        if (booleanResult.getStatus().isSuccess()) {
                            if (booleanResult.getValue()) {
                                // ★Android Payが使える
                            } else {
                                // ★Android Payが使えない
                            }
                        } else {
                            // Error making isReadyToPay call
                            Log.e(TAG, "isReadyToPay:" + booleanResult.getStatus());
                        }
                    }
                });
    }

支払いに"Android Pay"を使用することを選択するためのボタンを表示

先の確認により、動作しているデバイス上でAndroid Payが使用できることが判明したので、ユーザにAndroid Payでの支払い方法があることを以下のようなボタンにより提示します。
Google社公開のアプリ"androidpay-quickstart"の
スクリーンショットを加工

コード: com.google.android.gms.samples.wallet.CheckoutActivity.createAndAddWalletFragment
// [START fragment_style_and_options]
WalletFragmentStyle walletFragmentStyle = new WalletFragmentStyle()
        .setBuyButtonText(WalletFragmentStyle.BuyButtonText.BUY_WITH)
        .setBuyButtonAppearance(WalletFragmentStyle.BuyButtonAppearance.ANDROID_PAY_DARK)
        .setBuyButtonWidth(WalletFragmentStyle.Dimension.MATCH_PARENT);

WalletFragmentOptions walletFragmentOptions = WalletFragmentOptions.newBuilder()
        .setEnvironment(Constants.WALLET_ENVIRONMENT)
        .setFragmentStyle(walletFragmentStyle)  // ★設定したWalletFragmentStyleをWalletFragmentOptionsインスタンスへ設定
        .setTheme(WalletConstants.THEME_DARK)
        .setMode(WalletFragmentMode.BUY_BUTTON)
        .build();

このAndroid Payボタンを表示する際に、以下の設定を行えます。
  • Android Payボタンの見た目
  • Android Payボタンに表示する文字列
  • Android Payボタンが格納されているフラグメントの幅・高さ
  • Android Payが動作する環境

Android Payボタンの見た目

API: com.google.android.gms.wallet.fragment.WalletFragmentStyle.setBuyButtonAppearance(int)
WalletFragmentStyle.BuyButtonAppearance.
ANDROID_PAY_DARK
WalletFragmentStyle.BuyButtonAppearance.
ANDROID_PAY_LIGHT
WalletFragmentStyle.BuyButtonAppearance.
ANDROID_PAY_LIGHT_WITH_BORDER

Android Payボタンに表示する文字列

API: com.google.android.gms.wallet.fragment.WalletFragmentStyle.setBuyButtonText(int)
WalletFragmentStyle.BuyButtonText.
BUY_WITH
WalletFragmentStyle.BuyButtonText.
LOGO_ONLY
WalletFragmentStyle.BuyButtonText.
DONATE_WITH
またWalletFragmentOptions.newBuilder.setModeで設定する値は、Android Payボタンを表示する際の値は、WalletFragmentMode.BUY_BUTTON固定とする必要があります。変更すると以下のような表示になります。

Android Payボタンが格納されているフラグメントの幅・高さ

API(pixel指定): com.google.android.gms.wallet.fragment.WalletFragmentStyle.setBuyButtonWidth/Height(int width)
設定したい幅・高さをPixelで指定。ただし、以下の値を設定することで相対設定が可能です。
  • WalletFragmentStyle.Dimension.MATCH_PARENT
  • ViewGroup.LayoutParams.MATCH_PARENTと同じような動作になる。
  • WalletFragmentStyle.Dimension.WRAP_CONTENT
  • ViewGroup.LayoutParams.WRAP_CONTENTと同じような動作になる。
API(値・単位指定): com.google.android.gms.wallet.fragment.WalletFragmentStyle.setBuyButtonWidth/Height(int unit, float width)
設定する単位とを指定することで設定。単位は以下のものが使用可能です。
単位名称概要
WalletFragmentStyle.Dimension.UNIT_DIPdip(device independent pixels)指定
WalletFragmentStyle.Dimension.UNIT_INインチ指定
WalletFragmentStyle.Dimension.UNIT_MMミリメートル指定
WalletFragmentStyle.Dimension.UNIT_PTポイント指定
WalletFragmentStyle.Dimension.UNIT_PXピクセル指定
WalletFragmentStyle.Dimension.UNIT_SPsp(scaled pixel)指定


Android Payが動作する環境

API: com.google.android.gms.wallet.fragment.WalletFragmentOptions.setEnvironment(int)
名称概要
WalletConstants.ENVIRONMENT_PRODUCTION本番環境。クレジットカード会社と契約された企業との取引以外できないと思われる
WalletConstants.ENVIRONMENT_TESTテスト環境。動作確認などに使用できる
WalletConstants.ENVIRONMENT_SANDBOX非推奨の値。WalletConstants.ENVIRONMENT_TESTを使用すべきとのこと
WalletConstants.ENVIRONMENT_STRICT_SANDBOX非推奨の値。WalletConstants.ENVIRONMENT_TESTを使用すべきとのこと

上記の設定を行うことでボタンの設定を行うことができるので、それをフラグメント化します。
コード: com.google.android.gms.samples.wallet.CheckoutActivity.createAndAddWalletFragment
mWalletFragment = SupportWalletFragment.newInstance(walletFragmentOptions);

MaskedWalletを取得するために、MaskedWalletRequestインスタンスを生成。
コード: com.google.android.gms.samples.wallet.CheckoutActivity.createAndAddWalletFragment
// Now initialize the Wallet Fragment
String accountName = ((BikestoreApplication) getApplication()).getAccountName();
MaskedWalletRequest maskedWalletRequest;
if (mUseStripe) {
    // Stripe integration
    maskedWalletRequest = WalletUtil.createStripeMaskedWalletRequest(
            Constants.ITEMS_FOR_SALE[mItemId],
            getString(R.string.stripe_publishable_key),
            getString(R.string.stripe_version));
} else {
    // Direct integration
    maskedWalletRequest = WalletUtil.createMaskedWalletRequest(
            Constants.ITEMS_FOR_SALE[mItemId],
            getString(R.string.public_key));
}

作成したMaskedWalletRequestをフラグメントに設定するために、WalletFragmentInitParamsをインスタンス化し、それを初期パラメータとしてフラグメントに設定。
また、このとき、WalletFragmentInitParams.Builder.setMaskedWalletRequestCodeによりAndroid Payボタン押下の結果をActivity.onActivityResultで受ける際のリクエストコードを指定します。
コード: com.google.android.gms.samples.wallet.CheckoutActivity.createAndAddWalletFragment
WalletFragmentInitParams.Builder startParamsBuilder = WalletFragmentInitParams.newBuilder()
        .setMaskedWalletRequest(maskedWalletRequest)  // ★WalletFragmentInitParamsにMaskedWalletRequestインスタンスを設定
        .setMaskedWalletRequestCode(REQUEST_CODE_MASKED_WALLET)
        .setAccountName(accountName);
mWalletFragment.initialize(startParamsBuilder.build()); // ★フラグメントにWalletFragmentInitParamsインスタンスを設定

設定が完了したフラグメント(未だボタンを表示している状態)を表示するために、リソースID: R.id.dynamic_wallet_button_fragmentにフラグメントを設定。
これによりAndroid Payボタンが表示されます。このボタンをユーザが選択すると、このフラグメントで隠蔽された中で処理され、その結果がActivity.onActivityResultに通知されます。
この際に指定されるリクエストコードは、先にWalletFragmentInitParams.Builder.setMaskedWalletRequestCodeで設定した値となります。
Activity.onActivityResultでこのリクエストコードをハンドリングして、成功した際は引数で渡されたIntentからMaskedWalletを取得できるので、それを使用してユーザに使用するクレジットカード情報の確認を行います。
※ サンプルアプリでは、このタイミングでConfirmationActivity(引数に取得したMaskedWalletインスタンス)を表示します。
コード: com.google.android.gms.samples.wallet.CheckoutActivity.createAndAddWalletFragment
// add Wallet fragment to the UI
getSupportFragmentManager().beginTransaction()
        .replace(R.id.dynamic_wallet_button_fragment, mWalletFragment)  // ★フラグメントを表示
        .commit();

"Android Pay"で実際に支払いを行う

取得したMaskedWalletの情報をユーザに確認してもらい、使用するクレジットカードや商品の配送先を決定する契機になります。
なお、使用するクレジットカードや配送先の表示については、SDKにて表示してくれます。
https://developers.google.com/android-pay/payment-flows
上記のサイトから引用

上記の画像内の境界線から"Confirm order"ボタンより上までのクレジットカード情報と商品の配送先情報を表示するフラグメントを生成します。
その際、以下の設定が可能となっています。
  • クレジットカード情報と商品の配送先情報の文字サイズや文字色
  • クレジットカード情報と商品の配送先情報のヘッダ部分の文字サイズや文字色
  • クレジットカード情報と商品の配送先情報内のボタンの背景
  • クレジットカード情報と商品の配送先情報の背景
  • ロゴ画像
定義されているリソースIDや値を設定することでカスタマイズが可能となっています。
ロゴ画像は以下のものが設定可能ですが、非推奨を除くとWalletFragmentStyle.LogoImageType.ANDROID_PAYのみとなります。
名称概要
WalletFragmentStyle.LogoImageType.ANDROID_PAYAndroid Payのロゴマーク。
 
WalletFragmentStyle.LogoImageType.GOOGLE_WALLET_CLASSIC 非推奨の値。
恐らく、このようなマーク。
WalletFragmentStyle.LogoImageType.GOOGLE_WALLET_MONOCHROME 非推奨の値。
恐らく、このようなマーク。



クレジットカード情報と商品の配送先情報の文字サイズや文字色などの設定
コード: com.google.android.gms.samples.wallet.ConfirmationActivity.createAndAddWalletFragment
WalletFragmentStyle walletFragmentStyle = new WalletFragmentStyle()
        .setMaskedWalletDetailsTextAppearance(
                R.style.BikestoreWalletFragmentDetailsTextAppearance)
        .setMaskedWalletDetailsHeaderTextAppearance(
                R.style.BikestoreWalletFragmentDetailsHeaderTextAppearance)
        .setMaskedWalletDetailsBackgroundColor(
                getResources().getColor(R.color.bikestore_white))
        .setMaskedWalletDetailsButtonBackgroundResource(
                R.drawable.bikestore_btn_default_holo_light);

モードに"WalletFragmentMode.SELECTION_DETAILS"を設定することで、クレジットカード情報や商品の配送先情報の選択用のフラグメントになります。
コード: com.google.android.gms.samples.wallet.ConfirmationActivity.createAndAddWalletFragment
WalletFragmentOptions walletFragmentOptions = WalletFragmentOptions.newBuilder()
        .setEnvironment(Constants.WALLET_ENVIRONMENT)
        .setFragmentStyle(walletFragmentStyle)
        .setTheme(WalletConstants.THEME_LIGHT)
        .setMode(WalletFragmentMode.SELECTION_DETAILS)
        .build();

作成したWalletFragmentOptionsを使用してフラグメントを作成
コード: com.google.android.gms.samples.wallet.ConfirmationActivity.createAndAddWalletFragment
mWalletFragment = SupportWalletFragment.newInstance(walletFragmentOptions);

取得したMaskedWalletをフラグメントに設定するために、WalletFragmentInitParamsをインスタンス化し、それを初期パラメータとしてフラグメントに設定。
このとき、WalletFragmentInitParams.Builder.setMaskedWalletRequestCodeにより同フラグメントからActivity遷移が発生したときに通知されるリクエストコードを指定します。
また、使用するログイン済みGoogleアカウント名称(メールアドレス)を一緒に設定することができますが、設定していない場合でも、その後選択可能なようです。
コード: com.google.android.gms.samples.wallet.ConfirmationActivity.createAndAddWalletFragment
// Now initialize the Wallet Fragment
String accountName = ((BikestoreApplication) getApplication()).getAccountName();
WalletFragmentInitParams.Builder startParamsBuilder = WalletFragmentInitParams.newBuilder()
        .setMaskedWallet(mMaskedWallet)
        .setMaskedWalletRequestCode(REQUEST_CODE_CHANGE_MASKED_WALLET)
        .setAccountName(accountName);
mWalletFragment.initialize(startParamsBuilder.build());

設定が完了したフラグメントを表示するために、リソースID: R.id.dynamic_wallet_masked_wallet_fragmentにフラグメントを設定。
これによりクレジットカード情報や商品の配送先情報が表示されます。 コード: com.google.android.gms.samples.wallet.ConfirmationActivity.createAndAddWalletFragment
// add Wallet fragment to the UI
getSupportFragmentManager().beginTransaction()
        .replace(R.id.dynamic_wallet_masked_wallet_fragment, mWalletFragment)
        .commit();

ユーザが確認を終えて、"Confirm order"ボタンを押下すると、FullWalletを取得するとために、FullWalletRequestインスタンスを作成します。
このとき、ユーザが選択したWallet情報がMaskedWalletとして取得している(変更されたときはActivity.onActivityResultで通知され更新している)ので、そこに設定されているGoogleTransactionIDを取得し、FullWalletRequestに設定する。
また、この際請求する金額を商品の金額、配送費、税金の項目に分割して、それぞれをLineItemインスタンス化する。
コード: com.google.android.gms.samples.wallet.WalletUtil.createFullWalletRequest
public static FullWalletRequest createFullWalletRequest(ItemInfo itemInfo,
        String googleTransactionId) {

    List lineItems = buildLineItems(itemInfo, false);

    String cartTotal = calculateCartTotal(lineItems);

    // [START full_wallet_request]
    FullWalletRequest request = FullWalletRequest.newBuilder()
            .setGoogleTransactionId(googleTransactionId)
            .setCart(Cart.newBuilder()
                    .setCurrencyCode(Constants.CURRENCY_CODE_USD)
                    .setTotalPrice(cartTotal)
                    .setLineItems(lineItems)
                    .build())
            .build();
    // [END full_wallet_request]

    return request;
}

取得したFullWalletRequestを使用して、com.google.android.gms.wallet.Wallet.Payments.loadFullWalletを用いて、FullWalletの取得を試みる。
この際、結果を受けるためのリクエストコードを指定する。
コード: com.google.android.gms.samples.wallet.FullWalletConfirmationButtonFragment.getFullWallet
private void getFullWallet() {
    FullWalletRequest fullWalletRequest = WalletUtil.createFullWalletRequest(mItemInfo,
            mMaskedWallet.getGoogleTransactionId());

    // [START load_full_wallet]
    Wallet.Payments.loadFullWallet(mGoogleApiClient, fullWalletRequest,
            REQUEST_CODE_RESOLVE_LOAD_FULL_WALLET);
    // [END load_full_wallet]
}

FullWalletの取得要求の結果が、Activity.onActivityResultに渡される。成功していれば、渡されたIntentからFullWalletを取得できる。
このFullWalletに請求に必要な全クレジットカード情報が暗号化されて記載されているので、それを実際にクレジットカード会社に請求する自社サーバなどに送信して請求することが求められています。
FullWalletに記載されている情報について、長くなったので別途記載します。

参考文献:
https://developers.google.com/android-pay/tutorial
https://developers.google.com/android-pay/diagrams
https://codelabs.developers.google.com/codelabs/android-pay/index.html

コメントの投稿