j3k0 / cordova-plugin-purchase

In-App Purchase for Cordova on iOS, Android and Windows
https://purchase.cordova.fovea.cc
1.29k stars 529 forks source link

(v11 regression? ) Purchases stuck pending/cancelled in some countries #1331

Closed AshleyScirra closed 1 year ago

AshleyScirra commented 1 year ago

We use cordova-plugin-purchase in Construct (www.construct.net), and recently updated from v10.6.1 to v11.0.0. After doing this, a user reported that their IAPs don't work in some countries now, such as Indonesia. They get left at "pending", then eventually get cancelled. They described this to us in this issue: https://github.com/Scirra/Construct-3-bugs/issues/6118

Looking at cordova-plugin-purchase's release notes, v11.0.0 updates to Google Play Billing library v4.0, but the notes say this update should have happened in a non-breaking way. The other changes in v11.0.0 don't look related (this happens with non-subscription IAPs).

Apparently this has come up elsewhere, and this forum thread notes: https://forum.unity.com/threads/iap-doesnt-work-for-indonesia.1165847/

Yes, you need to handle purchaseState = 4, a deferred purchase ... When you see purchaseState = 4, don't award the product and continue to return Pending from ProcessPurchase until the user finally pays at a Google-approved physical store. When purchaseState = 1, then award the product and return Complete from ProcessPurchase. This is a new purchase type from Google.

However looking over our own code's calls to cordova-plugin-purchase, this doesn't appear to be something to handle on our side. I think this needs to be fixed in cordova-plugin-purchase - perhaps there is a breaking change in v11 after all. Or if not please let me know what code we need to change on our side!

I see there's a v12, but it's not published to npm yet and it may involve further changes, so a patch if necessary for v11 would be useful.

j3k0 commented 1 year ago

Hi.

This case is handled by the plugin. I just made sure of that:


In the Java code, we make sure to enable pending purchases:

https://github.com/j3k0/cordova-plugin-purchase/blob/master/src/android/cc/fovea/PurchasePlugin.java#L232

Those get reported here:

https://github.com/j3k0/cordova-plugin-purchase/blob/master/src/android/cc/fovea/PurchasePlugin.java#L529

The event bubbles up to javascritp here:

https://github.com/j3k0/cordova-plugin-purchase/blob/master/src/js/platforms/plugin-adapter.js#L101

Which calls this:

https://github.com/j3k0/cordova-plugin-purchase/blob/master/src/js/platforms/android-productdata.js#L115

Which calls setProductData here:

https://github.com/j3k0/cordova-plugin-purchase/blob/master/src/js/platforms/android-productdata.js#L115

The "pending" case is handled here:

https://github.com/j3k0/cordova-plugin-purchase/blob/master/src/js/platforms/android-productdata.js#L45-L50

Product state is "initiated".


When the purchase is approved later on, the "approved" event will be triggered (because a purchaseUpdated event will be triggered with the new purchaseState)

Among possible problems:

  1. Your code is not set to handle "approved" event at app startup? If so, it will not handle pending purchases. It's a requirement on both iOS and Android.
  2. store.ready never triggers I've seen that happen when some custom receipt validator are used that don't fully implement the protocol (but I think I've only seen that on iOS).

v12 implements billing library v5, but with google introducing conceptual changes, it felt too "hacky" and we moved toward v13 which will definitely introduce breaking changes, to accommodate all the new capabilities of Play Billing Library v5 in a clean way. It will also allows multiple payment platforms to co-exist in the plugin (to offer multiple payment options in the apps, now that legislation make this possible).

AshleyScirra commented 1 year ago

Thanks for the quick response. Our code is relatively straightforward I think. Slightly simplifying our production code, we pretty much have just:

// On init
store.when("product").approved(OnApproved);
store.when("product").verified(OnVerified);
store.when("product").finished(OnFinished);

function OnApproved(product)
{
    product.verify();
}

function OnVerified(product)
{
    product.finish();
}

function OnFinished(product)
{
    // handle successful purchase
}

So we're handling approved events for all products.

@KekGames is the affected customer - are you using a validator URL by any chance?

KekGames commented 1 year ago

@KekGames is the affected customer - are you using a validator URL by any chance?

No

j3k0 commented 1 year ago

Ideally some logs of what happens at initialization when there is a pending purchase would be helpful. I'll see if I can reproduce on my end as well.

KekGames commented 1 year ago

I didn't try to reproduce and debug it if that's what you mean. Maybe @AshleyScirra can help you with this.

AshleyScirra commented 1 year ago

It's difficult for me to help test: I'm based in the UK, Construct is basically middleware and so we don't publish our own apps, let alone ones that take payments in Indonesia. I think if we need any kind of debug log we'll need to get that from @KekGames.

KekGames commented 1 year ago

@AshleyScirra I can't debug it myself since it works in my country. And I have no idea how to collect logs from other players, does Construct 3 have functionality for this?

AshleyScirra commented 1 year ago

@j3k0 - any ideas what we could do to try and diagnose this?

flori9 commented 1 year ago

I'm still using an older version of this plugin (will upgrade soon), and am also seeing this. I also remember seeing this same behaviour at least a year ago too. So I don't think it's related to the plugin upgrade.

Having said that, I'm not sure whether this is a bug at all. Aren't this just people (maybe kids) who click the "I'll pay later in a store" button that's available in countries like Indonesia, who actually have no intention to pay at all? I'm not exactly sure how that would display in the backend but I think it may look exactly like this - a pending payment to give people time to pay, and then a cancelation when they haven't paid after a few days.

v12 implements billing library v5, but with google introducing conceptual changes, it felt too "hacky" and we moved toward v13 which will definitely introduce breaking changes,

I totally understand that you'd like to keep your code clean and maintainable, and some breaking changes are fine, but with the forced Billing upgrades from Google every year please don't break too much if at all possible! Updating purchase code is not my favorite way to spend my time, which is one of the reasons that I really like abstraction of plugins like this - it's really awesome if I can just update the plugin yearly and don't worry about it otherwise.

j3k0 commented 1 year ago

I totally understand that you'd like to keep your code clean and maintainable, and some breaking changes are fine, but with the forced Billing upgrades from Google every year please don't break too much if at all possible! Updating purchase code is not my favorite way to spend my time, which is one of the reasons that I really like abstraction of plugins like this - it's really awesome if I can just update the plugin yearly and don't worry about it otherwise.

Feel free to bring that discussion to PR #1330 (I'll answer those concerns there right now).

AshleyScirra commented 1 year ago

Aren't this just people (maybe kids) who click the "I'll pay later in a store" button that's available in countries like Indonesia, who actually have no intention to pay at all?

@KekGames - do you think it is possible this is what you are seeing in your case?

KekGames commented 1 year ago

@AshleyScirra I doubt it, there are so far 22 purchase attempts from these particular countries. 100% of them follow this exact pattern, and none of the purchases from other countries do. My uneducated guess is that, perhaps these countries have some specific regulations for IAPs, so Google Play handles them differently and the plugin doesn't know how to react?

flori9 commented 1 year ago

It's not strange that it's only some countries, as pending purchases (pay with cash/card in a store) is only supported in some countries, though I haven't been able to find a list. This are also mostly relatively poor countries, so I wouldn't expect to sell a lot of IAP there anyway. And I am seeing some succesful purchases from these countries (e.g. Indonesia) as well, so there isn't something that makes all purchases there fail, There may be another reason. So if there's any way to investigate this I'm interested, as I'm seeing a whole lot of this as well - hard to count as this point but probably over a 100 cases in over a year. Edit: I'm by the way also seeing this on another app of mine that uses GameMaker and thus a whole different IAP framework, so if it's an issue it's rather widespread

KekGames commented 1 year ago

@flori9 maybe you're right, especially since you had some successful purchases there (I just assumed it didn't work at all)

AshleyScirra commented 1 year ago

@KekGames - does your app have a history of successful IAPs in these countries which appear to have stopped working recently? Or have you never seen successful IAPs in these countries with your app or any past app?

If you have never seen successful IAPs in these countries, I think 22 is a relatively small sample and, to me, the explanation that people set up payments but don't complete them would seem to make sense. I would guess a lot of people would try to click through a payment to see if they get the purchase before having to finish the payment - i.e. attempting to get away without paying - and all these attempts will come up as pending purchases that eventually cancel.

KekGames commented 1 year ago

@AshleyScirra I released this game only a few months ago and it got some traffic from these countries. I didn't find any purchase attempts, successful or otherwise, for my older games. Yes, flori9's guess is reasonable.

j3k0 commented 1 year ago

I'll close this for now as we can't gather more data and it doesn't look to be an issue specific to this plugin. Anyone who has more to add, free to reopen this issue.