/* Swisscom Safe Connect Copyright (C) 2014 Swisscom 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.swisscom.safeconnect.billing; import android.app.Activity; import android.content.Intent; import android.os.AsyncTask; import com.swisscom.safeconnect.BuildConfig; import com.swisscom.safeconnect.backend.BackendConnector; import com.swisscom.safeconnect.model.PlumberPurchaseResponse; import com.swisscom.safeconnect.model.PlumberResponse; import com.swisscom.safeconnect.model.PlumberStatus; import com.swisscom.safeconnect.model.PlumberSubscriptionResponse; import com.swisscom.safeconnect.model.Subscription; import com.swisscom.safeconnect.utils.Config; import java.util.ArrayList; import java.util.List; /** * Created by vadim on 30.09.14. */ public class PurchaseManager { private static final String DEV_KEY = ""; // (arbitrary) request code for the purchase flow private static final int RC_REQUEST = 0; /** * available subscriptions. Name of the subscription should coincide with the SKU * name in the Developer Console */ private Subscription[] subscriptions; /** * helper object */ private IabHelper helper; /** * our backend */ private BackendConnector backend; private Activity activity; private Inventory inventory; public static interface Callback<T> { public void onRequestCompleted(T result); public void onRequestFailed(int errorCode); } private class ConsumeDoneListener implements IabHelper.OnConsumeFinishedListener { private Callback<Subscription> callback; private Subscription subscription; public ConsumeDoneListener(Subscription subscription, Callback<Subscription> callback) { this.callback = callback; this.subscription = subscription; } @Override public void onConsumeFinished(final Purchase purchase, IabResult result) { if (helper == null) return; if (result.isSuccess()) { callback.onRequestCompleted(subscription); } else { callback.onRequestFailed(0); } } } private void finishPurchase(final String phone, final Purchase purchase, final Callback<Subscription> callback) { backend.finishPurchase(phone, purchase.getDeveloperPayload(), purchase.getSignature(), purchase.getOriginalJson(), new BackendConnector.ResponseCallback<PlumberSubscriptionResponse>() { @Override public void onRequestComplete(int statusCode, PlumberSubscriptionResponse response) { if (statusCode == 200 && response.getStatus() == PlumberStatus.SUCCESS) { if (callback != null) { Subscription s = new Subscription(response); if (purchase.getSku().equals(s.getSku())) { helper.consumeAsync(purchase, new ConsumeDoneListener(s, callback)); } else { callback.onRequestFailed(0); } } } else { if (callback != null) { callback.onRequestFailed(0); } } } }); } public PurchaseManager(Activity activity, Subscription[] s) { this.activity = activity; subscriptions = s; backend = new BackendConnector(activity); } /** * using the available Plumber subscriptions, queries Google Play for additional data * about them, such as prices * @param callback */ public void queryInventory(final Callback<Inventory> callback) { if (inventory != null) callback.onRequestCompleted(inventory); List<String> skus = new ArrayList<String>(subscriptions.length); for (Subscription s: subscriptions) { skus.add(s.getSku()); } helper.queryInventoryAsync(true, skus, new IabHelper.QueryInventoryFinishedListener() { @Override public void onQueryInventoryFinished(IabResult result, Inventory inv) { // ignore signature verification failures, we are anyway check it on a server if (result.getResponse() != IabHelper.IABHELPER_VERIFICATION_FAILED && result.getResponse() != IabHelper.BILLING_RESPONSE_RESULT_OK) { if (callback != null) callback.onRequestFailed(0); } else { callback.onRequestCompleted(inv); inventory = inv; } } }); } /** * call to initialize the billing */ public void init(final Callback<PlumberResponse> callback) { helper = new IabHelper(activity, DEV_KEY); helper.enableDebugLogging(BuildConfig.DEBUG, Config.TAG); helper.startSetup(new IabHelper.OnIabSetupFinishedListener() { public void onIabSetupFinished(IabResult result) { if (!result.isSuccess()) { callback.onRequestFailed(0); return; } ensureSubscriptionsConsumed(new Callback<Subscription>() { @Override public void onRequestCompleted(Subscription result) { callback.onRequestCompleted(null); } @Override public void onRequestFailed(int errorCode) { callback.onRequestFailed(0); } }); } }); } /** * gets the owned subscription and consumes (activates) it */ public void ensureSubscriptionsConsumed(final Callback<Subscription> callback) { // Listener that's called when we finish querying the items and subscriptions we own IabHelper.QueryInventoryFinishedListener gotInventoryListener = new IabHelper.QueryInventoryFinishedListener() { public void onQueryInventoryFinished(IabResult result, Inventory inventory) { boolean consuming = false; // Have we been disposed of in the meantime? If so, quit. if (helper == null) return; // ignore signature verification failures, we are anyway check it on a server if (result.getResponse() != IabHelper.IABHELPER_VERIFICATION_FAILED && result.getResponse() != IabHelper.BILLING_RESPONSE_RESULT_OK) { if (callback != null) callback.onRequestFailed(0); return; } // check what subscription plan we currently own for (Subscription s: subscriptions) { Purchase p = inventory.getPurchase(s.getSku()); if (p != null) { finishPurchase(Config.getInstance().getPhoneNumber(), p, callback); consuming = true; } } if (!consuming) callback.onRequestCompleted(null); } }; helper.queryInventoryAsync(gotInventoryListener); } /** * purchases the subscription with a specified name * @param subscription */ public void purchaseSubscription(final Subscription subscription, final Callback<Subscription> callback) { final IabHelper.OnIabPurchaseFinishedListener purchaseDoneListener = new IabHelper.OnIabPurchaseFinishedListener() { public void onIabPurchaseFinished(IabResult result, final Purchase purchase) { // if we were disposed of in the meantime, quit. if (helper == null) return; if (result.isFailure() ) { callback.onRequestFailed(result.getResponse()); return; } // need async because of verification routine new AsyncTask<Void, Void, Void>() { private boolean verified = false; @Override protected Void doInBackground(Void... params) { verified = verifyDeveloperPayload(subscription.getId(), purchase); return null; } @Override protected void onPostExecute(Void aVoid) { if (!verified) { callback.onRequestFailed(0); return; } finishPurchase(Config.getInstance().getPhoneNumber(), purchase, callback); } }.execute(); } }; if (subscription == null) { callback.onRequestFailed(0); } else { backend.startPurchase(Config.getInstance().getPhoneNumber(), subscription.getId(), new BackendConnector.ResponseCallback<PlumberPurchaseResponse>() { @Override public void onRequestComplete(int statusCode, PlumberPurchaseResponse response) { if (statusCode == 200 && response.getStatus() == PlumberStatus.SUCCESS) { helper.launchPurchaseFlow(activity, subscription.getSku(), RC_REQUEST, purchaseDoneListener, response.getUid()); } else { callback.onRequestFailed(0); } } }); } } public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { if (helper != null) { return helper.handleActivityResult(requestCode, resultCode, data); } else { return false; } } /** * Verifies the developer payload of a purchase. Do not call on UI thread! */ private boolean verifyDeveloperPayload(int subscriptionId, Purchase p) { String payload = p.getDeveloperPayload(); // find subscription id return backend.verifyPurchaseIdSync(Config.getInstance().getPhoneNumber(), payload, subscriptionId).getStatus() == PlumberStatus.SUCCESS; } }