In-App Purchase in iOS

By | July 20, 2016

In-App purchase is a way of selling digital content from inside the apps. This helps the app developers to sell their apps for free and monetize the app in a clean and efficient way.
In-App purchase has been a major source of revenue for Apple and Developers almost similar to Paid Apps.

Below are the steps involved in In-App purchases are..

  1. You should have a unique ID for the app created in iTunes Connect.
  2. Once the app is created in iTunes , Go to Features Tab and add an in-App Purchase by clicking the “+” button.
  3. Enter unique ID for your In-App Purchase and description and also a Demo Screenshot for Apple to verify. This screenshot will not be displayed in the AppStore Page of the App.
  4. And the most important of all, Make sure you added your Bank Information in the Payments Section.

There are four types of In-App Purchase depending on the app’s nature and the kind of the purchase:

  • Consumable: This kind of product can be purchased multiple times, and not just once.
  • Non-Consumable: Purchase of non-consumable product is made just once.
  • Renewable Subscriptions: This purchase must be renewed from time to time (the interval can be specified in the iTunes Connect), and it’s suitable in cases you sell a service that requires a subscription.
  • Non-Renewable Subscriptions: A non-renewable subscription lasts one time only and expires. It’s useful in cases the provided service doesn’t require a new subscription, and it’s going to end after a period of time.

Source Code

In this demo we will have an app that will have ads, so this in-app purchase will remove ads from the app.

I have these constants defined..


#define PURCHASED @"PURCHASED"
#define NOT_PURCHASED @"NOT_PURCHASED"
#define IN_APP_REMOVE_ADS @"com.coderzheaven.remove_ads" // this is the in-app purchase id which is set in iTunes.

We are saving a value “PURCHASED” in pList when the purchase is done successfully.
Also make sure that you have a file added to the resources named “AppData.plist” for the preferences to work.


-(void) showPBinView : (UIView *) view;
{
    if(activityIndicatorView){
        [activityIndicatorView removeFromSuperview];
    }
    activityIndicatorView = [[UIActivityIndicatorView alloc]
                             initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    activityIndicatorView.frame = CGRectMake(10.0, 0.0, 50.0, 50.0);
    activityIndicatorView.center = view.center;
    [activityIndicatorView hidesWhenStopped];
    [view addSubview:activityIndicatorView];
    [activityIndicatorView startAnimating];
    [view bringSubviewToFront:activityIndicatorView];
}

#pragma mark - In-App Purchases

// Call this function on Purchase Button with Restore set to NO.
-(void)fetchAvailableProducts : (BOOL) isRestore{
    
    self.isRestore = isRestore;
    
    NSSet *productIdentifiers = [NSSet setWithObjects:IN_APP_REMOVE_ADS,nil];
    productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
    productsRequest.delegate = self;
    [productsRequest start];
    
}

// Call this action on "Restore Purchases Button
-(void)checkPurchasedItems
{
    self.isRestore = NO;
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

//Then this delegate Function Will be fired
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    //NSLog(@"received restored transactions: %d", (int)queue.transactions.count);
    for (SKPaymentTransaction *transaction in queue.transactions)
    {
        NSString *productID = transaction.payment.productIdentifier;
        
        if ([productID isEqualToString:IN_APP_REMOVE_ADS]) {
            [self writeToPList:IN_APP_REMOVE_ADS :PURCHASED];
            NSLog(@"Restored Saving -> %@",productID);
            [self showAlert:@"Remove Ads"  message:@"Your 'Remove Ads' Purchase has been restored" okLabel:@"OK"];
            break;
        }
    }
}

- (BOOL)canMakePurchases
{
    return [SKPaymentQueue canMakePayments];
}

- (void)purchaseMyProduct:(SKProduct*)product{
    if ([self canMakePurchases]) {
        SKPayment *payment = [SKPayment paymentWithProduct:product];
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
    else{
        [self showAlert:@"Purchases are disabled in your device" message:nil okLabel:@"OK"];
    }
}

#pragma mark StoreKit Delegate

-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
    
    for (SKPaymentTransaction *transaction in transactions) {
        
       // NSLog(@"transaction.transactionState %l", transaction.transactionState);
        switch (transaction.transactionState) {
                
            case SKPaymentTransactionStatePurchasing:
                NSLog(@"Purchasing");
                break;
            case SKPaymentTransactionStatePurchased:
                if ([transaction.payment.productIdentifier isEqualToString:IN_APP_REMOVE_ADS]) {
                    NSLog(@"Purchased");
                    [self writeToPList:IN_APP_REMOVE_ADS :PURCHASED];
                    [[NSNotificationCenter defaultCenter] postNotificationName:@"RemoveAdsPurchased" object:nil];
                }
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                [self hidePB];
                break;
            case SKPaymentTransactionStateRestored:
                NSLog(@"Restored ");
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                [[NSNotificationCenter defaultCenter] postNotificationName:@"RemoveAdsPurchased" object:nil];
                [self hidePB];
                [self writeToPList:IN_APP_REMOVE_ADS :PURCHASED];
                break;
            case SKPaymentTransactionStateFailed:
                NSLog(@"Purchase failed ");
                [self.utils writeToPList:IN_APP_REMOVE_ADS :NOT_PURCHASED];
                [self hidePB];
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                [[NSNotificationCenter defaultCenter] postNotificationName:@"RemoveAdsNotPurchased" object:nil];
                break;
            default:
                [self hidePB];
                [self writeToPList:IN_APP_REMOVE_ADS :NOT_PURCHASED];
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                [[NSNotificationCenter defaultCenter] postNotificationName:@"RemoveAdsNotPurchased" object:nil];
                break;   
        }
        
    }
    
}

-(void) hidePB{
    [activityIndicatorView stopAnimating];
    self.view.userInteractionEnabled = YES;
}

-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    SKProduct *validProduct = nil;
    int count = (int)[response.products count];
    
    if (count > 0) {
        
        validProducts = response.products;
        validProduct = [response.products objectAtIndex:0];
        
        if ([validProduct.productIdentifier isEqualToString:IN_APP_REMOVE_ADS]) {
            NSLog(@"Product Title: %@",validProduct.localizedTitle);
        }
        
        if(self.isRestore){
            [self checkPurchasedItems];
        }
        else{
            [self hidePB];
            NSLog(@"Purchasing item");
            [self purchaseMyProduct:validProduct];
        }
    
    } else {
        //[self.utils showAlert:@"Not Available" message:@"No products to purchase" okLabel:@"OK"];
        [self writeToPList:IN_APP_REMOVE_ADS : NOT_PURCHASED];
        [self hidePB];
    }
}

-(NSString *) readFromPList : (NSString *) key{
    
    NSError *error;
    NSString *plistPath;
    NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                              NSUserDomainMask, YES) objectAtIndex:0];
    plistPath = [rootPath stringByAppendingPathComponent:@"AppData.plist"];
    
    NSString *bundle = [[NSBundle mainBundle] pathForResource:@"AppData" ofType:@"plist"];
    [[NSFileManager defaultManager] copyItemAtPath:bundle toPath: plistPath error:&error];
    NSMutableDictionary *plistDict = [[NSMutableDictionary alloc] initWithContentsOfFile: plistPath];
    
    return [plistDict objectForKey:key];
}

-(void) writeToPList:(NSString *)key : (NSString *) value{
    
    NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *plistPath = [rootPath stringByAppendingPathComponent:@"AppData.plist"];
    NSMutableDictionary* plistDict = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
    [plistDict   setValue:value forKey:key];
    [plistDict writeToFile:plistPath atomically: YES];
    return ;
}


- (void) showAlert:(NSString *) alertTtitle message:(NSString *) alertMessage okLabel:(NSString *) okLabel
{
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:alertTtitle
                                                    message:alertMessage
                                                   delegate:nil
                                          cancelButtonTitle:okLabel
                                          otherButtonTitles:nil];
    [alert show];
}

To initiate a call…


 if([[self readFromPList:inAppProductKey] isEqualToString:PURCHASED]){
       NSLog(@"%@", PURCHASED);
 }else{
       NSLog(@"%@", NOT_PURCHASED);
       [self fetchAvailableProducts:YES];
 }

How to Test

For testing in the Sandbox, you need to create test users in the SandBox Testers Section.
So Go to Users and Roles in the iTunes Account and create sandbox users.
Now Logout of the user in the Device and login with the Sandbox Test User.

Now run the app and when asked for login, login with the test user and make the purchase,
you will not be charged for this.

Please send your valuable feedback to coderzheaven@gmail.com

Leave a Reply

Your email address will not be published. Required fields are marked *