Android Open Source - gdx-pay Purchase Manager Android Open I A B






From Project

Back to project page gdx-pay.

License

The source code is released under:

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUC...

If you think the Android project gdx-pay listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*******************************************************************************
 * Copyright 2011 See AUTHORS file.//from w w w .ja va  2  s.  co m
 *
 * 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.badlogic.gdx.pay.android.openiab;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.onepf.oms.OpenIabHelper;
import org.onepf.oms.SkuManager;
import org.onepf.oms.appstore.googleUtils.IabHelper;
import org.onepf.oms.appstore.googleUtils.IabResult;
import org.onepf.oms.appstore.googleUtils.Inventory;
import org.onepf.oms.appstore.googleUtils.Purchase;
import org.onepf.oms.appstore.googleUtils.SkuDetails;

import android.app.Activity;
import android.content.Intent;
import android.util.Log;

import com.badlogic.gdx.pay.Information;
import com.badlogic.gdx.pay.Offer;
import com.badlogic.gdx.pay.OfferType;
import com.badlogic.gdx.pay.PurchaseManager;
import com.badlogic.gdx.pay.PurchaseManagerConfig;
import com.badlogic.gdx.pay.PurchaseObserver;
import com.badlogic.gdx.pay.Transaction;

/** The purchase manager implementation for Android via <a href="http://www.onepf.org/openiab">OpenIAB</a>. Supported stores
 * include:
 * <ul>
 * <li>Google Play
 * <li>Amazon
 * <li>Samsung Apps
 * <li>Nokia
 * <li>Open Store
 * <li>SlideME
 * <li>Aptoide
 * </ul>
 * Please note that Fortumo is not included.
 * <p>
 * Include the gdx-pay-android-openiab.jar for this to work (plus gdx-pay-android.jar). Also update the "uses-permission" settings
 * in AndroidManifest.xml and your proguard settings.
 * 
 * @author noblemaster */
public class PurchaseManagerAndroidOpenIAB implements PurchaseManager {

  /** Debug tag for logging. */
  private static final String TAG = "GdxPay/OpenIAB";

  /** Our Android activity. */
  private Activity activity;
  /** The request code to use for onActivityResult (arbitrary chosen). */
  private int requestCode;

  /** The registered observer. */
  PurchaseObserver observer;
  /** The configuration. */
  PurchaseManagerConfig config;

  /** Our OpenIAB helper class through which we get access to the various markets. */
  OpenIabHelper helper;
  /** The inventory with all the prices/details. */
  Inventory inventory;

  public PurchaseManagerAndroidOpenIAB (Activity activity, int requestCode) {
    this.activity = activity;

    // the request code for onActivityResult
    this.requestCode = requestCode;
  }

  @Override
  public String storeName () {
    // return the correct store name
    if (helper != null) {
      return storeNameFromOpenIAB(helper.getConnectedAppstoreName());
    } else {
      // create temporary helper to retrieve store name (not very clean though!)
      return null;
    }
  }

  private String storeNameFromOpenIAB (String storeNameOpenIAB) {
    if (storeNameOpenIAB == null) {
      return null;
    } else {
      if (storeNameOpenIAB.equals(OpenIabHelper.NAME_GOOGLE)) {
        return PurchaseManagerConfig.STORE_NAME_ANDROID_GOOGLE;
      } else if (storeNameOpenIAB.equals(OpenIabHelper.NAME_AMAZON)) {
        return PurchaseManagerConfig.STORE_NAME_ANDROID_AMAZON;
      } else if (storeNameOpenIAB.equals(OpenIabHelper.NAME_SAMSUNG)) {
        return PurchaseManagerConfig.STORE_NAME_ANDROID_SAMSUNG;
      } else if (storeNameOpenIAB.equals(OpenIabHelper.NAME_NOKIA)) {
        return PurchaseManagerConfig.STORE_NAME_ANDROID_NOKIA;
      } else if (storeNameOpenIAB.equals(OpenIabHelper.NAME_SLIDEME)) {
        return PurchaseManagerConfig.STORE_NAME_ANDROID_SLIDEME;
      } else if (storeNameOpenIAB.equals(OpenIabHelper.NAME_APTOIDE)) {
        return PurchaseManagerConfig.STORE_NAME_ANDROID_APTOIDE;
      } else if (storeNameOpenIAB.equals(OpenIabHelper.NAME_APPLAND)) {
        return PurchaseManagerConfig.STORE_NAME_ANDROID_APPLAND;
      } else if (storeNameOpenIAB.equals(OpenIabHelper.NAME_YANDEX)) {
        return PurchaseManagerConfig.STORE_NAME_ANDROID_YANDEX;
      } else {
        // we should get here: the correct store should always be mapped!
        Log.e(TAG, "Store name could not be mapped: " + storeNameOpenIAB);
        return storeNameOpenIAB;
      }
    }
  }

  private String storeNameToOpenIAB (String storeName) {
    if (storeName == null) {
      return null;
    } else {
      if (storeName.equals(PurchaseManagerConfig.STORE_NAME_ANDROID_GOOGLE)) {
        return OpenIabHelper.NAME_GOOGLE;
      } else if (storeName.equals(PurchaseManagerConfig.STORE_NAME_ANDROID_AMAZON)) {
        return OpenIabHelper.NAME_AMAZON;
      } else if (storeName.equals(PurchaseManagerConfig.STORE_NAME_ANDROID_SAMSUNG)) {
        return OpenIabHelper.NAME_SAMSUNG;
      } else if (storeName.equals(PurchaseManagerConfig.STORE_NAME_ANDROID_NOKIA)) {
        return OpenIabHelper.NAME_NOKIA;
      } else if (storeName.equals(PurchaseManagerConfig.STORE_NAME_ANDROID_SLIDEME)) {
        return OpenIabHelper.NAME_SLIDEME;
      } else if (storeName.equals(PurchaseManagerConfig.STORE_NAME_ANDROID_APTOIDE)) {
        return OpenIabHelper.NAME_APTOIDE;
      } else if (storeName.equals(PurchaseManagerConfig.STORE_NAME_ANDROID_APPLAND)) {
        return OpenIabHelper.NAME_APPLAND;
      } else if (storeName.equals(PurchaseManagerConfig.STORE_NAME_ANDROID_YANDEX)) {
        return OpenIabHelper.NAME_YANDEX;
      } else {
        // we should get here: the correct store should always be mapped!
        Log.e(TAG, "Store name could not be mapped: " + storeName);
        return storeName;
      }
    }
  }
  
  private void refreshSKUs() {
      // refresh the identifiers/SKUs: ensures we have the correct mapping in case PurchaseManagerConfig was updated
      for (int i = 0; i < config.getOfferCount(); i++) {
      Offer offer = config.getOffer(i);
  
      // map store-specific identifiers with our default identifier!
      String identifier = offer.getIdentifier();
      Set<Map.Entry<String, String>> identifierForStores = offer.getIdentifierForStores();
      for (Map.Entry<String, String> entry : identifierForStores) {
        String storeNameOpenIAB = storeNameToOpenIAB(entry.getKey());
        String identifierForStore = entry.getValue();
        if (!(SkuManager.getInstance().getStoreSku(storeNameOpenIAB, identifier).equals(identifierForStore))) {
          SkuManager.getInstance().mapSku(identifier, storeNameOpenIAB, identifierForStore);
        }
      }
    }
  }

  @Override
  public void install (final PurchaseObserver observer, PurchaseManagerConfig config) {
    this.observer = observer;
    this.config = config;

    // build the OpenIAB options. Pass in the storeKeys as follows:
    // -------------------------------------------------------------------------
    // config.addStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_GOOGLE, "<store key GooglePlay>");
    // config.addStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_AMAZON, "<store key Amazon>");
    // ...
    // -------------------------------------------------------------------------
    Map<String, String> storeKeys = new HashMap<String, String>(16);
    if (config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_GOOGLE) != null) {
      storeKeys.put(storeNameToOpenIAB(PurchaseManagerConfig.STORE_NAME_ANDROID_GOOGLE),
        (String)config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_GOOGLE));
    }
    if (config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_AMAZON) != null) {
      storeKeys.put(storeNameToOpenIAB(PurchaseManagerConfig.STORE_NAME_ANDROID_AMAZON),
        (String)config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_AMAZON));
    }
    if (config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_SAMSUNG) != null) {
      storeKeys.put(storeNameToOpenIAB(PurchaseManagerConfig.STORE_NAME_ANDROID_SAMSUNG),
        (String)config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_SAMSUNG));
    }
    if (config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_NOKIA) != null) {
      storeKeys.put(storeNameToOpenIAB(PurchaseManagerConfig.STORE_NAME_ANDROID_NOKIA),
        (String)config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_NOKIA));
    }
    if (config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_SLIDEME) != null) {
      storeKeys.put(storeNameToOpenIAB(PurchaseManagerConfig.STORE_NAME_ANDROID_SLIDEME),
        (String)config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_SLIDEME));
    }
    if (config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_APTOIDE) != null) {
      storeKeys.put(storeNameToOpenIAB(PurchaseManagerConfig.STORE_NAME_ANDROID_APTOIDE),
        (String)config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_APTOIDE));
    }
    if (config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_APPLAND) != null) {
      storeKeys.put(storeNameToOpenIAB(PurchaseManagerConfig.STORE_NAME_ANDROID_APPLAND),
        (String)config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_APPLAND));
    }
    if (config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_YANDEX) != null) {
      storeKeys.put(storeNameToOpenIAB(PurchaseManagerConfig.STORE_NAME_ANDROID_YANDEX),
        (String)config.getStoreParam(PurchaseManagerConfig.STORE_NAME_ANDROID_YANDEX));
    }
    final OpenIabHelper.Options.Builder builder = new OpenIabHelper.Options.Builder();
    builder.setVerifyMode(OpenIabHelper.Options.VERIFY_SKIP);
    builder.setStoreSearchStrategy(OpenIabHelper.Options.SEARCH_STRATEGY_INSTALLER_THEN_BEST_FIT);
    builder.addStoreKeys(storeKeys);

    // refresh the SKUs list
    refreshSKUs();
    
    // start OpenIAB (needs to be run on UI-thread for some reason!?)
    activity.runOnUiThread(new Runnable() {       
          @Override
          public void run() {
            helper = new OpenIabHelper(activity, builder.build());
            helper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
                public void onIabSetupFinished (IabResult result) {
                    if (!result.isSuccess()) {
                        // error setting up the
                        helper = null;
                        inventory = null;

                        // remove observer and config as well
                        PurchaseManagerAndroidOpenIAB.this.observer = null;
                        PurchaseManagerAndroidOpenIAB.this.config = null;

                        // notify about the problem
                        observer.handleInstallError(new RuntimeException("Problem setting up in-app billing: " + result));
                    } else {
                        // do a restore first to get the inventory
                        boolean querySkuDetails = true; // --> that way we get prices and title/description as well!
                        helper.queryInventoryAsync(querySkuDetails, new IabHelper.QueryInventoryFinishedListener() {
                            @Override
                            public void onQueryInventoryFinished (IabResult result, Inventory inventory) {
                                // store the inventory so we can lookup prices later!
                                PurchaseManagerAndroidOpenIAB.this.inventory = inventory;

                                // notify of successful initialization
                                observer.handleInstall();
                            }
                        });
                    }
                }
            });
          }
        });
  }

  @Override
  public boolean installed () {
    return helper != null;
  }

  @Override
  public void dispose () {
    // dispose OpenIAB and underlying store
    if (helper != null) {
      helper.dispose();
      helper = null;
      inventory = null;

      // remove observer and config as well
      observer = null;
      config = null;
    }
  }

  @Override
  public void purchase (final String identifier) {
        // refresh the SKUs list
        refreshSKUs();
      
    // purchase if we have a helper
    if (helper != null) {
      String payload = null;
  
      // make a purchase
      helper.launchPurchaseFlow(activity, identifier, IabHelper.ITEM_TYPE_INAPP, requestCode,
        new IabHelper.OnIabPurchaseFinishedListener() {
          @Override
          public void onIabPurchaseFinished (IabResult result, Purchase purchase) {
            if (result.isFailure()) {
              // the purchase has failed
              
              if (result.getResponse() == IabHelper.IABHELPER_USER_CANCELLED) {
                observer.handlePurchaseCanceled();
              }
              else {
                observer.handlePurchaseError(new RuntimeException(result.toString()));
              }
            } else {
              // parse transaction data
              Transaction transaction = transaction(purchase);
  
              // forward result to listener
              observer.handlePurchase(transaction);
  
              // if the listener doesn't throw an error, we consume as needed
              Offer offer = config.getOffer(purchase.getSku());
              if (offer == null) {
                Log.d(TAG, "Offer not found for: " + purchase.getSku());
              } else if (offer.getType() == OfferType.CONSUMABLE) {
                // it's a consumable, so we consume right away!
                helper.consumeAsync(purchase, new IabHelper.OnConsumeFinishedListener() {
                  @Override
                  public void onConsumeFinished (Purchase purchase, IabResult result) {
                    if (!result.isSuccess()) {
                              // FIXME: if consume fails, the purchase manager should take note and 
                        //        try to consume again at a later point in time...
                      Log.e(TAG, "Error while consuming: " + result);
                    }
                  }
                });
              }
            }
          }
        }, payload);
    }
    else {
      Log.e(TAG, "ERROR: purchase(): openIAB helper == null");
    }
  }

  @Override
  public void purchaseRestore () {
        // refresh the SKUs list
        refreshSKUs();
    
        // restore if we have a helper
    if (helper != null) {
      // ask for purchase restore
      boolean querySkuDetails = true; // --> that way we get prices and title/description as well!
      helper.queryInventoryAsync(querySkuDetails, new IabHelper.QueryInventoryFinishedListener() {
        @Override
        public void onQueryInventoryFinished (IabResult result, Inventory inventory) {
          // store the inventory so we can lookup prices later!
          PurchaseManagerAndroidOpenIAB.this.inventory = inventory;
  
          // build list of purchases
          List<Purchase> purchases = inventory.getAllPurchases();
          List<Transaction> transactions = new ArrayList<Transaction>(purchases.size());
          for (int i = 0; i < purchases.size(); i++) {
            transactions.add(transaction(purchases.get(i)));
          }
  
          // send inventory to observer
          observer.handleRestore(transactions.toArray(new Transaction[transactions.size()]));
  
          // if the observer above didn't throw an error, we consume all consumeables as needed
          for (int i = 0; i < purchases.size(); i++) {
            Purchase purchase = purchases.get(i);
            Offer offer = config.getOffer(purchase.getSku());
            if (offer == null) {
              Log.d(TAG, "Offer not found for: " + purchase.getSku());
            } else if (offer.getType() == OfferType.CONSUMABLE) {
              // it's a consumable, so we consume right away!
              helper.consumeAsync(purchase, new IabHelper.OnConsumeFinishedListener() {
                @Override
                public void onConsumeFinished (Purchase purchase, IabResult result) {
                  if (!result.isSuccess()) {
                    // NOTE: we should only rarely have an exception due to e.g. network outages etc.
                    Log.e(TAG, "Error while consuming: " + result);
                  }
                }
              });
            }
          }
        }
      });
    }
    else {
      Log.e(TAG, "ERROR: purchaseRestore(): openIAB helper == null");
    }
  }

  @Override
    public Information getInformation(String identifier) {
      // not implemented yet for this purchase manager
        return Information.UNAVAILABLE;
    }

    /** Converts a purchase to our transaction object. */
  Transaction transaction (Purchase purchase) {
    // obtain the SKU details (could be null!)
    SkuDetails skuDetails = inventory.getSkuDetails(purchase.getSku());

    // build the transaction from the purchase object
    Transaction transaction = new Transaction();
    transaction.setIdentifier(purchase.getSku());

    transaction.setStoreName(storeNameFromOpenIAB(purchase.getAppstoreName()));
    transaction.setOrderId(purchase.getOrderId());

    transaction.setPurchaseTime(new Date(purchase.getPurchaseTime()));
    transaction.setPurchaseText((skuDetails != null ? "Purchased " + skuDetails.getTitle() + " for " + skuDetails.getPrice() + ": " + skuDetails.getDescription()
                                                    : "Purchased"));
    transaction.setPurchaseCost(-1);   // -1 for Unknow: FIXME: need parser for cost (amount and currency code!)
    transaction.setPurchaseCostCurrency(null);

    if (purchase.getPurchaseState() != 0) {
      // order has been refunded or cancelled
      transaction.setReversalTime(new Date()); // TODO: Amazon IAP 2.0 has "cancelDate" which can be obtained via original Json
      transaction.setReversalText(purchase.getPurchaseState() == 1 ? "Cancelled" : "Refunded");
    } else {
      // still valid!
      transaction.setReversalTime(null);
      transaction.setReversalText(null);
    }

    transaction.setTransactionData(purchase.getOriginalJson());
    transaction.setTransactionDataSignature(purchase.getSignature());
    return transaction;
  }

  public void onActivityResult (int requestCode, int resultCode, Intent data) {
    // forwards activities to OpenIAB for processing
    helper.handleActivityResult(requestCode, resultCode, data);
  }

  @Override
  public String toString () {
    return "OpenIAB/" + helper.getConnectedAppstoreName();
  }
}




Java Source Code List

com.badlogic.gdx.pay.Information.java
com.badlogic.gdx.pay.OfferType.java
com.badlogic.gdx.pay.Offer.java
com.badlogic.gdx.pay.PurchaseManagerConfig.java
com.badlogic.gdx.pay.PurchaseManager.java
com.badlogic.gdx.pay.PurchaseObserver.java
com.badlogic.gdx.pay.PurchaseSystem.java
com.badlogic.gdx.pay.Transaction.java
com.badlogic.gdx.pay.android.IAP.java
com.badlogic.gdx.pay.android.amazon.PurchaseManagerAndroidAmazon.java
com.badlogic.gdx.pay.android.openiab.PurchaseManagerAndroidOpenIAB.java
com.badlogic.gdx.pay.android.ouya.PurchaseManagerAndroidOUYA.java
com.badlogic.gdx.pay.desktop.apple.PurchaseManagerDesktopApple.java
com.badlogic.gdx.pay.gwt.googlewallet.PurchaseManagerGwtGoogleWallet.java
com.badlogic.gdx.pay.ios.apple.PurchaseManageriOSApple.java
com.badlogic.gdx.pay.server.PurchaseVerifierManager.java
com.badlogic.gdx.pay.server.PurchaseVerifier.java
com.badlogic.gdx.pay.server.impl.PurchaseVerifierAndroidAmazon.java
com.badlogic.gdx.pay.server.impl.PurchaseVerifierAndroidGoogle.java
com.badlogic.gdx.pay.server.impl.PurchaseVerifieriOSApple.java
com.badlogic.gdx.pay.server.util.Base64Util.java