/* Copyright (c) 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.appsimobile.appsii.iab; import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.IntentSender.SendIntentException; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; import org.json.JSONException; /** * A specific iab-helper implementation. Gives access to the purchase function * if the iab-library */ public class IabPurchaseHelper extends BaseIabHelper { /** * Creates an instance. After creation, it will not yet be ready to use. You must perform * setup by calling {@link #startSetup} and wait for setup to complete. This constructor does * not * block and is safe to call from a UI thread. * * @param ctx Your application or Activity context. Needed to bind to the in-app billing * service. */ public IabPurchaseHelper(Context ctx) { super(ctx); } /** * Handles an activity result that's part of the purchase flow in in-app billing. If you * are calling {@link #launchPurchaseFlow}, then you must call this method from your * Activity's {@link android.app.Activity@onActivityResult} method. This method * MUST be called from the UI thread of the Activity. * * @param resultCode The resultCode as you received it. * @param data The data (Intent) as you received it. * * @return Returns true if the result was related to a purchase flow and was handled; * false if the result was not related to a purchase, in which case you should * handle it normally. */ public static int handleActivityResult( int resultCode, Intent data, OnIabPurchaseFinishedListener purchaseListener, String itemType, String devPayload) { if (data == null) { logError("Null data in IAB activity result."); return IABHELPER_BAD_RESPONSE; } int responseCode = getResponseCodeFromIntent(data); String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA); String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE); if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) { logDebug("Successful resultcode from purchase activity."); logDebug("Purchase data: " + purchaseData); logDebug("Data signature: " + dataSignature); logDebug("Extras: " + data.getExtras()); logDebug("Expected item type: " + itemType); if (purchaseData == null || dataSignature == null) { logError("BUG: either purchaseData or dataSignature is null."); logDebug("Extras: " + data.getExtras().toString()); return IABHELPER_UNKNOWN_ERROR; } Purchase purchase; try { purchase = new Purchase(itemType, purchaseData, dataSignature); String sku = purchase.getSku(); // Verify signature if (!Security.verifyPurchase(RSA_CODE, purchaseData, dataSignature)) { logError("Purchase signature verification FAILED for sku " + sku); return IABHELPER_VERIFICATION_FAILED; } logDebug("Purchase signature successfully verified."); if (!Security.verifyDeveloperPayload(purchase, devPayload)) { logError("Purchase payload verification FAILED for sku " + sku); return IABHELPER_DEVELOPER_PAYLOAD_FAILED; } logDebug("Developer payload successfully verified."); } catch (JSONException e) { logError("Failed to parse purchase data."); e.printStackTrace(); return IABHELPER_BAD_RESPONSE; } if (purchaseListener != null) { purchaseListener.onIabPurchaseSuccess(purchase); } return BILLING_RESPONSE_RESULT_OK; } else if (resultCode == Activity.RESULT_OK) { // result code was OK, but in-app billing response was not OK. logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode)); return responseCode; } else if (resultCode == Activity.RESULT_CANCELED) { logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode)); return IABHELPER_USER_CANCELLED; } else { logError("Purchase failed. Result code: " + Integer.toString(resultCode) + ". Response: " + getResponseDesc(responseCode)); return IABHELPER_UNKNOWN_PURCHASE_RESPONSE; } } /** * Returns whether subscriptions are supported. */ public boolean subscriptionsSupported() { return mSubscriptionsSupported; } public int launchInAppItemPurchaseFlow(Activity act, String sku, int requestCode, String developerPayload) { return launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, requestCode, developerPayload); } public int launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode, String extraData) { return launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, requestCode, extraData); } /** * Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase, * which will involve bringing up the Google Play screen. The calling activity will be paused * while * the user interacts with Google Play, and the result will be delivered via the activity's * {@link android.app.Activity#onActivityResult} method, at which point you must call * this object's {@link #handleActivityResult} method to continue the purchase flow. This method * MUST be called from the UI thread of the Activity. * * @param act The calling activity. * @param sku The sku of the item to purchase. * @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or * ITEM_TYPE_SUBS) * @param requestCode A request code (to differentiate from other responses -- * as in {@link android.app.Activity#startActivityForResult}). * @param developerPayload Extra data (developer payload), which will be returned with the * purchase data * when the purchase completes. This extra data will be permanently bound to that purchase * and will always be returned when the purchase is queried. */ public int launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode, String developerPayload) { checkSetupDone("launchPurchaseFlow"); if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) { return IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE; } try { logDebug("Constructing buy intent for " + sku + ", item type: " + itemType); Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, developerPayload); int response = getResponseCodeFromBundle(buyIntentBundle); if (response != BILLING_RESPONSE_RESULT_OK) { logError("Unable to buy item, Error response: " + getResponseDesc(response)); return response; } PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT); logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode); act.startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, new Intent(), 0, 0, 0); return BILLING_RESPONSE_RESULT_OK; } catch (SendIntentException e) { logError("SendIntentException while launching purchase flow for sku " + sku); e.printStackTrace(); return IABHELPER_SEND_INTENT_FAILED; } catch (RemoteException e) { logError("RemoteException while launching purchase flow for sku " + sku); e.printStackTrace(); return IABHELPER_REMOTE_EXCEPTION; } } /** * Consumes a given in-app product. Consuming can only be done on an item * that's owned, and as a result of consumption, the user will no longer own it. * This method may block or take long to return. * * @throws IabException if there is a problem during consumption. */ int consume(String sku, String token) { checkSetupDone("consume"); try { if (token == null || token.equals("")) { logError("Can't consume " + sku + ". No token."); return IABHELPER_MISSING_TOKEN; } logDebug("Consuming sku: " + sku + ", token: " + token); int response = mService.consumePurchase(3, mContext.getPackageName(), token); if (response == BILLING_RESPONSE_RESULT_OK) { logDebug("Successfully consumed sku: " + sku); } else { logDebug("Error consuming consuming sku " + sku + ". " + getResponseDesc(response)); } return response; } catch (RemoteException e) { Log.wtf("Appsii", "Remote exception while consuming. Sku: " + sku, e); return IABHELPER_REMOTE_EXCEPTION; } } /** * Callback that notifies when a purchase is finished. */ public interface OnIabPurchaseFinishedListener { /** * Called to notify that an in-app purchase finished. If the purchase was successful, * then the sku parameter specifies which item was purchased. If the purchase failed, * the sku and extraData parameters may or may not be null, depending on how far the * purchase * process went. * * @param info The purchase information (null if purchase failed) */ void onIabPurchaseSuccess(Purchase info); } }