[Kotlin][Android M] Android Payを試す [MaskedWallet]

2015.06.02

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Android Pay

こんにちは。こむろです。 Google I/O 2015でAndroid Mと共にお披露目されたAndroid Payについて、なかなか良いTutorialのサイトが公開されていたので、それを写経しつつどんなことをやれば実装できるのかを試してみましょう。

Codelab

今年は何やら素晴らしいサイトが公開されているようですね。Codelabです。このページには様々な新しい機能のチュートリアルが分かりやすく紹介されています。ステップを一つ一つていねいに写経していけば、新しい機能の実装を学ぶことができるようです。さっそく作っていきましょう。

スクリーンショット 2015-06-02 20.53.02

そのまま写経するだけでは学びが少ないので、折角なのでモリモリKotlinで書き換えていきます。

Masked Wallet

直訳すると覆面財布。恐らく「安全な決済を行うため、一度登録したら安全な情報以外極力見せない」という意味でのMaskedではないでしょうか。まあ、ネット通販とかでもよくありますね。クレジットカードの後半4桁しか出てこない情報。ああいったものをイメージしておけば良いのではないでしょうか。

Android Payのチュートリアルを開くと、2つのセクションに分かれているようです。まずはこのMasked Walletの情報を登録する方法。そしてその情報を使って決済を行う方法。

チュートリアル

チュートリアルは中盤辺りに「Android Pay」の項目がありました。こちらを選択します。

スクリーンショット 2015-06-02 21.03.07

選択するとチュートリアルが表示されます。ステップごとに並んでおり非常に分かりやすいですね。

スクリーンショット 2015-06-02 21.05.42

ステップ1

いつも通りAndroid Studioを開き、プロジェクトを作成します。プロジェクトテンプレートはチュートリアルにあるとおり「Blank Activity」を選びましょう。実際のステップはこちらになります。

今回はKotlinで記述するためにプロジェクトの設定をKotlin用に設定します。設定の仕方はこちら

ステップ2

build.gradleを編集します。Android PayはGoogle Play Servicesの一つのようです。実際のステップはこちら

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.android.gms:play-services:7.5+'
    compile 'com.android.support:support-v4:+'
    compile 'com.android.support:appcompat-v7:+'
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

手順に従って、Gradleファイルを編集したらSyncを実行しておきます。

ステップ3

AndroidManifest.xml を編集します。Google Play Servicesを利用するのでいつも通りmeta-dataを追記します。さらにBluetooth LEを利用する時と同様に、meta-dataでwallet apiを有効に設定するようです

<meta-data
    android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
<meta-data
    android:name="com.google.android.gms.wallet.api.enable" android:value="true" />

ステップ4

レイアウトに関しては、チュートリアルの指示通りに書き換えていきます。Blank Activityのテンプレートで作成していると、ルートのレイアウトがRelative Layoutになってるようなので、ここはチュートリアルのコードをぺたっと貼ってしまった方が簡単です。実際のステップはこちら

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/wallet_button_holder"
        android:layout_width="200dip"
        android:layout_height="48dip"
        android:layout_marginTop="10dip"
        android:layout_marginBottom="10dip"
        android:layout_gravity="center_horizontal"
        />
</LinearLayout>

ステップ5

MaskedWalletRequestを生成するためのファクトリメソッドを作成します。

private fun generateMaskedWalletRequest(): MaskedWalletRequest? {
    val maskedWalletRequest = MaskedWalletRequest.newBuilder()
    .setMerchantName("Google I/O Codelab")  
    .setPhoneNumberRequired(true)     // 電話番号を要求
    .setShippingAddressRequired(true) // 送付先の住所を要求
    .setCurrencyCode("USD")     // 通貨単位?  
    .setCart(Cart.newBuilder()      // カート内のアイテム
                .setCurrencyCode("USD") // 通貨単位?
                .setTotalPrice("10.00")   // カート内の合計金額
                .addLineItem(LineItem.newBuilder()  // アイテムを追加
                    .setCurrencyCode("USD") // 通貨t(ry
                    .setDescription("Google I/O Sticker") // アイテムの説明
                    .setQuantity("1")   // 個数
                    .setUnitPrice("10.00")  // 単位金額
                    .setTotalPrice("10.00") // 合計金額
                        .build()
                ).build()
    ).setEstimatedTotalPrice("15.00").build()   // 見積もり金額・・・?
    return maskedWalletRequest
}

ひと通り確認してみました。MaskedWalletRequest.BuildersetCart()の説明を見ると以下のように記述されています。

Sets an optional shopping cart to use for this purchase.

MaksedWalletRequestをBuildする際には、買い物をするカートの中身は設定しなくても良いようです。

後半に出てくるFullWalletRequestの方を確認すると、"Sets the shopping cart. This field is required unless this request is for a billing agreement. If this request is for a billing agreement, this field is inapplicable and should not be set."とあります。Optionalという単語が見当たらないため、恐らくこちらでは設定が必須な項目なのでしょう。

ステップ6

ここで、SupportWalletFragmentの設定を行うようです。実際のステップはこちらこちら

普段のFragmentの生成方法とは少し異なるようです。

public class MainActivity : AppCompatActivity() {

    // Masked Wallet
    private var walletFragment: SupportWalletFragment? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        walletFragment = getSupportFragmentManager().findFragmentByTag(WALLET_FRAGMENT_ID) as SupportWalletFragment?

        if(walletFragment == null) {

            // Wallet Fragment Style
            val walletFragmentStyle = WalletFragmentStyle()
                    .setBuyButtonText(BuyButtonText.BUY_NOW)
                    .setBuyButtonWidth(Dimension.MATCH_PARENT)

            // Wallet Fragment Options
            val walletFragmentOptions = WalletFragmentOptions.newBuilder()
                    .setEnvironment(WalletConstants.ENVIRONMENT_SANDBOX)
                    .setFragmentStyle(walletFragmentStyle)
                    .setTheme(WalletConstants.THEME_LIGHT)
                    .setMode(WalletFragmentMode.BUY_BUTTON)
                    .build()

            // Wallet Fragmentを作成
            walletFragment = SupportWalletFragment.newInstance(walletFragmentOptions)
            val startParamsBuilder = WalletFragmentInitParams.newBuilder()
                    .setMaskedWalletRequest(generateMaskedWalletRequest())
                    .setMaskedWalletRequestCode(MASKED_WALLET_REQUEST_CODE)
                    .setAccountName("Google I/O Codelab")

            // Wallet Fragmentを初期化
            walletFragment?.initialize(startParamsBuilder.build())
            
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.wallet_button_holder, walletFragment, WALLET_FRAGMENT_ID).commit()
    }

    companion object {
        val MASKED_WALLET_REQUEST_CODE = 888
        val WALLET_FRAGMENT_ID = "wallet_fragment"
    }
}

WalletFragmentStyleWalletFragmentOptionsが登場しました。WalletFragmentStyleでは、「購入する」ボタンのスタイルをコードで設定しているようです。通常のボタンとは違う特殊なComponentなんでしょうか。 WalletFragmentOptionsでは、スタイルの適用、実効する環境の設定など「購入する」ボタンの動作を定義する設定を行うようです。

これらをWalletFragmentInitParamsでまとめてFragmentの初期化に利用しています。このオブジェクトもBuilderを利用して生成しています。

ステップ7

Intentで別画面へ遷移し、そこで決済情報を入力するようです。Intentの返却値を取得して処理する部分を記述します。実際の手順はこちら

private var maskedWallet: MaskedWallet? = null

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    when(resultCode) {
        Activity.RESULT_OK -> {
            maskedWallet = data?.getParcelableExtra(WalletConstants.EXTRA_MASKED_WALLET)
            Toast.makeText(this@MainActivity, "覆面財布をてにいれた!", Toast.LENGTH_SHORT).show()
        }

        Activity.RESULT_CANCELED -> {
            Toast.makeText(this@MainActivity, "きゃんせるだ!", Toast.LENGTH_SHORT).show()
        }

        WalletConstants.RESULT_ERROR -> {
            Toast.makeText(this@MainActivity, "しっぱいしました", Toast.LENGTH_SHORT).show()
        }
    }
}

これでMaskedWalletのチュートリアルは実装が済みました。確認してみましょう。

実行

AppCompatActivityを継承したからか、画面が黒い。

device-2015-06-02-220834

「Buy Now」をぽちっと押すとクレジットカードの番号入力画面になります。ここではチュートリアルにあるように以下のテスト用ダミー番号が有効です。

  • Card Type: Visa
  • Card Number: 4111 1111 1111 1111
  • CVC: any three digits
  • Expiration: any date in the future

正常に登録できるとこんな感じ。

device-2015-06-02-221259

クレジットカード情報の登録画面は、スクリーンショットも何もかも不可能でした。Permissionで保護されているようです。そりゃそうですね。プログラム上からスクリーンショット撮ることなんて造作も無いことでしょうし。正しい動作かと思います。

一点、送付先住所の入力の箇所ですが、適当な郵便番号だと入力のValidateに引っかかるようです。きちんと選択した米国州のZip Codeを入力すればうまくいきます。さらに電話番号については、電話帳の番号が自動的に入力されますが、先頭の0を除去しないとこれもまた電話番号のValidateに引っかかってしまうようです。

まとめ

まずは決済情報を入力するためのMaskedWalletを実装してみました。意外とサクサクすすめられ、チュートリアルに沿ってやれば1時間かからない程度で終わります。こういったきちんと動くチュートリアルが整備されていることは非常に重要なのではないでしょうか。 今回は、個人的な趣味も兼ねてKotlinで実装しています。次回は、FullWalletの方も試してみることにします。

参照