Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subsequent product register/refresh doesn't work on iOS #333

Closed
gsfioravanti opened this issue Nov 4, 2015 · 13 comments
Closed

Subsequent product register/refresh doesn't work on iOS #333

gsfioravanti opened this issue Nov 4, 2015 · 13 comments

Comments

@gsfioravanti
Copy link

I'm encountering a strange issue with this plugin on iOS (and discrepancies in behavior between Android):

Our app has hundreds of products for in-app purchase so rather than registering each product up front, we’d like to just register each product when the user taps to purchase. Here’s our relevant code:

function initializeStore() {

    show('initialize store');
    store.verbosity = store.DEBUG

    store.ready(function() {
        console.log('store ready')
    })

    store.error(function(error) {
        console.log('ERROR ' + error.code + ': ' + error.message);
    });

    store.when("product").cancelled(function (product) {
        show('cancelled triggered: ' + JSON.stringify(product))
    });

    store.when("product").error(function (product) {
        show('error triggered: ' + JSON.stringify(product))
    });

}

function initiatePurchase(productSKU, typeOfPurchase) { 
    var purchaseID = appStoreProductID(productSKU, typeOfPurchase) // function that returns the productID as the app store has it

   // this callback will break on Android where no updates are sent
    store.when(purchaseID).updated(function (product) { 
        if (product.canPurchase ) {
           store.order(product.id)
        }
    });

    store.once(purchaseID).approved(function (product) {
        order.finish();
    });

    store.once(purchaseID).owned(function (product) {
        savePurchase(product)
    });

    var registerObj = {
            id:    purchaseID,
            alias: (typeOfPurchase == 'est' ? productSKU : productSKU + "_rental"),
            type:  (typeOfPurchase == 'est' ? store.NON_CONSUMABLE : store.CONSUMABLE)
        }

    store.register(registerObj)

    store.refresh()

    // the below code works on Android, but not iOS
    var productToPurchase = store.get(productSKU)

    if (productToPurchase) {
        setTimeout(function(){
            alert('init ordering' + productToPurchase.id)
            store.order(productToPurchase.id)
        },1000);
    }

}

On iOS, the first purchase attempt completes properly, but subsequent attempts just never trigger the ordering. And we're also not sure if this is a sandbox issue because sometimes we also get "application data" back as the "id" after registering subsequent products.

And then some code works on Android, but not iOS and vice versa.

If anyone can help us resolve this issue, we’d be happy to pay your hourly consulting rate.

@j3k0
Copy link
Owner

j3k0 commented Nov 5, 2015

    store.once(purchaseID).approved(function (product) {
        order.finish();
    });

should be:

    store.once(purchaseID).approved(function (order) { // order!
        order.finish();
    });

@gsfioravanti
Copy link
Author

Thanks! I fixed that, but still having a problem on iOS.

So this is what it looks like currently:

function show (p) {
    alert(p)
}
function initializeStore() {

    show('initialize store');
    store.verbosity = store.DEBUG

    store.ready(function() {
        console.log('store ready')
    })

    store.error(function(error) {
    });

    completeStoreInit()
}

function completeStoreInit() {
    // When any product gets updated, refresh the In-App Purchase UI elements.
    show('complete store init')

    store.when("product").updated(function (product) {
        show('a product updated:' + JSON.stringify(product))
    });

    store.when("product").cancelled(function (product) {
        show('cancelled triggered: ' + JSON.stringify(product))
    });

    store.when("product").error(function (product) {
        show('error triggered: ' + JSON.stringify(product))
    });

    store.refresh();
}

function initiatePurchase(productSKU, typeOfPurchase) { // android
    show('initiate purchase')
    var purchaseID = appStoreProductID(productSKU, typeOfPurchase)
    show('purchasing:' + purchaseID)

    store.once(purchaseID).approved(function (order) {
        show("Purchase approved: " + order.id);
        show('order approved:' + JSON.stringify(order))
        show('send order finish')
        order.finish();
    });

    store.once(purchaseID).owned(function (product) {
        show("Product owned: " + product.id);

        savePurchase(product)
    });

    show('registering product')

    var registerObj = {
            id:    purchaseID,
            alias: (typeOfPurchase == 'est' ? productSKU : productSKU + "_rental"),
            type:  (typeOfPurchase == 'est' ? store.NON_CONSUMABLE : store.CONSUMABLE)
        }

    show(JSON.stringify(registerObj))
    store.register(registerObj)

    show('refresh')
    store.refresh()

    var productToPurchase = store.get(productSKU)

    show('product to purchase:' + JSON.stringify(productToPurchase))

    if (productToPurchase) {
        setTimeout(function(){
            show('init ordering' + productToPurchase.id)
            store.order(productToPurchase.id)
        },5000);
    }
}

So on fresh launch of the app, the first purchase goes through perfectly. But then if I tap another product to purchase, the last alert I see is "init ordering" with the new purchaseID. And then that's it. No other alerts or messages.

Would you be available to consult with us via screen share to help us resolve this?

@gsfioravanti
Copy link
Author

So here's what I just noticed:

  1. Purchased first product on fresh launch -> works perfectly
  2. Try a second product in same session -> App Store alert never appears asking to confirm my purchase
  3. Try to purchase the first product again in same session -> App Store alert appears asking to confirm purchase and then subsequently letting me know that it's already been purchased

So perhaps subsequent products are not getting registered properly with the App Store?

Is there a way to reset the store after each purchase?

@gsfioravanti
Copy link
Author

Another discovery ...

Each of our products is available as a non-consumable or consumable (rental) with slightly different IDs.

The non-consumable products will work perfectly on the first purchase on a fresh launch of the app. The consumable products will not trigger the App Store alert even on first attempt.

Not sure if this is relevant, but the non-consumable products register as something like this:

var registerObj = {
id: "mypd_JFY4IK0AKF4_est",
alias: "JFY4IK0AKF4",
type: store.NON_CONSUMABLE)
}

var registerObj = {
id: "mypd_JFY4IK0AKF4_rental_2",
alias: "JFY4IK0AKF4 rental",
type: store.CONSUMABLE)
}

@rotoxl
Copy link

rotoxl commented Nov 16, 2015

Hi @gsfioravanti,

i'm experiencing exactly the same issue. I also do register products on demand, one at a time (because i use 50+ non consumable products).
My code is different from your in several points (I register product and then add events; there are several differences in the way we use "updated"/"approved" events) but both have the same problem.
Have you managed to deal with this problem? I think its related with purchases restoration but can't find any workaround.

Thank you

@arielfaur
Copy link
Collaborator

I am having the same issue on iOS, Any updates on this? Has any of you tried to register all products in advance (using a for loop or something..). I have 1600+ products, I am not sure the device will be able to handle that.

@rotoxl
Copy link

rotoxl commented Mar 14, 2016

Hi @arielfaur,

I did try that approach, and even though I had to register far less products than you (~100) it didn't seem to work. I then changed to a 'on demand' approach making only the right register action on the product info page (my app is similar to google play store, with a big store and a product page). The only issue is that you need to keep prices synced.

Good luck

@arielfaur
Copy link
Collaborator

Hi @rotoxl

How did you implement on-demand product registration? Whenever I try to register a second product after having called store.refresh() for the first time I get nothing (no product details or description).

@rotoxl
Copy link

rotoxl commented Mar 14, 2016

Hi again, I'm going to try to explain the flow. My first advice is: keep your android purchase code completely appart from iOS', cause you may break things.

  1. User entes store. All the prices are queried from the datasource (remember, you are still not going to register products)

  2. Then, a user enters my product page. I show product details and I register this product

    store.register({
        id:     productID,
        alias:  productID,
        type:   store.NON_CONSUMABLE
        })
    
    //Remove all handlers, if present
    store.off(this.handler.updated)
    store.off(this.handler.cancelled)
    store.off(this.handler.approved)
    store.off(this.handler.finished)
    store.off(this.handler.error)
    store.off(this.handler.restore)
    
    //Set all handlers
    
    //generic handler for every product
    this.handler.updated=store.when('product').updated(function (product) {...}
    
    //specific handlers for this product
    this.handler.cancelled=store.when(productID).cancelled(function (product) {...}
    this.handler.approved=store.when(productID).approved(function (order) {...}
    this.handler.finished=store.when(productID).finished(function (order) {...}
    
    store.ready(function() {})
    
    this.handler.error=store.error(function(error) {})
    
    store.refresh()
    
  3. A user buys my product. Here I have a tricky thing cause I keep on asking for purchase every 5 seconds, until the order state has changed. I do it this way cause I found that pressing the button a 2nd or 3rd time was way better than doing it just once

storekit.load(productID)
store.order( store.products.byAlias[productID] )

I hope this helps

@arielfaur
Copy link
Collaborator

Thanks for such a detailed information. I have a question about your workflow. My Android app is already working so I am focusing exclusively on iOS here.
In step 1 where you query prices from a datasource I assume you mean a separate DB. Also, I should query product description each time too since I am not getting any product info from the plugin after calling store.refresh() for the first time. Is that correct?

I am not sure I follow what you're doing in step 3. Will storekit.load(productID) load your product info from the AppStore? (I bet you're not getting prices here?) Seems that method isn't documented in the API.

@rotoxl
Copy link

rotoxl commented Mar 15, 2016

Hi @arielfaur,

Step 1: that's correct, I take product info & price from my datasource (right now, a javascript file synced by phonegap-plugin-contentsync: http://www.raymondcamden.com/2015/05/19/working-with-the-new-phonegapcordova-contentsync-plugin/), just to be able to defer the product registration.

Step 3: ummm, didn't notice that it was undocumented, maybe I took that from http://www.joshmorony.com/how-to-create-ios-in-app-purchases-with-phonegap-build/. I don't get prices here, as you've guessed, I should need to remove the line and test my app.

Good luck

@stale
Copy link

stale bot commented May 11, 2018

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.

@danieldanielecki
Copy link

So here's what I just noticed:

  1. Purchased first product on fresh launch -> works perfectly
  2. Try a second product in same session -> App Store alert never appears asking to confirm my purchase
  3. Try to purchase the first product again in same session -> App Store alert appears asking to confirm purchase and then subsequently letting me know that it's already been purchased

So perhaps subsequent products are not getting registered properly with the App Store?

Is there a way to reset the store after each purchase?

I had exactly the same problem when it worked out correctly on Android. The problem was that I was registering product when they were supposed to be purchased and this does not work on iOS. To have it working properly on iOS you need to register all product upfront and then all your purchases will be available.

Working example on iOS:

myStartingFunction = function() {
  this.gems5Button = this.game.add.button(this.game.width * 0.0001, this.game.height * 0.2155, 'gems5', this.startPaymentGems5, this);
  this.gems20Button = this.game.add.button(this.game.width * 0.4001, this.game.height * 0.2155, 'gems20', this.startPaymentGems20, this);

// Prepare products, on iOS they have to be preloaded upfront to work properly.
  store.register({
    id:    "XX.YY.ZZ.gems5",
    alias: "Gems 5",
    type:  store.CONSUMABLE
  });
  store.register({
    id:    "XX.YY.ZZ.gems20",
    alias: "Gems 20",
    type:  store.CONSUMABLE
  });
}

startPaymentGems5 = function () {
  'use strict';
  var that = this;

  store.order("XX.YY.ZZ.gems5"); // Initialize purchase.

  // Handle approved purchase.
  store.when("XX.YY.ZZ.gems5").approved(function (order) {
    // Add extra gems.
    localStorage.gems = parseInt(localStorage.gems) + 5; // Add 5 gems.

    order.finish(); // Finish purchase.
  });

  store.refresh(); // Refresh the store to start everything.
};

startPaymentGems20 = function () {
  'use strict';
  var that = this;

  store.order("XX.YY.ZZ.gems20"); // Initialize purchase.

  // Handle approved purchase.
  store.when("XX.YY.ZZ.gems20").approved(function (order) {
    // Add extra gems.
    localStorage.gems = parseInt(localStorage.gems) + 20; // Add 20 gems.

    order.finish(); // Finish purchase.
  });

  store.refresh(); // Refresh the store to start everything.
};

Working on Android, not working on iOS:

myStartingFunction = function() {
  this.gems5Button = this.game.add.button(this.game.width * 0.0001, this.game.height * 0.2155, 'gems5', this.startPaymentGems5, this);
  this.gems20Button = this.game.add.button(this.game.width * 0.4001, this.game.height * 0.2155, 'gems20', this.startPaymentGems20, this);
}

startPaymentGems5 = function () {
  'use strict';
  var that = this;

  store.register({
    id:    "XX.YY.ZZ.gems5",
    alias: "Gems 5",
    type:  store.CONSUMABLE
  });

  store.order("XX.YY.ZZ.gems5"); // Initialize purchase.

  // Handle approved purchase.
  store.when("XX.YY.ZZ.gems5").approved(function (order) {
    // Add extra gems.
    localStorage.gems = parseInt(localStorage.gems) + 5; // Add 5 gems.

    order.finish(); // Finish purchase.
  });

  store.refresh(); // Refresh the store to start everything.
};

startPaymentGems20 = function () {
  'use strict';
  var that = this;

  store.register({
    id:    "XX.YY.ZZ.gems20",
    alias: "Gems 20",
    type:  store.CONSUMABLE
  });

  store.order("XX.YY.ZZ.gems20"); // Initialize purchase.

  // Handle approved purchase.
  store.when("XX.YY.ZZ.gems20").approved(function (order) {
    // Add extra gems.
    localStorage.gems = parseInt(localStorage.gems) + 20; // Add 20 gems.

    order.finish(); // Finish purchase.
  });

  store.refresh(); // Refresh the store to start everything.
};

Suggestion by @rotoxl in issue#333 (comment) does really applies good to this use case, i.e. keep your android purchase code completely apart from iOS', cause you may break things. Lazy loaded registrations looks like are not supported (at lest on iOS from my experience), recently I've submitted an issue#938 about it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants