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

Verification Failures #140

Closed KingdomGames closed 5 years ago

KingdomGames commented 9 years ago

Are there any examples of how to handle verification failures?

Using 3.9, my products are stuck in the APPROVED state and never return to valid, making them un-purchaseable forevermore.

I've tried setting the product state back to VALID in the unverified callback, but no dice.

Every time the app is opened store.refresh is called and the product once again tries to verify / validate. If its receipt data is bad however, this loop will never be broken: (Open App --> store.refresh --> product.approved --> product.verify --> FAIL).

I am really lost on this, any help would be greatly appreciated

westoj commented 9 years ago

I am also not sure how to handle this, in the docs it says that "if the validator returns a purchase expired error code then the product will automatically lose its owned status" but I can't get this to work with a custom validation method. If I find anything out I will post here, please do the same.

daveande commented 9 years ago

@KingdomGames @OJcode14 did you ever come up with a solution to this? I'm experiencing the same thing. If a product.verify() fails with a custom validation method, how do we stop the plugin from continuing to try to verify?

westoj commented 9 years ago

Not sure if this is what you were looking for but in #162 I asked how to make a product lose owned status with a custom verification.

daveande commented 9 years ago

Thanks @OJcode14 . That's not quite my case, so I'm still a little unclear what the process should be. I have a consumable product and the following validator:

store.validator = function(product, callback) {
            sendToServer(product).then(
                function(data) { //deferred resolve
                  console.log('server validated order');
                  callback(true, product);
                },
                function(data) { //deferred reject
                  console.log('server error validating order');
                  callback(false, data.error);
                }
            );
        };

where sendToServer() makes a call to my server with the transaction details. My server tries to validate the purchase with Apple or Google Play, and if it fails it returns an error. When my server returns an error in sendToServer(), the plugin continues to try to verify() the product for 5 times. After the 5th error the plugin fires the unverified event but leaves the product in a state such that 1) it cannot be purchased by the user and 2) when the store is refreshed the product is "approved" and kicks off the "verify()" loop again, resulting in the same unpurchaseable product.

What am I supposed to do when a transaction is not verified to avoid this loop?

westoj commented 9 years ago

Yeah, I'm not sure, I kind of dumped the verified/unverified events and handle it manually depending on what my server tells me is the state of the transaction.

daveande commented 9 years ago

@j3k0 any pointers here?

Even if my server verification fails, product.canPurchase remains "false" and the product is in an "approved" state until product.finish() is called. Even if the server verification fails are we to still call product.finish()?

The docs say:

To verfify a purchase you'll have to do three things:

   -  configure the validator.
   - call product.verify() from the approved event, before finishing the transaction.
   - finish the transaction when transaction is verified.

What do we do when the transaction fails verification? Thank you!

westoj commented 9 years ago

what did you do here @daveande ?

daveande commented 9 years ago

@OJcode14 I didn't come up with a solution. I just ended up just calling product.finish() on all transactions, regardless of server verification. It still seems like there should be a way to handle the transaction if it fails server verification, but I couldn't come up with a solution that avoided leaving the product in an "approved" state.

Jacse commented 9 years ago

Hmm, I'm having the exact same problems as @daveande, it tries to verify 5 times and only on the 5th time it correctly fires an expired-error

ghost commented 9 years ago

The problem is that the documentation for the error object is wrong, it should be like: callback(false, { code: store.PURCHASE_EXPIRED, message: "..." });

Not "error : {code: ...}"

That sets the product back to valid, but on restart it is still validated again, so that is still a problem.

ghost commented 8 years ago

Any news on this? There is no way to abort a purchase? Either the customer is charged, or you have to retry forever? Even though it might never go through?

Dolemite1347 commented 8 years ago

Any updates on this? I am having the same problem with an app I have in App Store.

stebogit commented 8 years ago

Hello, any news on this? I'm having the same problem in App Store. I am using a server side validator. Is it ok to finish the order when unverified?

store.when("myproduct").unverified(function (order) {
    console.log("Sorry, transaction unverified");
    order.finish();
});

Thanks

j3k0 commented 8 years ago

Hey. It's OK to finish some unverified orders. Make sure you understand the reason of validation failure.

Without much explanation, what I think is valid behavior.

stebogit commented 8 years ago

Wow, that was fast, thanks! So if I don't finish the order/product, should I just leave it alone and do nothing? It seems to me just weird leaving it like pending forever... I have only consumable products, so I should not even bother handling/checking the expired status right?

j3k0 commented 8 years ago

Exactly, for a consumable no need to bother with expiry status. Invalid transactions are either due to "fake" transactions generated on a jailbroken device, or a sign that something went wrong went validating with your server. In the second case, it has to stay pending. In the first case, I guess any behavior is fine (for example, start a background loop that drowns the users battery)

stebogit commented 8 years ago

In my understanding the (consumable) product should end up being unverified only if the transaction is actually never been made 'legally' by/through the app (passing through store.verify()) or if the app doesn't receive an answer for our server/validator, for whatever reason. In this case I should call product.finish() only if the product is valid.

store.when("myproduct").unverified(function (product) {
    alert("Sorry, something wrong happened...");
    if (product.status == store.VALID) {
        product.finish();
    }
});

Am I correct? Or should I implement the if statement in the error callback?

j3k0 commented 8 years ago

VALID state means the product exists on the store. (INVALID when product doesn't).

This is different from verified and unverified.

Anyway, if the transaction isn't valid (not the product) you can finish the transaction as well. It'll clean it up from the queue, that's actually better.

rmanganiello commented 7 years ago

Any update on this? My question is... If the server validation fails (because connection error, google or ios server errors, incorrect token, magic bunny...), how can i change the product state back to VALID? This is my code:

store.when(subscription.id).approved(function(p) {
   alert("Store approved");
   verifyTransaction(p).then(function() {
       p.finish();
   }).catch(function(error) {
       alert(angular.toJson(error));
   });
});
function verifyTransaction(product) {
   var token;
   if (product.transaction.type == "ios-appstore") {
       token = product.transaction.appStoreReceipt;
   } else {
       token = product.transaction.purchaseToken;
   }
    return $http.post(validatorSubscriptionPurchaseUrl, {
        product_id: product.id,
        purchase_token: token
    });
}
stale[bot] commented 6 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.

matte5031 commented 5 years ago

Is it fine to call product.finish(); inside the validator method? verified never fires for me, so considering just to finish inside store.validator after my backend has returned if the receipt was valid or not . Is that fine or must i finish within the verified method? Thanks