2012年12月31日月曜日

In-app Billing Version3 翻訳(In-app Billing Reference編)

EGGの曽川です。

リファレンスの翻訳です。
原文はこちらです。
まだ幾つかドキュメントはありますが、In-app Billing Version3に関するドキュメントは一旦終了します。

アプリ内課金リファレンス
このドキュメントはアプリ内課金バージョン3を使った技術的なリファレンスです。

サーバのレスポンスコード

次の表はGooglePlayからアプリに対して送られる、サーバのレスポンスコードの全てです。
GooglePlayは同期的にレスポンスコードを送ります。(レスポンスBundleRESPONSE_CODEキーにマッピングされているint型の値)
アプリは全てのレスポンスコードを処理しなければなりません。

表1.アプリ内課金バージョン3のAPI呼び出しのレスポンスコード一覧
レスポンスコード 説明
BILLING_RESPONSE_RESULT_OK 0 成功
BILLING_RESPONSE_RESULT_USER_CANCELED 1 ユーザがバックキーを押した、またはダイアログをキャンセルした
BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE 3 課金APIのバージョンがリクエストしたタイプをサポートしていない
BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE 4 リクエストされたサービスは購入できない状態にある
BILLING_RESPONSE_RESULT_DEVELOPER_ERROR 5 APIに与えられた引数が無効。このエラーは署名が正しくない、アプリ内課金の設定がGooglePlayで行われていない、AndroidManifestに必要な権限がない。
BILLING_RESPONSE_RESULT_ERROR 6 APIの動作中に致命的なエラーが起こった。
BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED 7 アイテムが既に購入されているので失敗した。
BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED 8 アイテムを所有していないために消費に失敗した。


APIリファレンス

アプリ内課金バージョン3のAPIは、バージョン3のサンプルアプリケーションに含まれるInAppBillingService.aidlファイルに定義されています。

getSkuDetails()メソッド
このメソッドはサービスIDに紐づくサービス詳細リストを返します。
GooglePlayから送られたレスポンスBundleは、クエリの結果がDETAILS_LISTキーをマップとするArrayListに格納されています。
詳細リストのそれぞれのString内には、1つのサービス(JSON形式)のサービス詳細が含まれます。
サービス詳細のJSON文字列のフィールドを、表2にまとめています。

表2.getSkuDetailsリクエストから返されるサービスアイテム詳細のJSONフィールドの説明
キー 説明
productId 商品のサービスID
type アプリ内の購入タイプで、値は必ず「inapp」
price 通貨記号を含むアイテムの価格のフォーマット。価格には消費税は含まれていない。
title サービスのタイトル
description サービスの説明

getBuyIntent()メソッド
このメソッドは、RESPONSE_CODEキーに紐づくint型のレスポンスコードと
BUY_INTENTキーに紐づいたアプリ内アイテムの購入フローを起動するPendingIntentを返します。
PendingIntentを受け取ると、GooglePlayは注文データでレスポンスIntentを送ります。
レスポンスIntentから返されるデータを表3にまとめています。

表3.アプリ内課金のバージョン3の購入リクエストからのレスポンスデータ
キー 説明
RESPONSE_CODE 購入が完了した場合は0それ以外はエラー
INAPP_PURCHASE_DATA 注文詳細を含むJSON形式の文字列。(表4のJSONフィールドの説明を参照)
INAPP_DATA_SIGNATURE 開発者の秘密鍵で署名された購入データの署名を含む文字列

表4は注文のレスポンスデータを返すJSONフィールドを説明しています。

表4.INAPP_PURCHASE_DATAのJSONフィールドの説明
フィールド 説明
orderId トランザクションのためのユニークな注文ID。Google Walletの注文IDに対応します。
packageName 購入の起点となったアプリのパッケージ
productId アイテムのサービスID。全てのアイテムはサービスIDを持っており、GooglePlayパブリッシャーサイト上のアプリのサービスリストで指定しなければいけません。
purchaseTime 購入された時間。エポック時間からのミリ秒(1970/01/01).
purchaseState 注文の購入状態。可能な値は0 (購入)、1 (キャンセル)、2 (払い戻し).
developerPayload 注文に関する補足情報を含む開発者が指定した文字列。getBuyIntentリクエストを行う際にこのフィールドの値を指定する事ができます。
purchaseToken 指定されたアイテムとユーザのペアのための購入を一意に識別するトークン

getPurchases()メソッド
このメソッドは、現在ユーザが保有する未消費のサービスを返します。
表5はBundleとして返されるのレスポンスデータのリストです。

表5.getPurchasesリクエストのレスポンスデータ

キー 説明
RESPONSE_CODE 0の場合は成功。それ以外はエラー。
INAPP_PURCHASE_ITEM_LIST StringArrayList:アプリからの購入サービスIDを含むリスト。
INAPP_PURCHASE_DATA_LIST StringArrayList:アプリからの購入詳細を含むリスト。(リスト内の各INAPP_PURCHASE_DATA項目に保存された詳細データのリストは表4を参照してください。)
INAPP_DATA_SIGNATURE_LIST StringArrayList:アプリからの購入の署名を含むリスト。
INAPP_CONTINUATION_TOKEN ユーザが保有するアプリ内サービスの次のセットを検索するための継続トークン含む文字列。
これはユーザが保有するサービスが非常に多い場合のみ、GooglePlayサービスがセットします。
継続トークンがレスポンスに存在している場合は、getPurchasesを別に呼び出し、受信した継続トークンを渡さなければいけません。
後のgetPurchasesの呼び出しは、さらなる購入やおそらく別のトークンを返します。

2012年12月30日日曜日

In-App Billing Version 3 翻訳(Implementing In-app Billing編)

EGGの曽川です。

アプリ内課金の実装方法について翻訳しました。
原文はこちらです。
誤訳等あればご指摘ください。
ドキュメントは文字のみなのでイメージを掴むたい方は、In-app Billing Version3 (サンプルを使う編)もご覧ください。

アプリ内課金の実装
GooglePlay上のアプリ内課金は、GooglePlayを使用したアプリ内課金のリクエストの送信や、アプリ内課金のトランザクション管理のための簡単でシンプルなインタフェースを提供します。
以下の情報はバージョン3のAPIを使用して、アプリからアプリ内課金サービスをどのように呼び出すかの基本をカバーしています。

注意:完成した実装を見て、アプリのテストする方法を学ぶためにSelling In-app Productsトレーニングクラスを見てください。
トレーニングクラスは完成しているアプリ内課金のサンプルアプリを提供します。
これは、重要なタスクの処理、接続のセットアップ、課金リクエストの送信、GooglePlayからのレスポンスの処理、メインアクティビティからアプリ内課金をコールするためのバックグラウンドスレッドの管理を含みます。

作業を開始する前に、アプリ内課金を実装する事が容易になる概念を理解するために、In-app Billing Overviewを読んでください。
アプリ内で課金を実行するために次の手順を実行してください。
  1. Billingライブラリプロジェクトを追加する。
  2. AndroidManifest.xmlの更新
  3. ServiceConnectionを作成し、IInAppBillingServiceをバインドします。
  4. アプリからIInAppBillingServiceにアプリ内課金のリクエストを送信します。
  5. GooglePlayからのアプリ内課金のレスポンスを処理します。

AIDLファイルをプロジェクトへ追加

サンプルアプリTrivialDriveは、GooglePlayのアプリ内課金サービスのインタフェースに定義されている
Android Definititon Language(AIDL)ファイルを含んでいます。
このファイルを追加すると、Androidのビルド環境はインタフェースファイル(IIAppBillingService.java)を作ります。
課金リクエストのIPCメソッド呼び出しを行うためにこのインタフェースを使います。
プロジェクトにアプリ内課金バージョン3のライブラリを追加するには:
  1. IInAppBillingService.aidlファイルをAndroidプロジェクトにコピーします。
    • もしEclipseを使用している場合は、IInAppBillingService.aidl/srcディレクトリにインポートします。
      Eclipseがプロジェクトをビルドした際に、インタフェースファイルを自動的に生成します。
    • もしEclipseプロジェクトを使用しない環境の場合は、
      /src/com/android/vending/billingディレクトリを作成し、IInAppBillingService.aidlをコピーします。
      AIDLファイルをプロジェクトに置き、IIAppBillingService.javaを生成するためにAntツールを使用します。
  2. アプリをビルドします。
    プロジェクトの/genディレクトリにIInAppBillingService.javaという名前で生成されたファイルがあるはずです。

AndroidManifestの更新

アプリ内課金は、アプリとGooglePlayサーバ間の全ての通信を処理するGooglePlayアプリに依存しています。
GooglePlayアプリを使用するには、適切なアクセス許可を要求しなければなりません。
AndroidManifest.xmlファイルにcom.android.vending.BILLINGパーミッションを追加する事でこれをおこなうことができます。
もし、アプリがアプリ課金のパーミッションを宣言せずに課金リクエストを試みる場合は、GooglePlayは要求を拒否しエラーを返します。

アプリに必要な権限を付与するには、AndroidManifest.xmlファイルに次の行を追加します。
<uses-permission android:name="com.android.vending.BILLING">


Service Connectionの作成

アプリは、GooglePlayとのメッセージのやり取りを容易にするServiceConnectionを持っていなければなりません。
少なくとも次のことを行ってください。

  • IInAppBillingServiceとのバインド
  • GooglePlayアプリへの課金リクエスト送信(IPCメソッド呼び出しなど)
  • 各課金リクエストで返される同期的なレスポンスメッセージの処理

IInAppBillingServiceのバインド
GooglePlayとアプリ内課金サービスの接続を確立するには、IInAppBillingServiceにActivityをバインドするServiceConnectionを実装します。
接続が確立された後、IInAppBillingServiceインスタンスの参照を取得する、onServiceDisconnectedonServiceConnectedメソッドをオーバライドします。
IInAppBillingService mService;

ServiceConnection mServiceConn = new ServiceConnection() {
   @Override
   public void onServiceDisconnected(ComponentName name) {
       mService = null;
   }

   @Override
   public void onServiceConnected(ComponentName name, 
      IBinder service) {
       mService = IInAppBillingService.Stub.asInterface(service);
   }
};

ActivityのonCreateメソッドで、bindServiceメソッドを呼び出すことでバインドを実行します。
アプリ内課金サービスの参照と、作成したServiceConnectionインスタンスをIntentメソッドとして渡します。
@Override
public void onCreate(Bundle savedInstanceState) {    
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);        
   bindService(new 
      Intent("com.android.vending.billing.InAppBillingService.BIND"),
          mServiceConn, Context.BIND_AUTO_CREATE);
これでGooglePlayサービスとのやり取りのためにmServiceの参照を使用できます。

重要Activityが終了したらアプリ内課金サービスからアンバインドすることを忘れないでください。
もし、アンバインドをしなければ、開いているサービス接続がデバイスのパフォーマンスを低下させます。
この例では、ActivityのonDestroyメソッドをオーバライドすることにより、アプリ内課金サービス接続のmServiceConnをどのようにアンバインドを実行するか示しています。
@Override
public void onDestroy() {
   if (mServiceConn != null) {
      unbindService(mServiceConn);
   } 
}
IInAppBillingServiceとバインドするサービス接続の完全な実装については、トレーニングクラスのSelling In-app Productsを参照してください。


アプリ内課金のリクエストを作成する

一度アプリがGooglePlayに接続されたら、アプリ内サービスの購入リクエストを開始できます。
GooglePlayはユーザが支払方法を入力するチェックアウトインタフェースを提供するので、アプリは直接支払いのトランザクションを操作する必要はありません。
アイテムが購入されると、GooglePlayはユーザがアイテムの所有権を持っていることと、ユーザが同じサービスIDのアイテムを消費するまで、アイテムの購入が発生しないようにすること判別します。
アプリ内でアイテムがどのように消費されるか制御することができ、GooglePlayに再度購入ができるように通知します。
またユーザに関する購入リストをすぐに取得するために、GooglePlayに問い合わせることができます。
これは例えば、ユーザがアプリを起動した時にユーザの課金を復元したい時に非常に便利です。

購入可能なアイテムの問い合わせ
あなたのアプリ内で、アプリ内課金バージョン3を使ってGooglePlayからアイテムの詳細の問い合わせができます。
アプリ内課金サービスにリクエストを渡すために、まず"ITEM_ID_LIST"のキーでサービスIDを持つ、StringのArrayListを含むBundleを生成します。(それぞれの文字列は購入可能なアイテムのサービスIDです。)
ArrayList skuList = new ArrayList();
skuList.add("premiumUpgrade");
skuList.add("gas");
Bundle querySkus = new Bundle();
querySkus.putStringArrayList(“ITEM_ID_LIST”, skuList);
この情報をGooglePlayから取得するために、アプリ内課金バージョン3のgetSkuDetailsメソッドを呼び出すために、アプリ内課金バージョン("3")、アプリのパッケージ名、購入タイプ("inapp")と作成したBundleを渡します。
Bundle skuDetails = mService.getSkuDetails(3, getPackageName(), “inapp”, querySkus);
リクエストが成功した場合、返されたBundleは、BILLING_RESPONSE_RESULT_OK (0)のレスポンスコードを持ちます。

警告:メインスレッド上でgetSkuDetailsを呼んではいけません。このメソッドを呼び出すと、メインスレッドをロックするネットワークリクエストを行います。
代わりに別のスレッドを作成し、そのスレッド内からgetSkuDetailsメソッドを呼び出してください。

GooglePlayからのすべてのレスポンスコード見るには、In-app Billing Referenceを参照してください。

クエリの結果はDETAILS_LISTキーのStringのArrayListに格納されます。
購入情報はJSON形式の文字列に格納されます。
返されたサービス詳細情報の種類を確認するには、In-app Billing Referenceを参照してください。
この例では、先程のコードスニペットから返されたskuDetailsのBundleからアプリ内アイテムの価格を取得しています。
int response = skuDetails.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList responseList 
      = skuDetails.getStringArrayList("DETAILS_LIST");
   
   for (String thisResponse : responseList) {
      JSONObject object = new JSONObject(thisResponse);
      String sku = object.getString("productId");
      String price = object.getString("price");
      if (sku.equals(“premiumUpgrade”)) mPremiumUpgradePrice = price;
      else if (sku.equals(“gas”)) mGasPrice = price;
   }
}

アイテムの購入
アプリから課金リクエストを開始するには、アプリ内課金サービスのgetBuyIntentメソッドを呼び出します。
アプリ内課金APIバージョン("3")、アプリパッケージ名、アイテムを購入するためのサービスID、購入タイプ(inapp)、developerPayload文字列をメソッドに渡します。
developerPayload文字列は、購入情報と一緒にGooglePlayに返送して欲しい、追加の引数を指定するために使用されます。
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(),sku, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
リクエストが成功した場合、返されたBundleBILLING_RESPONSE_RESULT_OK (0)のレスポンスコードと、購入フローを開始するために使用出来るPendingIntentを持っています。
GooglePlayからのすべてのレスポンスコードを見るには、In-app Billing Referenceを参照してください。
次に、返されたBundleBUY_INTENTキーを使用して、PendingIntentを取得します。
PendingIntent pendingIntent = buyIntentBundle.getParcelable(“BUY_INTENT”);
購入トランザクションを完了するために、startIntentSenderForResultメソッドを呼び出し、作成したPendingIntentを使用します。
この例では、リクエストコードとしての1001という任意の値を使用しています。
startIntentSenderForResult(pendingIntent.getIntentSender(),1001, new Intent(), Integer.valueOf(0),Integer.valueOf(0),Integer.valueOf(0));
GooglePlayは、PendingIntentからアプリのonActivityResultメソッドにレスポンスを送信します。
onActivityResultメソッドはActivity.RESULT_OK (1)またはActivity.RESULT_CANCELED (0)の結果コードを持ちます。
レスポンスIntentに返された注文情報の種類を見るには、In-app Billing Referenceを参照してください。

注文の購入データは、レスポンスIntentINAPP_PURCHASE_DATAキーにマップされているJSON形式の文字列です。
例えば以下のとおりです。
'{ 
"orderId":"12999763169054705758.1371079406387615", 
"packageName":"com.example.app",
"productId":"exampleSku",
"purchaseTime":1345678900000,
"purchaseState":0,
"developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
“purchaseToken”:“rojeslcdyyiapnqcynkjyyjh”
}'
前の例の続きは、レスポンスIntentからレスポンスコード、購入データ、署名を取得します。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
   if (requestCode == 1001) {     
      int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
      String purchaseData = data.getStringExtra(“INAPP_PURCHASE_DATA”);
      String dataSignature = data.getStringExtra(“INAPP_DATA_SIGNATURE”);
        
      if (resultCode == RESULT_OK) {
         try {
            JSONObject jo = new JSONObject(purchaseData);
            String sku = jo.getString("productId");
            alert("You have bought the " + sku + ". Excellent choice, 
               adventurer!");
          }
          catch (JSONException e) {
             alert("Failed to parse purchase data.");
             e.printStackTrace();
          }
      }
   }
}
セキュリティの推奨事項:購入リクエストを送信すると、購入リクエストを一意に識別する文字列トークンを作成し、developerPayloadにこのトークンを含めます。
トークンとしてランダムに生成された文字列を使用することができます。
GooglePlayから購入レスポンスを受け取った時、返されたデータ署名、orderIddeveloperPayloadを確認してください。
セキュリティを強化するために、自身のセキュアなサーバ上でチェックを実行してください。
orderIdが以前に処理されていないユニークな値であり、developerPayload文字列が購入リクエスト以前送信したトークンと一致することを必ず確認してください。

購入アイテムの問い合わせ
アプリからユーザが行った購入に関する情報を取得するためには、アプリ内課金APIバージョン3サービスのgetPurchasesを呼び出します。
アプリ内課金APIバージョン("3")、アプリのパッケージ名、購入タイプ("inapp")をメソッドに渡します。
Bundle ownedItems = mService.getPurchases(3, getPackageName(), “inapp”, null);
GooglePlayサービスは、現在端末にログインしているユーザアカウントの購入のみ返します。
リクエストが成功した場合、返されたBundleはレスポンスコードとして0を持っています。
返されたBundleはサービスID、それぞれの購入の注文詳細リスト、それぞれの購入の署名が含まれています。

パフォーマンスを改善するために、getPurchasesが最初に呼び出された時、アプリ内課金サービスはユーザが所有している700の商品までしか返しません。
ユーザが多数の商品を所有している場合、GooglePlayは更に多くの商品を検索できることを示すために、返されたBundle内で文字列トークンにマップされたINAPP_CONTINUATION_TOKENキーを含んでいます。
アプリはそれ以降のgetPurchasesの呼び出しを行うために、引数としてこのトークンを渡します。
GooglePlayは、ユーザが所有するすべての商品がアプリに送られるまで、返されたBundle内に継続トークンを返し続けます。

getPurchasesによって返されるデータの詳細については、 In-app Billing Referenceを参照してください。
次の例はレスポンスからデータを取得する方法を示します。
int response = ownedItems.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList ownedSkus = 
      ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
   ArrayList purchaseDataList = 
      ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
   ArrayList signatureList = 
      ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE");
   String continuationToken = 
      ownedItems.getString("INAPP_CONTINUATION_TOKEN");
   
   for (int i = 0; i < purchaseDataList.size(); ++i) {
      String purchaseData = purchaseDataList.get(i);
      String signature = signatureList.get(i);
      String sku = ownedSkus.get(i);
  
      // 購入情報を使って何かします。
      // 例えば、ユーザが所有している商品の最新のリストの表示
   } 

   // continuationToken != nullの場合、getPurchasesを再度呼んで 
   // 更にアイテムを検索するためにトークンを渡します。
}

購入の消費
GooglePlayで購入したアイテムの所有権を追跡するために、アプリ内課金バージョン3のAPIを使用することができます。
アイテムが購入されると、「所有」であるとみなされ、GooglePlayから購入することはできません。
GooglePlayで再度購入可能な状態にする前に、消費リクエストを送らなければいけません。
すべての管理対象の商品は消耗可能です。
消費のメカニズムをどのように使用するかはあなた次第です。
一般的にユーザが複数回購入したいと思うであろう、一時的な利点を持つ商品のために消費を実装します。(例:ゲーム内通貨や装備)
一般的に、一度の購入で恒久的な効果を提供する商品のために消費は実装しません。(例:プレミアムアップグレード)

購入の消費を記録するためには、アプリ内課金サービスにcomsumePurchaseメソッドをを送り、削除するための購入識別の値であるpurchaseToken文字列を渡します。
purchaseTokenは、購入リクエストの成功後にGooglePlayサービスによってINAPP_PURCHASE_DATA文字列で返されたデータの一部です。
この例では、token変数にpurchaseTokenで識別された商品の消費を記録しています。
int response = mService.consumePurchase(3, getPackageName(), token);
警告:メインスレッドでconsumePurchaseメソッドを呼び出してはいけません。
のメソッドを呼び出すと、メインスレッドをロックするネットワークリクエストを行います。
代わりに別のスレッドを作成し、そのスレッド内からconsumePurchaseメソッドを呼び出してください。

ユーザに提供されたアプリ内商品のどのように制御と追跡するかはあなたの責任です。
例えば、ユーザがゲーム内通貨を購入した場合、プレイヤーの購入した通貨の量のインベントリを更新する必要があります。

セキュリティの推奨事項:ユーザへ消費可能なアプリ内購入の利益を提供する前に、消費リクエストを送らなければいけません。
アイテムの提供前にGooglePlayから消費成功レスポンスの受信を確認してください。

Except as noted, this content is licensed under Creative Commons Attribution 2.5.

2012年12月18日火曜日

In-app Billing Version3 (サンプルを使う編)

チームEGGの曽川です。

実装の翻訳に入る前に実際にどういう動きをするのか、サンプルを使って動かしてみたいと思います。
基本的には、以前のアプリ内課金の作業と似ているのでこちらも参考にしてください。
アプリのアップロード完了待ちの時間もあるので、確認までに2~3時間程度必要になる可能性があります。

GooglePlayでの作業
  1. 新デザインに切り替える
  2. 新しいアプリを追加
  3. サービスとAPIの欄にある公開鍵をメモる
  4. 設定でテスト用アカウントを追加
  5. 「設定」→「アカウントの詳細」→「テスト用のアクセス権がある Gmail アカウント」にテストで使う端末で登録しているGoogleアカウントを入力 ※デベロッパーコンソールの管理アカウント以外じゃないとダメ


アプリ側の作業
  1. SDKManagerを使用してGooglePlayBillingLibraryをダウンロード
  2. サンプルをEclipseにインポート
  3. {android-sdk}/google/play_billing/in-app-billing-v03/samples 上記ディレクトリの中の「TrivialDrive」
  4. パッケージの変更
  5. AndroidManifest.xmlの修正。パッケージとActivityの記述を修正する。 ※com.exampleで始まってはダメです。
  6. MainActivity.java修正
  7. MainActivity.javaのbase64EncodedPublicKeyを、先ほどメモした公開鍵を貼り付けて置き換える。
  8. 実行する
  9. この時点ではまだ「アイテムは見つかりませんでした。」と表示されます。
  10. アップロード用のkeystoreを使ってアプリを署名する
  11. デバッグ署名ではダメ。keystoreの作り方はこちら


もう一度GooglePlayでの作業
  1. アプリをアップロードする
  2. アップロード処理が完了するまでの2~3時間程度かかります。 ※公開はしなくていいですが、どうしても上手くいかない場合は公開することでうまくいく事があります。
  3. アプリ内サービスを追加する
  4. 「premium」と「gas」
  5. 端末にadbコマンドでアプリを入れる
  6. ※GooglePlayにアップしたものと、端末のアプリは同一バイナリである必要はありません。 パッケージ名と署名とバージョンコードさえ一致していれば大丈夫です。
  7. アプリを起動し、「BUY GAS」を押してみる
購入するとお金が発生するのでご注意を。
購入した場合は、デベロッパーコンソールからGoogleCheckoutに行き、払い戻しをしてください。

2012年12月13日木曜日

In-app Billing Version 3 翻訳(Version 3 API 構成編)

チームEGGの曽川です。

今回は更に実装に近い部分に入っていきます。
誤訳やよい表現がありましたらご指摘ください。
原文はこちらです。

アプリ内課金バージョン3

アプリ内課金バージョン3 APIは、アプリにアプリ内課金の組み込みことを簡単にします。
このバージョンの機能には、改良された同期フロー、消耗品の所有を追跡するAPI、アプリ内課金のローカルキャッシングを含みます。

サービスタイプ

サービスタイプ、SKU、価格、説明などをGooglePlayデベロッパーコンソールを使用してサービスを定義します。
詳細については、Administering In-app Billingを参照してください。
バージョン3のAPIは管理対象のアプリ内サービスタイプのみサポートします。

管理対象アプリ内サービス
管理対象のアプリ内サービスはGooglePlayによって所有情報が管理されるアイテムです。
ユーザがマネージドアプリ内アイテムを購入すると、GooglePlayはそれぞれの購入情報を保管します。
これにより、特定のユーザーが購入したアイテムの状態を復元するために、後でいつでもGoogle Playに問い合わせることができます。
この情報は、ユーザがアプリをアンインストールしても端末を変えてもGooglePlay上で永続的に保存されます。

バージョン3のAPIを使用している場合、アプリの管理対象のアイテムの消費を管理できます。
通常は複数回購入できるアイテム(ゲーム内通貨、燃料、魔法の呪文)の消費を実装します。
一度購入すると、管理対象のアイテムはGoolePlayに消費リクエストを送り、アイテムを消費するまで購入できません。
アプリ内サービスの消費の詳細については、「アイテムの消費」を参照してください。


アイテムの購入

バージョン3のAPIを使用した一般的な購入の流れは次の通りです。
  1. アプリは、使用しているアプリ内課金APIのターゲットバージョンがサポートされているか決定するためにisBliingSupportedリクエストをGooglePlayに送ります。
  2. アプリの開始やログインした時に、アイテムをユーザが所持しているか決定するためにGooglePlayに確認することを推奨します。
    ユーザのアプリ内課金を照会するためには、getPurchasesリクエストを送信します。
    リクエストが成功すれば、GooglePlayは購入したアイテムのサービスIDのリスト、各購入アイテムの詳細リスト、購入のための署名リストを含むBundleを返します。
  3. 通常、購入可能なサービスを知らせたいと思います。
    アプリ内サービスの詳細を照会するために、アプリはgetSkuDetailsリクエストを送信できます。
    照会するリクエストにサービスIDのリストを指定しなければなりません。
    リクエストが成功すれば、GooglePlayはサービス価格、タイトル、説明、購入タイプを含むサービス詳細のBundleを返します。
  4. アプリ内サービスがユーザに所有されていない場合、購入を開始することができます。
    購入リクエストを開始するために、アプリは。他のパラメータを一緒に購入するアイテムのサービスIDを指定して、getBuyIntentリクエストを送信します。
    デベロッパーコンソールで新しいアプリ内サービスを作成する際は、サービスIDを記録する必要があります。
    1. GooglePlayはアプリが使用するPendingIntent(購入の支払いをするUIを開始するため)を含むBundleを返します。
    2. アプリは startIntentSenderForResultメソッドを呼び出すことで、Pending Intentを起動します。
    3. 購入フローが終了すると(ユーザが正常にアイテムを購入した、または購入をキャンセルした)、GooglePlayはレスポンスインテントonActivityResultメソッドに送ります。
      onActivityResultの結果のコードは、課金が成功したかキャンセルしたかを示す結果コードを持っています。
      レスポンスインテントは、purchaseTokenの文字列(購入トランザクションを一意に識別するためのGooglePlayが生成)を含む購入されたアイテムの情報を含みます。
      Intentは購入の署名(開発者のプライベートキーで署名された)を含みます。
バージョン3のAPI呼び出しとサーバーの応答の詳細については、In-app Billing Referenceを参照してください。

図1.購入リクエストのための基本的なシーケンス

アイテムの消費
アプリ内サービスのユーザの所有を追跡するために、消費の仕組みを使用します。
バージョン3では、全てのアプリ内サービスは管理されます。
アプリ内で購入した全てのアイテムについてのユーザの所有権は、GooglePlayによって保持されることを意味します。
そしてアプリはユーザの購入情報を必要なときに照会できます。
ユーザがアイテムを正常に購入すると、購入がGooglePlayに保存されます。
一度アイテムを購入すれば、「所有」されているとみなされます。
「所有」された状態のアイテムは、GooglePlayから購入することはできません。
GooglePlayで再び購入可能な状態にする前に、「所有」アイテムの消費リクエストを送らなければいけません。
アイテムの消費は、「所有していない」状態に戻し、以前の購入データを破棄します。

ユーザが所有するサービスのリストを取得するためには、アプリからgetPurchasesをGooglePlayに送信します。
アプリはconsumePurchaseを送信することで消費リクエストを行うことができます。
リクエストの引数で、購入時はGooglePlayから得た、アイテムの一意となるpurchaseToken文字列を指定しなければなりません。
GooglePlayは消費が正常に記録されたかどうかを示すステータスコードを返します。

非消費/消費アイテム
アプリ内サービスを非消費/消費アイテムを決めるのはあなた次第です。

非消費アイテム
通常、アプリ内で一度購入すると永続的にメリットを提供するため、アイテムの消費を実装しません。
購入後、アイテムは永続的にユーザのGoogleアカウントに関連付けられます。
非消耗品の例としては、プレミアムアップグレードやレベルパックです。

消費アイテム
非消費アイテムとは対照的に、購入を複数回利用できるようにアイテムの消費を実装できます。
通常、消費アイテムは特定の一時的な効果を提供します。
例えば、ゲーム内キャラクターのライフ増加やインベントリ内に臨時でコインを獲得することです。
アプリ内で購入したアイテムのメリットや効果を供給することを、アプリ内サービスのプロビジョニング(支給)と呼ばれます。
あなたはアプリサービスがどのようにユーザに提供されるか、制御や追跡する責任があります。

重要:アプリ内で消費アイテムをプロビジョニング(支給)する前に、GooglePlayに消費リクエストを送り、消費が記録されたことを示す成功リクエストを受け取らなければいけません。

●アプリでの消費アイテム購入の管理
消費アイテムを購入するための基本的なフローは以下の通りです。

  1. getBuyIntentを呼び出し、購入フローを起動します。
  2. 購入が正常に完了したら、GooglePlayからレスポンスBundleを取得する。
  3. 購入に成功した場合は、consumePurchaseを呼び出し、購入を消費します。
  4. 消費が正常に完了したかどうかを示すGooglePlayからのレスポンスコードを取得します。
  5. 消費が完了したら、アプリのサービスのプロビジョニング(支給)をおこないます。

その後、ユーザがアプリを起動するかログインする時に、未決済の消費アイテムをユーザが所有するかチェックする必要があります。
もし、あるならばアイテムを消費し、支給してください。

  1. ユーザにより所有されているアイテムを照会するためにgetPurchasesリクエストを送ります。
  2. 消耗品がある場合は、comsumePurchaseを呼び出し、アイテムを消費します。この手順は、アプリが消費アイテムの購入注文を完了したが、停止もしくはアプリが消費リクエストを送信する機会の前に切断されたかもしれないので必要です。
  3. 消費が正常に終了したかどうかを示す、GooglePlayからのレスポンスコードを示します。
  4. 消費が成功していれば、アプリでサービスを支給してください。


図2.消費リクエストのための基本的なシーケンス

ローカルキャッシング

GooglePlayクライアントは現在、デバイス上でローカルにアプリ内課金情報をキャッシュするので、更に頻繁に情報を照会するためにバージョン3のAPIを使えます(例えばgetPurchaseの呼び出しを介して)。
以前のAPIのバージョンとは異なり、バージョン3の多くの呼び出しは、GooglePlayとのネットワーク接続を介する代わりにキャッシュのルックアップを通して処理されます。
これはAPIの応答時間が格段に上がっています。

Except as noted, this content is licensed under Creative Commons Attribution 2.5.

2012年12月12日水曜日

In-App Billing Version 3 翻訳(Android Developer Blog編)

チームEGGの曽川です。

アプリ内課金バージョン3が出たようです。
非常に簡単になったらしいので、少しずつ訳していきたいと思います。

今回はAndroid Developer Blogの記事です。
※翻訳ミス・改善等ありましたら、お知らせください。


●Google Developers Blog
アプリ内課金はGooglePlay(当時はAndroidマーケット)で発表されて以来、長い道のりを歩んできました。
1年半後にGooglePlay上の収益トップのアプリの大半がアプリ内課金を使用し、
数千の開発者は、体験版、仮想グッズと同時に定期購読でアプリを収益化しています。

アプリ内課金は今日さらに拡大しており、さらに強力で柔軟に成功するアプリを構築し続けることができます。
バージョン3では次の新機能が導入されています。
・設計の改善:アプリの記述、デバッグ、メンテナンスが簡単になります。
以前は数百行のコードが必要でしたが、わずか50行で実装可能です。
・さらに堅牢なアーキテクチャによりトランザクションの失敗を少なくする。
・高速なAPIコール用のローカルキャッシング
・購入の消費の管理やサービス情報のクエリのを使用するような待望の機能

アプリ内課金のバージョン3はアプリ内アイテム、定期購読(2013年2月から)、無料期間のある定期購読を利用可能です。
GooglePlayの最新版を実行しているAndroid2.2以上の端末でサポートされています。(90%以上のアクティブな端末)

以前のリリースしていた非同期構成の4つの異なったアプリコンポーネントを必要とする代わりに、新しいバージョンのAPIは、開発者が同期的なリクエストとレスポンスへの応答を1つのActivityで行えることを可能にしています。
これらは全てわずか数行で達成されます。
実装のコスト削減は、新しく課金ソリューションを実装する開発者の絶好の機会です。

●より簡単になった実装
バックグラウンドサービスを介して非同期通知を行う以前のモデルと比較して、
新しいAPIは同期しており、アプリに購入結果をすぐに送ります。
これはアプリのライフサイクル内で購入結果に対する非同期の応答が不要になり、開発者がアプリ内アイテムを販売するために書かなければならないコードを大幅に簡単にします。

購入画面を起動するためには、APIから購入インテントを取得し、起動するだけです。

Bundle bundle = mService.getBuyIntent(3, "com.example.myapp", MY_SKU, ITEM_TYPE_INAPP, developerPayload);

PendingIntent pendingIntent = bundle.getParcelable(RESPONSE_BUY_INTENT);
if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) {
    // 購入フローの開始(GooglePlayのUIを表示します).
    // 結果はonActivityResult()を介して通知されます。.
    startIntentSenderForResult(pendingIntent, RC_BUY, new Intent(),
        Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
}

そして、購入結果をActivity#onActivityResultメソッドで処理します。
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == RC_BUY) {
        int responseCode = data.getIntExtra(RESPONSE_CODE);
        String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
        String signature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);

        // 購入情報をここで処理します。
        // (プレミアムアップグレードのような恒久的なアイテム[アップグレードによる効果の適用を意味する]
        // Xゴールドのコインのように消耗品アイテムのために
        // 一般的にアプリはここで購入の消費を開始します。)
    }
}

また、前バージョンからの違いは、全ての購入はGooglePlayで管理されます。
これは、購入したアイテムの所有をいつでも照会できることを意味します。
管理対象外のアイテムと同じ仕組みを実装するために、アプリケーションは購入の後直ちにアイテムを消費し、消費の成功と同時にアイテムのメリットを提供することができます。

●ローカルキャッシング
APIは、アプリが端末内のアプリ内課金情報を簡単に利用できるようにキャッシュするGooglePlayストアアプリの新機能を利用しています。
この機能を利用すると多くのAPIの呼び出しは、GooglePlayへ通信して接続する代わりにキャッシュのルックアップを通じてサービスを受けます。
これは大幅にAPIの応答速度を向上します。
例えば、アプリがこの呼び出しを利用して所有する項目を照会できます。

Bundle bundle = mService.getPurchases(3, mContext.getPackageName(), ITEM_TYPE_INAPP);
if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) {
    ArrayList mySkus, myPurchases, mySignatures;
    mySkus = bundle.getStringArrayList(RESPONSE_INAPP_ITEM_LIST);
    myPurchases = bundle.getStringArrayList(RESPONSE_INAPP_PURCHASE_DATA_LIST);
    mySignatures = bundle.getStringArrayList(RESPONSE_INAPP_PURCHASE_SIGNATURE_LIST);

    // アイテムの処理をここでおこなう
}

以前のバージョンでは、所有アイテムの紹介は負荷の高いサーバコールだったため、開発者は頻繁に行う事で落胆しました。
しかし、新しいバージョンのローカルキャッシュの実装はアプリは起動開始時にクエリを毎回実行でき、その後は必要に応じてクエリを実行できます。

●サービス情報
待望のAPIの機能が導入されています。
GooglePlayから直接商品情報を照会できます。
開発者はアイテムのタイトルや説明・価格をプログラム上で取得できます。
通貨変換や書式設定は必要ありません。
価格はロケールに応じてユーザの通貨で表示されフォーマットされます。

Bundle bundle = mService.getSkuDetails(3, "com.example.myapp", 
        ITEM_TYPE_INAPP, skus); // skus is a Bundle with the list of SKUs to query
if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) {
    List detailsList = bundle.getStringArrayList(RESPONSE_SKU_DETAILS_LIST);
    for (String details : detailsList) {
        // 詳細はSKU詳細のJSON内にあります。(説明、価格)
        // SKU:最小管理単位
    }
}

これは、例えば開発者がデベロッパーコンソールで価格を更新して、ユーザに表示するアプリのコードを変更する必要なしに、このAPIを呼び出して更新された価格を表示(特別プロモーションやセールなど)できます。

●サンプルアプリケーション
APIに加えて、アプリ内課金を実装例のサンプルアプリをリリースしています。
また、JSON文字列やBundleから変換/復元されたデータ構造、署名の検証、バックグラウンド処理を自動的に管理できるユーティリティ(開発者がアプリケーションのUIスレッドからAPIを直接呼び出すことができるようにするため)の一般的に書かれる定型的なコードを実装するヘルパークラスも含んでいます。
Googleは実装のプロセスを簡素化するために、アプリ内課金に慣れていない方はサンプルコードを活用する事を非常に推奨します。
サンプルアプリケーションはAndroid SDK Managerを介してダウンロードする事が出来ます。

●アプリの固有キー
アプリ内課金バージョン3で導入された他の変更に加えて、ライセンスとアプリ内課金のキーの管理方法さらに改善しました。
キーは現在「デベロッパーごと」の代わりに「アプリごと」に設定されています。
これはGoogle Play デベロッパーコンソールプレビュー上にある、各アプリの"Services & APIs"ページで利用可能です。
既存のアプリは現在のキーで動作し続けます。

●始めましょう
新しいAPIを使用してアプリ内課金を実装するために、アプリ内課金の更新されたドキュメントから始まり、アプリ内サービスの販売のトレーニングクラスに行きましょう。
アプリ内課金バージョン3を使用するためにGooglePlayデベロッパーコンソールプレビューを使用する必要があります。

2012年12月7日金曜日

Google Maps Android API v2の使い方

EGGの曽川です。

Android公開当初からあまりアップデートのなかったGoogleMapsでしたが、
とうとうアップデートされ、v2という形になりました。
非常に使いやすくなったのでぜひ使ってみてください。
以下では地図の表示までを行います。
地図の表示までの作業は主に、「APIキーの生成」、「Androidプロジェクトの作成」の2つです。


●APIキー生成手順
このAPIキーがあればGoogleMapsが表示されます。
・キーストアのSHA1をメモる
--------------------
キーストアはどこにあるのか?
デバッグの場合
Eclipseの設定から[Android->Build->Default debug keystore]で指定されているものがデバッグ用のキーストア。


リリースの場合
自分で作成して保管しているはず
--------------------
SHA1を取得するコマンドを実行する
keytool -list -v -keystore [キーストア]
※デバッグキーストアの場合はパスワード不要

・Google API Consoleからプロジェクト作成
https://code.google.com/apis/console/

・左側の[Services]から[Google Maps Android API v2]をONにする

・API Accessから[Create new Android key]を押す

・空欄に以下を入力
[メモったキーストアのSHA1;アプリのパッケージ名]
例:45:B5:E4:6F:36:AD:0A:98:94:B4:02:66:2B:12:17:F2:56:26:A0:E0;com.example

・作成するとAPIKeyが表示されているのでメモる。
APIキー準備完了


●デモ(東京タワーとスカイツリーを別の地図上に表示する)
・SDK ManagerでGoogle Play servicesをインストール


・Google Play servicesのライブラリをEclipseにインポート
{android-sdk}/extras/google/google_play_services/libproject/google-play-services_lib
※GoogleMaps等を使うために必要なライブラリ(isLibrary形式)
・プロジェクトのライブラリとして加える
・Androidプロジェクトを作成
※プロジェクトのパッケージ名はGoogleAPIで指定したものと合わせること
・以下のファイルを作る
public class MainActivity extends FragmentActivity {

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

}

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#EDEDED"
    android:orientation="vertical" >
    <fragment
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="4dp"
        android:layout_weight="1"
        class="com.google.android.gms.maps.SupportMapFragment"
        map:cameraTargetLat="35.658599"
        map:cameraTargetLng="139.745443"
        map:cameraTilt="60"
        map:cameraZoom="18" />
    <fragment
        android:id="@+id/map2"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="4dp"
        android:layout_weight="1"
        class="com.google.android.gms.maps.SupportMapFragment"
        map:cameraTargetLat="35.7105827"
        map:cameraTargetLng="139.811487"
        map:cameraTilt="60"
        map:cameraZoom="18" />
</LinearLayout>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mapdemo"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
    <!-- 必要なパーミッション群 -->
    <!-- 通信に必須 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 地図キャッシュ -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- GPS取得に必須 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <!-- GoogleMaps表示 -->
    <uses-permission android:name="com.example.mapdemo.permission.MAPS_RECEIVE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <permission
        android:name="com.example.mapdemo.permission.MAPS_RECEIVE"
        android:protectionLevel="signature" />
    <!-- OpenGLES2.0 -->
    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <!-- GoogleMapsを使用するAPIキーとか -->
        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="ここにAPIキーを入れる" />
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
(※com.example.mapdemoを自分のパッケージ名で置き換えること)
(※applicationのmata-dataを自分のAPIキーで置き換えること)

デモ画像

地図複数同時表示や角度・場所の指定もレイアウトだけでできました。
コレは便利ですね。