onepf / OpenIAB

Open In-App Billing for Google Play, SlideMe, Amazon Store, Nokia Store, Samsung Apps, Yandex.Store, Appland, Aptoide, AppMall and Fortumo.
http://onepf.org/openiab/
Apache License 2.0
475 stars 171 forks source link

Somewhat confused about purchasing and verifying past purchases #148

Closed GarrickWinter closed 10 years ago

GarrickWinter commented 10 years ago

Hey there! So I am using the Unity plugin and trying to set up IAB for my Android game (targeting just the Google Play Store for now). This is a simple app just to figure out how things work, and I have three SKUs the player can buy that should be one-time purchases with a permanent effect on some aspects of the gameplay. However, I'm a bit confused as to how to go about doing things.

Most importantly, I'm not sure how to check whether a player has already purchased a given SKU or not. I would assume that this is what OpenIAB.queryInventory() is for, but that function doesn't seem to return anything and doesn't take any parameters, and diving into OpenIAB_Android's implementation doesn't really illuminate anything, as I'm not familiar with the API calls being made.

More generally, I am confused about Step 5 in the 5-step integration outlined in the description (https://github.com/onepf/OpenIAB/tree/master/unity_plugin).

When is OnPurchaseSucceded() in the example called, and what calls it?

Do I need to call OpenIAB.queryInventory() before purchasing things?

How does OpenIAB handle things if the player purchases something they've already purchased? (Ideally this will never happen, so long as I can check the player's past purchases and disable the options in the GUI, but I'd like to understand how the system works.)

What sort of script does all this code belong in? Should I just make a script for handling my game's IAB and put this in there with the other code setting up IAB?

If someone could explain in a bit more detail how I should concretely set up IAB for my game, or point me towards some kind of tutorial that would explain it, I'd very much appreciate it!

oorlov commented 10 years ago

Hi @GarrickWinter!

In GP/OpenStores all non-consumable items (inventory) are stored in Appstore until you call "consume" for particular item. So you can retrieve inventory from Appstore at any time you need it. Basically flow looks like this:

These 2 listeners are responsible for retrieving queryInventory() results:

    OpenIABEventManager.queryInventorySucceededEvent += queryInventorySucceededEvent;
    OpenIABEventManager.queryInventoryFailedEvent += queryInventoryFailedEvent;

They should be specified before you call queryInventory().

So, to make OpenIAB working, you need to implement all listeners from step 2, call init() and queryInventory()

oorlov commented 10 years ago

135 link to Tutorial issue to take this case into account

GarrickWinter commented 10 years ago

Okay, thanks! I got most of it working, but now when I try to test OpenIAB.purchaseProduct() with my own published SKUs, I get a "Item could not be found" error. What kind of information should I pass to purchaseProduct()? What is the form/format of the SKU information it requires? And what is DeveloperPayload?

GarrickWinter commented 10 years ago

To clarify, I have tried passing the following strings to purchaseProduct():

"sku_name" "com.CompanyName.ProductName.sku_name" "com.companyname.productname.sku_name"

None of these work. I have already published the in-app products on the Developer Console, and my game APK is published as a Beta. I am testing from a device with the primary e-mail account set up as a tester.

Passing "android.test.purchased" works as expected.

oorlov commented 10 years ago

Not sure, what's wrong, probably Beta or primary publisher e-mail leads to such behavior, so lets eliminate these conditions:

what do you get?

GarrickWinter commented 10 years ago

So after looking at the AngryBots demo code and comparing to my own, I realised what seemed to be the problem - it looks like I wasn't using mapSku() properly! I had been using:

OpenIAB.mapSku(SKU_Name, OpenIAB_Android.STORE_GOOGLE, "google-play.sku_name");

But when I switched that to

OpenIAB.mapSku(SKU_Name, OpenIAB_Android.STORE_GOOGLE, "sku_name");

It worked fine. For future reference, I am passing "sku_name" as a parameter to purchaseProduct(), and NOT "com.company.product.sku_name" or variants thereof, but as I understand the mapping I actually think it doesn't matter, so long as you are passing the same thing to purchaseProduct() as you passed as first parameter to mapSku().

Now I have encountered the fact that The Publisher Cannot Purchase This Item, but that should be easy to solve by setting up a second testing account.

oorlov commented 10 years ago

It's not clear where "google-play.sku_name" came from… when did you decide to use it? (will put to Tutorial)

In fact if you work only with Google Play, you can totally skip OpenIAB.mapSku calls. If no mapping is set for particular SKU in current store, the parameter of purchaseProduct(sku, … ) is used as is. Mapping is needed if you launch in several Appstores and SKUs for the same products differs in different Appstores (i.e. Amazon requires SKU names unique in the Universe)

So I recommend to use SKU with name format "com.company.product.sku_name" everywhere. It's suitable for all Appstores and you can skip mappings at all. (Only exception is SamsungApps because they generate SKU by themselves and you need to copy-paste it in code)

Do you have any other issues here?

GarrickWinter commented 10 years ago

When I looked into the OpenIABTest.cs file that was included in the Unity plugin, it contained the following:

private void Start() { // Map sku for different stores OpenIAB.mapSku(SKU, OpenIAB_Android.STORE_GOOGLE, "google-play.sku"); OpenIAB.mapSku(SKU, STORE_CUSTOM, "onepf.sku"); }

Seeing that, I assumed the "google-play." prefix was necessary for SKUs located on the Google Play store.

Thanks for explaining how the system works when not mapping - I'll keep that in mind!

Otherwise, I think the system is working. My test account is now able to purchase the in-app products for the correct price. Thanks!

GarrickWinter commented 10 years ago

So I've encountered something a bit problematic. It seems that when I call OpenIAB.queryInventory() immediately after OpenIAB.init(), my game crashes, while calling it later upon a button press works fine. I want to call queryInventory() at startup in order to make sure that the game knows from the get-go what purchases the player has made. How do I know when OpenIAB is ready for me to call queryInventory() ?

oorlov commented 10 years ago

What do you see on logs? In 0.9.4 it can be done by OpenIAB.enableDebugLogging(true)

oorlov commented 10 years ago

BTW, do you call queryInventory from "init successful" callback?

    OpenIABEventManager.billingSupportedEvent += billingSupportedEvent;

If "init failed" callback is triggered, call queryInventory() leads to exception

    OpenIABEventManager.billingNotSupportedEvent += billingNotSupportedEvent;
oorlov commented 10 years ago

Hi @GarrickWinter!

Does suggestions help with your case?

rosarioconti commented 10 years ago

@GarrickWinter I think it is crashing because of Unity and of the not singletoncall to the init. If you are familiar with unity yo ushould not about that. I have tested and it actually crash as you said. So try to use it in a singleton. hope it helps

GarrickWinter commented 10 years ago

Sorry I only just got back now. I actually found a workaround that was sufficient for my use case. Instead of calling Query immediately after Init, I call Query after any button press, and wait for the inventory success event to actually respond to the button press, ensuring that further screens in the game all work correctly and that the inventory was queried as well (just calling Query and immediately responding to the button would, if the scene changed, prevent the event from being triggered). The only downside is a slight delay, but otherwise everything worked fine.

The idea that using a singleton might work smoother is intriguing. I'll try that out next time I play around with OpenIAB.

tagcode commented 10 years ago

Hi, and tnx for great plugin. (Edit: Problem solved) Still, I seem to have a small problems. Can't figure out. Init (->billingSupportedEvent), QueryInventory (->queryInventorySucceededEvent) works ok and returns to callback, but purchaseProduct(sku) invokes no callback, no Fail or Succeed. Also, the Inventory in queryInventory() doesn't have my SKUs.

I am running with account other than developer account, and with an .apk that is in-production. (I tried beta and beta group too, no difference.)

When I go to the developer console, I can see my SKU's under the product's menu/In-app Purchases. Both SKUs are Active. The SKU id is in parenthesis. The public key is correct, it's the BASE64 encoded string.

I've ran with package from the Asset Store (12Mar2014), and from github (20Mar2014), both produce same results.

Here is some of the code.

private void Awake() {
    OpenIABEventManager.billingSupportedEvent += billingSupportedEvent;
    OpenIABEventManager.billingNotSupportedEvent += billingNotSupportedEvent;
    OpenIABEventManager.queryInventorySucceededEvent += queryInventorySucceededEvent;
    OpenIABEventManager.queryInventoryFailedEvent += queryInventoryFailedEvent;
    OpenIABEventManager.purchaseSucceededEvent += purchaseSucceededEvent;
    OpenIABEventManager.purchaseFailedEvent += purchaseFailedEvent;
    OpenIABEventManager.consumePurchaseSucceededEvent += consumePurchaseSucceededEvent;
    OpenIABEventManager.consumePurchaseFailedEvent += consumePurchaseFailedEvent;
}

private void OnDestroy() {
    OpenIAB.unbindService();
    // Remove all event handlers
    OpenIABEventManager.billingSupportedEvent -= billingSupportedEvent;
    OpenIABEventManager.billingNotSupportedEvent -= billingNotSupportedEvent;
    OpenIABEventManager.queryInventorySucceededEvent -= queryInventorySucceededEvent;
    OpenIABEventManager.queryInventoryFailedEvent -= queryInventoryFailedEvent;
    OpenIABEventManager.purchaseSucceededEvent -= purchaseSucceededEvent;
    OpenIABEventManager.purchaseFailedEvent -= purchaseFailedEvent;
    OpenIABEventManager.consumePurchaseSucceededEvent -= consumePurchaseSucceededEvent;
    OpenIABEventManager.consumePurchaseFailedEvent -= consumePurchaseFailedEvent;
}

public void InitIAP()
{
    // Map sku for different stores
    OpenIAB.mapSku(SKU, OpenIAB_Android.STORE_GOOGLE, SKU);

    // Init
    Options options = new Options();
    options.verifyMode = OptionsVerifyMode.VERIFY_SKIP;
    options.storeKeys.Add(OpenIAB_Android.STORE_GOOGLE, PUBLIC_KEY);        
    // Transmit options and start the service
    OpenIAB.init(options);
    //OpenIAB.enableDebugLogging (true);
}

public void Buy()
{       
    if (billingSupported) {
        DebugText.Write("Querying Inventory");
        OpenIAB.queryInventory();
        DebugText.Write ("Purchasing: " + SKU);
        DebugText.ClearTxt (20);
        OpenIAB.purchaseProduct (SKU);
    }
}

private void billingSupportedEvent() {
    billingSupported = true;
    DebugText.Write("Billing Supported");
    DebugText.ClearTxt (10);
     }

I've read the example through https://github.com/onepf/OpenIAB-angrybots/blob/master/Assets/Scripts/OpenIAB/BillingDemo.cs

(The only difference is that the example doens't set options.verifyMode.)

When purchase is called, nothing happens. Shouldn't a store window open or atleast failEvent be invoked?

I don't know what paths to exhaust any more. Any ideas?

Edit. I also have these permissions in the .apk's AndroidManifest.xml ( I checked with APKtool. ) uses-permission android:name="com.android.vending.BILLING" uses-permission android:name="org.onepf.openiab.permission.BILLING" uses-permission android:name="android.permission.INTERNET" uses-permission android:name="com.sec.android.iap.permission.BILLING"

Edit2: PROBLEM SOLVED! I was missing "org.onepf.openiab.UnityProxyActivity" Activity from AndroidManifest.xml.

akarimova commented 10 years ago

@GrimReio what do you think, should we close this issue?