前回、前々回とアプリ内課金の購入編とリストア編を取り上げてきました。
次はキャンセルの場合の処理です。WiFiCutterでは、購入したら別にキャンセルはできないのでいらんかなーと思っていたんですが、どうもクレジットカードがなんらかの原因で使えない場合はキャンセルが発生することがわかりました。
それが発生して教えてくれたのがtwitterで仲のよいフォロワーさんだったので助かりました。
やっぱりキャンセル処理も必要だなぁということで、実装してみました。例によって、テクブ本ベースです。まずは、静的なアプリ内課金テストを行ないました。Activityから注文処理を呼び出します。そのときに渡すアプリ内サービスIDを、テスト用のandroid.test.canceledにしておきます。
// paidはPreferencesオブジェクト
paid.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
// キャンセルのテスト
if (!mBillingService.requestPurchase(TEST_PURCHASE.CANCELED)) {
Toast.makeText(WifiCutterPreferences.this,
R.string.no_connected,
Toast.LENGTH_LONG).show();
}
return true;
}
これを実装すると、偽物の購入画面が現われます。偽物のカードを選択して、購入を押すと、擬似的に購入処理が走りますが、キャンセルの疑似処理なので、キャンセルが発生します。

購入を押すと…
上の画像のように、注文が失敗します。これでキャンセルのブロードキャストが発生します。
PURCHASE_STATE_CHANGEDでJSONが送られてくるのですが、ordersのJSONArrayの中の注文処理のpurchaseStateが1になっています。purchaseStateは、0が購入、1がキャンセル、2が購入の払い戻しのようです(2は試してないけど)
{
"nonce":******************************,
"orders":[
{
"notificationId":"android.test.canceled",
"orderId":"transactionId.android.test.canceled",
"packageName":"com.okolabo.android.wificutter",
"productId":"android.test.canceled",
"purchaseTime":1321622270724,
"purchaseState":1 // キャンセル
}
]
}
BillingService.javaの、purchaseStateChangedメソッドで、購入もリストアもキャンセルもやることになります。テクブ本のサンプルだと、vp.purchaseStateの値をチェックしていないので、なんでも購入処理になってしまうので、vp.purchaseStateを比較して処理を分けます。
また、テクブ本ではメソッドの最後にPurchaseController.purchaseComplete();を実行していましたが、購入時とキャンセル時で表示されるトーストを変えるために、最後ではなくorder毎の処理の際にメッセージを出すようにしました(まぁ自分のアプリの場合は1注文しかないからという算段ですが。あまりよくないかも)。それに合わせて、PurchaseStateを引数に持たせるようにコールバックメソッドを修正しています(別に見習わなくてもいい部分です)
あ、PurchaseStateはテクブ本のConsts.java内に定義されているenumです。
// BillingService.java
private void purchaseStateChanged(String signedData, String signature) {
ArrayList<Security.VerifiedPurchase> purchases;
// 受信したトランザクション情報の整合性を検証する
purchases = Security.verifyPurchase(signedData, signature);
if (purchases == null) {
return;
}
// トランザクション情報を処理する
ArrayList<String> notifyList = new ArrayList<String>();
for (VerifiedPurchase vp : purchases) {
if (vp.notificationId != null) {
// トランザクションごとに通知IDを保持する
notifyList.add(vp.notificationId);
}
// 制限を外す
if (vp.productId.equals("android.test.canceled")) {
if (vp.purchaseState == PurchaseState.PURCHASED) {
Log.d(TAG, "purchaseState == purchase");
purchase();
} else if (vp.purchaseState == PurchaseState.CANCELED) {
Log.d(TAG, "purchaseState == cancel");
purchaseCancel();
} else if (vp.purchaseState == PurchaseState.REFUNDED) {
Log.d(TAG, "purchaseState == refunded");
purchaseCancel();
}
// 画面の更新とトーストによる通知を行なう
PurchaseController.purchaseComplete(vp.purchaseState);
}
}
// トランザクション情報を取得できた場合は、CONFIRM_NOTIFICATIONSリクエストを送信する
if (!notifyList.isEmpty()) {
String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
confirmNotifications(notifyIds);
}
}
purchaseStateの値を比較して処理を変えています。purchaseメソッドとpurchaseCancelメソッドはBillingService.java内に定義した購入処理とキャンセル処理です。これで、購入、キャンセル、リストアとある程度はできるようになるんじゃないかと思います。
一通り動作を確認したら、Activityの購入処理で渡していたアプリ内サービスIDをandroid.test.canceledから正規のIDに変更しておきましょう。これで、あとはマーケットに載せてテストアカウントでテストしてみて、大丈夫だったらOKです。
アプリ内課金のテスト方法についても説明してありますので、よかったらテクブ本を手に取ってみてください!(^O^)

