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] this.iap2.order not opening the purchase pop up #1247

Open j0zt opened 2 years ago

j0zt commented 2 years ago

I have implemented in app purchase in ionic3. Hope i have included all the relevant codes. Note: I have used custom validator. Earlier the purchase popup shown and i have paid. But after including the validator, the popup failed to open. i.e this,iap2.order is not called.

I have included my source code below with the Debug logs.

Can anyone help me to sort out this issue.

This is the setup function which is called in platform ready. setup() { this.iap2.verbosity = this.iap2.DEBUG; this.registerProducts(); this.iap2.get('PRODUCT_ID'); this.registerHandlersForPurchase("PRODUCT_ID"); this.iap2.refresh(); }

I have called this checkout function when a button is clicked. checkout() { this.registerHandlersForPurchase('PRODUCT_ID'); let product = this.iap2.get('PRODUCT_ID'); alert('Product Info: ' + JSON.stringify(product)); this.iap2.order("PRODUCT_ID"); alert('Purchase Succesful'); }

This is my listener function..

`registerHandlersForPurchase(productId) { let self = this.iap2; this.iap2.when(productId).updated((product: IAPProduct) => { console.log('Update Listener Loaded' + JSON.stringify(product)); if(product.loaded && product.valid && product.state === this.iap2.APPROVED) { product.finish(); } });

this.iap2.when(productId).registered((product: IAPProduct) => {
});
this.iap2.when(productId).owned((product: IAPProduct) => {

});
this.iap2.when(productId).approved((product: IAPProduct) => {
    product.verify();
});
this.iap2.when(productId).refunded((product: IAPProduct) => {
});
 this.iap2.validator = function (product: IAPProduct, callback) {    
    this.services
      .Data("customvalidator/",receipt)
      .subscribe(
        (response) => {
               if (response.status === 'success') {
                    callback(true, product.transaction);                 
                    product.finish();
               } else 
                    alert(response.error);
               }           
      callback(false, 'validation-fails');
    }, err => {          
      callback(false, 'validation-fails');
    });
}.bind(this); 

this.iap2.when(productId).verified((product: IAPProduct) => {
   alert('verified');      
  product.finish();
});

this.iap2.when(productId).cancelled((product) => {
   alert("cancelled");      
});

this.iap2.when(productId).expired((product: IAPProduct) => { alert('expired'); });
// Track all store errors this.iap2.error( (err) => { alert('Store Error ' + JSON.stringify(err)); });

}`

Debug log:

[Log] plan name -> – "PRODUCT_ID" (cordova.js, line 1540) [Log] [store.js] DEBUG: update() (cordova.js, line 1540) [Log] InAppPurchase[js]: loading appStoreReceipt (cordova.js, line 1540) [Log] InAppPurchase[js]: infoPlist: com.testapp.com,1.5.39,0,???? (cordova.js, line 1540) [Log] [store.js] DEBUG: state: com.testapp.com -> (cordova.js, line 1540) [Log] [store.js] DEBUG: state: com.testapp.com -> registered (cordova.js, line 1540) [Log] [store.js] DEBUG: ios -> product com.testapp.com registered (cordova.js, line 1540) [Log] [store.js] DEBUG: state: com.testapp.com -> approved (cordova.js, line 1540) [Log] [store.js] DEBUG: state: com.testapp.com -> approved (cordova.js, line 1540) [Log] InAppPurchase[js]: loading appStoreReceipt (cordova.js, line 1540) [Log] InAppPurchase[js]: infoPlist: com.testapp.com,1.5.39,0,???? (cordova.js, line 1540) [Log] [store.js] DEBUG: state: com.testapp.com -> approved (cordova.js, line 1540) [Log] [store.js] DEBUG: verify -> {"success":false,"data":"validation-fails"} (cordova.js, line 1540) [Log] [store.js] DEBUG: verify -> error: "validation-fails" (cordova.js, line 1540) [Log] InAppPurchase[js]: loading appStoreReceipt (cordova.js, line 1540) [Log] InAppPurchase[js]: infoPlist: com.testapp.com,1.5.39,0,???? (cordova.js, line 1540) [Log] [store.js] DEBUG: verify -> {"success":false,"data":"validation-failsz"} (cordova.js, line 1540) [Log] [store.js] DEBUG: verify -> error: "validation-failsz" (cordova.js, line 1540) [Log] InAppPurchase[js]: loading appStoreReceipt (cordova.js, line 1540) [Log] InAppPurchase[js]: infoPlist: com.testapp.com,1.5.39,0,???? (cordova.js, line 1540) [Log] [store.js] DEBUG: verify -> {"success":false,"data":"validation-fails"} (cordova.js, line 1540) [Log] [store.js] DEBUG: verify -> error: "validation-fails" (cordova.js, line 1540) [Log] InAppPurchase[js]: loading appStoreReceipt (cordova.js, line 1540) [Log] InAppPurchase[js]: infoPlist: com.testapp.com,1.5.39,0,???? (cordova.js, line 1540) [Log] [store.js] DEBUG: verify -> {"success":false,"data":"validation-failsz"} (cordova.js, line 1540) [Log] [store.js] DEBUG: verify -> error: "validation-failsz" (cordova.js, line 1540) [Log] InAppPurchase[js]: loading appStoreReceipt (cordova.js, line 1540) [Log] InAppPurchase[js]: infoPlist: com.testapp.com,1.5.39,0,???? (cordova.js, line 1540) [Log] [store.js] DEBUG: verify -> {"success":false,"data":"validation-fails"} (cordova.js, line 1540) [Log] [store.js] DEBUG: verify -> error: "validation-fails" (cordova.js, line 1540) [Log] [store.js] DEBUG: validation failed, no retrying, trigger an error (cordova.js, line 1540) [Log] InAppPurchase[js]: loading appStoreReceipt (cordova.js, line 1540, x82) [Log] [store.js] DEBUG: state: PRODUCT_ID -> requested (cordova.js, line 1540) [Log] Update Listener Loaded{"id":"PRODUCT_ID","alias":"PRODUCT_ID","type":"paid subscription","group":"20442245","state":"requested","title":"Monthly Subscription Plan","description":"","priceMicros":79000000,"price":"₹ 79.00","currency":"INR","countryCode":"IN","loaded":true,"canPurchase":false,"owned":false,"introPrice":null,"introPriceMicros":null,"introPricePeriod":null,"introPricePeriodUnit":null,"introPricePaymentMode":null,"ineligibleForIntroPrice":null,"discounts":[],"downloading":false,"downloaded":false,"additionalData":{"applicationUsername":""},"transaction":null,"billingPeriod":1,"billingPeriodUnit":"Month","valid":true} (cordova.js, line 1540, x3) [Log] [store.js] DEBUG: state: PRODUCT_ID -> requested (cordova.js, line 1540) [Log] Update Listener Loaded{"id":"PRODUCT_ID","alias":"PRODUCT_ID","type":"paid subscription","group":"20442245","state":"requested","title":"Monthly Subscription Plan","description":"","priceMicros":79000000,"price":"₹ 79.00","currency":"INR","countryCode":"IN","loaded":true,"canPurchase":false,"owned":false,"introPrice":null,"introPriceMicros":null,"introPricePeriod":null,"introPricePeriodUnit":null,"introPricePaymentMode":null,"ineligibleForIntroPrice":null,"discounts":[],"downloading":false,"downloaded":false,"additionalData":{"applicationUsername":""},"transaction":null,"billingPeriod":1,"billingPeriodUnit":"Month","valid":true} (cordova.js, line 1540, x3) [Log] InAppPurchase[js]: infoPlist: com.testapp.com,1.5.39,0,???? (cordova.js, line 1540) [Log] [store.js] INFO: ios -> transaction 1000000881827077 purchased (1 in the queue for PRODUCT_ID) (cordova.js, line 1540) [Log] [store.js] DEBUG: state: PRODUCT_ID -> approved (cordova.js, line 1540) [Log] [store.js] DEBUG: product -> defer finishing PRODUCT_ID(cordova.js, line 1540) [Log] InAppPurchase[js]: infoPlist: com.testapp.com,1.5.39,0,???? (cordova.js, line 1540) [Log] [store.js] INFO: ios -> transaction 1000000882300126 purchased (26 in the queue for PRODUCT_ID) (cordova.js, line 1540) [Log] [store.js] DEBUG: state: PRODUCT_ID-> approved (cordova.js, line 1540)

ArnauKokoro commented 2 years ago

This happens when store.ready event is not fired.

1248

j0zt commented 2 years ago

@ArnauKokoro The issue is not fixed.

Please anybody give clear explanation of how to implement the in app purchase.

reatailret commented 2 years ago

@ArnauKokoro The issue is not fixed.

Please anybody give clear explanation of how to implement the in app purchase.

Check common documentation for IAP testing for ios. Not related to plugin.

j0zt commented 2 years ago

@reatailret yes i have checked it. For your info, i have already implemented the iap purchase in latest ionic version.

But i am implementing it in ionic3.

Without receipt validation, the purchase popup works fine.

But when i include the server side custom validation, the listeners loops continuously and app closed.

So is there any logic to include the iap validator callback.

Also recently after including the validator i got the below error.

InAppPurchase[js]: exception in loadReceipts.callback: "TypeError: undefined is not an object

Due to this server side validation not called. But everything is correct.

Still the purchase not working.

CraigRonald555 commented 2 years ago

@reatailret yes i have checked it. For your info, i have already implemented the iap purchase in latest ionic version.

But i am implementing it in ionic3.

Without receipt validation, the purchase popup works fine.

But when i include the server side custom validation, the listeners loops continuously and app closed.

So is there any logic to include the iap validator callback.

Also recently after including the validator i got the below error.

InAppPurchase[js]: exception in loadReceipts.callback: "TypeError: undefined is not an object

Due to this server side validation not called. But everything is correct.

Still the purchase not working.

I imagine the condition where you call 'callback(false)' is being met and therefore stops IAPs from working. I found that if you call callback(false), the store.order command no longer works or takes a very long time to work again. My recommendation is to always call 'callback(true)' and handle any issues in the .verified listener.

If you need the data from the validator listener, I suggest storing it in local storage then accessing it from the .verified listener.

j0zt commented 2 years ago

Thanks @CraigRonald555 Now i have removed the callback and in fact removed the plugin validator.

Simply validates the receipt in server side and set product verify.

CraigRonald555 commented 2 years ago

@j0zt Good choice, seems a lot easier to handle the majority of the work on the server side

j0zt commented 2 years ago

@CraigRonald555 So my final code is below

this.iap2.when(productId).verified((product: IAPProduct) => { alert('verified'); this.services .Data("customvalidator/",receipt) .subscribe( (response) => { if (response.status === 'success') { product.finish(); } else { alert(response.error); } }); });

Is it correct to validate the receipt as above?

CraigRonald555 commented 2 years ago

@CraigRonald555 So my final code is below

this.iap2.when(productId).verified((product: IAPProduct) => { alert('verified'); this.services .Data("customvalidator/",receipt) .subscribe( (response) => { if (response.status === 'success') { product.finish(); } else { alert(response.error); } }); });

Is it correct to validate the receipt as above?

I'm not sure tbh, I guess if your server is handling the call to Apple and returns correctly then you should be okay. Only way to know is to test.

ucsbricks commented 2 years ago

@j0zt did you get the purchase popup working again? I have a similar issue. canPurchase property is false and .order() does not trigger the popup today. But both behaved correctly, when I coded and successfully tested my current feature branch some weeks ago.

ucsbricks commented 2 years ago

canPurchase is false as my products are suddenly stuck in REGISTERED lifecycle state but should be – and formerly were – VALID. @j0zt is this the same in your app?

CraigRonald555 commented 2 years ago

@ucsbricks

Is the team in your signing and capabilities tab in xCode definitely linked to the same one which the subscriptions were created in AppStoreConnect?

Also I assume you’re calling store.refresh() after registering the products right?

If it’s not because of either of them, I’d be cautious of using the store.validator function. If you’re using callback(false) in there it can lead to issues I found.

Maybe if you shared your code it’d be easier to find the problem?

j0zt commented 2 years ago

@ucsbricks I will share you the full working code within few days..

ucsbricks commented 2 years ago

@CraigRonald555 the team is set correctly and yes, store.refresh() is called. store.validator does not use callback(false) in my case. It sends the data to one of our servers which validates the receipts, add the product purchased to the user’s account and sends a response to the app.

As I found out, products behave correctly often, but sometimes they are all stuck in registered state after the app was started. Recalling store.refresh() does not solve the issue then. So far, I have seen this misbehavior on iOS (13.x to 15.1 beta 4) only – on all tested devices. I will do some extended Android testing today…

ucsbricks commented 2 years ago

@j0zt I am still stuck in trying to fix the issue… Sometimes the app is working, but most of the time it fails. If you could share your solution, I would be very glad. Thanks.

ucsbricks commented 2 years ago

I seems I finally got it fixed and store.validator still uses an external server for its task. In short: first the validator is assigned, then all product events are registered, products are registered inside the store ready event handler and then store.refresh() is called inside this handler too.

franalt commented 2 years ago

Could you expand on that fix? Maybe show some code? There are a lot of people having trouble with this issue (Me being one of them).

ucsbricks commented 2 years ago

@franalt I will try to provide an extract from my project. But I could take me some days before I can do this… Just returned to the office after some days off and the to-do list is quite long. Stay tuned.

ucsbricks commented 2 years ago

Update: I had to pull several iOS releases from the App Store, as the system purchase popup does never show when called. This might be related to #1266. Dev and TestFlight builds worked, but builds installed from the App Store failed unexpectedly. The solution mentioned above may or may not be part of the issue…

j3k0 commented 2 years ago

What is the status of this issues. Can you share your final code and logs with store.verbosity == store.DEBUG, I can take a look.

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.