Quick Links: Download Gideros Studio | Gideros Documentation | Gideros community chat | DONATE
OPENIAB in-app purchases — Gideros Forum

OPENIAB in-app purchases

jdbcjdbc Member
edited March 2014 in General questions
Hi all again.

I am asking what about a plugin for http://www.onepf.org/openiab. This is a project that enables to use several in-app with same API.

I know Gideros Labs http://giderosmobile.com/labs/iab provides for OUYA, Google Billing and Amazon, but this provides more app stores with same API.

Comments

  • jdbcjdbc Member
    edited March 2014
    oh but there is one :)
    http://giderosmobile.com/labs/iab
    But OPENIAB provides a java wrapped API for Yandex, Samsung and T-Store. Less effort to Lua - java plugin.

    And what about "OpenIAB Lib detects which appstore installed the app."
  • ar2rsawseenar2rsawseen Maintainer
    edited March 2014
    But ours supports also IOS (under the same API and workflow) and additionally OUYA, and also detects available stores.
    Here is the full code working crossplatform with every supported store:
    require "iab"
    local iaps = IAB.detectStores()
    iab = nil
    if iaps[1] == "google" then
    	iab = IAB.new(iaps[1])
    	iab:setUp("google-key")
    	--using google product identifiers
    	iab:setProducts({p1 = "googleprod1", p2 = "googleprod2", p3 = "googleprod3"})
    elseif iaps[1] == "amazon" then
    	iab = IAB.new(iaps[1])
    	--using amazon product identifiers
    	iab:setProducts({p1 = "amazonprod1", p2 = "amazonprod2", p3 = "amazonprod3"})
    elseif iaps[1] == "ios" then
    	iab = IAB.new(iaps[1])
    	--using ios product identifiers
    	iab:setProducts({p1 = "iosprod1", p2 = "iosprod2", p3 = "iosprod3"})
    end
     
    --load previous purchases
    purchases = dataSaver.loadValue("purchases")
    --if there were no purchases
    if not purchases then
    	--create and store empty table
    	purchases = {}
    	dataSaver.saveValue("purchases", purchases)
    end
     
    --if we have a supported store
    if iab then
    	--set which products are consumables
    	iab:setConsumables({"p1", "p2"})
     
    	--if purchase complete
    	iab:addEventListener(Event.PURCHASE_COMPLETE, function(e)
    		--if it was not previousle purchases
    		if not purchases[e.receiptId] then
    			--save purchase
    			purchases[e.receiptId] = true
    			dataSaver.saveValue("purchases", purchases)
     
    			if (e.productId == "p1") then
    				--p1 was purchased
    			elseif (e.productId == "p2") then
    				--p2 was purchased
    			elseif (e.productId == "p3") then
    				--p3 was purchased
    			end
    			AlertDialog.new("Purchase Completed", "Purchase successfully completed", "Ok"):show()
    		end
    	end)
     
    	--if there was an error
    	iab:addEventListener(Event.PURCHASE_ERROR, function(e)
    		AlertDialog.new("Purchase Canceled", e.error, "Ok"):show()
    	end)
     
    	--call restore on each app starts
    	--or make a button to allow users to restore purchases
    	iab:restore()
    end
     
    --some where in your code to make a purchase
    stage:addEventListener(Event.MOUSE_UP, function()
    	if iab then
    		iab:purchase("p1")
    	end
    end)
    What IAB Interface provides:
    Same API through all supported In-app billing frameworks on all platforms
    Completely same workflow for all In-app Billing frameworks
    Can dynamically detect which stores are installed on device and return the list to you in prioritized order, so you can have one single binary for all stores
    Provides internal product ID usage through all In-app Billing frameworks, which might use different product IDs
    Provides unique Receipt ID even if In-app Billing does not support it
    Manages all the confirmation and consumption internally

    More info: http://docs.giderosmobile.com/interface/iab

    Likes: SinisterSoft

    +1 -1 (+1 / -0 )Share on Facebook
  • ar2rsawseenar2rsawseen Maintainer
    edited March 2014
    Yes it might need some more work and polishing and testing now, but the end result will be better and easier to add new additional stores
  • jdbcjdbc Member
    edited March 2014
    Cool

    It was one idea for reduce development effort on Gideros Labs IAP plugin :-)

    I suppose we can use

    iab:setProducts({p1 = "prod1", p2 = "prod2", p3 = "prod3"})

    with the same product Ids for all stores:
  • ar2rsawseenar2rsawseen Maintainer
    yes, that was the idea, since ios and Google have different formats for product Ids you can set up and use same ids internally and let plugin handle store specific ids
  • I added IAB with the lib from labs - I removed the gideros google billing stuff first.

    But when I try iab:restore() I get a crash with this in the eclipse log:

    03-01 21:46:41.755: D/HeyzapSDK(30234): Tracking heyzap-start event.
    03-01 21:46:44.550: E/IabHelper(30234): In-app billing error: Illegal state for operation (queryInventory): IAB helper is not set up.
    03-01 21:46:44.565: D/AndroidRuntime(30234): Shutting down VM
    03-01 21:46:44.565: W/dalvikvm(30234): threadid=1: thread exiting with uncaught exception (group=0x41c2a700)
    03-01 21:46:44.580: E/AndroidRuntime(30234): FATAL EXCEPTION: main
    03-01 21:46:44.580: E/AndroidRuntime(30234): java.lang.IllegalStateException: IAB helper is not set up. Can't perform operation: queryInventory
    03-01 21:46:44.580: E/AndroidRuntime(30234): at com.giderosmobile.android.plugins.iab.google.IabHelper.checkSetupDone(IabHelper.java:790)
    03-01 21:46:44.580: E/AndroidRuntime(30234): at com.giderosmobile.android.plugins.iab.google.IabHelper.queryInventoryAsync(IabHelper.java:615)


    IabHelper is in the set of files included in the src folder, etc...


    Any ideas?
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • SinisterSoftSinisterSoft Maintainer
    edited March 2014
    Ahh - figured it out:

    --if there was an error
    iab:addEventListener(Event.PURCHASE_ERROR, function(e)
    AlertDialog.new("Purchase Cancelled",e.error,"Ok"):show()
    end)

    iab:addEventListener(Event.RESTORE_COMPLETE, function(e)
    AlertDialog.new("restore iab","","Ok"):show()
    end)

    iab:addEventListener(Event.AVAILABLE, function(e)
    iab:restore()
    end)


    iab:isAvailable()
    --call restore on each app starts
    --or make a button to allow users to restore purchases
    --iab:restore()
    end


    If you just do an iab:restore() then it will fail (and fail with a crash!)- you have to see if it's available and do the restore in that event. You instructions don't mention that you should check availablity first.
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • ar2rsawseenar2rsawseen Maintainer
    Hmm, true will implement also internal check ;)

    Likes: SinisterSoft

    +1 -1 (+1 / -0 )Share on Facebook
  • Hi

    google has stopped support for version 2 and forcing us to use version 3 now so instead of googlebilling plugin i am using iab plugin now but have some problem in it just my understanding is not clear so please help me with below points

    how can i buy android.test.purchased multiple time as i am intending this to test to purchase coins? it was working fine i can purchase multiple time with android.test.purchased with version 2 but with version 3 it can be purchased only one time

    also is it like managed product will purchased only on 1 time and un managed product can be purchased any number of time means i dont have to do anything in code
  • ok now as per google doc it says,

    Unmanaged products behave differently if you are using in-app billing v3 rather than in-app billing v2. If you are using in-app billing v3, Unmanaged products are treated as Managed products and will need to be explicitly consumed. Learn more

    so how can i consume item or plugin is doing this for me automatically

    Likes: Yan

    +1 -1 (+1 / -0 )Share on Facebook
  • ar2rsawseenar2rsawseen Maintainer
    you need to use setConsumables method
    http://docs.giderosmobile.com/interface/iab

    to specify that this item is consumable

    Likes: hgvyas123

    +1 -1 (+1 / -0 )Share on Facebook
  • Thanks bro it worked :)
  • jdbcjdbc Member
    Hi again in this thread.

    I was trying to use in-app purchases with SlideMe appstore, so I was studying again OpenIab:
    https://github.com/onepf/OpenIAB

    I have created a new class IapOpen implementing IabInterface and it seems it works also for Google Play, and I have add a new method startSetup Iad and IabInterface to call mHelper.startSetup().

    I also was trying to use the strategy searching:
     
    OpenIabHelper.Options.Builder builder = new OpenIabHelper.Options.Builder();
    builder.addStoreKey(OpenIabHelper.NAME_SLIDEME, key)			.setStoreSearchStrategy(OpenIabHelper.Options.SEARCH_STRATEGY_INSTALLER_THEN_BEST_FIT)
    .setVerifyMode(OpenIabHelper.Options.VERIFY_SKIP);
    In the lua part I use:
    iab = IAB.new("open")
    iab:setUp(slideme_key)
    I suppose there is one way to specify IAB.new("open", "slideme")...
  • jdbcjdbc Member
    edited June 2015
    This is my IabOpen.java file:
    package com.giderosmobile.android.plugins.iab.frameworks;
     
    import java.io.UnsupportedEncodingException;
    import java.lang.ref.WeakReference;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.Hashtable;
    import java.util.List;
     
    import org.onepf.oms.OpenIabHelper;
    import org.onepf.oms.appstore.googleUtils.IabHelper.OnIabPurchaseFinishedListener;
    import org.onepf.oms.appstore.googleUtils.IabHelper.OnConsumeFinishedListener;
    import org.onepf.oms.appstore.googleUtils.IabHelper.OnIabSetupFinishedListener;
    import org.onepf.oms.appstore.googleUtils.IabHelper.QueryInventoryFinishedListener;
    import org.onepf.oms.appstore.googleUtils.IabResult;
    import org.onepf.oms.appstore.googleUtils.Inventory;
    import org.onepf.oms.appstore.googleUtils.Purchase;
     
    import com.giderosmobile.android.plugins.iab.Iab;
    import com.giderosmobile.android.plugins.iab.IabInterface;
    import org.onepf.oms.appstore.googleUtils.SkuDetails;
     
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.util.Log;
    import android.util.SparseArray;
     
    /**
     * 
     * 
     * <a href="http://forum.gideros.rocks/profile/author" rel="nofollow">@author</a> jdbc
     *
     */
    public class IabOpen implements IabInterface, 
    							OnIabPurchaseFinishedListener, 
    							OnConsumeFinishedListener, 
    							OnIabSetupFinishedListener,
    							QueryInventoryFinishedListener
    {
     
    	private static WeakReference<Activity> sActivity;
    	private OpenIabHelper mHelper;
    	private boolean wasChecked = false;
    	private int sdkAvailable = -1;
     
    	public static Boolean isInstalled(){
     
    		/*if (Iab.isPackageInstalled("org.onepf.oms"))
    			return true;*/
     
    		return true;
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void onCreate(WeakReference<Activity> activity) {
    		sActivity = activity;
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void onDestroy() {
    		if (mHelper != null) 
    			mHelper.dispose();
    		mHelper = null;
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void onStart() {
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void onActivityResult(int requestCode, int resultCode, Intent data) {
    		Log.d("IabTest", "onActivityResult");
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void init(Object parameters) {
     
    		SparseArray<byte[]> p = (SparseArray<byte[]>)parameters;
    		try {
    			String key = new String(p.get(0), "UTF-8");
    			OpenIabHelper.Options.Builder builder = new OpenIabHelper.Options.Builder();
    			builder.addStoreKey(OpenIabHelper.NAME_SLIDEME, key)
    					.setStoreSearchStrategy(OpenIabHelper.Options.SEARCH_STRATEGY_INSTALLER_THEN_BEST_FIT)
    					.setVerifyMode(OpenIabHelper.Options.VERIFY_SKIP);
    					//.setVerifyMode(OpenIabHelper.Options.VERIFY_EVERYTHING);
    			mHelper = new OpenIabHelper(sActivity.get(), builder.build());
     
    		} catch (UnsupportedEncodingException e) {
    			e.printStackTrace();
    		}
     
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void check() {
    		if(sdkAvailable == 1)
    			Iab.available(this);
    		else if(sdkAvailable == 0)
    			Iab.notAvailable(this);
    		else
    			wasChecked = true;
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void startSetup()
    	{
    		mHelper.startSetup(this);
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void request(Hashtable<String, String> products) {
     
    		List<String> skuList = new ArrayList<String>();
        	Enumeration<String> e = products.keys();
    		while(e.hasMoreElements())
    		{
    			String prodName = e.nextElement();
            	skuList.add(products.get(prodName));
            }
     
            mHelper.queryInventoryAsync(true, skuList, this);
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void purchase(String productId) {
     
    		int RC_REQUEST = 10001;
    		String payload = "";
     
    		mHelper.launchPurchaseFlow(sActivity.get(), productId, RC_REQUEST, (OnIabPurchaseFinishedListener) this, payload);
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void restore() {
    		Log.d("IabTest", "restoring");
    		Iab.restoreComplete(this);
    		Iab.restoreError(this, "Test Restore Error");
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void onIabPurchaseFinished(IabResult result, Purchase info) {
     
    		if (result.isFailure()) {
    			Iab.purchaseError(this, result.getMessage());
    			return;
    		}
    		if(Iab.isConsumable(info.getSku(), this))
    		{
    			mHelper.consumeAsync(info, this);
    		}
    		else
    		{
    			Iab.purchaseComplete(this, info.getSku(), info.getOrderId());
    		}
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void onConsumeFinished(Purchase purchase, IabResult result) {
    		if (result.isSuccess()) {
    			Iab.purchaseComplete(this, purchase.getSku(), purchase.getOrderId());
    	    }
    	    else {
    	    	Iab.purchaseError(this, result.getMessage());
    	    }
     
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void onIabSetupFinished(IabResult result) {
    		if (result.isSuccess())
    			 sdkAvailable = 1;
    		 else
    			 sdkAvailable = 0;
    	     if(wasChecked)
    	     {
    	    	 if(sdkAvailable == 1)
    	    		 Iab.available(this);
    	    	 else
    	    		 Iab.notAvailable(this);
    	     }
     
    	}
     
    	<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
    		if (result.isFailure()) {
    			Iab.productsError(this, "Request Failed");
    			return;
    		}
     
    		Hashtable<String, String> products = Iab.getProducts(this);
    		SparseArray<Bundle> arr = new SparseArray<Bundle>();
    		Enumeration<String> e = products.keys();
    		int i = 0;
    		while(e.hasMoreElements())
    		{
    			String prodName = e.nextElement();
    			SkuDetails details = inventory.getSkuDetails(products.get(prodName));
            	if(details != null)
            	{
            		Bundle map = new Bundle();
            		map.putString("productId", products.get(prodName));
            		map.putString("title", details.getTitle());
            		map.putString("description", details.getDescription());
            		map.putString("price", details.getPrice());
            		arr.put(i, map);
            		i++;
            	}
            }
            Iab.productsComplete(this, arr);
     
    	}
    }
  • jdbcjdbc Member
    edited June 2015
    Changes in Iab.java file:
     
    ...
     
    private static String[] stores = {"Google", "Amazon", "Ouya", "Samsung", "Fortumo", "Nokia", "Open"};
     
    ...
     
    public static void setup(String iabtype, Object parameters){
    		String adp = modifyName(iabtype);
    		iab.get(adp).init(parameters);
     
    		startSetup(iabtype);
    	}
     
    ...
     
    public static void startSetup(final String iabtype) {
    		try
    		{
    			Runnable myRunnable = new Runnable(){
     
    				<a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    				public void run() {
    					String adp = modifyName(iabtype);
    					iab.get(adp).startSetup();
    				}
     
    			};
    			sActivity.get().runOnUiThread(myRunnable) ;
    		}
    		catch(Exception ex)	{}
    	}
  • ar2rsawseenar2rsawseen Maintainer
    Wow impressive, care to submit PR in Github? :)
  • jdbcjdbc Member
    edited June 2015
    For Google Play app store, it works using the following java code in the IapOpen class and init method:
    <a href="http://forum.gideros.rocks/profile/Override" rel="nofollow">@Override</a>
    	public void init(Object parameters) {
     
    		SparseArray<byte[]> p = (SparseArray<byte[]>)parameters;
    		try {
    			String key = new String(p.get(0), "UTF-8");
    			OpenIabHelper.Options.Builder builder = new OpenIabHelper.Options.Builder();
    			builder.addStoreKey(OpenIabHelper.NAME_GOOGLE, key)
    					.setStoreSearchStrategy(OpenIabHelper.Options.SEARCH_STRATEGY_INSTALLER_THEN_BEST_FIT)
    					.setVerifyMode(OpenIabHelper.Options.VERIFY_SKIP);
    					//.setVerifyMode(OpenIabHelper.Options.VERIFY_EVERYTHING);
    			mHelper = new OpenIabHelper(sActivity.get(), builder.build());
     
    		} catch (UnsupportedEncodingException e) {
    			e.printStackTrace();
    		}
     
    	}
    In the lua part
    iab = IAB.new("open")
    iab:setUp(googleplay_key)
    I suppose the only difference with the rest of app stores (Amazon, Yandex, AppMall, SlideMe, Aptoide,...) is this init method and productId list. But Google Play and SlideMe productId list is the same. :-)

    So the only difference is the app public key :
    builder.addStoreKey(OpenIabHelper.NAME_GOOGLE, key)
  • jdbcjdbc Member
    edited June 2015
    Wow impressive, care to submit PR in Github? :)
    I will when I finish my OpenIAB integration, the Android project with java code and jar files will be submitted to Github, may be to allow integration with current Gideros IAB plugin.

    I have discovered we must to use the following command to allow app store SlideMe discovery using OpenIAB java classes.

    adb install -i com.slideme.sam.manager /path/to/YourApp.apk

    ¿How does Gideros Iab plugin detect the stores?
  • ar2rsawseenar2rsawseen Maintainer
    edited June 2015
    Yes basically app must be installed by slideme store to be detected

    Gideros provides a method in each Iab[Store] class to detect if this store is available
    like:

    https://github.com/gideros/gideros/blob/master/plugins/iab/source/Android/src/com/giderosmobile/android/plugins/iab/frameworks/IabGoogle.java#L30
  • jdbcjdbc Member
    Yes basically app must be installed by slideme store to be detected

    Gideros provides a method in each Iab[Store] class to detect if this store is available
    like:

    https://github.com/gideros/gideros/blob/master/plugins/iab/source/Android/src/com/giderosmobile/android/plugins/iab/frameworks/IabGoogle.java#L30
    After a little investigating OpenIab API I know how to detect app stores:

    OpenIab calls to OpenIapHelper.startSetup method depending on the OpenIapHelper.Options object. This Options object contains all app stores keys and app lookup strategy (installer or billing provider), so my IabOpen could be the same for all Open app stores (SlideMe, Yandex, AppMall, Aptoide) if I could provide a list of app stores key from lua and the product id will be the same as Google Play.
  • ar2rsawseenar2rsawseen Maintainer
    @jdbc thats great

    iab:setup method supports passing multiple arguments

    as you call
    iab:setup(value1, value2, value3)
    which will get SparseArray on Android side with all values in same sequence

    so you can also provide all needed keys that way

    About look up strategy, then IAB, uses something similar, when you call
    IAB.detectStores(priority1, priority2, ... priorityN)
    you provide a priority list of stores to detect, so it returns the table of supported stores in this priority order, skipping the ones it does not support.

    Maybe it can be used in OpenIab case too
  • jdbcjdbc Member
    @ar2rsawseen I know IAB plugin provides a look up strategy, but I do not know if it is better than detect stores will be transparent for user.

    I mean, from Lua it was easier to do something as:
      local iap = IAB.new("open")
      iap.setup({"googleplay" = googleplay_key, "slideme" = slideme_key, "yandex", yandex_key, "appmall", appmall_key}
      iap.setPreferred("slideme")
    in one step and later java code detects stores to avoid if .. then lua code.



  • ar2rsawseenar2rsawseen Maintainer
    well if then is needed either way, because device might not support any of the stores at all.
  • jdbcjdbc Member
    edited June 2015
    well if then is needed either way, because device might not support any of the stores at all.
    Anyway the point I want to use the same IabOpen class to support all Open app store, so I am trying to make something like:
      iab = IAB.new("open")
      iab:setUp("SlideME", slideme_key, "Yandex", yandex_key)
    and from IapOpen.init method retrieve the list of app keys for each app store. Another solution could be created a new IapSlideMe class extending IapOpen and only overriding the init and isInstalled methods. This second option will follow your design in IAB plugin.
  • jdbcjdbc Member
    Wow impressive, care to submit PR in Github? :)
    I have submit Gideros project and Android source code to https://github.com/jdbcdev/GiderosOpenIab

    This provides the integration of OpenIab and Gideros IAB plugin. Finally in the Lua part you need to provide a list of (app_stores_names, app_key)
     
    	iab = IAB.new("open")
    	iab:setUp(NAME_GOOGLE, google_key,
                              NAME_SLIDEME, slideme_key,
    			  NAME_APPLAND, appland_key
    			)
    I provided an example working on Google Play, SlideMe and AppLand, but it should work with Yandex and Aptoide too.
  • jdbcjdbc Member
    I was trying to integrate OpenIAB and Fortumo with Gideros IAB plugin.

    It seems that IabFortumo class sets up the prices to "0.00".

    How can I set up prices in Fortumo for my products?
Sign In or Register to comment.