Ionic-native: [Feature request] Change In app purchase support

Created on 21 Nov 2016  路  23Comments  路  Source: ionic-team/ionic-native

The present supported plugin by ionic-native is:
https://github.com/AlexDisler/cordova-plugin-inapppurchase
However, this plugin is impossible to use, it breaks the entire app. (Android)

The purpose of Ionic Native would be a curated list of cordova plugins.
So, in this case, it would support this plugin:
https://github.com/j3k0/cordova-plugin-purchase

It is a more complete solution and supports windows platform.

help wanted needs testing plugin request

Most helpful comment

I have forked the repo and fixed the change detection bug on Android + merged a PR that fixes another bug. I think j3k0 plugin is harder to use an equally abandoned by the user, unfortunately.

My fork: https://github.com/biesbjerg/cordova-plugin-inapppurchase

All 23 comments

I have forked the repo and fixed the change detection bug on Android + merged a PR that fixes another bug. I think j3k0 plugin is harder to use an equally abandoned by the user, unfortunately.

My fork: https://github.com/biesbjerg/cordova-plugin-inapppurchase

This should be fixed in the most recent version of the plugin
It had an outdated promise polyfil.
Can you test and let me know?

I've been looking into the in app purchase plugins for the past few days and as far as I can tell, the current one (https://github.com/AlexDisler/cordova-plugin-inapppurchase) supported by Ionic native is now not maintained, but also the cordova-plugin-purchase plugin (https://github.com/j3k0/cordova-plugin-purchase) seems to work better as it is the recommended way by Apple regarding listening to store changes.

I've also seen that someone has already written type definitions for it so would be good to see this support added to Ionic native as well, even if it doesn't replace the older one.

https://github.com/werk85/typed-cordova-plugin-purchase/blob/master/index.d.ts

Hm, yeah sounds like it might need to be added as an alternative option.
Then at 4.0.0, we could remove the older plugin.

@ihadeed thoughts?

@mhartington

We can definitely add the j3k0 IAP plugin. I'll try to implement it over the next couple of weeks.

They both seem to have tons of issues, I can't really decide which one is the ideal choice since I never used any of them. So I'm not sure if we should remove the old plugin in 4.x as of now. We can (1) do a community vote/survey to decide, or (2) keep both of them and document their pros/cons and leave it up to the developer to pick what they want.

Hey guys,

I just implemented the plugin here: https://github.com/driftyco/ionic-native/commit/179f87b606779c05cc6f565be1883009cd430808

I didn't test it, and I'm not 100% sure if this is the most efficient implementation.. I might see more room for improvement once I review it again.

In the meanwhile, I would appreciate it if you can test it out and let me know if there are any bugs or suggestions.

  • Run the following command to install the plugin npm i --save @ionic-native/in-app-purchase-2
  • Import InAppPurchase2 class from @ionic-native/in-app-purchase-2 (to your service/component and your NgModule)
  • Use the plugin as documented in their repo. The only difference is:

    • .read() function returns a Promise instead of taking a callback

@ihadeed I'm definitely going to be testing both of these plugins so I can give you a heads up as to +/- of using each. The AlexDisler plugin is not supported, but so far it has worked for me except in one edge case where the user has to change his/her billing information on itunes while purchasing.

Anything specific you need help with on implementation? Or just testing?

@ihadeed So I'm playing with the new plugin and I have run into a bit of an issue. Not sure what direction you were trying to take here.

Looking at the documentation, this is how I would expect to register the IAPProductEvents, but the typings are not playing nice with this.

    this.store.when(productId).approved( (product: IAPProduct) => {
      // Purchase was approved
      alert('Approved');
      product.finish();
    });

While you're implementation says that the function approved returns a function (product: IAPProduct) => void

I'll look into editing it.

So editing them, the ts was,

    owned: (product: IAPProduct) => void;

and now should be

    approved: (callback: IAPQueryCallback) => void;

https://github.com/ionic-team/ionic-native/pull/1705

@ihadeed I could definitely use your help figuring this plugin out. Since there are so many bugs or related issues I'm not sure what is our problem and what is a problem with the plugin.

For example, I have I think configured everything correctly but when I do a

        this.store.register({
          id: productId,
          alias: productId,
          type: this.store.NON_RENEWING_SUBSCRIPTION
        });

Which seems legit, but I when i check there are no products registered.

      this.store.ready((status) => {
        console.log(JSON.stringify(this.store.get(productId)));
        console.log('Store is Ready: ' + JSON.stringify(status));
        console.log('Products: ' + JSON.stringify(this.store.products));
      });
// Prints : Products: []

Edit / Update: Figured it out. Made a pull request! It looks like the full process is working but lots of edge cases to test still.

@thielCole I haven't tried the plugin personally.

I think that .ready() fires as soon as the store is ready. Which occurs before the product is actually registered.

Did you try listening to the registered event?

this.store.when(productId, 'registered', product => console.log(product));

@thielCole I'm assuming your most recent PR fixes this issue.

Yup, it was just that the register function was not synched with the Cordova functionality.

@ihadeed Is there a plan for when this could be released as a "supported" plugin? I am using it currently in my live app if there is testing that needs to be done.

@thielCole thanks for the feedback.

I will publish this in the next release. Just need to figure out if this should be in-app-purchase2 or just in-app-purchase.

I think it should be added as a new plugin. I'm using the existing plugin and am not experiencing issues, so I don't want to migrate to a new Cordova plugin.

That's a good question...

Maybe you should call it in-app-purchase-promise and in-app-purchase-events seeing as one is promise based and the other is event callback based...

I don't know. To give you some of my experience with the whole thing, both of the plugins worked when everything went well, but I have found that this plugin helps me cover a few more edge cases that the other plugin. Plus my code definitely looks quite a bit more simple. Example:

聽this.inAppPurchase.getProducts([productId])
          .then( (products) => {
            this.inAppPurchase.buy(productId).then( (data) => {
              console.log('Data', data);
              this.logger.logEvent('purchase', { programId: this.program._id });
              return this.inAppPurchase.consume(data.productType, data.receipt, data.signature);
            }).then( () => {
              loader.dismiss();
              console.log('Consumed Product!');
              this.subscribe();
            }).catch( (err) => {
              // Validate Purchase through backend?
              // Look for cases where the app paused
              if (this.platform.is('ios')) {
                this.platform.resume.first().subscribe( (event) => {
                  console.log('App Coming Back from Pause');
                  this.inAppPurchase.getReceipt().then( (purchases) => {
                    console.log('Get Reciept Purchases');
                    console.log(JSON.stringify(purchases));
                    this.billing.getReceiptInfo(purchases).subscribe( (receipts) => {
                      console.log('returned: ' , JSON.stringify(receipts) );
                      // Find if a transaction has taken place in the last 10 minutes (1000 * 60 * 10)
                      let found = false;
                      let currentTransaction;
                      _.forEach( receipts, (transaction) => {
                        if (transaction.productId === this.program.appleProductId) {
                           if (Math.abs( Date.now() - transaction.purchaseDateMs) < 600000) {
                              found = true;
                              currentTransaction = transaction;
                           };
                        }
                      });
                      if (found) {
                        this.logger.logEvent('consumed_after_pause', {});
                        loader.dismiss();
                        console.log('Consumed Product!');
                        this.subscribe();
                      } else {
                        this.toastCtrl.create({
                          message: 'No current purchase found. If this is an error, contact us at',
                          duration: 3000
                        }).present();
                        loader.dismiss();
                      }
                    }, (err) => {            
                      this.toastCtrl.create({
                        message: 'Error Validating Purchase. If this continues, contact us at',
                        duration: 2000
                      }).present();
                      console.log('Server Error', err);
                      loader.dismiss();
                    });
                  }).catch( (err) => {
                    console.log('Error resuming');
                    loader.dismiss();
                    this.toastCtrl.create({
                      message: 'Error Finding Purchase',
                      duration: 2000
                    }).present();
                  });
                });
              } else {
                console.warn('Error Purchasing Item', err);
                console.log(JSON.stringify(err));
                loader.dismiss();
              }
            });
          }).catch( (err) => {
            loader.dismiss();
            console.warn('Error Getting Item', err);
            console.log(JSON.stringify(err));
            this.toastCtrl.create({
              message: 'Error Retrieving Store Items',
              duration: 2000
            }).present();
          });

To This...

     // Register Product
      console.log('Registering Product ' + JSON.stringify(productId));
      this.store.verbosity = this.store.DEBUG;
      this.store.register({
        id: productId,
        alias: productId,
        type: this.store.NON_RENEWING_SUBSCRIPTION
      });
      // Handlers
      this.store.when(productId).approved( (product: IAPProduct) => {
        // Purchase was approved
        this.logger.logEvent('purchase_approved', {});
        product.finish();
        this.loader.dismiss();        
        this.subscribe();
      }); 

      this.store.when(productId).registered( (product: IAPProduct) => {
        console.log('Registered: ' + JSON.stringify(product));
      });

      this.store.when(productId).updated( (product: IAPProduct) => {
        console.log('Loaded' + JSON.stringify(product));
      });

      this.store.when(productId).cancelled( (product) => {
        if (this.loader.isOverlay) {
          this.loader.dismiss();   
          this.logger.logEvent('purchase_cancelled' , {});  
          alert('Purchase was Cancelled');
        }
      });

      this.store.error( (err) => {
        this.loader.dismiss();
        alert('Store Error ' + JSON.stringify(err));
      });

      this.store.ready().then((status) => {
        console.log(JSON.stringify(this.store.get(productId)));
        console.log('Store is Ready: ' + JSON.stringify(status));
        console.log('Products: ' + JSON.stringify(this.store.products));
      });

      // Errors
      this.store.when(productId).error( (error) => {
        this.loader.dismiss();        
        alert('An Error Occured' + JSON.stringify(error));
      });
      // Refresh Always 
      console.log('Refresh Store');
      this.store.refresh();
    } catch (err) {
      console.log('Error On Store Issues' + JSON.stringify(err));
    }

I personally would rather not rename the first plugin and cause a breaking change, unless if we are going to use the new plugin as the primary IAP plugin.

I think for now I'll just keep it as in-app-purchase2 until we get more feedback from the community. If the old plugin starts causing issues, and the majority of the users like the new plugin, then we can switch the names. Maybe we should leave the renaming till the next major release(s) ..

@ihadeed Definitely makes sense. I also see in 4.0.0 the docs need to be updated. I would be happy to fix some of the docs on this one if you open up another ticket and assign it to me.

@thielCole what's wrong with the docs?

https://ionicframework.com/docs/native/in-app-purchase-2/

The automated docs are definitely not looking great.

@thielCole oh ok, that was fixed recently by https://github.com/ionic-team/ionic-native/pull/1791. It's just not available on the production site yet. See http://ionic-site-staging.herokuapp.com/docs/native/in-app-purchase-2/

Hi to all, I have some problem with ionic in app Purchase. So i need to change price of product programmatically, but i can't. I get price only from Google Play Console by default. Please tell me how i can do this in my project?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wwallace picture wwallace  路  4Comments

lycwed picture lycwed  路  4Comments

icchio picture icchio  路  3Comments

sabariferin picture sabariferin  路  4Comments

hobbydevs picture hobbydevs  路  3Comments