2013年8月1日木曜日

Android 4.3 と 開発者ツールのアップデート

 Android Jelly-Beanが4.3へアップデートされてました。
新しいNexus7に搭載積まれるらしいですが、既存Nexus (4/7/10)とGalaxy Nexus HSPA+にも既にアップデート展開されているようです。

 アナウンスでは、新しいAndroid 4.3 (コードネームはJelly-Beanのまま)と、開発者ツールであるAndroid NDK (R9)、旧バージョンのAndroid OS搭載端末へ新しいバージョンの機能をサポートするためのAndroid Support Library (R18)もリリースされました。(気が付けばAndroid SDK Managerにアップデートが通知されてました。)

 NDK(R9)には、Android 4.3で追加されたOpenGL ES 3.0 API と Android 4.3 API へのネイティブアクセスがサポートされたようです。
また、気になるSupport Library (R18)ではついにAction Bar APIがサポートされました。これでAndroid 2.1端末においてもAction Barが使えるようになりますか。え?もうAction Bar Sherlockで実装してるから要らない?

 その他トピックとしては、RTL言語のテキスト処理をサポートするBidiFormatterユーティリティの追加ViewPager初期レイアウト処理時処理変更、同じくViewPagerが誤ってTYPE_VIEW_SCROLLEDイベントを取りこむ問題の修正DrawerLayoutとSlidingPaneLayoutにてプロジェクトコードが編集されている間、例外を投げないよう修正ExploreByTouchHelperの追加Google Cast 開発者向けプレビューのサポートをする新しいV7 mediarouter Libraryの追加RenderScriptのAndroid 2.2サポート...etc まぁ色々機能修正・強化されましたと。サポートライブラリがあると、どうしても下位互換を考えなければならなくなるのである程度切ってくれてもいいんですがね。
肝心のAndroid 4.3は何が新しくなったかというと、大きくは以下の点が新しくなったとのこと。

  • OpenGL ES 3.0のサポート
    より綺麗な2D&3Dグラフィックスを使ったゲームを作れるようになりましたと。
    GLSL ESシェーディング言語の新バージョン、高品質ETC2/EACテクスチャ圧縮...etc。NDK(R9)から利用できます。 端末のグラフィックスハードウェアに依存するので注意が必要との事です。使う予定はありませんが。

  • Bluetooth SMART のサポート
    低消費電力通信規格「Bluetooth Low Energy」に対応。
    最新のAndroid (4.3のこと) を実行しているBluetooth Smart Ready対応端末では、事実上全てのBluetooth対応製品と互換性があるってことになるらしい。前から持ってたキーボードやヘッドフォンから、Pebbleなどのスマートウォッチまで対応できると。
    bluetooth.comにも記事が掲載されています。
    Blutooth SmartのネイティブサポートがAndroid 4.3に組み込まれているから 開発者はリソースと開発時間を節約するために単一のAndroid Bluetooth APIを利用できる。

  • 制限付きプロファイルのサポート
    タブレット用のマルチユーザー機能にさらに、制限付きプロフィールがサポートされました。
    所有者(管理者?)が、ユーザーに対してきめ細かい制限をかけることができるようになりました。 例えばアプリの課金機能は子どもに使わせない...とか。
    もちろんアカウントの権限管理は法人顧客相手にも有用な機能なので、今後は権限を意識したアプリ開発も必要になるということですね。

  • 最適化された位置情報とセンサー
    Google PlayサービスのロケーションAPIを最適化し、バッテリー使用量を最小限にしました。
    • ハードウェアジオフェンシング
      デバイスのハードウェアで位置測定を行います。ソフトウェアでの位置計測に比べて電力効率が良くなります。 デバイスが移動中の場合などに、Google PlayサービスのロケーションAPIはこの最適化を活用して バッテリー消費を抑えます。(要対応ハードウェア)
    • Wi-Fiスキャン専用モード
      バッテリー節約と位置精度向上のために、Wi-Fiネットワークに接続せずにスキャン状態を保つことができます。 位置情報サービスのためにWi-Fiに依存するアプリはユーザーにこの設定を有効にするよう依頼することができます。 このWi-Fiスキャン専用モードはハードウェア依存しないAndroid 4.3プラットフォームの機能です。

  • 新しいメディア機能
    • Modular DRM Framework
      Modular DRM Frameworkの追加により、簡単に独自ストリーミングプロトコルをMPEG-DASHに統合することができる。
    • VP8 Encoder
      内臓されているVP8エンコードフレームワークへネイティブAPIからアクセスできるようになりました。
    • サーフェスからのビデオエンコーディング
      OpenGL ES surfaceからのストリームをバッファコピーすることなく直接エンコーダーに送れる。
    • メディアマルチプレクサ
      Mpeg4-Audio StreamとVideo Streamを1つのMpeg4に結合する新しいメディアマルチプレクサのAPIが使用できる。
    • リモートコントロールクライアント
      Android 4.0以降メディアプレイヤーと同等のアプリケーションは、 デバイスロックスクリーン、ノティフィケーションおよびBluetooth機器から リモートコントロールクライアントを通じて再生コントロールができましたが、 これからはリモートコントロールクライアントを通して、再生中のプログレス表示と特定の再生位置に ジャンプするコマンドを受け取る事ができるようになりました。

  • 美しいアプリケーションを構築する為の新しい方法
    • Notificationへのアクセス
      新しいAPIを使ってアプリがユーザーパーミッションを得てnotificationのストリームを観察し、 Bluetooth機器にnotificationを表示するなど、ユーザーが望む任意の方法でnotificationを通知することができます。 また、ユーザーはアプリがnotificationへのアクセスの有効・無効を任意に切り返ることができます。
    • オーバーレイの表示
      基礎レイアウト構成を乱すことなく透明なオーバーレイをトップビューおよび ビューグループの上に一時的に作成することができます。 もういちいち透過Activityで表示しなくてもいいってことですかね。
    • Optical boundsレイアウトモード
      新しいレイアウトモードです。
    • カスタム回転アニメーションタイプ
      デバイスを回転させたときのウィンドウのアニメーション種類を定義できます。 jump-cut、cross-fade、または標準のウィンドウ回転のいずれかをウィンドウプロパティに設定できます。 ウィンドウはフルスクリーンで、他のウィンドウに覆われていない場合に指定されたアニメーションを使用します。
    • スクリーンオリエンテーションモード
      端末が回転している場合にアプリが適切な向きで表示されているか確認するためのに Activityに新しいOrientationモードを設定できます。他にも、アプリは現在の向きに画面をロックするための 新しいモードも使用することができます。これは、ビデオを撮影しながら回転を無効にしたいカメラを使 用したアプリケーションの場合に便利です。
    • クイックレスポンスを処理するインテント
      任意のアプリでクイックレスポンスを処理できる新しいPublic Intentが導入されました。
      クイックレスポンスとは、電話に出たり、端末のロック解除をしなくてもテキストメッセージで応答できる機能です。 このメッセージングを任意のアプリで行うことができるようになりました。 アプリは、Intentを監視し、メッセージングシステムを介して発信者へメッセージを送る事ができます。

  • インターナショナルユーザーのサポート
    RTL言語に関する機能が改善され、サポート範囲が広がったと。詳細は省略。
    Support LibraryにもRTL言語のサポートが入ってたのでより国際言語対応しますという流れなのでしょうか。 開発者向けオプションの疑似ロケール機能を使って、簡単に違う地域のロケール・言語をエミュレートできるので ローカライゼ―ションテストに役立ちます。

  • アクセシビリティ と UIオートメーション(自動化)
    Android 4.3以降、キーボードショートカット、ジェスチャー入力によるナビゲーションなどの重要な イベントをアクセシビリティサービスで監視し、フィルタリングすることができます。 サービスでは、システムまたは他のアプリへイベントが通知される前に必要に応じて処理を行うことができます。 Android 4.3のAccessibilityフレームワークに構築された新しいUI自動化フレームワークは、 ユーザーの操作をシミュレートし、画面内容のプロパティを取得することによってデバイスのUIとやり取りを行うことができます。 UI自動化フレームワークを使用すると、基本的な操作、画面の回転、入力イベントの生成、スクリーンショットの取得など 多くの操作を行うことができます。 これは、複数のアプリケーションにまたがるアクションまたは処理手順を含む 実際のユーザーシナリオを想定したテストを自動化するためのパワフルな方法です。

  • エンタープライズとセキュリティ
    • WPA2-エンタープライズネットワーク向けのWi-Fi設定
      WPA2エンタープライズアクセスポイントへ接続するために必要なWi-Fiの資格情報を設定することができるようになりました。
      開発者は、企業内で使用される認証方式のための拡張認証プロトコル(EAP)とカプセル化されたEAP(Phase2)認証情報を設定する ための新しいAPIを使用することができます。 Wi-Fiへのアクセスと設定を変更するパーミッションを持つアプリは、 EAP(Phase2含む)認証方式の様々な認証資格情報を設定することができます。
    • SELinuxでAndroid sandboxの強化
      UIDベースのアプリケーションサンドボックスを強化するためにSELinuxの Linuxカーネルの強制アクセス制御 (MAC:Mandatory access control)システムを使っています。 これは、潜在的なセキュリティの虚弱性に対してOSを保護します。

  • キーチェーンの機能強化
    ルート証明書の管理を行う KeyChain APIが機能強化されました。

  • Android Keystore Provider
    APIを使用して、アプリがユーザーとの対話なしに他アプリや他ユーザーから使用されないキーを作成、 保存しキーストアに追加することができます。キーストアは端末外へのエクスポートはできません。

  • アプリからのsetuidを制限
    /systemパーティションはzygote -spawned processesによりnosuidにマウントされており、 アプリからのsetuidコマンドの実行を防ぎます。 これは、潜在的なセキュリティの虚弱性によるrootへの攻撃を減らします。

  • 新しいパフォーマンス解析ツール
    • Systraceの機能強化
      アプリのパフォーマンスを解析するために、より多くの情報を収集することができるようになりました。 ガベージコレクション、リソースの読込やその他ハードウェア、カーネル、Dalvik VMからのトレースデータを収集できます。
    • GPUプロファイルの画面表示
      開発者向けオプションから設定を変更することにより、GPUのプロファイルを画面に表示できます。

  • StrictMode warning for file URIs
    アプリ間で ”file://”プロトコルを使ってファイルを共有させようとした場合、 適切な権限がないと警告されるようにポリシー制約が変わったようです。
    アプリ内で"file://"って使うの普通?
 記事が長くなってしまいましたが、機能強化、セキュリティアップデート、利便性向上…と純粋なアップデートだったと思います。
画面の2D&3D描写の改善や、タッチ時の処理速度低下の低減など、ユーザーが直に触れる部分も改善されているようなので今後のAndroidに益々期待です。

尚、記事内で内容に間違いがあった場合はご指摘頂けると大変助かります。
誤字脱字の指摘も大歓迎です。

2013年7月22日月曜日

Google Play In-App Billing テスト方法

続きの続き。Google PlayのIAB(In-App Billing)のテストの話を乗せときます。
あまり載せることがないので、基本的には オフィシャル の内容ほぼそのままです。

Google Playのアプリ内課金テストではAmazonのIAPのようなテストアプリは使いません。同様の機能をPlayStoreが既に備えています。また、本番環境でのテストをする方法も既に用意されています。

2つのテスト方法
Google Play には、アプリ内課金の実装テストに役立つ2つの機能が用意されています。
  1. サンドボックステスト
    • 予めテスト用に予約されたアイテムIDを使って、所定のレスポンス時のテストを行える。
  2. 実購入テスト
    • Google Playを通じて実際の購入と同じ環境でアプリ内課金を行うことができる。こちらはGoogle Playへの通信を行い、実際にGoogle Walletへの取引が発生する。尚、テストではこの課金はキャンセルされ実際の請求は発生しない。

どちらのテスト方法もアプリを公開する前に行うためのテストですが、それぞれ役割が違います。
  • サンドボックステストは、主にアプリ内課金機能の単体テストレベルで利用します。
  • 実購入テストは、公開前の結合・システムテストレベルで行うテストに利用します。

この2つのテスト方法を利用するには以下の条件があります。
  • テストにはPlayStoreアプリが必要になるため、実端末でしか行えない。
    (エミュレータ不可)
  • 端末はAndroid 1.6以上かつ、PlayStoreアプリのVersion 2.3.4以上が必要。
    Android 3.0の場合は、MyApps Version 5.0.12以上が必須環境となります。

テストできるアプリ内課金種類
 テストとして購入できるアプリ内アイテムは、「アプリ内管理アイテム(買切り型アイテム)」と、「消費するアプリ内管理アイテム」の2つです。「Subscription」のテスト購入はできません...残念。

サンドボックステスト
 予約されている所定のアイテムIDを使ってアプリ内課金処理の実装をテストできます。このテストでは、実装が正しくGoogle Playからのレスポンスを処理し、署名を検証できることを確認できます。

各予約済みアイテムIDを使用して購入リクエストを行うと、Google Playがそれぞれ特定のレスポンスを返してくれます。もちろん課金は発生しません。尚、このテスト方法では支払方法を指定することはできません。
また、サンドボックステストを行うためにAPKをアップロードしたり、アプリ内で販売するアイテムリストを公開する必要はありません。

予約済みアイテムID
サンドボックステストでは、以下4つの予約済みアイテムIDを使ってテストをすることができます。
  1. android.test.purchased
    購入成功レスポンスを返します。署名の検証テストのためにJSON文字列とレスポンスに署名が含まれています。
  2. android.test.canceled
    購入処理中にエラーが検知された・購入がキャンセルされた時のレスポンスを返します。これは登録されているクレジットカードが無効だった場合やユーザー操作により購入がキャンセルされた時に返されるレスポンスです。
  3. android.test.refunded
    払い戻されたときのレスポンスを返します。
    払い戻しはGoogle Play In-App Billingサービスから行うことはできません。 払い戻しは、販売者自身のGoogleWalletから行う必要があり、Google Walletから払い戻しが行われた 通知を受けた後、Google Playからアプリへ通知されます。
    払い戻し処理に関する詳細は、「 Handling IN_APP_NOTIFY messages 」と「 In-app Billing Pricing 」を参照してください。
  4. android.test.item_unavaliable
    リクエストされたアイテムIDが、公開しているアプリ内アイテムリストに存在しなかった場合のレスポンスを返します。
 いくつかの予約済みアイテムIDを使用したサンドボックステストでは、レスポンスに署名を返すことができます。レスポンスに署名含まれるパターンは以下の表のとおりです。

過去にAPKを公開済み APKをドラフトアプリとしてアップロード(非公開) アプリを実行しているユーザーアカウント レスポンスに署名が含まれるか?
No No Any 非署名
No No 開発者アカウント 署名
Yes No Any 非署名
Yes No 開発者アカウント 署名
Yes No テストアカウント 署名
Yes No Any 署名

サンドボックステストの手順
 サンドボックステストを行うために特別必要な手順はありません。
  1. テストする端末にアプリをインストールします。
  2. 開発者Google アカウントでログインします。
  3. テストする端末のPlayStoreまたはMyAppsアプリのバージョンが必要なバージョンであることを確認して下さい。
  4. テストするアプリで予約済みプロダクトIDを使用してアプリ内課金を行う。

実購入テスト
 サンドボックステストで動作確認がとれたら、実際にアプリ内購入を行ってテストしましょう。このテストは、アプリをリリースする前に、実際のGoogle Playと通信を行った本番環境で課金を行います。Google Walletにも課金処理が走りますが、予めテストアカウントを指定しておくことにより当該テストアカウントの取引を自動的にキャンセルすることで実際の課金無しに本番環境でテストすることができます。

Note:
実購入を行うテストアカウントは端末上に設定されている必要があります。端末に複数アカウントが設定されていた場合、アプリ内課金を行う際はダウンロードしたアカウントへ課金が発生します。ダウンロードしたユーザーアカウントが端末上に設定されていなかった場合は、メインのユーザーアカウントで購入しようとします。

※ここではGooglePlayのデベロッパーアカウントを持っている必要があります。デベロッパー登録には25US$を支払う必要があります。また支払のためのクレジットカードも必要です。

実購入テストの設定
『テストアカウント』の設定
 公開前のアプリをダウンロードし、実購入テストを行えるアカウントを設定する必要があります。テストアカウントの設定方法はデベロッパーコンソールにて以下の手順で設定します。 In-App Billing V3を利用している場合、ここで設定したアカウントだけがテスト購入できるアカウントとなります。
  1. 左メニューの「Settings」を選択します。
  2. 表示されたページ内の「Account details」を選択します。
  3. 表示されたページ内の「License Testing」セクションにある 「Gmail accounts with testing status」テキストボックスへテストアカウントとして設定するアカウントのGMailアドレスを入力してください。
  4. 入力後は、必ず「Saved」で保存してください。
設定完了後、15分以内に設定内容が反映されます。繁栄されればそのアカウントでドラフトアプリのアプリ内課金が利用可能になります。

※尚、テストアカウントに開発者アカウントは設定できません。
 (GoogleWallet上、自分から購入することができないため。)

Android端末に設定されているメインアカウントの変更方法
 公式的にはファクトリーリセット(工場出荷状態へ戻す)し、セットアップ時またはシステム設定から再度メインアカウントを登録する必要があります。端末によっては異なる方法でもできるという話がありますが、オフィシャル的にはこれが一番?みたいですので面倒でも課金処理周りですから手堅く一度リセットして設定しましょう。

実購入テストの実施
アプリ内課金の購入テストを行うには以下の手順に従ってください。
  1. テスト対象アプリのAPKファイルをデベロッパーコンソールへアップロードします。
    (未公開のままでOK)
    • アップロードするAPKファイルは、リリース用のKeyStoreで署名されている必要があります。
  2. アプリ内で販売するアイテムリストを作成・公開する。
    • アプリ内で販売するアイテムがデベロッパーコンソール上で「公開」状態になっていることを確認して下さい。
  3. Android端末にアプリをインストール
    • デベロッパーコンソールで設定したテストアカウントが設定されている端末へアプリをインストールします。
    • PlayStoreアプリ または MyAppsアプリのバージョンが最新または対応バージョンであることを確認して下さい。
  4. アプリ内でアイテムを購入する。(テストの実施)
※デバイスでテストするAPKとアップロードしたアプリのバージョンNo.は一致している必要があります。


 お金がらみの機能でバグ出すとレビュー欄が荒れるので、本番環境を使ってしっかりテストしたいところ。 欲を言えば、Subscriptionのテストが簡単にできる機能も欲しいところです。あっちのテストの方が色々面倒なんで。

2013年7月16日火曜日

ファイル共有サービス『CoreDrive』 レビュー

「ねこじゃらし」さんが提供しているファイル共有サービス「 CoreDrive 」を試してみたのでレビュー。

≪メリット≫
  • ブラウザ上で 様々な形式のファイル をプレビュー表示できる。
  • 期限やパスワードを設定して共有ができる。
  • ファイル別にメッセージをやりとりできる。
    これは少し斬新な機能かも。しかし、多人数でメッセージのやりとりが頻繁に行われた場合見辛くなること請け合い。
  • 専用Clientソフトが不要。全てブラウザより閲覧・アップロード可能。
    これは、DropboxやPogoplugなんかでも同じですね。 ただ、スマートフォンからの操作であれば専用アプリがあった方が操作性が良いと思います。
  • facebookアカウントでの共有が可能。

≪デメリット≫
  • (無料プランの場合) 1ファイルの最大容量に250MB制限が付き、
  • (無料プランの場合) アップロードファイル数の制限が50ファイルまで。
  • 通知メールがうざい。
    ボード(ファイルを区分するためのフォルダ的なもの)の 新規作成、リネーム、削除、ボードへの他ユーザーの招待、 招待のキャンセル、参加拒否、ボードからの脱退といった アクションでメールによる通知が行われる。 メールの振り分け設定で処理しておかないと すぐにメールであふれてしまう。 この設定は今のところ変更できない模様。
  • 現段階で、iPhone/スマートフォンの専用アプリがなくスマホでもブラウザからアクセスすることになるが、サイトを表示してみてもスクロールなどが不自然でかつ、表示崩れを起こす場面もあり使い勝手に少々難あり。

≪総評≫
現段階では、Dropbox, SugarSyncやPogoplugといったメジャーなクラウドストレージサービスには及ばないものの、 PicasaやFlickrより限定的に、かつ写真や動画以外のドキュメントを共有したい場合には有効なサービスなのではないでしょうか。

利用シーンとしてパっと思いついたのは、イベントなどで限定された期間内で参加メンバーへ告知・資料を公開する場合です。イベントなどでは期間や参加者が限られるので、パスワードをメンバーに伝達しておき公開しているボードの期限をイベント期間で限定してしまえばイベント後にはアクセスできなくする事ができて便利です。

逆に、ビジネスシーンでこのサービスを利用しようとはあまり思えないのも事実。
有料会員になっても、共有ファイル数が10倍にしかならない。ファイル容量が無くても1カウント取られるのが痛いです。

受けた印象としては、現段階ではビジネスでゴリゴリ使うというよりも、 SNSの機能の拡張としてこのサービスを利用するのがいいのかなと感じました。
年内にはwindows、OSX、iOSやAndroidなどに対応した クライアントが出てくるらしいので今後に期待。

2013年7月12日金曜日

Subscription - In-App Billing

前回の「In-App Billing Version 3 概要」の続きで、In-App BillingにおけるSubscriptionについての記事です。技術的な話というよりもSubscriptionを売るときに考えなきゃならない事がメインです。公式サイトのSubscriptionページを解釈して書いてます。

Subscriptionって?
定期的(毎月または年間)に自動更新され課金が発生するアプリ内課金アイテムの種類です。有料会員用コンテンツへのアクセス権や、期間で課金したい機能などの販売に利用できます。

他のアプリ内課金アイテムと同様にアプリ内にて販売でき、もちろんアプリにて直接取引を行う必要はありません。GooglePlayがGoogleWalletを通して統一された課金手続きにより処理します。これは、ユーザーへ一貫した購入フローを提供するためです。

外部サービスのSubscriptionの使用
アプリ内に既存の外部サービスSubscription加入者を作成することもできます。
  • ウェブサイトなどで既にSubscriptionを販売していて、アプリからそのSubscriptionを購入しているかどうかを判断するためにアプリに独自のビジネスロジックを追加することができます。
  • 必要があれば、多くの異なるアプリ間で共有できる横断的なサブスクリプションも実装できます。例えばあなたの全アプリ、ゲームまたは他のコンテンツへアクセスするための月会費・年会費としてのSubscriptionを販売することも可能です。これを実装するためには、あなたのアプリにて有効なSubscriptionを判断しコンテンツへのアクセスを許可させるビジネスロジックを追加する必要があります。

販売者の責務
サブスクリプションの販売者は、ユーザーが有効なサブスクリプションによってコンテンツ・機能を利用する権利を有している限り該当コンテンツ・機能を提供し続けなければなりません。これを行わなかったり、有効なサブスクリプションやコンテンツ・機能を削除するなどして利用できなくした場合は処罰の対象となります。

詳細については、 公式サイトのポリシードキュメント を参照してください。

アイテムの属性設定
販売するSubscriptionアイテムはデベロッパーコンソールにて設定できます。Subscriptionアイテムには、以下の属性を設定できます。
  • 課金種別:Subscriptionに設定
  • Subscription ID:Subscriptionの識別ID
  • 公開状態:未公開/公開
  • 言語:Subscriptionを表示するためのデフォルト言語
  • タイトル:Subscriptionアイテムのタイトル
  • 説明:Subscrptionの詳細説明
  • 価格:1Subscription当たりの契約価格
  • 期間:毎月 or 毎年
  • 自動更新の可否:可/不可

デベロッパーコンソールのアイテムを追加して構成する方法の詳細は こちらの公式ページ を参照してください。

価格設定
Subscriptionの作成時に任意の通過で価格を設定できます。ただし、各Subscriptionは0以上の価格を設定する必要があります。また、同じコンテンツに対する価格の違う複数のSubscriptionも作成できます。
(例えば、年間購読の場合は毎月よりも割り引かれた価格を設定するなど)

【注意】
 Subscriptionの価格を変更する場合、新しい価格のSubscriptionを新規作成し、アプリでその新しいSubscriptionを提供するようにしてください。そうすることで既に購入しているユーザーは元の価格のSubscriptionで課金されたまま、新しいユーザーは新しい価格のSubscriptionで課金されます。

自動継続
Subscriptionは、デベロッパーコンソールにて以下のどちらかの期間で自動継続を設定できます。
  • 毎月―初回購入後、毎月購入日にSubscriptionを自動継続して課金します。
  • 毎年―初回購入後、毎年購入日と同じ日にSubscriptionを自動継続して課金します。
Subscriptionでは、設定した金額で無期限に自動継続されます。
Subscriptionが継続された時、GooglePlayがユーザーへ課金を行いメールでその旨を通知します。課金期間は購入日に基づいてSubscriptionの設定期間により計算されます。また、自動継続された際の請求は購入時と同じ支払方法になります。(クレジットカード支払やキャリア課金など)

試用期間
タダ(無料)で有料機能をお試しできる期間を設定することができます。
  • Subscriptionへ試用期間を設定するためにアプリを変更する必要はありません。デベロッパーコンソールのSubscriptionアイテムの設定で試用期間を設定することができます。
  • 試用期間を設定する場合、最低でも7日以上の日数を設定する必要があります。
  • 設定した試用期間を過ぎた場合、自動的にそのSubscriptionは設定されている本来の金額で契約されます。
  • 販売者は、いつでもSubscriptionの試用期間を変更できます。しかし、既に試用期間中のSubscriptionを持っているユーザーに対しては期間の変更は適用されません。
  • 試用期間はSubscriptionアイテム毎に個別に設定できます。
試用期間は0円の課金を行うことで実現しています。試用期間中は無料なので請求は発生しませんが、GooglePlayが0円のSubscriptionを購入したことをユーザーへ電子メールで通知します。

ユーザーは必要があれば任意のタイミングでSubscriptionをキャンセルできます。

解約について
In-App Billing APIに解約機能はありません。よってアプリ内からSubscriptionの解約は行えません。Subscriptionの解約は、PlayStoreアプリから行います。

解約されたSubscriptionの扱い

有効期間中にユーザーがSubscriptionを解約しても、払い戻しはされません。
その代りに、キャンセルされても購入したSubscriptionの期間が終了するまでキャンセルされたSubscriptionであっても有効なままとなります。

いくつかのケースでは、解約のためにユーザーが直接連絡する必要があるケースがあります。また、必要に応じてサーバーAPIを使用して直接ユーザーのSubscriptionをキャンセルすることができます。

アプリのアンインストール
ユーザーがサブスクリプションを購入したアプリをアンインストールしようとした場合、PlayStoreアプリが有効なサブスクリプションが存在することをユーザーに通知します。
  • ユーザーがアプリのアンインストールを続行した場合
    • アプリは削除されますがサブスクリプションは有効なまま課金も継続されます。(※アプリのアンインストール時に自動的にサブスクリプションがキャンセルされませんのでご注意ください。)
  • ユーザーがアンインストールをキャンセルした場合
    • アプリとサブスクリプション両方とも残ります。
※ユーザーは、PlayStoreアプリのMy Apps画面よりいつでも関連するサブスクリプションをキャンセルすることができます。

払い戻し
 Google Playでは払い戻し画面を用意していないので、返金を要求する場合は直接連絡を頂く必要があります。払い戻しの要求を受けた場合、そのユーザーのサブスクリプションをキャンセル、または既にキャンセルされている事を確認する為にサーバーAPIを使用できます。

しかし、GooglePlayはキャンセルされたサブスクリプションにおいても期間中はサブスクリプションが有効であるとみなしている事に留意してください。サブスクリプションをキャンセルし払い戻されたユーザーであってもそのキャンセルされたサブスクリプションの期間中はコンテンツへのアクセスができます。

【重要】
キャンセルされたサブスクリプションの一部払い戻しは、現時点で対応していません。


取引手数料
  • 一般にGooglePlayの条件は、GoogleWalletの標準的な支払方法を通じてのみアプリ内サブスクリプションを販売できます。
  • GooglePlayに公開するアプリにおいて、サブスクリプションを販売するためにはIn-app Billingのトランザクションを処理する必要があり、GooglePlayとアプリの外での購入手続きへのリンクを提供することはできません。条件とポリシーに関する詳細については、 公式サイトのポリシー を参照してください。
  • サブスクリプションの購入にかかる取引手数料は一般的なアプリ内課金の手数料(取引金額の30%)と同じです。

注文番号
特定Subscriptionの取引を追跡するために、GoogleWalletは全ての基本マーチャント注文番号を再発行を提供します。 以下のように基本注文番号の末尾に整数を付加することによって、連続したSubscriptionの取引を表します。
12999556515565155651.5565135565155651 (基本販売者注文番号)
12999556515565155651.5565135565155651..0 (初期購入注文ID)
12999556515565155651.5565135565155651..1 (最初の定期的購入注文ID)
12999556515565155651.5565135565155651..2 (2回目の定期購入注文ID)
Google Playの注文コードフィールドに注文番号が含まれています。
In-App Billing Version 3では、INAPP_PURCHASE_DATA JSONフィールドに、Version 2では、PURCHASE_STATE_CHANGED に含まれています。

購入トークン
Subscriptionの支払がGoogle Walletで承認された後、In-App Billing APIを通じてGooglePlayから購入トークンを取得できます。

この購入トークンは、デバイス上に保存したり、バックエンドサーバへ渡すことができ、Google Playが提供しているHTTPベースのAPI "Purchase State API " を利用してバックエンドサーバからリモートでサブスクリプションを検証したり、キャンセルするために利用できます。

以上。

2013年7月4日木曜日

In-App Billing Version 3 概要

いまさら感はありますが、Google Play Store アプリ内課金サービスである In-App Billing Version 3でSubscriptionが対応された為、おさらい的にOverviewから自分なりの解釈を加えて説明資料を残しておきます。

Google Play Store In-App Billing とは
Android アプリに対して課金機能を提供するAPI。
  • Play Store アプリを経由しGoogle Walletを通じて課金処理を行ってくれます。 さらに、アプリ内で購入するための決済処理UIも提供してくれます。
  • 課金する商品(アイテム)の設定を行うための管理機能は、 デベロッパーコンソール( http://play.google.com/apps/publish )にて提供されています。
  • ※ Developer Consol への登録はアプリを公開したりするサイト・サービスです。 登録には25 USドルが必要になります。
Version 3 の新しい機能
  1. 購入情報がキャッシュされるようになった
    • Play Store アプリが端末内で購入情報をキャッシュするようになりました。
      これで、購入情報取得リクエスト時にすごい待たされることもなくなる?
  2. やっと Subscriptionに対応した
    • Version 3 の新機能というよりは、最近(?)やっとついた機能です。
      Version 3 が出て暫く経っても Subscriptionに関してはVersion 2でしか 対応してなかったのですが、対応されたようです。よかったよかった。。。

取り扱える課金種類
In-App Billing サービスを利用して販売できるアイテム種類は以下の3つです。
基本、AmazonのIn-App Purchasingと変わりませんが、少々取扱いが違います。  
  • アプリ内管理アイテム
    1. 非消費型-アプリ内管理アイテム
      買い切り型アイテム。 広告非表示機能とか有料版アップグレードなどの類のもの。 買ったらその機能を使用する権利を永続的に保持できるアイテム。
    2. 消費型-アプリ内管理アイテム
      使ったらなくなるアイテムのことです。 例えば、ゲーム内の通貨や燃料、行うごとに課金されるコンテンツなどが該当します。 アイテム数もGoogle Play サーバ上で購入した数を管理しており、 消費する際には消費することもGoogle Playへ通知する必要があります。
  • サブスクリプション
    毎月または年間期間単位で、アプリ内のコンテンツ、サービス、または機能を 販売することできるアプリ内課金アイテム種別の1つです。
    他のアプリ内管理アイテムと同様に、デベロッパーコンソールから サブスクリプションアイテムを設定して、アプリ内から販売できます。

一般的なアイテム購入までの処理フロー図
公式サイトの説明から少し独自に変えてあります。
  1. バージョン確認リクエスト
    端末にインストールされているPlay StoreアプリがIn-App Billing API Version 3に 対応しているか確認するために、”isBillingSupported()”リクエストを送信します。
    リクエストが成功した場合、サポートしている or 非サポートの情報を返します。

  2. 購入情報取得リクエスト
    ユーザーの購入履歴や購入情報詳細を照会するには、"getPurchases()"リクエストを送信します。 リクエストが成功した場合、Google Playから「レスポンスコード」、「購入済みアイテムのリスト」、 「購入情報リスト」、「署名リスト」、「トークン情報」を含むバンドルを返します。

  3. アイテム詳細取得リクエスト
    デベロッパーコンソールで設定した、アプリ内で購入可能なアイテムの詳細を Google Playを通して取得できます。アイテムのIDを指定して"getSkuDetails()"リクエストを送信し、 成功するとGoogle Playはアイテムの「タイトル」、「説明」、「アイテム種別」、 「価格」を含むバンドルが返します。

  4. 購入リクエスト
    購入手続きUIを起動するためのインテントを取得するため、 購入対象となるアイテムのIDを指定して、"getBuyIntent()"リクエストを送信します。 リクエストが成功するとGoogle Playから、アイテム購入用UIを起動するための Pending Intentを含むバンドルが返されます。

  5. 購入用UI起動~購入手続き
    • [4.購入リクエスト]で取得したアイテム購入用UIを起動します。
      購入処理はPlay Storeアプリにて行われるので、 アプリは"startIntentSenderForResult()"メソッドでこのインテントを起動し、 購入処理結果を、"onActivityResult()"で受け取る必要があります。
    • "onActivityResult()"へ返される情報には、購入された/キャンセル、 エラーが発生したを示すレスポンスコード、購入詳細情報、 開発者の秘密鍵で署名された購買データの署名が含まれます。
消費可能アプリ内管理アイテムの消費処理フロー図
消費可能アイテムの所持数(購入された数)は、Google Play にて管理しています。
アイテムの使用(プロビジョニング)する前に、Google Playに消費リクエストを送信しアイテムが正常に消費されたことを確認する必要があります。
この手順は、消費リクエストを送信する前にアプリが停止してたり、ネットワーク接続待ちをしている可能性があるため事前に必要です。

...とりあえずここまでが概要です。次はSubscriptionについて書きたいと思います。

2013年6月4日火曜日

Amazon Android アプリストアがオープン

 今日(2013/6/4)より、Kindle Fire / Kindle Fire HD、PC、Mac、Android OS搭載のスマートフォン、タブレットからアプリやゲームを購入できる「Amazon Androidアプリストア」がAmazon.co.jp にてオープンしたとのこと。

以前より、Amazon.comなどでは展開されているサービスですね。

これでPCでAmazonアプリストアでアプリを探して購入しておけば、端末でも購入したアプリをインストールできるようになりました。やり方は…

  • Kindleなら
    • 「アプリ」から「クラウド」を開いてPCで購入したアプリをタップでアプリをインストールできます。
  • Androidなら
    • 「Amazonアプリストア」アプリで、メニューボタン→「マイアプリ」画面を開き
      「クラウド」を選択、アプリの横に表示されるダウンロードボタンをタップしてインストールが可能。(※表示されない場合は「リフレッシュ」をタップして再読込を行う。)

 開発者的には、PCからでも見れるから「Amazon.co.jp」を訪れる多くのお客様に売れるようになったということとと、Amazonアプリストアの自分のアプリ詳細ページへのリンクすることができるようになりました。

PC・ブラウザ用のアプリ詳細ページへのリンクURL

  http://www.amazon.co.jp/gp/product/[ASIN番号]/ref=mas_pm_[アプリ名]

ちなみに Amazonアプリストアで開けるURL...

  http://www.amazon.com/gp/mas/dl/android?p=[パッケージ名]&ref=mas_pm_[アプリ名]
(ブラウザで開いたらAmazon.comの商品詳細ページに飛んでしまいましたが、アプリで開くと日本語のアプリ詳細ページが表示されました。)

※ASIN番号 (Amazon Standard Item Number):商品を識別する10桁の番号

詳細は、Trademark Guidelines の "Linking Instructions" に記載されています。

Amazonアプリストア…流行るかなぁ?

2013年5月31日金曜日

無料のGitプライベートリポジトリが利用できる "gitBREAK"

 株式会社ビズリーチが提供しているcodebreak;(コードブレイク)にて、Gitリポジトリの無料ホスティングサービス「gitBREAK」サービスを提供開始したとの事で早速登録してみました。

codebreakサイトURL(http://www.codebreak.com/)

■すごいところ:

  • プライベートリポジトリが無料
  • プライベートリポジトリの数が無制限
  • 共有可能ユーザー数も無制限
  • サイトが日本語

無料でプライベートリポジトリが作れるサービスとしては、
Bitbucket (http://www.atlassian.com/ja/software/bitbucket/overview) 
もありますが、こちらは 無料プランだと共有できるユーザーは5人までの制限が付きます。

無料で制限なしなんて素晴らしいサービスだ!
などと浮かれて登録したら思ったよりゴールまでの道のりが長く大変でした。

注意点としては、この「gitBREAK」は "codebreak;"のサービスの一環であるということ。

codebreak;(コードブレイク) とは...
 『コードブレイクは、IT・Webエンジニアのコラボレーションを支援する、エンジニア限定のサイトです。』 (コードブレイクのページ (https://www.codebreak.com/contents/about/) より引用)
要約すると、職業としてのプログラマやシステムエンジニアなどのIT屋さん限定だとのことです。

 私はcodebreak;を利用するのが初めてなので、codebreak;のアカウント作成から行ったのですが、gitBreakのフル機能を使うには、アカウントにレジュメを入力しなければならないとのこと。
 そのレジュメを記入した上で審査され、審査通過して初めて上記の素晴らしい無制限サービスを享受できるのです。レジュメの審査には3営業日程かかるとのことですが、レジュメの審査が通らない内は以下の制限付きサービス内容で我慢するしかありません。

■レジュメ審査通過前のサービス内容

  • パブリックリポジトリ数:無制限
  • プライベートリポジトリ数:1個 -->レジュメ承認されたら無制限に
  • 使用可能容量:200MB -->審査後も変更なし。

 このレジュメ入力作業が大変です。特に職務経歴書の形式がここと違った場合、転職活動する気分でレジュメを再作成していくことになります。これがとても面倒です。

そしてここまで来て気が付きました。「容量:200MB」だということに。
足りない…。そこは無制限じゃないんだ。。。
Bitbucketは容量無制限なので気にしていなかった。

どうもこの使用可能容量を増やすためには 100MB増やす為に1人の codebreak;への招待が必要だという記述があります。(最大紹介数10人 = 100MB x 10人 = 最大1GB 増やせる。)
この招待は、した側もされた側も両方に100MB入るのとのことです。

■結論

 確かに0円でプライベートリポジトリを作成できます。ただ、Bitbucketのソレとは明らかに何か違う「無料」でした。 「0円でGitリポジトリを複数持ち、かつ大勢のユーザーと共有したい。でも公開はできない。」という場合は今のところgitBREAK一択なのではないでしょうか。しかし、いかんせん容量が小さいのがネックです。(招待できる人がいるなら別ですが。。。)

Amazon In-App Purchasing API (アプリ内課金) テスト方法

Amazon Distribution Portalの公式マニュアル(英語)は下記URL先です。
(https://developer.amazon.com/sdk/in-app-purchasing/documentation/testing-iap.html)
開発環境と動かしたいアプリが揃ったら、次は動かす環境の整備です。

Amazon SDK APIのテストについて

 Amazon In-App Purchasing API には、検証環境というものが存在しません。しかも、Amazon SDK APIを通じてAmazon Client経由で通信を行う為には、Distribution Portalを通じて公開されているアプリでなければなりません。

SDK Tester

 それじゃ公開するまでテストもできないの?そんな訳ありません。Amazonはテストの為のAmazon Clientの動きをエミュレートするアプリを用意しています。

"SDK Tester" は、Amazon SDK の開発者がアプリ公開前に本番環境を想定してテストすることができる開発ツールです。これにより開発者は、Amazon SDK APIによって生成される全てのレスポンスをカバーするテストをアプリ公開前に実行できます。Amazonは、このSDK Testerでテストして動作したコードは本番でも同じく動作することを保証しています。

 SDK Testerでは、テストユーザーの購入情報(レシート)を消したり、キャンセル処理を行うことができます。また、ユーザーを変更したり、IAP-APIのレスポンスステータスを任意に変更することができます。

SDK Tester の仕組み

 IAP-APIは、動作しているアプリがDistribution Portalで公開されているアプリかどうかを判別し、公開されていない(開発中の)アプリである場合、SDKTesterアプリへ接続します。(もちろんSDK Testerをインストールしていないとだめですが。。。)

 公開後は勝手にIAP-APIがAmazonClient側へ接続しに行ってくれるので、テスト時と本番時に設定を変えるとかの差異がなく非常に便利です。

SDK Tester のインストール

 Amazon Distribution Portal (https://developer.amazon.com/sdk.html) からダウンロードできる、Amazon SDK (Apps-SDK.zip)に含まれています。

格納されている場所は、
"<Apps-SDK.zipを解凍したディレクトリ>/Apps-SDK/InAppPurchasing/tools/AmazonSDKTester.apk"です。

この AmazonSDKTester.apk を デバイスにインストールします。
※インストールには、Android 2.3.3 (Android API Level 10) 以上の端末が必要です。
 Kindleなら第1世代からインストールできます。

Windowsならコマンドプロンプトから以下のコマンドでインストールできます。
※<Android SDKのインストールされているディレクトリ>\sdk\platform-toolsへパスを通しておいてください。

C:\>adb install -s <デバイスID> <App-SDK.zipを解凍したディレクトリ>\Apps-SDK\InAppPurchasing\tools\AmazonSDKTester.apk
デバイスIDの調べ方は、"adb devices" コマンドで接続されているデバイス、エミュレータのデバイスIDを確認することができます。

アイテムデータ定義ファイルの準備

 Distribution Portalでも課金アイテムは定義しなければなりませんが、同じくSDK Testerにも課金アイテムの定義が必要です。
SDK Testerでは、JSONファイルで課金アイテムの定義を行います。

 SDK Testerは、デバイスの "/mmt/sdcard/amazon.sdktester.json"ファイルを読み込み、その情報をもとにIAP-APIへレスポンスしてくれます。 このときJSONファイルに定義する課金アイテムの情報へ本番と同じデータを入力できるのがいいところです。

  • 定義構造

     amazon.sdktester.json の定義はJSON形式に従い、以下のような構造で行います。この時、SKUの順序は関係ありません。
    {
        SKU_1:{IAP_Item_1_Description},
        SKU_2:{IAP_Item_2_Description},
        .
        .
        .
        SKU_n:{IAP_Item_n_Description
    }
            
    上記のとおり、複数のSKUを並べて定義できます。この時、SKU定義間の "," に注意してください。尚、コンテンツタイプによって項目の定義内容は異なります。

  • 定義要素:Consumable / Entitled

     Consumable Content(消費型コンテンツ)と Entitled Content(買いきり型コンテンツ)の課金アイテム定義項目は以下のとおりです。
    {
      ...
      ”(SKU文字列)” : 
      {
        "itemType": "ENTITLED" または "CONSUMABLE", 
        "price": (金額の数字),
        "title": "(商品名称)"
        "description": "(商品説明文)",
        "smallIconUrl": "(商品画像アイコン(サムネイル)URL)"
      }
      ...
    }
            

  • 定義要素:Subscription

     期間購入(Subscription)型コンテンツのアイテム定義項目は以下のとおりです。
    {
      ...
      ”com.amazon.buttonclicker.subscription.1mo (SKU文字列)” : 
      {
        "itemType": "SUBSCRIPTION",
        "price": (金額の数字),
        "title": "(商品名称)",
        "description": "(商品説明文)",
        "smallIconUrl": "(商品画像アイコン(サムネイル)URL)",
        "subscriptionParent": "com.amazon.buttonclicker.subscription"
      }
       ...
    }
            
     定義自体はほとんど変わりません。しかし、他と大きく違うのが "subscriptionParent"(親SKU)を指定している点です。 サブスクリプションの親SKUは、サブスクリプションが何のためにあるのかを表します。そしてその親SKUに紐づく子SKUは、価格や期間を表す条件のSKUとなります。
     この紐付けを行うことで、例えば「○○新聞の定期購読」の「1か月間のサブスクリプション」を購入したのに、 「1週間のサブスクリプション」を重複して買うことを防ぐことができます。

SDK Testerの使い方

 SDKテスターのスタート画面です。真ん中の3つのボタンをタップすると、それぞれの機能の画面を表示します。


  • Active Transactions

     有効な取引情報を表示します。また、テストの為にその取引をキャンセル(返品)すること取引情報をクリアすることができます。

  • Change Logged in User

     現在ログイン中のユーザー名を表示します。またログイン中のユーザー名を変更することができます。

  • Interactive Mode Preferences

     IAP-APIからのレスポンスを任意に設定できます。



    1. Purchase API

      PurchasingManager.initiatePurchaseRequest() -> ObserverのonPurchaseResponse()へのレスポンスの設定。
      • Default:正常
      • Already Entitled:既に購入済み
      • Invalid Sku:SKUが不正
      • Failed:リクエスト失敗
    2. Purchase Updates API

      PurchasingManager.initiatePurchaseUpdatesRequest() -> ObserverのPurchaseUpdatesResponse()へのレスポンスの設定。
      • Default:正常
      • Failed:リクエスト失敗
    3. Item Data API

      PurchasingManager.initiateItemDataRequest() -> ObserverのonItemDataResponse()へのレスポンスの設定。
      • Default:正常
      • Failed:リクエスト失敗


 これでアプリ内課金処理周りのテストケース前パターンをサクサクこなすことができます。

2013年5月29日水曜日

Amazon App Store In-App Purchasing API (アプリ内課金) の実装 #2

Amazon App Store In-App Purchasing API (アプリ内課金) の実装 #1 のつづき。
#1にて BasePurchasingObserver を継承したサブクラスまで記載したので、今度は PurchasingManager を使って実際に課金を行う処理について。

Purchasing Manager

  • In-App Purchasing API と情報をやりとりする重要なクラスです。Amazon Clientおよび Amazon Platform との間のアクセスを容易にします。
  • Purchasing Managerを使用するには、 #1で示したBasePurchasingObserverクラスを継承したサブクラスをPurchasing Managerへ登録する必要があります。アプリのActivityの onStart()メソッドで登録を行うのがベターです。

PurchasingManagerには以下の5つのメソッドがあります。
  1. registerObserver(PurchasingObserver purchasingObserver)
    • BasePurchasingObserver クラスを継承したサブクラスを PurchasingManager へ登録します。
    • 通常、Activity の onStart() メソッドにて呼出します。
  2. initiateGetUserIdRequest()
    • Amazon Client に現在ログインしているユーザーのアプリ特有IDの取得リクエストを送信します。
    • 通常、Activity の onStart() メソッドにて呼出しますが、いつでも取得できます。
  3. initiatePurchaseUpdatesRequest(Offset offset)
    • オフセットレシート情報のページ区分)指定されたレシート情報を取得するリクエストを送信します。
    • ここで返されるレシート情報は、Entitled Content(買い切り型コンテンツ)及び Subscription Content(期間購入型コンテンツ)のみで、Consumable Content(消費型コンテンツ)情報は取得できません。
    • デバイス間でユーザー購入情報を同期する処理に使用できます。
  4. initiateItemDataRequest(Set skus)
    • 指定されたSKU一連のアイテムデータを取得するリクエストを送信します。
    • コンテンツタイプ全てのアイテム情報を取得できます。
  5. initiatePurchaseRequest(String sku)
    • 指定されたSKUの購入リクエストを送信します。
      ※購入リクエストを行った後 Amazon Clientによって表示される購入ダイアログで購入ボタンをタップしない限り購入されません。
    • コンテンツタイプ全てのアイテムを購入できます。

PurchasingManagerを使う準備

 各所で記述していますが、PurchasingManagerを使用してアプリ内課金関連処理を行う前に最初にPurchasingManagerに対してコールバックさせる為のObserverを登録する必要があります。
@Override
protected void onStart() {
.
.
.
    // BasePurchasingObserverを継承して作成したサブクラスのインスタンスを取得
    IapObserver iapObserver = new IapObserver (this);

    // PurchasingManagerへObserverを登録する。
    com.amazon.inapp.purchasing.PurchasingManager.registerObserver(iapObserver);
.
.
.
}

アプリ特有IDの取得

 IAP-APIの課金では、Amazon ClientにログインしているユーザーIDに対して、そのアプリで使用する課金用のユーザーIDを使って管理を行います。

処理の手順は...
  1. PurchasingManager.initiateGetUserIdRequest()でユーザーID取得リクエストを行う。
  2. ObserverのonGetUserIdResponse(GetUserIdResponse getUserIdResponse)メソッドへコールバックする。
  3. 非同期処理でレスポンスオブジェクトからリクエストのステータス、ユーザーIDを取得してアプリで永続化する。

以下に、PurchasingManagerのユーザー取得リクエストメソッドと該当するObserverのメソッドを記述します。
Observerのより具体的な処理サンプルは #1のObserverサンプルコードを参照してください。
@Override
protected void onStart() {
.
.
.
// ユーザーID取得リクエストを行う
com.amazon.inapp.purchasing.PurchasingManager.initiateGetUserIdRequest();
.
.
.
}

@Override
public void onGetUserIdResponse(GetUserIdResponse getUserIdResponse) {
    // 非同期処理でレスポンスオブジェクトを検証
    new GetUserIdAsyncTask().execute(getUserIdResponse);
}

private class GetUserIdAsyncTask extends AsyncTask
{
    // onGetUserIdResponseで取得したGetUserIdResponseを格納
    GetUserIdResponse getUserIdResponse = params[0];
    
    if (getUserIdResponse.getUserIdRequestStatus() == GetUserIdRequestStatus.SUCCESSFUL) {
        // リクエスト成功。レスポンスからユーザーID取得
        Log.d(TAG, "UserID:" + getUserIdResponse.getUserId());
        // 永続化処理
        .
        .
        .
    } else {
        // リクエスト失敗
        .
        .
        .
    }
}

アイテムの購入処理

 やっと本題のアプリ内課金の購入処理についてです。実際の購入よる課金等の処理は全てIAP-APIが行ってくれるので、アプリ側で実装する内容は、購入情報(レシート)を検証して、その課金アイテムやコンテンツ・機能を使えるようにしてあげるというというものです。
 ストアに登録する商用アプリの場合は上記の他にも、購入権利をローカルに保持して不正が無い様定期的に権限を確認させる機能やマルチユーザー対応、消費型アイテム・コンテンツの管理、デバイス間同期処理を呼ぶ...などなど様々な前後処理が必要になります。しかし、ここではシンプルに購入 → 購入成功ダイアログ表示 までの処理を説明していきます。

※尚、Amazon Developer Portalでの課金アイテムの作り方や、SDKTesterの使い方・登録するJsonファイルなどについては触れません。この辺は別の記事に書いていこうと思います。

処理の手順は...
  1. PurchasingManager.initiatePurchaseRequest("購入アイテムのSKU")でアイテム購入リクエストを行う。
  2. Amazon Clientにて購入ダイアログが表示され、ユーザーが購入ボタンをタップするとIAP-APIで課金処理が行われる。
  3. ObserverのonPurchaseResponse(PurchaseResponse purchaseResponse)メソッドへコールバックする。
  4. 非同期処理でレスポンスオブジェクトからリクエストのステータス、コンテンツタイプなどのレシート情報を検証して課金コンテンツ・機能を利用可能にする。

以下に、PurchasingManagerの購入リクエストメソッドと該当するObserverのメソッドを記述します。
Observerのより具体的な処理サンプルは #1のObserverサンプルコードを参照してください。
// アイテムの購入リクエストを送信する。このリクエスト時にはリクエストIDが返り、
// コールバック後のレスポンスオブジェクトに含まれるリクエストIDと突き合わすことにより
// どのリクエストのレスポンスなのか判断する事ができる。
String requestId = com.amazon.inapp.purchasing.PurchasingManager.initiatePurchaseRequest("購入アイテムのSKU");

@Override
public void onPurchaseResponse(PurchaseResponse purchaseResponse) {
    new PurchaseAsyncTask().execute(purchaseResponse);
}

private class PurchaseAsyncTask extends AsyncTask {
    @Override
    protected Boolean doInBackground(PurchaseResponse... params) {
        // レシート検証処理
        .
        .
        .
    }
}

 非同期処理とUIスレッドの同期については、アプリによって色々な方法があると思いますので細かく触れませんが、私はCountDownLatchで同期させたりしてました。

上記に示した通り、IAP-APIを使うのはかなり簡単です。
PlayStoreの課金処理 (IAB-API v2とか特に)を実装した人ならIAP-APIの方が楽だと思います。

2013年5月28日火曜日

Amazon App Store In-App Purchasing API (アプリ内課金) の実装 #1

AmazonSDKのIn-App Purchasingを使った実装についてメモ。

アプリ内課金の購入処理概略

 In-App Purchasing (IAP) API を使ったアプリ内課金で購入するまでの大まかな処理フローを下図にまとめました。


(※詳細な処理については省略して書いています。)

大雑把に表現するとこんな手順・処理を踏んでアプリ内でアイテムの購入処理を行います。
ここで登場するのは以下の5つです。
  • ユーザー
    アプリを使用し、課金を行う人。
  • Activity
    課金を行うアプリのActivity(画面)。ここではUIスレッドで動作しているActivity。
  • Observer
    PurchasingManagerのレスポンスを監視し、そのレスポンスに対して処理を行うクラス。
  • PurchasingManager
    In-App Purchasing APIと購入情報等のやりとりを行うクラス。
  • AmazonClient
    Amazon App Store (Client)アプリ。ユーザーID管理、Amazon Platformとの通信を行い実際の課金処理を行うためのアプリ。
    実際はIAP-APIとしてPurchasingManagerを経由して情報を受渡されていますが、ここではわかりやすい実体をもつアプリで表記します。

フローを簡単に説明しますと、、、
  1. ユーザーがアプリの画面や確認ダイアログから、アイテム購入ダイアログを表示させる。
  2. ユーザーへAmazonClientのアイテム購入ダイアログを表示させるために、PurchasingManagerにて購入リクエストを送信する。
  3. 購入リクエストをAmazonClientに送信。
  4. 購入リクエストに対して対応するアイテムの購入ダイアログがユーザーに表示され、ユーザーが購入ボタンをタップする。
  5. AmazonClientにて購入処理が行われ、その購入処理結果をObserverにてキャッチする。
  6. レシート情報が送られてくるので、レシート情報をチェック。
  7. レシート情報に従い、購入された課金コンテンツ・機能を利用可能にする。
  8. 購入処理が正常に完了した通知を受け、AmazonClientが購入完了ダイアログをユーザーへ表示して一連の処理が完了する。
主要クラス

IAP-APIの処理にて、キーとなるクラスは以下の2つです。
  • com.amazon.inapp.purchasing.PurchasingManager
    Amazon Platformへのリクエストを行うためのクラスであり、 Amazon Platformからのレスポンスを取得し下記のオブサーバへ返すクラス。
    (フロー図の "PurchasingManager"です。)
  • com.amazon.inapp.purchasing.BasePurchasingObserver
    アプリにてAmazonPlatformからのレスポンスを取得する為に、PurchasingManagerへ登録し レスポンスを監視するクラス(抽象クラス)。
    (フロー図の "Observer" はこのクラスのサブクラスです。)

また、アプリ内課金で使用するクラス、用語について以下に簡単な説明を記述します。
  • アイテム:Item (com.amazon.inapp.purchasing.Item)
    • 課金アイテムのことを表します。商品タイトル、価格、商品説明などの商品情報を含みます。
    • 価格には通貨文字が含まれ、ロケールにもとづいて適切にフォーマットされています。
  • SKU (文字列)
    • 課金アイテムを表すユニークなID。
    • Amazon Developer Portalでログインし、アプリ情報のなかの [In-App Items] メニューにて作成できます。
  • レシート:Receipt (com.amazon.inapp.purchasing.Receipt)
    • ユーザーの全ての購入情報をもつオブジェクトです。
    • 全てのレシートは、レシート検証サービスを経由してアイテムの購入を検証する為に使用できます。 このレシート情報をもとにアプリ内でコンテンツや機能へのアクセス承認を行います。
  • オフセット:Offset (com.amazon.inapp.purchasing.Offset)
    • ページに分けられたレシート情報の位置を表します。
    • オフセットの値は、Base64エンコーディングされておりそのまま見て読める形式ではありません。

ResponceReceiverの登録

 IAP-APIは、全て非同期で処理が実行されます。アプリは、ResponceReceiverクラスを経由してAmazonClientからのBroadcast Intentを受信する必要があります。
 このResponceReceiverクラスは、アプリから直接使用する必要はありません。代わりにアプリに送られてくるBroadcast Intentを受信できる定義をアプリのAndroidManifest.xmlへ定義する必要があります。以下の通り記述してください。

.
.
.

    
        
        
    

.
.
.

※表示上の都合で、<action>タグの閉じるタグを別に設けてますが、「<action... />」で構いません。

BasePurchasingObserver

 Amazon Clientからの Broadcast Intent を取得する Response Receiverの登録が終わったら、次はResponse Receiverからのコールバックを受け取るクラスが必要になります。その為に、BasePurchasingObserverクラスを継承したサブクラスを作成しPurchasingObserverインタフェースを実装してPurchasingManagerへ登録する必要があります。

※このコールバック処理はUIスレッドで実行すると長時間UIスレッドを占有してしまうので通常はAsyncTaskなどの別スレッドで処理します。

以下は、BasePurchasingObserverを継承したサブクラスのサンプルコードです。
こんな感じのクラスを作って、ここでPurchasingManagerを経由してAmazonClientにリクエストした結果を取得し処理を行います。
package com.exsample.account;

import java.util.Date;
import java.util.LinkedList;
import java.util.Map;

import android.app.Activity;
import android.os.AsyncTask;
import android.util.Log;

import com.amazon.inapp.purchasing.BasePurchasingObserver;
import com.amazon.inapp.purchasing.GetUserIdResponse;
import com.amazon.inapp.purchasing.GetUserIdResponse.GetUserIdRequestStatus;
import com.amazon.inapp.purchasing.Item;
import com.amazon.inapp.purchasing.ItemDataResponse;
import com.amazon.inapp.purchasing.Offset;
import com.amazon.inapp.purchasing.PurchaseResponse;
import com.amazon.inapp.purchasing.PurchaseUpdatesResponse;
import com.amazon.inapp.purchasing.PurchasingManager;
import com.amazon.inapp.purchasing.Receipt;
import com.amazon.inapp.purchasing.SubscriptionPeriod;

public class SamplePurchasingObserver extends BasePurchasingObserver {
    
    // ログ出力用タグ
    private final static String TAG = "SamplePurchasingObserver";
    
    // コンストラクタでインスタンス化したアクティビティを受け取り
    // BasePurchasingObserverへコンテキスト渡す.
    public SamplePurchasingObserver(final Activity activity) {
        super(activity);
    }

    //
    // ObserverがPuchasingManagerに登録された時にコールバックします。
    // (PurchasingManager.registerObserver(observer)の後)
    // 
    // @param isSandboxMode サンドボックス環境で実行されているか否か
    //   true  : Amazon Client からの応答を受信している。
    //           (Amazon Client からダウンロードされたapkである場合はこちらを返す。)
    //   false : SDK Testerからの応答を受信している。
    //           (デバッグ、テスト用apkで動かしている場合はこちらを返す。)
    //
    @Override
    public void onSdkAvailable(boolean isSandboxMode) {
        PurchasingManager.initiateGetUserIdRequest();
    }

    // 
    // ユーザーID取得要求(PurchasingManager.initiateGetUserIdRequest())
    // のレスポンスが返った時にコールバックします。
    //
    // @param getUserIdResponse ユーザーID取得要求レスポンス
    //   リクエストが成功している場合、リクエストID、リクエストステータス及び、ユーザIDを含んだ状態で返ります。
    //
    @Override
    public void onGetUserIdResponse(GetUserIdResponse getUserIdResponse) {
        new GetUserIdAsyncTask().execute(getUserIdResponse);
    }

    //
    // アイテムデータ取得要求(PurchasingManager.initiateItemDataRequest())
    // のレスポンスが返った時にコールバックします。
    //
    // @param itemDataResponse アイテムデータ取得要求レスポンス
    //   リクエストが成功している場合、購入可能/購入不可能なアイテムのセットを含んだ状態で返ります。
    //
    @Override
    public void onItemDataResponse(ItemDataResponse itemDataResponse) {
        new ItemDataAsyncTask().execute(itemDataResponse);
    }

    //
    // 購入要求(PurchasingManager.initiatePurchaseRequest())
    // のレスポンスが返った時にコールバックします。
    //
    // @param purchaseResponse 領収書を含む購入要求レスポンス
    //   リクエストが成功している場合、リクエストID、リクエストステータス、購入した領収書を含んだ状態で返ります。
    //
    @Override
    public void onPurchaseResponse(PurchaseResponse purchaseResponse) {
        new PurchaseAsyncTask().execute(purchaseResponse);
    }

    //
    // 購入情報更新要求(PurchasingManager.initiatePurchaseUpdatesRequest())
    // のレスポンスが返った時にコールバックします。
    // この呼び出しは、ユーザーが別のデバイスにアプリをダウンロードした場合や、
    // アプリケーションを初期化・削除後再インストール後などに購入情報を同期する為に使用されます。
    //
    // @param purchaseUpdatesResponse
    //   リクエストが成功している場合、
    //   リクエストID、リクエストステータス、過去購入した領収書、取り消されたSKUのセット、及び該当する場合は次のオフセットを含んだ状態で返ります。
    //
    @Override
    public void onPurchaseUpdatesResponse(PurchaseUpdatesResponse purchaseUpdatesResponse) {
        new PurchaseUpdatesAsyncTask().execute(purchaseUpdatesResponse);
    }
    
    // ユーザー情報取得レスポンスの実処理を実行するAsyncTask.
    // onGetUserIdResponse()へコールバックした後に実行。
    private class GetUserIdAsyncTask extends AsyncTask<GetUserIdResponse, Void, Boolean> {
        @Override
        protected Boolean doInBackground(GetUserIdResponse... params) {
            GetUserIdResponse getUserIdResponse = params[0];

            if (getUserIdResponse.getUserIdRequestStatus() == GetUserIdRequestStatus.SUCCESSFUL) {
                // ユーザーID取得ステータス成功時
                return true;
            } else {
                // ユーザーID取得ステータス失敗時
                Log.d(TAG, "onGetUserIdResponse: Unable to get user ID.");
                return false;
            }
        }
        @Override
        protected void onPostExecute(Boolean result) {
            super.onPostExecute(result);
            if (result) {
                // ユーザーID取得成功時...購入状態(購入/取消)を更新する要求を開始するなどの処理を記述
            } else {
                // ユーザーID取得失敗時...エラーダイアログ表示処理など
            }
        }
    }
    
    //
    // 課金アイテム情報取得レスポンスの実処理を実行するAsyncTask
    // onItemDataResponse()へコールバックした後に実行。
    // ゲームのストアフロントでアプリ内課金アイテムを表示するときにこの情報を使用します。
    //
    private class ItemDataAsyncTask extends AsyncTask<ItemDataResponse, Void, Void> {
        @Override
        protected Void doInBackground(ItemDataResponse... params) {
            final ItemDataResponse itemDataResponse = params[0];
            
            switch (itemDataResponse.getItemDataRequestStatus()) {
            case SUCCESSFUL_WITH_UNAVAILABLE_SKUS:
                // 購入できないアイテムのSKU
                for (final String sku : itemDataResponse.getUnavailableSkus()) {
                    Log.d(TAG, "Unavailable SKU:" + sku);
                }
            case SUCCESSFUL:
                // 購入可能なアイテムのSKU
                final Map<String, Item> items = itemDataResponse.getItemData();
                for (final String key : items.keySet()) {
                    Item i = items.get(key);
                    Log.d(TAG, "Item: " + i.getTitle() + "\n " +
                               "Type: " + i.getItemType() + "\n " +
                               "SKU: " + i.getSku() + "\n " +
                               "Price: " + i.getPrice() + "\n " +
                               "Description: " + i.getDescription() + "\n");
                }
                break;
            case FAILED:
                // レスポンス失敗
                break;
            }
            return null;
        }
    }

    //
    // onPurchaseResponse()を受信した後に呼び出されます。
    // オブザーバが購入の応答を受信したときに開始され、AsyncTaskが正常に返されるとUIが更新されます。
    //
    private class PurchaseAsyncTask extends AsyncTask<PurchaseResponse, Void, Boolean> {
        @Override
        protected Boolean doInBackground(PurchaseResponse... params) {
            final PurchaseResponse purchaseResponse = params[0];
            if (!purchaseResponse.getUserId().equals("[購入リクエストを行ったユーザーID]")) {
                // カレントログインユーザIDがPurchaseレスポンスから返されたユーザIDと違う場合、
                // レスポンスに含まれるユーザーIDの購入情報を更新する。 
                // ※通常はユーザー別にオフセットの値をこどかへ永続化させておき、その値で購入情報を取得する。
                PurchasingManager.initiatePurchaseUpdatesRequest(Offset.fromString("[リクエストに含まれるユーザーのオフセット値]"));
            }

            switch (purchaseResponse.getPurchaseRequestStatus()) {
            case SUCCESSFUL:
                // 購入成功:
                // レシート情報から購入されたアイテムタイプ・レシート情報を検証し、課金アイテムを利用可能にします。
                final Receipt receipt = purchaseResponse.getReceipt();
                switch (receipt.getItemType())
                {
                case CONSUMABLE:
                    // 消費型コンテンツの場合の処理を記述
                    Log.d(TAG, "Purchasing Response Item is CONSUMABLE.\n" + "Sku=" + receipt.getSku());
                    break;
                case ENTITLED:
                    // 買い切り型コンテンツの場合の処理を記述
                    Log.d(TAG, "Purchasing Response Item is ENTITLED.\n" + "Sku=" + receipt.getSku());
                    break;
                case SUBSCRIPTION:
                    // 期間購入型コンテンツの場合の処理を記述
                    Log.d(TAG, "Purchasing Response Item is Subscription.\n" + "Sku=" + receipt.getSku());
                    break;
                default:
                    break;
                }
                return true;
            case ALREADY_ENTITLED:
                // 重複購入:
                // 顧客が既にアイテムを受け取っている場合、レシート情報は戻りません。
                // レスポンスに格納されたリクエストIDと、PurchasingManagerで購入リクエストを送信するときに返されるリクエストIDを照合することによって、
                // 購入処理が重複したのかを判断し、必要な処理を行います。
                Log.d(TAG, "レスポンスに格納されたリクエストID=" + purchaseResponse.getRequestId());
                break;
            case FAILED:
                // 購入失敗:
                // SKUの購入が無効・購入しなかったなどの場合は、FAILEDステータスが返ります。
                // アプリケーションからリクエストされたSKUとDevPortalのSKUに相違がある場合に発生することがあります。
                break;
            default:
                break;
            }
            return false;
        }

        @Override
        protected void onPostExecute(Boolean success) {
            super.onPostExecute(success);
            if (success) {
                // 購入成功/重複購入時
            } else {
                // 購入:キャンセルor失敗時
            }
        }
    }

    //
    // Started when the observer receives a Purchase Updates Response Once the AsyncTask returns successfully, we'll
    // update the UI.
    // 固有ユーザIDに紐づく更新された購入情報を処理...
    // オブサーバがonPurchaseUpdatesResponseを受信したときに開始されUI(画面オブジェクト)を更新する。
    //
    private class PurchaseUpdatesAsyncTask extends AsyncTask<PurchaseUpdatesResponse, Void, Boolean> {
        @Override
        protected Boolean doInBackground(PurchaseUpdatesResponse... params) {
            final PurchaseUpdatesResponse purchaseUpdatesResponse = params[0];

            // 更新購入情報のユーザIDが一致する事を確認
            if (!(purchaseUpdatesResponse.getUserId().equals("[購入リクエストを行ったユーザーID]"))) {
                return false;
            }

            // 何らかの理由で顧客が商品を取り消された場合、これらのアイテムのSKUは失効SKUのセットに含まれます。
            // 取り消されたSKUを取得して取り消された購入情報を更新します。
            // ※注意:
            //  getRevokedSkus()は、Entitlement Content (買い切り型コンテンツ) のみに適用され、
            //  期間購入型コンテンツ(Subscription Content)には適用されない。
            for (final String sku : purchaseUpdatesResponse.getRevokedSkus()) {
                Log.d(TAG, "Revoked Sku:" + sku);
            }
            
            switch (purchaseUpdatesResponse.getPurchaseUpdatesRequestStatus()) {
            case SUCCESSFUL:
                // 最終サブスクリプション期間保持変数
                SubscriptionPeriod latestSubscriptionPeriod = null;

                // サブスクリプション格納コレクション
                final LinkedList<SubscriptionPeriod> currentSubscriptionPeriods = new LinkedList<SubscriptionPeriod>();

                // 更新購入情報の全レシートをを読んでサブスクリプションの期間を取得
                for (final Receipt receipt : purchaseUpdatesResponse.getReceipts()) {
                    // レシートのアイテムタイプを取得してアイテムタイプ別に処理を行う
                    switch (receipt.getItemType()) {
                    case ENTITLED:
                        // 売り切り型コンテンツのレシートだった場合
                        Log.d(TAG, "receipt item type is ENTITLED.");
                        break;
                    case SUBSCRIPTION:
                        // 期間購入型(サブスクリプション)コンテンツのレシートだった場合
                        // サブスクリプションの購入情報更新は、次のいずれかの方法で行うことができます:
                        // 1. レシート情報から、ユーザーが現在有効なサブスクリプションを持っているかどうかを判断する。
                        //    ...有効なサブスクリプションを探すため、レシートから終了日のないサブスクリプションがあるかどうかをチェックする。
                        // 2. レシート情報から、ユーザーのサブスクリプション購入履歴を作成する。
                        //    ...過去の有効なサブスクリプションに基づいてコンテンツをアンロックするアプリでは、顧客の購買履歴を作成する必要があります。
                        //       例) 購入者が雑誌の年間購読サブスクリプションを持っている場合、
                        //           例え顧客が現在有効なサブスクリプションを持っていなくとも、顧客は購入したときから現在も雑誌へのアクセス権を持っていることになります。
                        Log.d(TAG, "Receipt Item Type is Subscription.");
                        
                        if (receipt.getSku().equals("アプリで購入できる商品のSKU")) {
                            // サブスクリプションの期間
                            final SubscriptionPeriod subscriptionPeriod = receipt.getSubscriptionPeriod();
                            // サブスクリプション開始日時
                            final Date startDate = subscriptionPeriod.getStartDate();

                            // 最新の開始日を持つ領収書を探す。
                            // 重複するサブスクリプションがある場合は、現在のサブスクリプションコレクションに追加
                            if (latestSubscriptionPeriod == null || startDate.after(latestSubscriptionPeriod.getStartDate())) {
                                // サブスクリプションの最終日時がNUll又は、
                                // 現在のサブスクリプションの開始日時より最後のサブスクリプション期間の開始日が後の場合
                                currentSubscriptionPeriods.clear();
                                latestSubscriptionPeriod = subscriptionPeriod;
                                currentSubscriptionPeriods.add(latestSubscriptionPeriod);
                            } else if (startDate.equals(latestSubscriptionPeriod.getStartDate())) {
                                // サブスクリプションの最終日時がNullでない又は
                                // 現在のサブスクリプションの開始日時より最後のサブスクリプション期間の開始日時が前の場合
                                currentSubscriptionPeriods.add(receipt.getSubscriptionPeriod());
                            }
                        }
                        break;
                    default:
                        break;
                    }
                }

                // すべての領収書が読まれた後、最後(最新)のサブスクリプションを確認します。
                // サブスクリプション終了日が入っている場合、サブスクリプションは期限切れ(無効)です。
                if (latestSubscriptionPeriod != null) {
                    // 全サブスクリプションのコレクションからサブスクリプション期間を取得
                    for (SubscriptionPeriod subscriptionPeriod : currentSubscriptionPeriods) {
                        if (subscriptionPeriod.getEndDate() != null) {
                            // サブスクリプション期間の終了日がある場合、
                            // そのサブスクリプションは有効ではないと判断し、課金アイテムをロックする処理を入れる。
                            break;
                        }
                    }
                }

                // レシートのオフセット永続化処理
                final Offset newOffset = purchaseUpdatesResponse.getOffset();
                // どこかへ保存...
                
                if (purchaseUpdatesResponse.isMore()) {
                    // レシート情報がまだあある場合
                    Log.d(TAG, "Initiating Another Purchase Updates with offset: " + newOffset.toString());

                    // 現在のオフセットを指定してさらに購入状態(購入/取消)情報を更新する要求を開始。
                    // onPurchaseUpdatesResponse(PurchaseUpdatesResponse) にコールバックします。
                    PurchasingManager.initiatePurchaseUpdatesRequest(newOffset);
                }
                return true;
            case FAILED:
                // 購入情報の取得に失敗した場合。
                return false;
            }
            return false;
        }

        @Override
        protected void onPostExecute(Boolean result)
        {
            super.onPostExecute(result);
            // レスポンスに含まれるリクエストIDや処理結果をもとに処理記述
        }
    }
}

長くなってきたので続きは #2 で。

2013年5月27日月曜日

Amazon App Store In-App Purchasing API (アプリ内課金) 説明

 以前、Amazon app storeにアプリ内課金(サブスクリプション)を実装したアプリを提供した。
その時、Amazon SDKにおける In-App Purchasing APIを使用してアプリ内課金処理を実装したのでその仕組みのメモ。

Amazon App Store

 PC、KindleやAndroidスマートフォン/タブレット向けのアプリマーケット。
(Amazon版 Play Store的なもの)
ユーザーはいつものAmazonアカウントで、いつものお買い物と同じようにアプリやコンテンツを購入することが可能。アプリ提供の他、おすすめ機能、カスタマーレビュー、1-Click決済を含むAmazon同等のショッピング機能が用意されている。
Androidデバイスでこのサービスを利用する為には、Amazon App Store(Client)アプリ(Googleでいうところの Play Storeアプリ的なもの)が必要。

課金の概要

 Amazon App Storeでは、アプリの中からゲーム内通貨、拡張パック、アップグレード、雑誌の購入などをできるようにする仕組みとして、IAP (In-App Purchasing) APIを公開しています。
(AmazonSDKとしてJARライブラリとして公開されており、ポータルサイトからダウンロードできます。)
IAP APIを利用するとデジタルコンテンツやサブスクリプション(定期購読などのこと)をアプリ内で販売することが可能になります。

課金の仕組み

  1. Amazon App Store (Client) と Amazon Service
     Kindle, Androidデバイスにおける Amazon App Storeの課金は、amazon app clientアプリが Amazonのサーバーへ3G/Wi-Fi回線を使用してリクエストを行い、領収書(レシート)を取得してアプリへ返すことで実現しています。

  2. Amazon Clientが担う機能
    • アプリ側で実装しなければならない機能
      1. ユーザーが購入できるようにアプリ内のアイテムのカタログを提示する。
      2. 購入されたアイテム・コンテンツのロック解除。
      3. 購入された消耗アイテムのトラッキング。
      4. 購入したデジタルコンテンツの使用。
    • Amazon App Store (Client)側で行われる機能
      1. 購入手順の管理。
      2. 支払処理の実施。
      3. 決済のセキュリティを含め、Amazon Platformとのセキュアな通信を行う。
      4. 資格を確認し、購入した領収書を検証する。
      5. サブスクリプションの自動更新や取り消しを含む管理を行う。
  3. コンテンツの販売方法
    1. ローカルコンテンツのロック解除方式
    2. リモートコンテンツのダウンロード方式
  4. SKU (technically a stock-keeping unit)
    • 購入可能なアイテムのユニーク(一意)識別子。
    • amazon Distribution Portalの開発者アカウント内で一意である必要がる。
    • ユーザが購入する直接のアイテム定義である。
購入可能なアイテム

Amazon App Storeで購入可能なアイテムは3種類。
  1. Consumable Content(消費型コンテンツ)
    使用に資格やアクセス権を必要とせず、何度でも購入可能。
    そのデバイスでのみ利用可能なコンテンツ。
  2. Entitled Content(買いきり型コンテンツ)
    使用する権利やアクセス権を必要とするコンテンツ。
    1度だけ購入しユーザアカウントが登録されている全ての互換性のあるデバイスで使用することができる。
  3. Subscription Content(期間購入型コンテンツ)
    一定の期間、資格やアクセス権を必要とするコンテンツ。 一定の期間その権利を持ち、自動で更新される。購入したユーザアカウントが登録されている全ての互換性のあるデバイスで使用することができる。
※名称等が若干違う場合があります。

処理のフローや実装、API(PurchasingManagerやBasePurchasingObserver)の詳細はまた別の記事で記載していきたいと思います。
記事内容に誤り等がありましたらご指摘願います。

2013年5月25日土曜日

Amazon AppStore アプリ内課金の実装--準備

はじめる前に

 In-App Purchasing API は、Amazon Mobile App SDKパッケージの一部として利用可能です。 これは、Androidアプリケーションプロジェクトに含めることができるJARライブラリとして提供されています。

 In-App PurchasingのJARライブラリは任意のIDEで利用可能ですが、ここではEclipseを使用して説明しています。 この作業を行う前に、Android開発環境が構築されている必要があります。また、この説明ではIn-App Purchasingを実装する対象となるAndroidアプリケーションプロジェクトが作成されていることを想定しています。作成されていない場合は作成してから作業を行ってください。

App-SDKをダウンロードして展開する

  1. [Apps-SDK.zip] ファイルを Amazon mobile app distribution Portal よりダウンロードしてください。 (https://developer.amazon.com/sdk/download/in-app-purchasing.html)
  2. ダウンロードした [Apps-SDK.zip] ファイルを解凍し、わりやすい場所へ置いてください。
    (※このディレクトリはプロジェクトから参照するようにするため後々移動しなくてよい場所へ配置してください。)

In-App Purchasing JARライブラリをクラスパスに追加する

  1. 追加するプロジェクトのルートフォルダを選択し、コンテキストメニューから [プロパティ] を選択します。
  2. プロパティウィンドウの左ツリーより、[Javaのビルドパス]を選択。
  3. 上部の[ライブラリ] タブをクリック。
  4. 右側の [外部 JAR 追加] ボタンをクリック。
  5. "JAR の選択"ダイアログで、/InAppPurchasing/lib/in-app-purchasing-1.0.3.jarを選択し [開く] ボタンをクリック。
  6. ビルドパスに "in-app-purchasing-1.0.3.jar" が追加されたことを確認して [OK] ボタンでプロパティウィンドウを閉じます。

In-App Purchasing APIリファレンスを追加する

  1. 追加するプロジェクトのルートフォルダを選択し、コンテキストメニューから [プロパティ] を選択します。
  2. プロパティウィンドウの左ツリーより、[Javaのビルドパス]を選択。
  3. 上部の[ライブラリ] タブをクリック。
  4. 先の手順で追加した、"in-app-purchasing-1.0.3.jar" の左側▲をクリックして詳細を表示させる。
  5. [Javadoc ロケーション: (なし)]をダブルクリックまたは、選択して右側の[編集]ボタンをクリック。
  6. "’in-app-purchasing-1.0.3.jar' の Javadoc" ウィンドウで、 Javadoc URLラジオボタンを選択し、Javadoc ロケーション・パスに "/InAppPurchasing/documentation/API-Reference" を指定する。
  7. [in-app-purchasing-1.0.3.jar] の Javadoc ロケーションが変更されたことを確認して、[プロパティ]ウィンドウを [OK]ボタンで閉じる。
これでプロジェクト内でアプリ内課金関連のパッケージのリファレンスがEclipse上で直接見れるようになります。

2013年5月24日金曜日

Amazon Kindle Fire アプリケーション開発環境構築-Kindle Fire SDK Add-on インストール

Kindle Fire SDK Add-on インストール手順

公式ガイドはこちら... https://developer.amazon.com/sdk/fire/setup.html

はじめに

 Kindle用モバイルアプリケーションを作成するためには、Androidアプリケーション開発環境が必要です。 以下の必要なソフトウェアをインストールし、Android開発環境を構築してください。
  • Java Development Kit version 6 (JDK6)
  • Android SDK
  • Eclipse (Java)
  • Eclipse用のAndroid Developer Tools (ADT)
 ※この記事ではAndroid開発環境構築については詳しく触れません。

 新しいKindle FireとKindle Fire HD Tabletのアプリケーションを開発するために、Android SDK 4.0.3 (API 15)をインストールする必要があります。また、Kindle Fire (第1世代)の開発には、Android SDK 2.3.3 (API 10)をインストールする必要があります。

Kindle Fire SDK Add-on サイトの登録

  1. Android SDK Managerを起動する.
    • ADTインストール済みのEclipseから起動する場合
      メニューの [ウィンドウ] -> [Android SDK マネージャー]を選択する。
      (PleadesでEclipseを日本語化している前提です。)
    • コマンドを実行して起動する場合
      <Android SDK インストールディレクトリ>/tools/android を実行する。
  2. Android SDK Managerのメニューから
    [Tools] -> [Manage Add-on Sites...]を選択する。
  3. Android SDK Manager - Add-on Sitesウィンドウが開いたら、[User Defined Sites]タグを選択し、右側の[New...]ボタンを押す。Add-on Site URL入力ダイアログが表示されるので以下のURLを入力し [OK]ボタンを押す。
    http://kindle-sdk.s3.amazonaws.com/addon.xml
    
  4. Add-on Siteを登録したら、Android SDK Manager - Add-on Sitesウィンドウを [Close]ボタンで閉じる。 閉じると同時にAndroid SDK Managerの表示が更新される。
Kindle Fire SDK Add-on のインストール

  1. 以下のパッケージの最新リビジョンをインストールします。パッケージ名の横にあるチェックボックスにチェックをつけてインストール対象に含めてください。
    • Tools
      • Android SDK Tools
      • Android SDK Platform-tools
    • Android 4.0.3 (API 15)
      • SDK Platform
      • ARM EABI v7a System Image
      • Intel x86 Atom System Image
      • Kindle Fire (2nd Generation)
      • Kindle Fire HD 7"
      • Kindle Fire HD 8.9"
    • Android 2.3.3 (API 10)
      • SDK Platform
      • Kindle Fire
    • Extras
      • Kindle Fire Device Definitions
      • Kindle Fire USB Driver(OSXの場合は不要)
      • Android Support Library
      • Intel x86 Emulator Accelerator (HAXM)
  2. パッケージの選択が完了したら、Android SDK Managerの右下にある
    [Install n packages...] ボタンを押してインストールを開始してください。
    ("n" は選択したパッケージ数です。)
  3. Choose Packages to Installダイアログが表示されます。
    インストールするすべてのパッケージのライセンスを確認し、AcceptまたはAccept Licenseラジオボタンを選択して同意してください。インストールするすべてのパッケージのライセンスに同意したら [Install]ボタンを押してインストールを実行します。
  4. インストールには時間がかかる場合があります。インストール完了後、Eclipseを再起動すれば作業完了です。お疲れ様でした!

bloggerへのSyntaxHighlighter導入と表示パラメータ調査メモ

Syntax Highlighterとは

調べものでWebを徘徊している時によく見る、
Blog記事上に綺麗にソースコードを表示する機能を持つJavaScript。


このBlogにもソースコードを載せていきたいので導入した時の調査メモ。

導入方法

  1. Syntax Highlighterを導入する
    Syntax Highlighter Scripts Generatorサイト
    (http://www.way2blogging.org/widget-generators/syntax-highlighter-scripts-generator)
     にて表示させたいテーマと強調表示させたい言語を選択し「Generate」ボタンをクリック。
  2. bloggerのテンプレートへ生成されたリンク・スクリプトをコピペする
    ページ下部に生成された定義が表示されるので、クリップボードへコピーしてBloggerのテンプレートのヘッダーへペースト。
    表示テーマのカスタムをしない場合はこれだけで準備完了!

使い方

  • <pre></pre>で囲った部分がソースコード表示される。 
  • <pre>タグのパラメータを指定することで表示方法を制御できる。
  • HTML編集で以下の様に記述する...
  • <pre class="brush: (強調させたい言語); (パラメータ)...>
        表示するソースコード
    </pre>
    

ソースコードの表示例(Java)

    public class Hoge{
        public static void main (String[] args) {
            ;
        }
    }
    
    この時の記述は以下のとおり。
    class="brush: xxxxx"を強調表示したい言語に置き換えて使用する。
    <pre class="brush: java">
    
    当たり前の話だが、ここで指定する言語指定は最初の定義生成時に選択してbloggerのテンプレートヘッダーに定義されている必要がある。

    ここで表示しているプレーンテキストの場合は以下の通り定義することで強調表示のない表示ができる。
    <pre class="brush: plain">
    

表示を制御するパラメータ

  • auto-links (true/false *default=true)
    投稿コード内の全てのURLをクリック可能なリンクにする。
    /*
     * auto-links: true でURL表示
     * http://devwalker.blogspot.jp
     */
    public class Hoge{
        String url = "http://devwalker.blogspot.jp";
    }
    
  • class-name
    highlighterエレメントにカスタムクラス(スタイル)を指定することができる。
    public class Hoge{
        ;
    }
    
  • collapse (true/false *default=false)
    trueの場合、コードボックスが折り畳まれる。
    /*
     * collapse=trueの場合の表示
     */
    public class TestClazz {
        private final static String HOGE = "hoge";
        public static void main(String[] args) {
            // code
        }
    }
    
    titleパラメータと併用すると便利。
    /*
     * collapse=trueの場合の表示
     */
    public class TestClazz {
        private final static String HOGE = "hoge";
        public static void main(String[] args) {
            // code
        }
    }
    
  • first-line (数字 *default=1)
    コードの左側に表示される行番号の開始番号を指定する。
    /*
     * 開始行番号を20に指定(first-line: 20)
     */
    public class TestClazz {
        public static void main(String[] args) {
            // code
        }
    }
    
  • gutter (true/false *default=true)
    行番号の表示/非表示設定。
    falseの場合、コードの左側に表示される行番号が非表示になる。
    public class TestClazz {
        public static void main(String[] args) {
            // code
        }
    }
    
  • highlight (数字。複数指定時の場合は[x,x,...]で指定)
    強調表示したいコードの行番号を指定することができる。
    first-lineパラメータで開始行番号を変更した場合は、その変更後の行番号を指定する必要がある。
    /*
     * highlight ="[5, 7]"を指定して強調表示
     */
    public class TestClazz {
        private final static String HOGE = "hoge";
        public static void main(String[] args) {
            // code
        }
    }
    
    /*
     * first-line: 21 を指定した場合、highlightオプションも
     * firstlineオプションに指定した開始行番号に基づき指定を変更する。
     * ※highlight: [27, 29]
     */
    public class TestClazz {
        private final static String HOGE = "hoge";
        public static void main(String[] args) {
            // code
        }
    }
    
  • html-script (true/false *default=false)
    trueの場合、コード内の任意のHTML/XMLが強調表示される。
    HTMLにPHPなどを混合している場合の表示に便利。特定の言語で動作します。
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>title</title>
    </head>
    <body>
        <script language="JavaScript">
        document.write("...");
        </script>
    </body>
    </html>
    
  • smart-tabs (true/false *default=true)
    スマートタブ機能の有効/無効を指定できる。けどイマイチ違いが良くわかりません。
    タブによる表示のズレを抑えてtab-sizeと同じ半角文字数幅に抑える機能でしょうか?

    smart-tabs:true
    public class Sample {
    12    hello  ya!
    }
    
    smart-tabs:false
    public class Sample {
    12    hello  ya!
    }
    
  • tab-size (数字 *default=4)
    コードボックスのタブサイズを指定できる。
    public class Sample {
     // tab-size:12 を指定するとタブが半角12文字分の扱いとなる
    }
    
  • toolbar (true/false *default=true)
    コードボックスのツールバーの表示/非表示を指定できる。(右上に表示される?ボタン)
    public class Sample {
        //
    }
    
  • title (文字列)
    コードボックスにタイトルを指定できます。
    // title: 'サンプルタイトル'
    public class Sample {
        //
    }
    
  • light (true/false *Default="false")
    trueの場合、行番号とツールバーが非表示になる。1・2行のコードを表示する時に便利。
    // 1・2行だけなら便利かも
    public final static String API_KEY_VALUE = "hogehoge";
    
  • pad-line-numbers (true/false/数字 *Default="false")
    行番号の番号0埋めを指定できるパラメータ。
    true=自動--表示行数に応じて行番号の0埋めをしてくれる。
    false=無効--行番号の0埋めをしない。
    数字=0埋めする桁数を指定。
    // pad-line-numbers: true
    public class TestClazz {
        private final static String HOGE = "hoge";
        public static void main(String[] args) {
        /*
         * 表示行数が2桁なので
         * 0埋めされて行番号が表示される。
         */
        }
    }
    
    // pad-line-numbers: false (Default)
    public class TestClazz {
        private final static String HOGE = "hoge";
        public static void main(String[] args) {
        /*
         * 行番号が2桁だが、
         * 行番号の0埋め表示はされない。
         */
        }
    }
    
    // pad-line-numbers: 4
    public class TestClazz {
        private final static String HOGE = "hoge";
        public static void main(String[] args) {
            /*
             * 指定した桁数で
             * 行番号が0埋め表示される。
             */
        }
    }
    
  • quick-code (true/false *Default="true")
    クイックコードコピー機能の有効/無効を設定します。
    あまりよくわかりません。。。
    // quick-code: false
    public class TestClazz {
        private final static String HOGE = "hoge";
    }
    

SyntaxHighlighterのコードより調査...