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

2015.06.26

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

FullWallet

前回MaskedWalletのチュートリアルを実行して、決済情報を登録しました。今回は商品を買うところまでのステップを実行していきます。

ステップ1

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"
        />

    <Button
        android:layout_width="200dp"
        android:layout_height="48dp"
        android:layout_gravity="center_horizontal"
        android:text="Confirm"
        android:onClick="requestFullWallet" />
    
</LinearLayout>

前回作成したMaskedWalletのボタンの下に配置しています。onClick イベントで requestFullWallet を指定しているので実装クラスにはこのメソッドが必要ですね。

ステップ2

FullWalletRequestを作成するファクトリメソッドを作成しましょう。実際のステップはこちらです。

MainActivity.kt にメソッドを定義します。

private fun generateFullWalletRequest(googleTransactionId:String): FullWalletRequest {
    val fullWalletRequest = FullWalletRequest.newBuilder()
            .setGoogleTransactionId(googleTransactionId) // Transaction ID?
            .setCart(Cart.newBuilder()
                    .setCurrencyCode("USD")
                    .setTotalPrice("10.10")
                    .addLineItem(LineItem.newBuilder()  // ステッカーを1アイテム作成
                            .setCurrencyCode("USD")
                            .setDescription("Google I/O Sticker")
                            .setQuantity("1")
                            .setUnitPrice("10.00")
                            .setTotalPrice("10.00")
                            .build())
                    .addLineItem(LineItem.newBuilder()  // 税金を1アイテムとして作成
                            .setCurrencyCode("USD")
                            .setDescription("Tax")
                            .setRole(LineItem.Role.TAX) // Roleを税金に設定
                            .setTotalPrice(".10")
                            .build())
                    .build())
            .build();
    return fullWalletRequest
}

MaskedWalletRequestに似てますが、Transactionという単語が見えるあたりが異なります。さらに前回も触れましたが、MaskedWalletRequestではOption扱いであった setCart ですが、FullWalletRequestでは必須設定項目です。

ステップ3

GoogleApiClientの初期化を行います。実際のステップはこちらです。

GoogleApiClientは、Android Pay以外でもGoogle Play Servicesを利用する場合は割とよく見るクラスだと思います。いつも通りCallbacksの設定と共に初期化を行います。googleApiClient はインスタンス変数としてクラスの先頭で宣言しておきましょう。

private var googleApiClient: GoogleApiClient? = null

MainActivity#onCreate に初期化処理を追記していきます。

googleApiClient = GoogleApiClient.Builder(this)
		            .addConnectionCallbacks(this)
		            .addOnConnectionFailedListener(this)
		            .addApi(Wallet.API, Wallet.WalletOptions.Builder()
		                    .setEnvironment(WalletConstants.ENVIRONMENT_SANDBOX)
		                    .setTheme(WalletConstants.THEME_LIGHT).build())
		            .build()

addConnectionCallbacks, addOnConnectionFailedListenerthis を指定していますが、まだこのActivityクラスには対応するインタフェースが実装されていません。このクラスにインターフェースを定義します。

public class MainActivity : AppCompatActivity(), GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

Kotlinの場合は、継承だろうがインタフェースの実装だろうがお構いなしに並列に書くのでこんな記述に。

スクリーンショット 2015-06-11 19.51.44

インタフェースは設定しましたが、実装が記述されていないので怒られています。実装を記述。このあたりはIDEの機能をザクザク使ってショートカットしていきます。

スクリーンショット 2015-06-11 19.52.32

全部選択してOKを押します。必要なインタフェースが実装完了。

override fun onConnectionSuspended(cause: Int) {
    throw UnsupportedOperationException()
}

override fun onConnected(bundle: Bundle?) {
    throw UnsupportedOperationException()
}

override fun onConnectionFailed(result: ConnectionResult?) {
    throw UnsupportedOperationException()
}

Kotlinで記載しているとこの段階でエラーが出てるのに気づきます。多重継承を行ったため、superで指定するクラスを見失ってますので、明確に指定します。

override fun onCreate(savedInstanceState: Bundle?) {
    super<AppCompatActivity>.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    ...(snip)

Activityクラスで呼び出している super は全てAppCompatActivityを指定するように修正します。

インターフェースの実装を自動で行った場合、throw UnsupportedOperationException() が記述されています。中身を実装するのを忘れないように、何もしないと例外をThrowして落ちるようになってますので、何もしない場合も削除する必要があります。

次にGoogleApiClientに接続します。

override fun onStart() {
    googleApiClient.connect()
    super<AppCompatActivity>.onStart()
}

override fun onStop() {
    googleApiClient.disconnect()
    super<AppCompatActivity>.onStop()
}

ステップ4

最後にFullWalletRequestを作成し、呼び出す処理を記述します。実際のステップはこちらです。

まずはRequestCodeの宣言から。

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

    val FULL_WALLET_REQUEST_CODE = 889
}

FullWalletRequestを生成し、FullWalletをロードする処理をメソッドとして定義します。このメソッドはlayoutファイル(activity_main.xml)で先ほど定義したクリック時に呼び出すメソッドにあたります。

public fun requestFullWallet(view: View) {
    maskedWallet?.let {
        Wallet.Payments.loadFullWallet(googleApiClient,
                generateFullWalletRequest(it.getGoogleTransactionId()),
                FULL_WALLET_REQUEST_CODE)
    }
}

先日のKotlin勉強会で教えてもらった let を早速利用してみました *1。ここの it は、maskedWallet を指しています。maskedWallet がnullの場合は実行されないので、it は必ず値が入っていますので特に気にせずメソッドを呼び出せます。

Intentの結果を受ける onActivityResult にFullWalletの結果コードの場合の処理を追加します。RequestCodeが FULL_WALLET_REQUEST_CODE だった時の処理は以下のように記述しました。

FULL_WALLET_REQUEST_CODE -> {
    when(resultCode) {
        Activity.RESULT_OK -> {
            fullWallet = data?.getParcelableExtra(WalletConstants.EXTRA_FULL_WALLET)
            fullWallet.let {
                Toast.makeText(this, it!!.getProxyCard().getPan().toString(), Toast.LENGTH_SHORT).show()       // クレジットカード番号表示
            }
        }

        WalletConstants.RESULT_ERROR -> {
            Toast.makeText(this, "An Error Occurred", Toast.LENGTH_SHORT).show()
        }
    }
}

前回のMaskedWalletの取得でやった処理とほとんど変わりません。変わることといえば、Cancelがないことくらいでしょうか。前回のMaskedWalletの処理と合わせると以下のようになります。ActivityResult を処理する全体を見てみるとこんな感じになっています。

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

    when(requestCode) {
        MASKED_WALLET_REQUEST_CODE -> {
            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()
                }
            }
        }

        FULL_WALLET_REQUEST_CODE -> {
            when(resultCode) {
                Activity.RESULT_OK -> {
                    fullWallet = data?.getParcelableExtra(WalletConstants.EXTRA_FULL_WALLET)
                    fullWallet?.let {
                        Toast.makeText(this, it.getProxyCard().getPan().toString(), Toast.LENGTH_SHORT).show()       // クレジットカード番号表示
                    }
                }

                WalletConstants.RESULT_ERROR -> {
                    Toast.makeText(this, "An Error Occurred", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
}

これでFullWalletの機能を利用する準備が整いました。まだステップは残ってますがひとまず実行してみます。

途中だけど実行

ちょっとだけ味見実行。MaskedWalletの処理を実行します。

device-2015-06-12-193910

MaskedWalletのデータが取得出来ました。

device-2015-06-12-193942

FullWalletの処理を実行。Confirmボタンをタップします。

device-2015-06-12-195045

処理に成功したのでクレジットカード番号が全て表示されました。

実際のアプリケーションでこの実装ではかなりザルなので問題ありますが *2これでOrderを確定したFullWalletを構築することが出来ました。あと1ステップ!

Notify Transaction Status

Transactionが完了したら、最後にTransaction Statusオブジェクトを送信する必要があるようです。

public fun generateNotifyTransactionStatusRequest(googleTransactionId:String, status:Int) : NotifyTransactionStatusRequest
            = NotifyTransactionStatusRequest.newBuilder().setGoogleTransactionId(googleTransactionId).setStatus(status).build()

単一式関数でぺろっと書いてみました。return がありません。このオブジェクトを送信するのは、FullWalletのRESULTが返ってきた箇所です。ActivityResult のところへ追加します。

Activity.RESULT_OK -> {
    fullWallet = data?.getParcelableExtra(WalletConstants.EXTRA_FULL_WALLET)
    fullWallet?.let {
        Toast.makeText(this, it.getProxyCard().getPan().toString(), Toast.LENGTH_SHORT).show()       // クレジットカード番号表示

        Wallet.Payments.notifyTransactionStatus(googleApiClient,
                generateNotifyTransactionStatusRequest(
                        it.getGoogleTransactionId(),
                        NotifyTransactionStatusRequest.Status.SUCCESS))
    }
}

最後のこの処理はなんでしょうか。リンク を見てみると以下のような記載があります。

Sends a notification to Google on whether the transaction succeeded or failed. This should always be called after payment processing as well as any failed validation checks.

どうも処理が成功したかどうかをGoogleに通知する必要があるようです。特に結果が返ってきたりするわけではないようなので、なかなか釈然としないものがあります。

まとめ

やはりWeb上での決済を行う場合は、Transactionの関係上やりとりする処理は多い印象です。ただこのあたり、Shippingアドレスやクレジットカード情報を自前で保存する必要がなく、既に色々な認証をとっている(であろう)Googleに任せられるのは非常に楽で良いのではないでしょうか。

自分のアプリに決済を簡単に載せられることで、今後より様々な箇所でスマートフォンを通した決済が可能になれば、それはそれで非常に便利な未来が待っていそうです。 とりあえず、今回チュートリアルをさらっとさらってみました。まだまだCodelabには多くのチュートリアルがあるので、ひと通りは触ってみようかと思います。

参照

脚注

  1. stdlib - let
  2. むしろここをどうすれば良いのかが一番知りたいところではありますが・・・