Handle cancelled IAP transactions

2019-03-27 01:10发布

问题:

I'm using StoreKit for in-app purchases. I'm finding that the API is quirky in its behavior when the user presses the "Cancel" button.

For example, if I push Cancel on the "Confirm Your In App Purchase" screen, I get a SKPaymentTransactionStateFailed transaction with error.code == SKErrorPaymentCancelled as I'd expect.

But if I push Buy and then press Cancel, I get a Failed transaction with error.code == 0. The error.localizedDescription is "Cannot connect to iTunes Store" which is clearly a lie.

It's tempting to treat all Failed transactions as ignorable cancellations, but I can also clearly see that if the device is offline in airplane mode, I get a Failed transaction with no alert popup; I should really notify the user to explain the problem in that case.

I note that MKStoreKit assumes all failures are cancellations. MKStoreManager's failedTransaction method is never called; MKStoreObserver always calls transactionCanceled for all Failed transactions. The MKStoreManager.h comments recommend no error message for transactionCanceled, which makes sense, but then who will notify the user about Failed non-cancelled transactions?

What's the best practice for handling these failures? Should I swallow errors? Always show an error, even if it's redundant?

回答1:

We have a pretty substantial user base buying stuff over mobile connections and only show alerts for

code != SKErrorPaymentCancelled && code != SKErrorPaymentNotAllowed

Apparently, it's the best you can do. I've also seen the weird behavior on cancellation that you mention, which is a framework bug as far as I can tell.



回答2:

The previous answer is pretty close. MKStoreKit can automatically show error messages for valid error conditions like parental control turned on and such that.

Despite that, to handle purchase cancellations, I've also provided a delegate (starting from v3.5) called transactionCanceled in MKStoreKitDelegate.

Handle that and stop any activity spinners or progress hud on the view controller that makes the purchase call...



回答3:

I just wanted to add that errors due to no internet connection should mostly be caught prior to any transaction using Apple's Reachability class IMO. This way you don't need to rely on Apple's API for a straight forward and common kind of error.



回答4:

I think it's your responsibility and decision to where show the alert for cancelled transaction or not. But you should definitely finish it, otherwise it'll drop to Failed all the time. So should be something like this:

if (transaction.error.code == SKErrorPaymentCancelled) {
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
} else {
    [self notifyError:transaction.error];
}

UPDATE: Due to The Business of iPhone App Development: Making and Marketing Apps that Succeed we should finish transaction after any update to Failed state. Would be interesting to know if there are situations when we shouldn't.



回答5:

Still have a one issue... After clicking on Buy button it will show another Alert view and ask about Account information.

if i did cancel there then it will goes into case SKErrorUnknown: then i cant show message like this "Your purchase could not be completed. Please check your network settings and try again later."

- (void) failedTransaction: (SKPaymentTransaction *)transaction
{   
    switch (transaction.error.code) {
        case SKErrorUnknown:
            NSLog(@"SKErrorUnknown");
            break;
        case SKErrorClientInvalid:
            NSLog(@"SKErrorClientInvalid");
            break;
        case SKErrorPaymentCancelled:
            NSLog(@"SKErrorPaymentCancelled");
        break;
        case SKErrorPaymentInvalid:
            NSLog(@"SKErrorPaymentInvalid");
            break;
        case SKErrorPaymentNotAllowed:
            NSLog(@"SKErrorPaymentNotAllowed");
        break;
        default:
            NSLog(@"No Match Found for error");
            break;
    }
    NSLog(@"transaction.error.code %@",[transaction.error description]);
    if (transaction.error.code == SKErrorPaymentCancelled) {
        [[MKStoreManager sharedManager] transactionCanceled:transaction];
    } else {
        [[MKStoreManager sharedManager] failedTransaction:transaction];
    }
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 
}