Android:アプリ内課金を実装してみた(購入編)

先日、AndroidアプリのWiFiCutterをリリースしました。
https://market.android.com/details?id=com.okolabo.android.wificutter

今までアプリは基本的に無料アプリに広告を載せるという方法をとっていましたが、頻繁に使われないアプリは、ユーティリティ系のアプリ、ウィジェットなどの場合は広告を見せるという方法ができないので、今回はアプリ内課金(In-App Billing)を使ってシェアウェアとしてリリースしてみました。

In-App Billingについて参考にしたサイトは以下の通り。

あと、テクブ本です!7章のアプリ内課金の説明でだいぶわかりました(宣伝!!)

自分がアプリ内課金に使ったソースも、テクブ本のサンプルコードを修正して実装しました。

実装に関してグダグダ書くと、すごい長いので、詳細が知りたい人はテクブ本や参考URLを参考にしてもらうとして(ぇ、知りたいのは、実際にはどーするの?というところでしょう。まぁ実際にはどうするの?ってのはテクブ本がかなり詳しいので、それを読むと本当にいいんですが、読み終わって咀嚼して理解するまでに時間がかかったので、実際のフローについて書きます。(テクブ本ベースです。あと、はしょってる部分があります。あと、コードはここでは見せず。長いので。)

  1. Activity側でボタンなどを作り、クリックしたら、BillingService.javaのrequestPurchaseメソッドを呼び出します。購入依頼って意味ですね。そのときのrequestPurchaseメソッドの引数には、デベロッパーコンソールの各アプリのアプリ内サービスで定義するアプリ内サービスIDを渡します。アプリ内サービスIDが、ゲームとかだと各アイテム用のIDとかになりますね。タイプとしては、アカウント毎に紐つくタイプ(WiFiCutterで使ってるのはこっち。アプリ使用権とかはこれ)と、何度も買える制限なしタイプ(某SNSの壊れる釣り竿とかいうやつ?)の2種類があります。
  2. 早速、横道にそれました。戻します。
    BillingService.requestPurchase(“hoge”)を呼ぶと、Android Marketアプリが課金サーバに対して非同期で接続を試みます。具体的には、抽象クラスのBillingRequestを継承したRequestPurchaseクラスがスレッド使ってマーケットに接続を試みます。うまくいくと、購入画面が出ます。
  3. 購入を押すと、購入処理が非同期で実行されます。結果はBroadcastされます。
    何度かBroadcastされて、PURCHASE_STATE_CHANGEDがBroadcastされたところまで話をはしょります。途中経過を知りたい人は参考URLを。
  4. BillingReceiverがactionのPURCHASE_STATE_CHANGEDを受け取ったら、BillingService.purchaseStateChangedメソッドを呼び出します。購入状態が変わったので、正しい処理かどうかを確認する必要があります。
  5. PURCHASE_STATE_CHANGEDで受け取った値には、シグネチャ(署名)があるので、正しいものか確認します。テクブ本だと、Security.javaのverifyPurchaseメソッドです。公開鍵は、デベロッパーコンソールの開発者のプロフィールのページにありますから、それをコピーします。サンプルアプリでは、端末内でシグネチャと公開鍵を照らし合わせて正しいか確認しているのですが、実はこれはよくありません

    公開鍵なんだから別にいいんじゃないのか?と思っていたのですが、説明を見る限り、リバースエンジニアリングされて他の開発者の公開鍵と入れ替えられると簡単にアプリがパクられちゃうよということのようです。それは困る…。
    Googleはサーバ側で署名チェックすることを推奨しているので、私はGAE側で署名チェックを実装しました。テクブ本のサンプルでいうと、Secirity.verifyメソッドで行なっている処理をサーバに移しました。そうすると、アプリ側に公開鍵を持たなくてもいいので、セキュリティ的にも向上します。(ただ、インターネット接続権限が必要になる…)

  6. 署名が正しくなければ不正なので処理を中断してエラー扱い。署名が正しければ、署名と別に受け取っているJSONのデータがあります。テクブ本のサンプルソースのままだと、そのJSONが解析されてVerifiedPurchaseクラスのリストが生成されます。productIdが、アプリ内サービスIDです。あとノンスとかありますが、それはセキュリティトークンなんでしょうと理解。
  7. VerifiedPurchaseのリストを拡張for文で処理します。vp.productIdを比較して購入したブツを判定し、それぞれの処理を実装します。WiFiCutterの場合は、ここでPreferencesで購入フラグをtrueにしてます。
  8. BillingService.purchaseStateChangedメソッドの最後で、PurchaseController.purchaseCompleteメソッドを呼んでます。ここで、画面の更新を行なっています。PurchaseCallbackという抽象クラスがあるので、これをActivity内で継承したクラスを作り、画面の書き換え処理などを実装し、PurchaseController.regist(PurchaseCallbackを継承したクラス)を行なっておくと、非同期の購入処理が終わってから、ちゃんと購入済みに書き換えられます。

購入処理だけだと、以上です。キャンセル処理などは実装してません(汗)かなりアバウトな実装をしているので、後々アプデしていこうと思っています(ダメだろ…)

さらにちょっとTipsと気付いてることを書いておきます。

  1. アプリ内課金はAndroid 1.6以降で使うことができますが、テクブ本のサンプルだと、1.6をサポートしていません。使っているメソッドが2.0以上で使えるものなので、1.6でアプリ内課金を使う場合は、リフレクションを使う必要があります。しかしそのリフレクションのサンプルは、参考サイトにあります。
    ローカルサービスの作成の、ペンディングインテントの起動のところを読んでください。
  2. 静的レスポンスを使ったテストができますから、最初は静的レスポンスで行ないましょう。予約済みのproductIdがあるのでそれを使います。参考URLとテクブ本に書いてあります。
    固定のレスポンスを使ったアプリ内購入のテスト
    Activityで渡すproductIdを”android.test.purchased”にするということになります。他のテストについてはサイトを参考にしてください。
  3. アプリ内課金を実装するアプリはマーケットに登録しなければなりませんが、非公開のままでよいです。アプリ内課金は動きます。
  4. デベロッパーコンソールのプロフィールから、テストアカウントを登録しておけば、そのアカウントで課金処理をテストすることができます。それ以外で行なうと、払い戻し処理ができなくなってしまう可能性が…
  5. Googleのサンプルのコードのほうが、綺麗な実装をしているのはわかる。が、いきなり読むにはわかりにくい…(俺のレベルが低いとも言える…)。

次回はアプリ内課金で購入済みのアプリのリストアの処理についてまた書こうと思います。
(再インストールとか、他の端末に同じアプリをインストールした場合の処理)


C言語の勉強

今日はずっとC言語の勉強をしていた。なんでC言語の勉強かと言われたら、別に使うかどうかもわからないのだけれども、なんか知らないとやっぱりどこか負い目を感じるというのがある。C言語で何か作りたいとかいうわけでもない。ただ、知っていれば、NDKとかで使えるときもあるだろうし、ADKとかでも使えるだろう。自分の実力の伸びしろを増やすという意味で有意義かなと思う。

twitterをやっていると、自分が何かしていることについて、色々と教えてもらえたりするので、やはり心強いと思う。反面、一気に話が飛び火してわけわからないのに巻き込まれたりする。返事に困る。無視していいものかどうか、と。「なるほど」としか、言えないし。まぁそこは気にしなくてもいいのかなという気もする。

なんかサンプル集みたいな本があれば、それを読んで作ってみたりとかしてみたいなぁと思うけれども。もしくはVisual C++とかで何か作るのがよいのだろうか?GUIのあるアプリをEclipseで作る方法とか全然知らんし。

そういうのも調べていけたらいいかなと思う。


日記:通勤用自転車サーチを更新

昨日に引き続き、更新しました。

http://319ring.appspot.com/

自転車グッズも検索できるようにしました。

作りたかった機能を作っている最中に、前提が変わってきたので、先に必要な機能を実装。自転車だけじゃなく、自転車グッズも検索できるようにしときたいなぁということで、それを実装しました。しかし、ページが多くなったりすると、誘導が難しくなりそうかなと思ったので(そもそもページ数少ないし)、タブ切替でフォームを変えて、検索結果は同じところに表示するようにすればいいんじゃないかと思って、そのようにしました。

その際、タブ表示をするのにjQuery UIを使いました。
いやー、すごい簡単で驚きです。jQueryすごいわー。みんなが使うわけですね、本当に。


日記:通勤用自転車サーチをパワーアップ

PythonのフレームワークのDjangoでGAE上に作っている通勤用自転車サーチのパワーアップを行いました。機能の追加点は主に、

  1. OpenIDによるログイン機能の実装
  2. ログイン状態で、検索結果のアイテムをお気に入りに登録
  3. お気に入りの閲覧・削除

などです。検索して、気になった自転車などをまた探すのが面倒だったりするかなと思ったので、お気に入りに登録する機能があればいいなと思ったのですが、ユーザ登録をしてもらうのはユーザにとって面倒くさいだろうから、OpenIDで実装しました。ライブラリの使い方さえわかれば、そんなに複雑ではないんですが、結構苦戦しました。OpenID認証用のライブラリがいくつかあるので、どれがいいのかがわからないとかありました。あとGAE上で使いやすいものかどうかなど。

追加したい機能はまだいくつかあるので、また後日実装していきたいと思います。


Django:django_openid_authを使ってると出るWarningに対処

Django-nonrelで、django_openid_authを使っていたら、ログにWarningが出ているのに気付いた。

/Users/********/Documents/Titanium Studio Workspace/Sample/src/django/contrib/auth/__init__.py:26: DeprecationWarning: Authentication backends without a `supports_object_permissions` attribute are deprecated. Please define it in .
DeprecationWarning)
/Users/********/Documents/Titanium Studio Workspace/Sample/src/django/contrib/auth/__init__.py:31: DeprecationWarning: Authentication backends without a `supports_anonymous_user` attribute are deprecated. Please define it in .
DeprecationWarning)

django_openid_auth/auth.pyのOpenIDBackendクラスにsupports_object_permissionsとsupports_anonymous_userを定義しろということらしい。ということで追加したら、Warningは出なくなりました。

# 略
class OpenIDBackend:
    """A django.contrib.auth backend that authenticates the user based on
    an OpenID response."""
    supports_object_permissions = False
    supports_anonymous_user = False
# 略