Java tutorial
// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * * Modifications: Alex Disler (alexdisler.com) * github.com/alexdisler/cordova-plugin-inapppurchase * */ package com.alexdisler.inapppurchases; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.concurrent.atomic.AtomicInteger; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.apache.cordova.CordovaInterface; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaWebView; import com.alexdisler.inapppurchases.IabHelper.OnConsumeFinishedListener; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.util.Log; public class InAppBillingV3 extends CordovaPlugin { protected static final String TAG = "google.payments"; public static final int OK = 0; public static final int INVALID_ARGUMENTS = -1; public static final int UNABLE_TO_INITIALIZE = -2; public static final int BILLING_NOT_INITIALIZED = -3; public static final int UNKNOWN_ERROR = -4; public static final int USER_CANCELLED = -5; public static final int BAD_RESPONSE_FROM_SERVER = -6; public static final int VERIFICATION_FAILED = -7; public static final int ITEM_UNAVAILABLE = -8; public static final int ITEM_ALREADY_OWNED = -9; public static final int ITEM_NOT_OWNED = -10; public static final int CONSUME_FAILED = -11; public static final int PURCHASE_PURCHASED = 0; public static final int PURCHASE_CANCELLED = 1; public static final int PURCHASE_REFUNDED = 2; private IabHelper iabHelper = null; boolean billingInitialized = false; AtomicInteger orderSerial = new AtomicInteger(0); private JSONObject manifestObject = null; private JSONObject getManifestContents() { if (manifestObject != null) return manifestObject; Context context = this.cordova.getActivity(); InputStream is; try { is = context.getAssets().open("www/manifest.json"); Scanner s = new Scanner(is).useDelimiter("\\A"); String manifestString = s.hasNext() ? s.next() : ""; Log.d(TAG, "manifest:" + manifestString); manifestObject = new JSONObject(manifestString); } catch (IOException e) { Log.d(TAG, "Unable to read manifest file:" + e.toString()); manifestObject = null; } catch (JSONException e) { Log.d(TAG, "Unable to parse manifest file:" + e.toString()); manifestObject = null; } return manifestObject; } protected String getBase64EncodedPublicKey() { JSONObject manifestObject = getManifestContents(); if (manifestObject != null) { return manifestObject.optString("play_store_key"); } return null; } protected boolean shouldSkipPurchaseVerification() { JSONObject manifestObject = getManifestContents(); if (manifestObject != null) { return manifestObject.optBoolean("skip_purchase_verification"); } return false; } protected boolean initializeBillingHelper() { if (iabHelper != null) { Log.d(TAG, "Billing already initialized"); return true; } Context context = this.cordova.getActivity(); String base64EncodedPublicKey = getBase64EncodedPublicKey(); boolean skipPurchaseVerification = shouldSkipPurchaseVerification(); if (base64EncodedPublicKey != null) { iabHelper = new IabHelper(context, base64EncodedPublicKey); iabHelper.setSkipPurchaseVerification(skipPurchaseVerification); billingInitialized = false; return true; } Log.d(TAG, "Unable to initialize billing"); return false; } @Override public void initialize(CordovaInterface cordova, CordovaWebView webView) { super.initialize(cordova, webView); initializeBillingHelper(); } protected JSONObject makeError(String message) { return makeError(message, null, null, null); } protected JSONObject makeError(String message, Integer resultCode) { return makeError(message, resultCode, null, null); } protected JSONObject makeError(String message, Integer resultCode, IabResult result) { return makeError(message, resultCode, result.getMessage(), result.getResponse()); } protected JSONObject makeError(String message, Integer resultCode, String text, Integer response) { if (message != null) { Log.d(TAG, "Error: " + message); } JSONObject error = new JSONObject(); try { if (resultCode != null) { error.put("code", (int) resultCode); } if (message != null) { error.put("message", message); } if (text != null) { error.put("text", text); } if (response != null) { error.put("response", response); } } catch (JSONException e) { } return error; } @Override public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) { Log.d(TAG, "executing on android"); if ("init".equals(action)) { return init(args, callbackContext); } else if ("buy".equals(action)) { return buy(args, callbackContext); } else if ("subscribe".equals(action)) { return subscribe(args, callbackContext); } else if ("consumePurchase".equals(action)) { return consumePurchase(args, callbackContext); } else if ("getSkuDetails".equals(action)) { return getSkuDetails(args, callbackContext); } else if ("restorePurchases".equals(action)) { return restorePurchases(args, callbackContext); } return false; } protected boolean init(final JSONArray args, final CallbackContext callbackContext) { if (billingInitialized == true) { Log.d(TAG, "Billing already initialized"); callbackContext.success(); } else if (iabHelper == null) { callbackContext.error(makeError("Billing cannot be initialized", UNABLE_TO_INITIALIZE)); } else { iabHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { public void onIabSetupFinished(IabResult result) { if (!result.isSuccess()) { callbackContext.error(makeError("Unable to initialize billing: " + result.toString(), UNABLE_TO_INITIALIZE, result)); } else { Log.d(TAG, "Billing initialized"); billingInitialized = true; callbackContext.success(); } } }); } return true; } protected boolean runPayment(final JSONArray args, final CallbackContext callbackContext, boolean subscribe) { final String sku; try { sku = args.getString(0); } catch (JSONException e) { callbackContext.error(makeError("Invalid SKU", INVALID_ARGUMENTS)); return false; } if (iabHelper == null || !billingInitialized) { callbackContext.error(makeError("Billing is not initialized", BILLING_NOT_INITIALIZED)); return false; } final Activity cordovaActivity = this.cordova.getActivity(); int newOrder = orderSerial.getAndIncrement(); this.cordova.setActivityResultCallback(this); IabHelper.OnIabPurchaseFinishedListener oipfl = new IabHelper.OnIabPurchaseFinishedListener() { public void onIabPurchaseFinished(IabResult result, Purchase purchase) { if (result.isFailure()) { int response = result.getResponse(); if (response == IabHelper.IABHELPER_BAD_RESPONSE || response == IabHelper.IABHELPER_UNKNOWN_ERROR) { callbackContext .error(makeError("Could not complete purchase", BAD_RESPONSE_FROM_SERVER, result)); } else if (response == IabHelper.IABHELPER_VERIFICATION_FAILED) { callbackContext .error(makeError("Could not complete purchase", BAD_RESPONSE_FROM_SERVER, result)); } else if (response == IabHelper.IABHELPER_USER_CANCELLED) { callbackContext.error(makeError("Purchase Cancelled", USER_CANCELLED, result)); } else if (response == IabHelper.BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED) { callbackContext.error(makeError("Item already owned", ITEM_ALREADY_OWNED, result)); } else { callbackContext .error(makeError("Error completing purchase: " + response, UNKNOWN_ERROR, result)); } } else { try { JSONObject pluginResponse = new JSONObject(); pluginResponse.put("orderId", purchase.getOrderId()); pluginResponse.put("packageName", purchase.getPackageName()); pluginResponse.put("productId", purchase.getSku()); pluginResponse.put("purchaseTime", purchase.getPurchaseTime()); pluginResponse.put("purchaseState", purchase.getPurchaseState()); pluginResponse.put("purchaseToken", purchase.getToken()); pluginResponse.put("signature", purchase.getSignature()); pluginResponse.put("type", purchase.getItemType()); pluginResponse.put("receipt", purchase.getOriginalJson()); callbackContext.success(pluginResponse); } catch (JSONException e) { callbackContext.error("Purchase succeeded but success handler failed"); } } } }; if (subscribe) { iabHelper.launchSubscriptionPurchaseFlow(cordovaActivity, sku, newOrder, oipfl, ""); } else { iabHelper.launchPurchaseFlow(cordovaActivity, sku, newOrder, oipfl, ""); } return true; } protected boolean subscribe(final JSONArray args, final CallbackContext callbackContext) { return runPayment(args, callbackContext, true); } protected boolean buy(final JSONArray args, final CallbackContext callbackContext) { return runPayment(args, callbackContext, false); } protected boolean consumePurchase(final JSONArray args, final CallbackContext callbackContext) { final Purchase purchase; try { String type = args.getString(0); String receipt = args.getString(1); String signature = args.getString(2); purchase = new Purchase(type, receipt, signature); } catch (JSONException e) { callbackContext.error(makeError("Unable to parse purchase token", INVALID_ARGUMENTS)); return false; } if (purchase == null) { callbackContext.error(makeError("Unrecognized purchase token", INVALID_ARGUMENTS)); return false; } if (iabHelper == null || !billingInitialized) { callbackContext.error(makeError("Billing is not initialized", BILLING_NOT_INITIALIZED)); return false; } iabHelper.consumeAsync(purchase, new OnConsumeFinishedListener() { public void onConsumeFinished(Purchase purchase, IabResult result) { if (result.isFailure()) { int response = result.getResponse(); if (response == IabHelper.BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED) { callbackContext.error(makeError("Error consuming purchase", ITEM_NOT_OWNED, result)); } else { callbackContext.error(makeError("Error consuming purchase", CONSUME_FAILED, result)); } } else { try { JSONObject pluginResponse = new JSONObject(); pluginResponse.put("transactionId", purchase.getOrderId()); pluginResponse.put("productId", purchase.getSku()); pluginResponse.put("token", purchase.getToken()); callbackContext.success(pluginResponse); } catch (JSONException e) { callbackContext.error("Consume succeeded but success handler failed"); } } } }); return true; } protected boolean getSkuDetails(final JSONArray args, final CallbackContext callbackContext) { final List<String> moreSkus = new ArrayList<String>(); try { for (int i = 0; i < args.length(); i++) { moreSkus.add(args.getString(i)); Log.d(TAG, "get sku:" + args.getString(i)); } } catch (JSONException e) { callbackContext.error(makeError("Invalid SKU", INVALID_ARGUMENTS)); return false; } if (iabHelper == null || !billingInitialized) { callbackContext.error(makeError("Billing is not initialized", BILLING_NOT_INITIALIZED)); return false; } iabHelper.queryInventoryAsync(true, moreSkus, new IabHelper.QueryInventoryFinishedListener() { public void onQueryInventoryFinished(IabResult result, Inventory inventory) { if (result.isFailure()) { callbackContext.error("Error retrieving SKU details"); return; } JSONArray response = new JSONArray(); try { for (String sku : moreSkus) { SkuDetails skuDetails = inventory.getSkuDetails(sku); if (skuDetails != null) { JSONObject detailsJson = new JSONObject(); detailsJson.put("productId", skuDetails.getSku()); detailsJson.put("title", skuDetails.getTitle()); detailsJson.put("description", skuDetails.getDescription()); detailsJson.put("price", skuDetails.getPrice()); detailsJson.put("type", skuDetails.getType()); response.put(detailsJson); } } } catch (JSONException e) { callbackContext.error(e.getMessage()); } callbackContext.success(response); } }); return true; } protected boolean restorePurchases(final JSONArray args, final CallbackContext callbackContext) { if (iabHelper == null || !billingInitialized) { callbackContext.error(makeError("Billing is not initialized", BILLING_NOT_INITIALIZED)); } else { iabHelper.queryInventoryAsync(new IabHelper.QueryInventoryFinishedListener() { public void onQueryInventoryFinished(IabResult result, Inventory inventory) { if (result.isFailure()) { callbackContext.error("Error retrieving purchase details"); return; } JSONArray response = new JSONArray(); try { for (Purchase purchase : inventory.getAllPurchases()) { if (purchase != null) { JSONObject detailsJson = new JSONObject(); detailsJson.put("orderId", purchase.getOrderId()); detailsJson.put("packageName", purchase.getPackageName()); detailsJson.put("productId", purchase.getSku()); detailsJson.put("purchaseTime", purchase.getPurchaseTime()); detailsJson.put("purchaseState", purchase.getPurchaseState()); detailsJson.put("purchaseToken", purchase.getToken()); detailsJson.put("signature", purchase.getSignature()); detailsJson.put("type", purchase.getItemType()); detailsJson.put("receipt", purchase.getOriginalJson()); response.put(detailsJson); } } } catch (JSONException e) { callbackContext.error(e.getMessage()); } callbackContext.success(response); } }); } return true; } @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { if (!iabHelper.handleActivityResult(requestCode, resultCode, intent)) { super.onActivityResult(requestCode, resultCode, intent); } } @Override public void onDestroy() { if (iabHelper != null) iabHelper.dispose(); iabHelper = null; billingInitialized = false; } }