0

I'm learning how to use in-app purchases and recently implemented it in my first app. To test it out on my phone, I created two test accounts and bought one item with each account, both different. When I delete the app and reopen it, it attempts to restore the data of both accounts and provides the upgrades regardless of whether I sign in or not. (paymentQueue: updatedTransactions: is being called and passing a transaction in the state SKPaymentTransactionStateRestored)

Could this be solved by validating the receipts? I've looked into it but have been putting it off since it looks a little daunting. Hopefully this is only a problem when testing multiple accounts.

For reference, I followed this guide but did the Transaction Observer methods in the App Delegate and the Product Request in a custom class. I also never reorded the transactions because apparently that's outdated. http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/

edit: I was just doing some testing and found that there are 0 transactions on the queue on startup right after being added as an observer but after the uidatedTransactions gets called there are 3.

My code for reference (Sorry if it's bad I'm still new):

#pragma mark - SKPaymentTransactionObserver

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    NSLog(@"Transactions Count: %d", [[[SKPaymentQueue defaultQueue] transactions] count]);
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:
                [self comleteTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                break;
            default:
                break;
        }
    }
}

//case methods

- (void)comleteTransaction:(SKPaymentTransaction *)transaction
{
    [self provideContent:transaction.payment.productIdentifier];
    [self finishTransaction:transaction wasSuccessful:YES];
}

- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
    [self provideContent:transaction.originalTransaction.payment.productIdentifier];
    [self finishTransaction:transaction.originalTransaction wasSuccessful:YES];
}

- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
    if (transaction.error.code != SKErrorPaymentCancelled)
        [self finishTransaction:transaction wasSuccessful:NO]; //serious error
    else
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; //user cancelled
}

//action methods

- (void)provideContent:(NSString *)productID
{
    //validate app receipt?

    if ([productID isEqualToString:UNLOCK_EVERYTHING_ID])
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:UNLOCK_EVERYTHING_PURCHASED_KEY];
    else if ([productID isEqualToString:REMOVE_ADS_ID])
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:REMOVE_ADS_PURCHASED_KEY];
    else if ([productID isEqualToString:BASIC_PACK_ID])
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:BASIC_PACK_PURCHASED_KEY];
    else if ([productID isEqualToString:CLOTHING_PACK_ID])
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:CLOTHING_PACK_PURCHASED_KEY];
    else if ([productID isEqualToString:FOOD_PACK_ID])
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:FOOD_PACK_PURCHASED_KEY];
    else if ([productID isEqualToString:ORIGINAL_PACK_ID])
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:ORIGINAL_PACK_PURCHASED_KEY];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful
{
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

    //post notification that transaction finished
    if (wasSuccessful) {
        [[NSNotificationCenter defaultCenter] postNotificationName:TRANSACTION_SUCCESSFUL object:nil];
    } else {
        [[NSNotificationCenter defaultCenter] postNotificationName:TRANSACTION_UNSUCCESSFUL object:nil];
    }
}
Lorenzo
  • 1,605
  • 14
  • 18
  • Does any part of the purchase transaction use the keychain? I'm making an assumption that reinstalling the app doesn't clear the keychain entry for a purchased item, and the app is not "restoring" purchases but simply detecting purchase state from a keychain entry – Daddy Jun 12 '14 at 19:54

2 Answers2

1

I didn't look at the tutorial, but if any part of the purchase process adds details to the keychain, deleting the app and reinstalling it will leave keychain entries intact. The app is probably checking purchase status... see here

iphone keychain items persist after application uninstall?

Community
  • 1
  • 1
Daddy
  • 9,045
  • 7
  • 69
  • 98
  • I never added anything to the keychain myself, could it have been added automatically? Either way, would this be a big enough of an issue to get my app rejected by Apple or should I look into it? – Lorenzo Jun 12 '14 at 20:32
  • Did you follow a tutorial or copy and paste a block of code, what happens inside the block that executes on a purchase handler? – Daddy Jun 12 '14 at 20:34
  • I followed it, I try not to copy and paste so I actually learn. I'll update my original question with the code. – Lorenzo Jun 12 '14 at 20:41
  • Interesting. It stores in the UserDefaults plist. Try setting a breakpoint on the `provideContent` method. I'm guessing that on `application:didFinishLaunching` the `provideContent` method is called, and restoring purchase state. The UserDefaults plist file should be destroyed with the app on delete. Is your app using iCloud? Maybe this plist is being restored via iCloud? – Daddy Jun 12 '14 at 20:51
  • I looked at the tutorial. You need to set a breakpoint in the paymentQueue: method to inspect the value returned, on if it is a restore/already purchased state. Could be a sandbox issue that is not present in production. – Daddy Jun 12 '14 at 20:58
  • I actually did and it was activating the restore state case. Hopefully that's what it is though. Thanks a bunch for your insight by the way! – Lorenzo Jun 12 '14 at 22:27
0

If anyone else is experiencing the same problem, I just solved it on my own by changing the RestoreState case to do the same thing as PurchasedState case. Turns out that using the original transaction to make the restore was unnecessary and just caused problems.

Lorenzo
  • 1,605
  • 14
  • 18