cordova-plugin-inapppurchase icon indicating copy to clipboard operation
cordova-plugin-inapppurchase copied to clipboard

Sharing my great collection or errors returned by iTunes Store (affecting 6% of users)

Open nilebma opened this issue 7 years ago • 14 comments

Hi Everyone.

I have an app in production, with In-App purchase working on Android and iOS (~12 sales validated every day on both platforms) thanks to this plugin :-)

However, for ~6% of total iOS users, my bug reporting tool shows me that errors are returned when trying to deal with the iTunes Store Service.

These errors appears when trying get products info (at every app start), to buy a product (non-consumable or consumable), or to restore purchase.

The message is always the same "Cannot connect to iTunes Store". It comes along with an enigmatic error code that could vary from : 0,2,-2,110,-1001,-1003.

I can't find any places where this errorCode are officially explained, does someone know a ressource for that ?

Overall these errors affects 6% or total users, and amongst them a lot of potential buyers :-/. Some of them are also complaining about their purchase not being activated in the app. I can see an errorCode number 2 for these buyers.

I checked the data reported by my tool, but I can't see any correlations between these errors and a particular version of iOS or iDevice. I also checked and rechecked my code, as well the way my products are set up in iTunes Connect.

I use the last version of the plugin, Ionic 3.6.0, and Cordova 7.0.1 (with crosswalk plugin).

Thanks,

Amaury.

nilebma avatar Aug 25 '17 12:08 nilebma

Some of them are also complaining about their purchase not being activated in the app. I can see an errorCode number 2 for these buyers.

This is happening for me on a percentage of my orders. Most orders complete successfully, but in some cases the error is returned even though the user is charged.

giltotherescue avatar Sep 07 '17 13:09 giltotherescue

Confirming this behavior on my end as well. We've been running in production for quite a few months (with peaks of 100+ new subs / day) and we still haven't figured out what the issue is. In addition to your examples, we also experience that the purchase promise never resolves nor rejects on the JS side.

We've been monitoring Stackoverflow, checked forums , and tbh – there seems to be so many potential sources for these error codes that I don't even know where to start. Other StoreKit libraries also experience several of these issues.

This plugin builds on RMStore, which have been abandoned for a while and could very well be the source of the issue. I think we might be better off rewriting the iOS code from scratch or build upon something like SwiftyStoreKit if they manage to solve this issue.

On the flip side, Android purchases works like a charm.

kristfal avatar Sep 14 '17 08:09 kristfal

@kristfal : about the purchase promise never being resolved, I present a (ugly but effective) workaround here : https://github.com/AlexDisler/cordova-plugin-inapppurchase/issues/113#issuecomment-325597717

nilebma avatar Sep 28 '17 09:09 nilebma

@nilebma thanks for the feedback. Do you run server side validation of the order? We are, and i’m afraid that your workaround wouldn’t work for us since we need the receipt data to do SSV. Please correct me if I’m wrong.

kristfal avatar Sep 28 '17 11:09 kristfal

I'm actually using a different cordova plugin (https://github.com/j3k0/cordova-plugin-purchase), but I was looking at this one due to an issue I just opened (https://github.com/j3k0/cordova-plugin-purchase/issues/631). That issue might be related to the problems that you all are seeing 😢

TLDR: It appears that when a user is prompted to update their App Store payment information before purchasing, a transaction will move to SKPaymentTransactionStateFailed before receiving SKPaymentTransactionStatePurchased once their information is updated.

Thus, a proper implementation of the iOS native side of things needs to be able to handle this. A brief glance at RMStore seems to suggest that they did not account for this edge case, but I'm not too familiar with Obj C so I could be wrong.

jonathankau avatar Oct 02 '17 18:10 jonathankau

I have solved the problem a different way. Am currently beta testing the fix in my app and will roll it into production soon. Here is my new process:

  1. Anytime a user visits the main page of the app, call getReceipt() to get the latest receipt. This includes all current and expired subscriptions.
  2. Send the receipt to a custom remote receipt validation script I wrote (I'm planning to open source this at some point, hit me up if you need it now).
  3. The remote receipt validation script returns a JSON object with the validity of the receipt and the bundle IDs for all active and expired subscriptions.
  4. With the EXPIRED subscriptions, I set an "isExpired" flag on the subscription in my user database. This way, when they attempt to use the subscription they see a note indicating it needs to be repurchased.
  5. With the ACTIVE subscriptions, I check to see if the user currently has the subscripton, and if not I automatically add the subscription for them. This solves the issue presented above.
  6. When the user completes a purchase I forward them back to the main page of the app, so that the receipt gets checked right away, and if the program is missing it is added right away.
  7. I am experimenting with the concept of caching the receipt for a certain period of time, in case the user is prompted too often to enter a password. The cache would be invalidated right before a new purchase is made, and anytime the "Restore Purchases" button (required by Apple) is clicked in the app.

Hope this helps anyone!

giltotherescue avatar Oct 06 '17 12:10 giltotherescue

@giltotherescue Any news on sharing your receipt validation script?

cmaas avatar Jun 06 '18 12:06 cmaas

@cmass Sorry, I never got around to open sourcing it. I am happy to discuss with you and share some code. You can reach me on telegram or twitter @giltotherescue.

giltotherescue avatar Jun 06 '18 17:06 giltotherescue

@giltotherescue Thanks for the offer. I actually figured AFTER my post that the validation server from Apple returns a simple JSON object. That's enough for me. I thought I had to parse the ASN.1 format or whatever they use for their transactions, which seemed like a pain in the ass. The rest seems pretty specific to my data model anyways ;)

cmaas avatar Jun 07 '18 10:06 cmaas

@giltotherescue Question:

Anytime a user visits the main page of the app, call getReceipt() to get the latest receipt. This includes all current and expired subscriptions.

Doesn't this trigger an iOS-popup asking the user to enter their iTunes password all the time?

cmaas avatar Jun 07 '18 14:06 cmaas

Yes, it does :-(

A better solution, which we eventually came up with, was to store the original receipt in our database. You can use that to validate from then on.

On Thu, Jun 7, 2018 at 9:55 AM Chris Maas [email protected] wrote:

@giltotherescue https://github.com/giltotherescue Question:

Anytime a user visits the main page of the app, call getReceipt() to get the latest receipt. This includes all current and expired subscriptions.

Doesn't this trigger an iOS-popup asking the user to enter their iTunes password all the time?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/AlexDisler/cordova-plugin-inapppurchase/issues/170#issuecomment-395450942, or mute the thread https://github.com/notifications/unsubscribe-auth/AAWqMiHwzj4so7IiFXv_G2DXtgHL6Dfdks5t6T7zgaJpZM4PCoBI .

giltotherescue avatar Jun 07 '18 15:06 giltotherescue

@giltotherescue

A better solution, which we eventually came up with, was to store the original receipt in our database.

But how did you receive the receipt at first after all? From my understanding, the problem is the following: There's a bug in the RMStore library. When a user requires to verify or change their payment details (like update the address or credit card), the promise is never returned or extremely late (minutes after the purchase). Thus, if the user sends the app to the background, JavaScript execution is halted or killed and the receipt can never be put in the database.

My solution now is: Show a small button in iOS only like "Account still inactive? Re-check payments" which triggers getReceipt() and does the whole validation logic you mentioned.

So, how do you receive the original receipt in the first place?

cmaas avatar Jun 08 '18 08:06 cmaas

Sorry for the delayed reply @cmaas. Most of the time, the receipt was received with no problem. For the times it didn't work (which was about 1% for me) I solved it the same way as you recommended.

giltotherescue avatar Jun 14 '18 20:06 giltotherescue

@nilebma Have you found any solution? We have a similar issue but in our case about 70%-80% of payments fail (error code 2 or code 0). Some may intentionally click cancel button while purchasing but for me the numbers are much too big to assume that this is normal behavior.

medizzy avatar Apr 10 '19 09:04 medizzy