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

[IOS] Ready event not fired #1248

Closed ArnauKokoro closed 1 year ago

ArnauKokoro commented 2 years ago

system info

iPhone X iOS 15 Plugin Version: 10.6.0

Expected behavior

Event Ready fired

Observed behavior

Event Ready is not fired. Products are loaded and registered but plugin never send ready event.

Steps to reproduce

When inside device ready callback add =>

store.ready(() => console.log("READY IN"))

SamiElkateb commented 2 years ago

Same for me, did you manage to solve it?

j3k0 commented 2 years ago

Hello, it might have to do with your receipt validation setup.

If a receipt validator is setup, ready will trigger after all receipts have been validated.

Can you share more code and logs?

kinggolf commented 2 years ago

I am seeing the same thing. Here's my code. I'll get some logs as well.

      this.plans.forEach((product) => {
        if (product.id !== 4) {
          const type =
            product.iapId === 'monthly'
              ? this.store.PAID_SUBSCRIPTION
              : this.store.NON_RENEWING_SUBSCRIPTION;
          this.store.register({ id: product.iapId, type });

          this.store.when(product.iapId).updated((p) => {
            if (p.owned && p.transaction) {
              this.activateSubscriptionHttp(
                p.transaction.id,
                p.transaction.original_transaction_id
                  ? p.transaction.original_transaction_id
                  : p.transaction.id,
                p.transaction.appStoreReceipt,
                null,
                product.iapId === 'monthly',
              );
            }
          });

          this.store.when(product.iapId).approved((p) => {
            p.verify();
          });
          this.store.when(product.iapId).verified((p) => {
            p.finish();
          });
          this.store.when(product.iapId).finished((p) => {
            if (p.transaction && p.type !== 'application') {
              this.activateSubscriptionHttp(
                p.transaction.id,
                p.transaction.original_transaction_id
                  ? p.transaction.original_transaction_id
                  : p.transaction.id,
                p.transaction.appStoreReceipt,
                p.transaction.purchaseToken,
                product.iapId === 'monthly',
              );
            }
          });
          this.store.when(product.iapId).cancelled((p) => {
            this.loadingService.dismissLoader(0, 'IAP cancelled');
          });
        }
      });
      this.store.ready(() => {
        console.log('Store is ready');
      });
      this.store.error((err) => {
        this.loadingService.dismissLoader(0, 'store error');
      });
      this.store.refresh();
kinggolf commented 2 years ago

Some logs. I am not seeing any previous receipts, which I thought would show in these log statements.

Not sure what should come after Product request started near very bottom.

 [log] - In setupIAPStore: product =  {"id":1,"iapId":"monthly","title":"MONTHLY MEMBERSHIP}
⚡️  [log] - [store.js] DEBUG: state: monthly -> registered
⚡️  [log] - [store.js] DEBUG: ios -> product monthly registered
⚡️  [log] - In setupIAPStore: product =  {"id":2,"iapId":"3_months","title":"90 DAY TEST PREP"}
To Native Cordova ->  InAppPurchase debug InAppPurchase33124809 ["options": []]
To Native Cordova ->  InAppPurchase setup InAppPurchase33124810 ["options": []]
⚡️  [log] - [store.js] DEBUG: state: 3_months -> registered
⚡️  [log] - [store.js] DEBUG: ios -> product 3_months registered
⚡️  [log] - In setupIAPStore: product =  {"id":3,"iapId":"12_months","title":"1 YEAR MEMBERSHIP"}
⚡️  [log] - [store.js] DEBUG: state: 12_months -> registered
⚡️  [log] - [store.js] DEBUG: ios -> product 12_months registered
⚡️  [log] - [store.js] DEBUG: store.trigger -> triggering action refreshed
⚡️  [log] - [store.js] DEBUG: ios -> initializing storekit
2021-10-14 11:04:59.657939-0700 App[657:25891] InAppPurchase[objc]: setup: OK
⚡️  To Native ->  App addListener 24167812
To Native Cordova ->  InAppPurchase load InAppPurchase33124811 ["options": [<__NSArrayM 0x282d2b510>(
monthly,
3_months,
12_months
)
]]
2021-10-14 11:04:59.827916-0700 App[657:25891] InAppPurchase[objc]: load: Getting products data
2021-10-14 11:04:59.828060-0700 App[657:25891] InAppPurchase[objc]: load: Set has 3 elements
2021-10-14 11:04:59.828200-0700 App[657:25891] InAppPurchase[objc]: load:  - monthly
2021-10-14 11:04:59.828254-0700 App[657:25891] InAppPurchase[objc]: load:  - 3_months
2021-10-14 11:04:59.828304-0700 App[657:25891] InAppPurchase[objc]: load:  - 12_months
⚡️  [log] - InAppPurchase[js]: setup ok2021-10-14 11:04:59.828414-0700 App[657:25891] InAppPurchase[objc]: load: Starting product request...

⚡️  [log] - [store.js] INFO: ios -> storekit ready
⚡️  [log] - [store.js] DEBUG: ios -> loading products
⚡️  [log] - InAppPurchase[js]: load ["monthly","3_months","12_months"]
2021-10-14 11:04:59.829015-0700 App[657:25891] InAppPurchase[objc]: load: Product request started
⚡️  WebView loaded
kinggolf commented 2 years ago

I do not think this is a plugin issue, nor an issue with the code. This app has been released since January and nothing in the IAP set up or implementation has changed w/ the latest updates. In fact I can roll back the code to version released to AppStore and see the exact same thing.

Seems to be related to iOS15 and TestFlight or sandbox users, or something along those lines.

kinggolf commented 2 years ago

I added manual store.refresh call:

⚡️  [log] - refreshStore
⚡️  [log] - [store.js] DEBUG: store.trigger -> triggering action refreshed
⚡️  [log] - [store.js] DEBUG: refresh -> checking products state (3 products)
⚡️  [log] - [store.js] DEBUG: refresh -> product id monthly (monthly)
⚡️  [log] - [store.js] DEBUG:            in state 'registered'
⚡️  [log] - [store.js] DEBUG: refresh -> product id 3_months (3_months)
⚡️  [log] - [store.js] DEBUG:            in state 'registered'
⚡️  [log] - [store.js] DEBUG: refresh -> product id 12_months (12_months)
To Native Cordova ->  InAppPurchase restoreCompletedTransactions INVALID ["options": []]
⚡️  [log] - [store.js] DEBUG:            in state 'registered'
⚡️  [log] - [store.js] DEBUG: store.trigger -> triggering action re-refreshed
To Native Cordova ->  InAppPurchase appStoreRefreshReceipt InAppPurchase251528113 ["options": []]
⚡️  [log] - InAppPurchase[js]: refreshing appStoreReceipt
2021-10-14 16:26:52.444235-0700 App[819:39234] InAppPurchase[objc]: appStoreRefreshReceipt: Request to refresh app receipt
2021-10-14 16:26:52.444504-0700 App[819:39234] InAppPurchase[objc]: appStoreRefreshReceipt: Starting receipt refresh request...
2021-10-14 16:26:52.445237-0700 App[819:39234] InAppPurchase[objc]: appStoreRefreshReceipt: Receipt refresh request started
2021-10-14 16:27:16.164750-0700 App[819:39234] [BackgroundTask] Background Task 2 ("SKProductsRequest"), was created over 30 seconds ago. In applications running in the background, this creates a risk of termination. Remember to call UIApplication.endBackgroundTask(_:) for your task in a timely manner to avoid this.
2021-10-14 16:27:27.171056-0700 App[819:39234] [BackgroundTask] Background Task 3 ("SKReceiptRefreshRequest"), was created over 30 seconds ago. In applications running in the background, this creates a risk of termination. Remember to call UIApplication.endBackgroundTask(_:) for your task in a timely manner to avoid this.

AppStore refresh receipt begins to start, but never finishes.

jscssphtml commented 2 years ago

Same Problem here. After refresh or when ordering a product it stucks at 'Product request started'. I testet on serveral devices. This problem only occurs on iOS15.

zvolnix commented 2 years ago

I am indeed having this issue as well however, when I test on simulator this does not happen, only on iOS device.

[BackgroundTask] Background Task 1 ("SKProductsRequest"), was created over 30 seconds ago. In applications running in the background, this creates a risk of termination. Remember to call UIApplication.endBackgroundTask(_:) for your task in a timely manner to avoid this.

****EDIT*** I restarted my device and ran the app again and it's now working!

franalt commented 2 years ago

I have the same issue on iOS 15, even on a release version of my application which was working flawlessly. It only ocurrs only on devices running iOS 15. Restarting the device partialy works, if the purchase fails and you try again you are stuck with the same problem, so its no fix... There seems to be some kind of issue with receipt validation on iOS 15. Anyone managed to find a fix or workaround?

kinggolf commented 2 years ago

Hey all. I just downloaded iOS 15.1 and tried IAP w/ no changes to my code since before my post 25 days ago. It worked!! Previously, I was on 15.0.2.

It was a little glitchy, e.g. had to sign in w/ Apple ID twice, but the IAP on TestFlight was successful for me.

I received this message when first powered up the app. I have never seen before, but seems to be related to Sandbox account and has been around. https://developer.apple.com/forums/thread/125164

franalt commented 2 years ago

I can verify iOS 15.1 fixes some things.

However, there is still an issue with expired subscriptions.

Upon calling store.refresh() i get the following:

DUBUG: state: productid -> requested DUBUG: state: productid -> approved DUBUG: ios: -> productid expired DUBUG: ios: -> productid owned=false DUBUG: state: productid -> valid

Calling store.get(productid).canPurchase returns true but calling store.order(productid) get me the same output as above, and no purchase window is opened.

Can anyone help me?

EDIT

I took out receipt validation, re-installed the app, and is working, partially.

I have an expired subscription, and upon calling store.refresh() i get the following:

DUBUG: state: productid -> valid DUBUG: state: productid -> approved DUBUG: state: productid -> finished DUBUG: state: productid -> owned

Calling store.get(productid).canPurchase returns false but for whatever reason calling store.order(productid) opens the purchase window (Which is correct).

So if anyone is facing this issue, remove the store.validator function.

I will skip client-side validation and enable/disable functionallity from the backend for now.

zvolnix commented 2 years ago

I have the same issue on iOS 15, even on a release version of my application which was working flawlessly. It only ocurrs only on devices running iOS 15. Restarting the device partialy works, if the purchase fails and you try again you are stuck with the same problem, so its no fix... There seems to be some kind of issue with receipt validation on iOS 15. Anyone managed to find a fix or workaround?

I never had the issue again after that also, when building do you use --prod tag? I always have issues with this plugin when I use --prod.

aliaftech commented 2 years ago

I am having same issue, where testflight works fine and order button does not work on live/production. What I have noticed is that if I click the button leave it sitting for 3 to 4 minutes then it starts working, not sure what is causing it but hard to debug when testflight works and production is showing issue

@j3k0 any idea on how can we fix this issue.

aliaftech commented 2 years ago

anyone able to solve this issue?

MarcRoemmelt commented 2 years ago

@aliaftech , @franalt This issue can occur when the validator is not completing correctly - usually because a custom method is specified which does not call the callback function.

Note that IOS generates an additional purchase on startup for the app itself (with the product.id === bundleId) even for free apps. Make sure that the callbackFN is also called for this purchase. This purchase does not exist on Testflight or on Android so the error is specific to live IOS apps.

Maybe this could be added the to documentation!

aliaftech commented 2 years ago

@MarcRoemmelt thank you for pointing out about the additional purchase.. is there any documentation that you followed for this? I am trying to check what will be correct way to trigger the callback for that purchase..

MarcRoemmelt commented 2 years ago

@aliaftech I am not aware of this being documented anywhere. Generally we are simply calling the callback with true since the app is either free or paid for if it was downloaded (this might be different if you share ad-hoc builds).

So just doing if (product.id === <BUNDLE_ID>) callback(true, product.transaction) should be fine in most cases.

mayankkataria commented 2 years ago

I'm facing the same issue in android as well

firebiird commented 2 years ago

I had this issue for a while. As another comment here has stated, if you have a custom validator you must make sure that you are resolving the callback for all validation requests. you can do this after you check if the product matches yours with a catch all statement of :

callback(true, product.transaction) 

Please make sure your not giving your hard work away for free before copying this code 😂

aliaftech commented 1 year ago

Note that IOS generates an additional purchase on startup for the app itself (with the product.id === bundleId) even for free apps. Make sure that the callbackFN is also called for this purchase. This purchase does not exist on Testflight or on Android so the error is specific to live IOS apps.

this is the main issue

alyleite commented 1 year ago

Thanks @MarcRoemmelt and @aliaftech. It worked