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] Appstore products appear to load, but products list is empty, Appstore Plugin undefined. #1394

Closed jmdjr closed 1 year ago

jmdjr commented 1 year ago

Observed behavior

[Log] [CdvPurchase] INFO: initialize() (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO: Adding platforms: [{"platform":"ios-appstore"}] (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO:  (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO: AppStore initializing... (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: bridge.init (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: setup ok (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: ready (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: bridge.init done (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO: AppStore initialized.  (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO: AppStore products: [{"id":"app.sgl.linker.iap.test.1","platform":"ios-appstore","type":"consumable"}] (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: bridge.load (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: load ["app.sgl.linker.iap.test.1"] (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: load ok: { valid:[{"id":"app.sgl.linker.iap.test.1","description":"test purchasing something","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"SGL: Test purchase 1","price":"$0.99","billingPeriod":0,"group":null,"priceMicros":990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null}] invalid:[] } (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: bridge.loaded: {"validProducts":[{"id":"app.sgl.linker.iap.test.1","description":"test purchasing something","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"SGL: Test purchase 1","price":"$0.99","billingPeriod":0,"group":null,"priceMicros":990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null}],"invalidProducts":[]} (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: eligibilities ready. (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] DEBUG: app.sgl.linker.iap.test.1 is valid: {"id":"app.sgl.linker.iap.test.1","description":"test purchasing something","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"SGL: Test purchase 1","price":"$0.99","billingPeriod":0,"group":null,"priceMicros":990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null} (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] DEBUG: registering existing product (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] DEBUG: Products loaded: [{"className":"Product","title":"SGL: Test purchase 1","description":"test purchasing something","platform":"ios-appstore","type":"consumable","id":"app.sgl.linker.iap.test.1","offers":[{"className":"Offer","id":"$","pricingPhases":[{"price":"$0.99","priceMicros":990000,"currency":"USD","paymentMode":"UpFront","recurrenceMode":"NON_RECURRING"}],"productId":"app.sgl.linker.iap.test.1","productType":"consumable","platform":"ios-appstore","offerType":"Default"}],"raw":{"id":"app.sgl.linker.iap.test.1","description":"test purchasing something","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"SGL: Test purchase 1","price":"$0.99","billingPeriod":0,"group":null,"priceMicros":990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null},"countryCode":"US"}] (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO: AppStore loaded: [{"className":"Product","title":"SGL: Test purchase 1","description":"test purchasing something","platform":"ios-appstore","type":"consumable","id":"app.sgl.linker.iap.test.1","offers":[{"className":"Offer","id":"$","pricingPhases":[{"price":"$0.99","priceMicros":990000,"currency":"USD","paymentMode":"UpFront","recurrenceMode":"NON_RECURRING"}],"productId":"app.sgl.linker.iap.test.1","productType":"consumable","platform":"ios-appstore","offerType":"Default"}],"raw":{"id":"app.sgl.linker.iap.test.1","description":"test purchasing something","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"SGL: Test purchase 1","price":"$0.99","billingPeriod":0,"group":null,"priceMicros":990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null},"countryCode":"US"}] (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: processing pending transactions (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] DEBUG: loading appstore receipt... (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: loading appStoreReceipt (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: infoPlist: app.lostseraph.linker,1.0,16809984,null (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] DEBUG: appstore receipt loaded (user-script:13, line 321)
[Warning] [CdvPurchase.AppleAppStore] WARNING: no appStoreReceipt (user-script:13, line 321)

and on the app none of the products are visible? after all this, everything looks great, but inspecting the appstore adapter

const appStore = store.getAdapter(CdvPurchase.Platform.APPLE_APPSTORE);

is undefined. I have In-App Purchases enabled in the XCode, and my implementation follows all the tutorials (for the most part) yet this is happening...

Expected behavior

The CdvPurchase.store.products list has the loaded products, or at least the appstore adapter reporting itself as undefined.

System Info

XCode v 14.2 iMac MacOS Ventura 13.2.1 Capacitor v. 4.6.0 (capacitor apps are based off cordova, don't see how this would make a difference here)

j3k0 commented 1 year ago

The list of products is empty because the App Store adapter didn't reach the "ready" state. I wonder if it will work with the integration of a receipt validation service? The use case is more broadly tested. You can try this quickly with a free account on https://www.iaptic.com/

jmdjr commented 1 year ago

smooth method to "recommend" your company's services... but honestly was hoping not to need any more accounts to use iap. going to test with the free account but really sad that I have to use a service to get a free component to function.

jmdjr commented 1 year ago

how do we integrate the receipt validation service? the documentation links: https://www.iaptic.com/documentation/setup/ios is out of date (the images are of older ui and step 5/6 no longer exist on those pages) and https://www.iaptic.com/documentation/validator-url/ is straight up 404.

jmdjr commented 1 year ago

image

j3k0 commented 1 year ago

smooth method to "recommend" your company's services... but honestly was hoping not to need any more accounts to use iap. going to test with the free account but really sad that I have to use a service to get a free component to function.

You don't have to. I'm trying to understand what doesn't work in your case. As far as I know you're the only user with this issue, so I'm trying to eliminate possible reasons.

The broken link and outdated screenshots are fixed.

jmdjr commented 1 year ago

That was rude of me. I am sorry. It was uncalled for. I am going to review the updates and post the updates.

jmdjr commented 1 year ago
[Log] onscript loading complete (user-script:11, line 1456)
[Log] -IAP-: CdvPurchase IS defined (main.js, line 1)
[Log] -IAP-: installing iaptic receipt validator... (main.js, line 1)
[Log] -IAP-: registering products... (main.js, line 1)
[Log] -IAP-: setting up listeners... (main.js, line 1)
[Log] -IAP-: Store initializing... (main.js, line 1)
[Log] [CdvPurchase] INFO: initialize() (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO: Adding platforms: [{"platform":"ios-appstore"}] (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO:  (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO: AppStore initializing... (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: bridge.init (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: setup ok (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: ready (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: bridge.init done (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO: AppStore initialized.  (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO: AppStore products: [{"id":"app.sgl.linker.iap.test.1","platform":"ios-appstore","type":"consumable"}] (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: bridge.load (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: load ["app.sgl.linker.iap.test.1"] (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: load ok: { valid:[{"id":"app.sgl.linker.iap.test.1","description":"test purchasing something","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"SGL: Test purchase 1","price":"$0.99","billingPeriod":0,"group":null,"priceMicros":990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null}] invalid:[] } (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: bridge.loaded: {"validProducts":[{"id":"app.sgl.linker.iap.test.1","description":"test purchasing something","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"SGL: Test purchase 1","price":"$0.99","billingPeriod":0,"group":null,"priceMicros":990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null}],"invalidProducts":[]} (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] INFO: eligibilities ready. (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] DEBUG: app.sgl.linker.iap.test.1 is valid: {"id":"app.sgl.linker.iap.test.1","description":"test purchasing something","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"SGL: Test purchase 1","price":"$0.99","billingPeriod":0,"group":null,"priceMicros":990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null} (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] DEBUG: registering existing product (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] DEBUG: Products loaded: [{"className":"Product","title":"SGL: Test purchase 1","description":"test purchasing something","platform":"ios-appstore","type":"consumable","id":"app.sgl.linker.iap.test.1","offers":[{"className":"Offer","id":"$","pricingPhases":[{"price":"$0.99","priceMicros":990000,"currency":"USD","paymentMode":"UpFront","recurrenceMode":"NON_RECURRING"}],"productId":"app.sgl.linker.iap.test.1","productType":"consumable","platform":"ios-appstore","offerType":"Default"}],"raw":{"id":"app.sgl.linker.iap.test.1","description":"test purchasing something","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"SGL: Test purchase 1","price":"$0.99","billingPeriod":0,"group":null,"priceMicros":990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null},"countryCode":"US"}] (user-script:13, line 321)
[Log] [CdvPurchase.Adapters] INFO: AppStore loaded: [{"className":"Product","title":"SGL: Test purchase 1","description":"test purchasing something","platform":"ios-appstore","type":"consumable","id":"app.sgl.linker.iap.test.1","offers":[{"className":"Offer","id":"$","pricingPhases":[{"price":"$0.99","priceMicros":990000,"currency":"USD","paymentMode":"UpFront","recurrenceMode":"NON_RECURRING"}],"productId":"app.sgl.linker.iap.test.1","productType":"consumable","platform":"ios-appstore","offerType":"Default"}],"raw":{"id":"app.sgl.linker.iap.test.1","description":"test purchasing something","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"SGL: Test purchase 1","price":"$0.99","billingPeriod":0,"group":null,"priceMicros":990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null},"countryCode":"US"}] (user-script:13, line 321)
[Log] -IAP-: AppStore users can make payments (main.js, line 1)
[Log] -IAP-: checking app store redemption sheet... (main.js, line 1)
[Log] -IAP-: Store initialize done... (main.js, line 1)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: processing pending transactions (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] DEBUG: loading appstore receipt... (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: loading appStoreReceipt (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore.Bridge] DEBUG: infoPlist: app.lostseraph.linker,1.0,16809984,null (user-script:13, line 321)
[Log] [CdvPurchase.AppleAppStore] DEBUG: appstore receipt loaded (user-script:13, line 321)
[Warning] [CdvPurchase.AppleAppStore] WARNING: no appStoreReceipt (user-script:13, line 321)
[Log] -IAP-: running update purchases... (main.js, line 1)
...
< redacted PI related logs >
...
[Log] -IAP-:  (main.js, line 1)
TESTS:
____________________________
list: false
actual product: undefined
test product: undefined

should I attach my iap connection logic?

jmdjr commented 1 year ago

it ends up allowing the appstore to initialize and the appstore is available, but the list is still empty and trying to get a product is undefined. later test show that while the appstore adapter is defined now, it is not "ready" after initializing the store.

jmdjr commented 1 year ago

I have a MVP of the project (takes out all the wrapping stuff and just the iap, which still doesn't connect. if that would be helpful? LinkerMVP_Purchasing.zip

j3k0 commented 1 year ago

It looks like the CdvPurchase plugin is loaded twice.

I added this in the service constructor (right after "CdvPurchase is defined")

 (CdvPurchase as any).createdAt = new Date();

And this:

 (CdvPurchase as any).debuggedAt = new Date();

In the debug_Purchases() function.

Just after startup, when I've seen the "CdvPurchase is defined" log message, I open Safari's developer tools, but createdAt is undefined.

... clicking the debug button I get debuggedAt defined, createdAt still undefined.

Somehow the CdvPurchase object used by the service is different from the global object, or this global object has been overiden.

Quick test: adding window._CdvPurchase = CdvPurchase in the service constructor. And see if window.CdvPurchase === CdvPurchase.

In the constructor: window.CdvPurchase === CdvPurchase is true, however from debug_Purchases, window._CdvPurchase !== CdvPurchase.... So the global object was overridden, as if the plugin was loaded twice?

Also notice, in debug_Purchases, _CdvPurchase.store.products contains the non-empty list of products...

This is the root of the issue.

I have no idea what triggers this "double loading" of the plugin, working on a workaround...

NOTE:

Sometime at startup I get this logs:

⚡️  [log] - [LostSeraph] [-IAP-]: CdvPurchase IS defined!!
⚡️  [error] - ERROR Error: Uncaught (in promise): TypeError: undefined is not an object (evaluating 'CdvPurchase.LogLevel.DEBUG')

From this code:

        this.logger.log("CdvPurchase IS defined!!");
        const store = CdvPurchase.store;
        store.verbosity = CdvPurchase.LogLevel.DEBUG;

It means there's really something fishy with the way the plugin's JavaScript is being loaded by the bundler.

jmdjr commented 1 year ago

thank you for continuing to work on this, I saw you push a couple of other changes, but are they not enough to solve the issue?

j3k0 commented 1 year ago

No, it didn't solve the issue, I tried a few quick fix that I thought would be simple... but eventually it's not that simple, as it either broke access to CdvPurchase's enums or the CdvPurchase.store object. I have to step through what your app is doing to figure out how is this happening in the first place and get a better understanding.

I see that the code of the plugin is present in the main.js (bundled js file) but also in plugins/cordova/etc/..., possible that there are 2 separate instances of the javascript code loaded...?

jmdjr commented 1 year ago

hrm... that is odd. it would explain why my particular project isn't working while others are just fine, if that is the case. I take it the mvp project helps?

j3k0 commented 1 year ago

MVP helps, because it allows to reproduce the issue.

j3k0 commented 1 year ago

Look, the 2 "CdvPurchase.store" instance have different types:

Screenshot 2023-03-20 at 18 49 05

One is "Store" the other is "F", I guess the second one is from a minified version of the same code while the first is the non-minified version. So the problem definitely isn't with the plugin, just that you somehow bundle and load the code twice.

jmdjr commented 1 year ago

I see how that can be troublesome... odd. I don't know why I am loading the unminified version and the minified version at the same time...

j3k0 commented 1 year ago

I believe capacitor is probably already loading the plugin's js and angular's build chain also do.

On Mar 20, 2023 at 18:53:50, John M Davis Jr @.***> wrote:

I see how that can be troublesome... odd. I don't know why I am loading the unminified version and the minified version at the same time...

— Reply to this email directly, view it on GitHub https://github.com/j3k0/cordova-plugin-purchase/issues/1394#issuecomment-1476597550, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABO3CNT767OK6KHZY47COTW5CDR5ANCNFSM6AAAAAAVYPUDEI . You are receiving this because you modified the open/close state.Message ID: @.***>

jmdjr commented 1 year ago

ah. ok. armed with that I can look to see how to break the double loading. thank you for your hard work!

j3k0 commented 1 year ago

Maybe there's something I could do in the plugin to distribute it in a way that better suits your case. Let me know. I keep digging a little on your issue.

On Mar 20, 2023 at 18:59:14, John M Davis Jr @.***> wrote:

ah. ok. armed with that I can look to see how to break the double loading. thank you for your hard work!

Message ID: @.*** com>

jmdjr commented 1 year ago

will do. I don't know how I double loaded the code, will try some unloading/reloading methods to see. seems like an edge case but if it helps make your codebase more resilient, maybe some minor reworking would be good.

j3k0 commented 1 year ago

I found a fix that works here at least:

In your code, replace:

import "cordova-plugin-purchase";

with:

import 'cordova-plugin-purchase/www/store.d';

This way, angular only includes the typings, not the actual code of the plugin.

jmdjr commented 1 year ago
⚡️  [log] - [LostSeraph] [-IAP-]: CdvPurchase IS defined

⚡️  [log] - [LostSeraph] [-IAP-]: installing iaptic receipt validator...

⚡️  [error] - ERROR Error: Uncaught (in promise): TypeError: undefined is not an object (evaluating 'this.store.log')

Iaptic@user-script:13:104:34

@capacitor://www.lostseraph.com/main.js:1:646682

onInvoke@capacitor://www.lostseraph.com/main.js:1:87039

run@capacitor://www.lostseraph.com/polyfills.js:1:1920

@capacitor://www.lostseraph.com/polyfills.js:1:16761

onInvokeTask@capacitor://www.lostseraph.com/main.js:1:86859

runTask@capacitor://www.lostseraph.com/polyfills.js:1:2541

_@capacitor://www.lostseraph.com/polyfills.js:1:9190

trying the updated input and pushed to device, this is the result.

j3k0 commented 1 year ago

Oh yeah, maybe because I made this change to your code's checkReady as well:

async checkReady() { return new Promise(resolve => { const wait = () => { if(!window.CdvPurchase || !window.CdvPurchase.store) {

Added this to the if condition: || !window.CdvPurchase.store

j3k0 commented 1 year ago

And make sure to remove the cordova-pluing-purchase import from iapCatalog as well

jmdjr commented 1 year ago

did that. added your extra check... still the same exception...

jmdjr commented 1 year ago

with the setup you provided

> window.CdvPurchase.store
< Store {0: "QUIET", 1: "ERROR", 2: "WARNING", 3: "INFO", 4: "DEBUG", 6777001: "SETUP", 6777002: "LOAD", 6777003: "PURCHASE", 6777004: "LOAD_RECEIPTS", 6777005: "CLIENT_INVALID", …}
> window._CdvPurchase.store
< TypeError: undefined is not an object (evaluating 'window._CdvPurchase.store')
j3k0 commented 1 year ago

My edited project, works fine here (notice I changed app ID in XCode project, might have to be reverted to your own). LinkerMVP_Purchasing_Edited.zip

j3k0 commented 1 year ago

window._CdvPurchase.store will be undefined unless you stored window._CdvPurchase somewhere in your code.

jmdjr commented 1 year ago

will try it :D... and ha. ok.

jmdjr commented 1 year ago

OK! whatever you did it works on your version. so now I get to find the differences between that edited and my local one. yay! thank you.

jmdjr commented 1 year ago

is the purchase supposed to show a confirmation before popping up the customary apple purchase dialog? I am only seeing the "type Y for purchase" and not the actual in-app purchase view.