j3k0 / cordova-plugin-purchase

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

IOS - subscription purchases generate bad appStoreReceipts & server validation shows nothing was purchased #982

Closed sfeast closed 4 years ago

sfeast commented 4 years ago

MacOS Mojave / Version 10.14.6 XCode 11.2.1 Cordova 9.0.0 / cordova-ios 5.1.1 cordova-plugin-purchase 10.0.1 Device: iOS all versions

Expected behavior

Subscription purchases provide a usable appStoreReceipt.

Observed behavior

I upgraded from plugin v5.0.2 to the latest v10.0.1 & everything appears to be working, including generating a transaction & appStoreReceipt, but when validating server-side it turns out nothing has been purchased. Note that I did not change my JS that handles purchase flow from the previous plugin - see below example.

Steps to reproduce

This is my purchase handling:


    function initStore() {
      store.validator = '<my validation URL>';

      //I have an array of products to register
      this.products.forEach(function(product){
        store.register({
          id: product.id,
          alias: product.alias,
          type:  store.PAID_SUBSCRIPTION
        });
      });

      store.when('product').approved(function(p) {
        let vP = p.verify();
        this.log('approved')

        vP.success(function(product, purchaseData){
          this.log('success')
        }.bind(this));

      }.bind(this));

      store.when('product').verified(function(p) {
          this.log('verify')
          p.finish();
      }.bind(this));

      store.when('product').owned(function(p) {
        this.log('owned');
      }.bind(this));

      store.ready(function() {this.log('ready')}.bind(this));
      store.refresh();
    }

All states are being triggered, however my server-side validator is showing something like this:

{
  "error": "failed to validate for empty purchased list",
  "response": {
    "receipt": {
      "receipt_type": "Production",
      "adam_id": 1241057014,
      "app_item_id": 1241057014,
      "bundle_id": "com.company.myapp",
      "application_version": "312",
      "download_id": 110044194199908,
      "version_external_identifier": 834461361,
      "receipt_creation_date": "2020-01-27 20:40:42 Etc/GMT",
      "receipt_creation_date_ms": "1580157642000",
      "receipt_creation_date_pst": "2020-01-27 12:40:42 America/Los_Angeles",
      "request_date": "2020-01-28 01:13:26 Etc/GMT",
      "request_date_ms": "1580174006726",
      "request_date_pst": "2020-01-27 17:13:26 America/Los_Angeles",
      "original_purchase_date": "2020-01-27 20:40:42 Etc/GMT",
      "original_purchase_date_ms": "1580157642000",
      "original_purchase_date_pst": "2020-01-27 12:40:42 America/Los_Angeles",
      "original_application_version": "312",
      "in_app": []
    },
    "status": 2,
    "environment": "Production",
    "service": "apple",
    "message": "The receipt is valid, but purchased nothing."
  }
}

e.g. the receipt passed to the server is valid but the user hasn't actually purchased anything. I'm currently returning a response of {ok: true, data: ...} even for these bad receipts but only because purchases are all generating bad receipts, so I'm letting them through for now (basically user is getting a free subscription currently to avoid complaints).

When purchasing with the 5.0.2 plugin version (as well as a couple of purchases made with the 10.0.1 plugin) the validator returns a full receipt with in_app array, latest_receipt_info, latest_receipt, etc. So I don't know what I'm doing wrong with the latest plugin as I haven't changed any code interfacing it 🤷‍♂

I've also noticed that the latest plugin logs the various states quite a bit more than previous versions - like 3 or 4 times, versus just once in the 5.0.2 version I was using.

sfeast commented 4 years ago

Just reverted my production app to version 5.0.2 of the plugin purchases working as expected again.

As mentioned above, my logs show the 10.0.1 plugin hitting the various states quite a bit, here's what I'm seeing typically in production: approved approved approved success ready verified success verified verified success verified owned owned owned owned

^ strange that ready is firing AFTER product purchase approval & verification success. The app initializes the store (with product registration & store.refresh) early on but ready isn't fired until this point on v10.0.1

While on the 5.0.2 version here's what I see in production: ready approved success verified owned

sfeast commented 4 years ago

Did some more debug testing on 10.0.1 & it works much better in development/sandbox mode vs production. Events still fire more frequently but valid appStoreReceipts are being generated & validated.

Also the ready event is fired earlier, soon after store.refresh() as I think it's supposed to be, while in production is comes after the purchase as seen in the previous comment.

Here's a sample of 10.0.1 event logging in development/sandbox mode: ready approved approved approved success verified success verified success verified

sfeast commented 4 years ago

@j3k0 @Dexus seems that I'm not the only one observing this either - https://github.com/j3k0/cordova-plugin-purchase/issues/966#issuecomment-570716405

j3k0 commented 4 years ago

It's not a bug in the plugin but in your validator (or app code, depends on how you see it). The plugin now offers the option to validate the application receipt. This is valid behaviour and even required in order to correctly determine discounts eligibility (so called intro price and discount offers on iOS).

It is probably sent to your validator because you call validate for all products, including the application product (newly introduced in version 10).

Please check the release notes for details:

https://github.com/j3k0/cordova-plugin-purchase/releases

Your receipt validator shouldn't return an error. A receipt without any purchase isn't invalid nor an error.

If you don't care about application receipt validation, you can just skip calling validate for the application production (whose ID is set to your bundle identifier and type is store.APPLICATION). A little "if" in the approved callback will do.

You can test with Fovea's receipt validator if you have a doubt about correct behaviour from a receipt validator: https://billing.fovea.cc/

sfeast commented 4 years ago

Ah I understand what you're describing here. I actually did read the release notes but it wasn't clear that any changes would be needed within the app code or the validator. Not to mention that purchases were going through successfully in dev/sandbox mode but not production so that makes the situation subtly deceptive. Probably some notice about this is needed in the release notes/docs.

That said, I still have a question. My validator was not actually returning an error, while I was waiting for Apple to approve my update with a revert to the old plugin I was allowing all purchases to go through. So I'm guessing that the reason why the full purchase flow events were being triggered multiple times is that as you say I was unknowingly validating all products. However when checking my Apple account's subscription stats the following days it is not showing that any subscriptions went through. Since I was validating & calling .finish() on everything, shouldn't at least the purchase have gone through with Apple?

geshub commented 4 years ago

@j3k0 @sfeast I also had to downgrade the plugin after releasing my app to production. All the tests were ok on IOS and Android when in Sandbox but in-app was not working on production. I tried to adjust my code after reading your explications about the new application product, but it's nearly impossible to debug in production. Also I noticed some finished transactions were coming back to validation on IOS after opening the app again. v10 is not an easy upgrade.

alexcroox commented 4 years ago

Note, an IF statement to compare against bundle identifier is required in both the validator method (if you use it) as well as the approved event.

For me, the application receipt never fires "approved" event on app load, it goes straight to the validation method itself.

sfeast commented 2 years ago

for reference: https://github.com/j3k0/cordova-plugin-purchase/issues/1155 https://github.com/j3k0/cordova-plugin-purchase/issues/1153

zlawson-ut commented 1 year ago

For others that encounter this issue, this did the trick in my case.