In App Purchase Restore Problem

Hi,

At the moment my app is pretty much finished aside from one problem i'm having with the in App Purchases.

My in App Purchases are non-consumables that open up a new view and the contents within that view.


The problem stems from the restoration of the in app purhcases once they have been bought.


While one can be bought, restored and opened perfectly fine, if the user then chooses to restore another the app crashes. But then when it is reopened the user can then restore the one that caused the crash. However if another is restored it crashes again and so on. So basically only one can be restored before the app crashes.


So seemingly something in my code isn't being set free when the in app purchase screen for one option is being closed and another one is opened.


Can someone please have a look at this code and give me their thoughts on what's going wrong.


I'll show two examples, one will be an in app purchase for Rock Genre, the other for Country Genre.


First the Rock Code:

#import "PurchaseRockViewController.h"
#import "ViewController.h"
@interface PurchaseRockViewController ()

@property (strong, nonatomic) ViewController *homeController;

@end
@implementation PurchaseRockViewController


- (IBAction)RestoreRock:(id)sender {

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

}
- (IBAction)BuyRock:(id)sender {
    SKPayment *payment = [SKPayment paymentWithProduct:_rockProduct];
    [[SKPaymentQueue defaultQueue] addPayment:payment];

}

-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {

    [self UnlockRockPurchase];

}
-(void)getRockProductID:(ViewController *)viewController {

    _homeController = viewController;
    if ([SKPaymentQueue canMakePayments]) {
        SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:self.rockProductID]];
        request.delegate = self;
        [request start];
    } else
    
        _rockProductDescription.text = @"Please enable in app purchase within your settings";
}
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {

    NSArray *products = response.products;
    if (products.count !=0) {
        _rockProduct = products[0];
        _rockProductTitle.text = _rockProduct.localizedTitle;
        _rockProductDescription.text = _rockProduct.localizedDescription;
        _rockBuyButton.enabled = YES;
    } else {
        _rockProductTitle.text = @"Product not found";
    }

    products = response.invalidProductIdentifiers;
    for (SKProduct *product in products) {
        NSLog(@"%@", product);
    }

}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {

    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:[self UnlockRockPurchase];
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:[self UnlockRockPurchase];
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:NSLog(@"Failed");
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            default:
                break;
        }
    }

}
-(void)UnlockRockPurchase {

    /
    _rockBuyButton.enabled = NO;
    [_rockBuyButton setTitle:@"Purchased" forState:UIControlStateDisabled];
    [_homeController PurchasedThree];

}


Then the country:

#import "PurchaseCountryViewController.h"
#import "ViewController.h"
@interface PurchaseCountryViewController ()

@property (strong, nonatomic) ViewController *homeController;

@end

@implementation PurchaseCountryViewController

- (IBAction)RestoreCountry:(id)sender {

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

- (IBAction)BuyCountry:(id)sender {
    SKPayment *payment = [SKPayment paymentWithProduct:_countryProduct];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { 
    [self UnlockCountryPurchase];
}
-(void)getCountryProductID:(ViewController *)viewController {

    _homeController = viewController;
    if ([SKPaymentQueue canMakePayments]) {
        SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:self.countryProductID]];
        request.delegate = self;
        [request start];
    } else

        _countryProductDescription.text = @"Please enable in app purchase within your settings";
}
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {

    NSArray *products = response.products;
    if (products.count !=0) {
        _countryProduct = products[0];
        _countryProductTitle.text = _countryProduct.localizedTitle;
        _countryProductDescription.text = _countryProduct.localizedDescription;
        _countryBuyButton.enabled = YES;
    } else {
        _countryProductTitle.text = @"Product not found";
    }

    products = response.invalidProductIdentifiers;
    for (SKProduct *product in products) {
        NSLog(@"%@", product);
    }

}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {

    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:[self UnlockCountryPurchase];
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:NSLog(@"Failed");
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:[self UnlockCountryPurchase];
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            default:
                break;
        }
    }

}
-(void)UnlockCountryPurchase {

    /
    _countryBuyButton.enabled = NO;
    [_countryBuyButton setTitle:@"Purchased" forState:UIControlStateDisabled];
    [_homeController PurchasedTwo];

}


And finally the code within the home view controller that initially links to these purchase screens when no purchase has been made, and then links to the unlocked content when purchase has been completed.


HomeViewContoller:

#import "ViewController.h"
#import "PurchaseCountryViewController.h"
#import "PurchaseRockViewController.h"

#define k_SaveTwo @"SavedItemTwo"
#define k_SaveThree @"SavedItemThree"

@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
}

- (IBAction)Country:(id)sender {
  
    NSUserDefaults *saveappTwo = [NSUserDefaults standardUserDefaults];
    bool savedTwo = [saveappTwo boolForKey:k_SaveTwo];
    if (!savedTwo) {
  
    PurchaseCountryViewController *third = [[PurchaseCountryViewController alloc] initWithNibName:nil bundle:nil];
    third.countryProductID = @"com.gobiasltd.studybeats.CountryGenre";
    [self presentViewController:third animated:YES completion:NULL];
    [third getCountryProductID:self];
    }
    else {
        UIViewController *controllerTwo = [self.storyboard instantiateViewControllerWithIdentifier:@"CountryNavigation"];
        [self presentViewController:controllerTwo animated:YES completion:nil];
      
    }
}
- (IBAction)Rock:(id)sender {
  
    NSUserDefaults *saveappThree = [NSUserDefaults standardUserDefaults];
    bool savedThree = [saveappThree boolForKey:k_SaveThree];
    if (!savedThree) {
      
    PurchaseRockViewController *fourth = [[PurchaseRockViewController alloc] initWithNibName:nil bundle:nil];
    fourth.rockProductID = @"com.gobiasltd.studybeats.RockGenre";
    [self presentViewController:fourth animated:YES completion:NULL];
    [fourth getRockProductID:self];
      
    }
    else
    {
        UIViewController *controllerThree = [self.storyboard instantiateViewControllerWithIdentifier:@"RockNavigation"];
        [self presentViewController:controllerThree animated:YES completion:nil];
    }
  
}
-(void)PurchasedTwo {

    NSUserDefaults *saveappTwo= [NSUserDefaults standardUserDefaults];
    [saveappTwo setBool:TRUE forKey:k_SaveTwo];
    [saveappTwo synchronize];
  
}
-(void)PurchasedThree {
  
    NSUserDefaults *saveappThree = [NSUserDefaults standardUserDefaults];
    [saveappThree setBool:TRUE forKey:k_SaveThree];
    [saveappThree synchronize];
  
}


As you can see both the rock and country views link to the home.

I just need to know if I am not releasing something or there is a fundamental problem in my code.

As mentioned one purchase can be made with no problem, but as soon as two or three are attempted the app will crash.


Any advice would be greatly appreciated. As mentioned this is the final hurdle in completing my application.

First, you described a problem with "restore" and then you posted lots of code but you did not post the code that causes a restore. Nowhere do you call the method (IBAction)RestoreCountry or (IBAction)RestoreRock and they were loaded without a nib so there is no connection to the IBAction.


You are inappropriately calling [self UnlockCountryPurchase]; from -(void)paymentQueueRestoreCompletedTransactionsFinished:

and that method is called whether or not there is a transaction that gets restored.

Most likely your class named "third" and "forth" are being deallocated because there are no more references to them after they are created in the method "(IBAction)Country" and "(IBAction)Rock". You can eliminate that problem by making the reference to third and forth global in scope and by setting them to nil when done:

@interface ViewController (){
    PurchaseRockViewController *fourth:

}
-(void)PurchasedThree { 
  three=nil; 
    NSUserDefaults *saveappThree = [NSUserDefaults standardUserDefaults]; 
    [saveappThree setBool:TRUE forKey:k_SaveThree]; 
    [saveappThree synchronize]; 
   
}

But then you would need to also remove the transactionObserver....Oh!! that's it - you left the transactionObserver alive and the class deallocated itself. But the class will be called by StoreKit because the transactionObserver wasn't removed. You need a removeTransactionObserver in a viewWillDisappear method (or the 'unlock' methods) in your delegates.

In App Purchase Restore Problem
 
 
Q