/** * Copyright (C) 2013 Gundog Studios LLC. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.godsandtowers.billing.amazon; import java.util.Map; import android.content.Context; import android.os.AsyncTask; 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.PurchaseResponse; import com.amazon.inapp.purchasing.PurchaseUpdatesResponse; import com.amazon.inapp.purchasing.PurchasingManager; import com.amazon.inapp.purchasing.Receipt; import com.gundogstudios.modules.Modules; /** * Purchasing Observer will be called on by the Purchasing Manager asynchronously. Since the methods on the UI thread of * the application, all fulfillment logic is done via an AsyncTask. This way, any intensive processes will not hang the * UI thread and cause the application to become unresponsive. */ public class PurchaseObserver extends BasePurchasingObserver { private static final String TAG = "Amazon-IAP"; private AmazonPurchaser purchaser; /** * Creates new instance of the ButtonClickerObserver class. * * @param buttonClickerActivity * Activity context */ public PurchaseObserver(Context context, AmazonPurchaser purchaser) { super(context); this.purchaser = purchaser; } /** * Invoked once the observer is registered with the Puchasing Manager If the boolean is false, the application is * receiving responses from the SDK Tester. If the boolean is true, the application is live in production. * * @param isSandboxMode * Boolean value that shows if the app is live or not. */ @Override public void onSdkAvailable(final boolean isSandboxMode) { Modules.LOG.verbose(TAG, "onSdkAvailable recieved: Response -" + isSandboxMode); PurchasingManager.initiateGetUserIdRequest(); } /** * Invoked once the call from initiateGetUserIdRequest is completed. On a successful response, a response object is * passed which contains the request id, request status, and the userid generated for your application. * * @param getUserIdResponse * Response object containing the UserID */ @Override public void onGetUserIdResponse(final GetUserIdResponse getUserIdResponse) { Modules.LOG.verbose(TAG, "onGetUserIdResponse recieved: Response -" + getUserIdResponse); Modules.LOG.verbose(TAG, "RequestId:" + getUserIdResponse.getRequestId()); Modules.LOG.verbose(TAG, "IdRequestStatus:" + getUserIdResponse.getUserIdRequestStatus()); new GetUserIdAsyncTask().execute(getUserIdResponse); } /** * Invoked once the call from initiateItemDataRequest is completed. On a successful response, a response object is * passed which contains the request id, request status, and a set of item data for the requested skus. Items that * have been suppressed or are unavailable will be returned in a set of unavailable skus. * * @param itemDataResponse * Response object containing a set of purchasable/non-purchasable items */ @Override public void onItemDataResponse(final ItemDataResponse itemDataResponse) { Modules.LOG.verbose(TAG, "onItemDataResponse recieved"); Modules.LOG.verbose(TAG, "ItemDataRequestStatus" + itemDataResponse.getItemDataRequestStatus()); Modules.LOG.verbose(TAG, "ItemDataRequestId" + itemDataResponse.getRequestId()); new ItemDataAsyncTask().execute(itemDataResponse); } /** * Is invoked once the call from initiatePurchaseRequest is completed. On a successful response, a response object * is passed which contains the request id, request status, and the receipt of the purchase. * * @param purchaseResponse * Response object containing a receipt of a purchase */ @Override public void onPurchaseResponse(final PurchaseResponse purchaseResponse) { Modules.LOG.verbose(TAG, "onPurchaseResponse recieved"); Modules.LOG.verbose(TAG, "PurchaseRequestStatus:" + purchaseResponse.getPurchaseRequestStatus()); new PurchaseAsyncTask().execute(purchaseResponse); } /** * Is invoked once the call from initiatePurchaseUpdatesRequest is completed. On a successful response, a response * object is passed which contains the request id, request status, a set of previously purchased receipts, a set of * revoked skus, and the next offset if applicable. If a user downloads your application to another device, this * call is used to sync up this device with all the user's purchases. * * @param purchaseUpdatesResponse * Response object containing the user's recent purchases. */ @Override public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse purchaseUpdatesResponse) { Modules.LOG.verbose(TAG, "onPurchaseUpdatesRecived recieved: Response -" + purchaseUpdatesResponse); Modules.LOG.verbose(TAG, "PurchaseUpdatesRequestStatus:" + purchaseUpdatesResponse.getPurchaseUpdatesRequestStatus()); Modules.LOG.verbose(TAG, "RequestID:" + purchaseUpdatesResponse.getRequestId()); new PurchaseUpdatesAsyncTask().execute(purchaseUpdatesResponse); } /* * Started when the Observer receives a GetUserIdResponse. The Shared Preferences file for the returned user id is * accessed. */ private class GetUserIdAsyncTask extends AsyncTask<GetUserIdResponse, Void, Boolean> { @Override protected Boolean doInBackground(GetUserIdResponse... params) { GetUserIdResponse getUserIdResponse = params[0]; if (getUserIdResponse.getUserIdRequestStatus() == GetUserIdRequestStatus.SUCCESSFUL) { String userId = getUserIdResponse.getUserId(); Modules.LOG.verbose(TAG, "onGetUserIdResponse: Got user ID: " + userId); return true; } else { Modules.LOG.verbose(TAG, "onGetUserIdResponse: Unable to get user ID."); return false; } } @Override protected void onPostExecute(final Boolean result) { super.onPostExecute(result); if (result) { // Call initiatePurchaseUpdatesRequest for the returned user to sync purchases that are not yet // fulfilled. } } } /* * Started when the observer receives an Item Data Response. Takes the items and display them in the logs. You can * use this information to display an in game storefront for your IAP items. */ private class ItemDataAsyncTask extends AsyncTask<ItemDataResponse, Void, Void> { @Override protected Void doInBackground(final ItemDataResponse... params) { final ItemDataResponse itemDataResponse = params[0]; switch (itemDataResponse.getItemDataRequestStatus()) { case SUCCESSFUL_WITH_UNAVAILABLE_SKUS: // Skus that you can not purchase will be here. for (String s : itemDataResponse.getUnavailableSkus()) { Modules.LOG.verbose(TAG, "Unavailable SKU:" + s); } case SUCCESSFUL: // Information you'll want to display about your IAP items is here // In this example we'll simply log them. Map<String, Item> items = itemDataResponse.getItemData(); for (String key : items.keySet()) { Item i = items.get(key); Modules.LOG.verbose(TAG, String.format( "Item: %s\n Type: %s\n SKU: %s\n Price: %s\n Description: %s\n", i.getTitle(), i.getItemType(), i.getSku(), i.getPrice(), i.getDescription())); } break; case FAILED: // On failed responses will fail gracefully. break; } return null; } } /* * Started when the observer receives a Purchase Response Once the AsyncTask returns successfully, the UI is * updated. */ private class PurchaseAsyncTask extends AsyncTask<PurchaseResponse, Void, Boolean> { @Override protected Boolean doInBackground(final PurchaseResponse... params) { try { PurchaseResponse purchaseResponse = params[0]; Receipt receipt = purchaseResponse.getReceipt(); if (receipt == null) { return false; } switch (purchaseResponse.getPurchaseRequestStatus()) { case SUCCESSFUL: purchaser.purchased(receipt.getSku(), ""); return true; case ALREADY_ENTITLED: return true; case FAILED: /* * If the purchase failed for some reason, (The customer canceled the order, or some other * extraneous circumstance happens) the application ignores the request and logs the failure. */ Modules.LOG.verbose(TAG, "Failed purchase for request " + receipt.getSku() + " failed for user " + purchaseResponse.getUserId()); return false; case INVALID_SKU: /* * If the sku that was purchased was invalid, the application ignores the request and logs the * failure. This can happen when there is a sku mismatch between what is sent from the application * and what currently exists on the dev portal. */ Modules.LOG.verbose(TAG, "Invalid Sku for request " + receipt.getSku()); return false; } } catch (Exception e) { Modules.REPORTER.report(e); Modules.LOG.error(TAG, "Error in PurchaseAsyncTask: " + e.toString()); } return false; } @Override protected void onPostExecute(final Boolean success) { super.onPostExecute(success); } } /* * Started when the observer receives a Purchase Updates Response Once the AsyncTask returns successfully, we'll * update the UI. */ private class PurchaseUpdatesAsyncTask extends AsyncTask<PurchaseUpdatesResponse, Void, Boolean> { @Override protected Boolean doInBackground(final PurchaseUpdatesResponse... params) { final PurchaseUpdatesResponse purchaseUpdatesResponse = params[0]; switch (purchaseUpdatesResponse.getPurchaseUpdatesRequestStatus()) { case SUCCESSFUL: for (final Receipt receipt : purchaseUpdatesResponse.getReceipts()) { final String sku = receipt.getSku(); switch (receipt.getItemType()) { case ENTITLED: purchaser.purchased(sku, ""); break; case CONSUMABLE: case SUBSCRIPTION: default: break; } Modules.LOG.verbose(TAG, "Invalid Sku for request " + receipt.getSku()); } return true; case FAILED: return false; } return false; } @Override protected void onPostExecute(final Boolean success) { super.onPostExecute(success); } } }