How To Implement InApp Payment System

In this article, you will learn how to implement InApp internal payment system in iOS and OSX.

What exactly InApp is

InApp is an internal payment system for iOS and OSX. It allows developers to let users pay for some functionality, additional digital content, service, or any subscription.

Suppose, you have an app where you are showing ads and you want to add a “No-Adv” feature which will disable advertisement* on a nominal price payment. In such a case, you can use InApp Payment which includes digital content and some functionality (i.e. blocks of code which will execute after user's payment). Needless to say, there are several apps on App Store which give us the option to buy extra coins or extra features or any additional material and all of them are using InApp Payment (IAP).

Since there are different kinds of Product Type (by Product Type, I mean the feature which we’ll implement) which fulfill different requirements, every requirement or feature belongs to any of the categories.

Apple has basically four purchase categories.

  • Consumable
  • Non-Consumable
  • Auto-Renewable Subscription
  • Non-Renewable Subscription

Consumable In App Purchase can be purchased each time the user wants it. Suppose you have a game where you want your user to buy coins, ammunition, guns, or any features which you can purchase a number of times. Such features fall in this category where you have no foundation for purchasing a single time.

Non-Consumable In App Purchase can be purchased just once. As the name suggests, it is the exact opposite of Consumable Purchase. It can only be purchased once by the user and is available to all the devices where you’ve signed the same iTunes account. In case of any mishap, you can restore non-consumable product without paying for the same feature.

Suppose you have an Image Editor app where you have purchased Professional effects. Such features belong to you or your app as long as you have your same iTunes account. Such types of products don't expire and they  belong to the lifetime of an iTunes account.

Auto-Renewable Subscription allows users to purchase periodic content or digital content for a set of time. With this purchase, subscription auto-renews after expiration. Suppose you have a Magazine App’s Subscription where you have to pay every month, it renews itself.

On the other hand, Non-Renewable Subscription allows users to purchase a service for a period of time. Then, it will expire. You are no longer allowed to use that service or feature. Or you may say, it’s a Time-Based access to certain content.

So far, we have covered what is InApp and the  kind of In App Purchases that Apple allows.

Stages we need to follow.

  1. Create InApp Product Records in iTunes Connect Portal.
  2. Add Tester User in iTunes to test your app in Sandbox Environment.
  3. Ultimately, add your Codes and perform Product Info Retrieval or Product Payment Request or Restoration.

Create InApp Product Record in iTunes Connect Portal

Log into your Apple account on iTunes Portal ( https://itunesconnect.apple.com/ ). After successful login, create a new app and open its app Information page by clicking on it.

Make sure you have the same Bundle ID of your Xcode project as you have in your App Information page.

On the top-left corner, you have Features tab where you will create new In App Purchase Product.



In this window, there is a plus button which will popup a window.




Here, you will choose the kind of product you want in your app. If you have no idea, then choose between Consumable or Non-Consumable.

As far as my requirement is concerned, I’m making my product Consumable.


Next, you’ll get an InApp purchase form where you are asked Reference Name and Product ID. Both of them must be unique and Product ID will be further used in your code. Better make it a descriptive identifier.

So far, we have done InApp product registration which is one of the prerequisites for debugging the process of InApp Payment.

Next, we’ll make a Test User Account which we will use in InApp Testing Purposes which is a Sandbox environment for testing.

Add Test User for InApp Testing

In Your iTunes Connect main screen, you have User And Roles where we will create a new Test User in Sandbox.



Click on the Plus (+) sign to create a new user. And, from here you will be moved to Registration page where you can pass dummy data for the sake of testing.



And, make a point when you are passing the App Store Territory as it determines the type of currency you have. Moreover, make a note of your Username and Password for further use.

And, you will use these credentials in your Device’s iTunes account for testing.

Configure your Device with Testing Account

[SNAPSHOT]

Go to Settings > App & iTunes Stores. There, you will click on your Apple ID and then, Sign Out.

Add your newly created sandbox account in iTunes username and password fields.

[SNAPSHOT]

Overview, What We Are Going To Do,

[Have to write]

Now, Open Your Xcode

Before you start writing a code, you need to include an external class file called RMStore which is a wrapper class over Apple’s StoreKit.framework, without any dependencies.

Or, you can install RMStore from Cocoapods.

In Cocoapod file,
  1. <code>  
  2. pod 'RMStore''~> 0.7'  
  3. </code>  
Also, you can manually add the external class file in your project.

Downloadhttps://github.com/robotmedia/RMStore

Before I get into codes, I want to get in the flow of code blocks. From Apple’s developer website, there is a descriptive image which tells about the In App Payment Code structure.




As per developers view, we have three phase,
  • Retrieving Information from the iTunes about the In App Products.
  • After Confirmation You are requesting to buy any of the product.
  • Last, we will determine the response whether payment is successful or failed.

Requesting Product Information

In this stage, we will ask from iTunes about the InApp Product desired Details. So, there is a method named,

  1. <code>  
  2.   
  3. - (void) requestProducts:(NSSet*)identifiers  
  4.              success:(RMSKProductsRequestSuccessBlock)successBlock  
  5.                 failure:(RMSKProductsRequestFailureBlock)failureBlock  
  6.   
  7. </code>  
Where, First parameter is NSSet type which stores the array of ProductIDs and last two are blocks.

Let’s add some reference declaration what we are going to use in further steps,
  1. <code>  
  2.   
  3. @implementation ProductListTableViewController{  
  4.       
  5.     UIActivityIndicatorView *activityView;  
  6.     NSMutableArray *modalProductArray;  
  7.     ModalProduct *product;  
  8.     NSMutableArray *LIST_OF_PRODUCT;  
  9. }  
  10. </code>  
So, it would be something like this,
  1.  <code>  
  2.     // Create an Array  
  3.     LIST_OF_PRODUCT = [[NSMutableArray alloc] initWithObjects:@"subscription_for_1Day",@"subscription_for_1Week",@"subscription_for_1Year",@"subscription_for_1Month", nil ];  
  4.       
  5.     // Tranform it into NSSet Type  
  6.     NSSet *productsSet = [NSSet setWithArray:LIST_OF_PRODUCT];  
  7.   
  8.   
  9. // Now, we'll call the RMStore Method which request about the product  
  10.   
  11. [[RMStore defaultStore] requestProducts:productIDList success:^(NSArray *products, NSArray *invalidProductIdentifiers) {  
  12.   
  13. // When Successfully identifies ProductID  
  14.   
  15. }  
  16. failure:^(NSError *error) {  
  17.   
  18.     // Your Codes when You have some error.  
  19. }  
  20.   
  21. </code>  
Troubleshoot Point

Suppose you are getting failure: callback every time you execute your code; then there must be something wrong with your Bundle ID or your ProductID name. It’s one of the fundamental issues while you are dealing with In App Payment.

Next, we will write code when you are in succes: block. And, there are two array arguments where one is for successful product identifiers and second is invalid product identifiers.
  1. <code>  
  2. // A Local Array where are storing Model Object’s instance  
  3.  modalProductArray = [[NSMutableArray alloc]init];  
  4.   
  5.   
  6. for(SKProduct *productObject in products)  
  7.                 {  
  8.                     // Create Modal Object Instance  
  9.                     product = [[ModalProduct alloc]init];  
  10.                       
  11.                     product.productName = productObject.localizedTitle;  
  12.                     product.productPrice = [RMStore localizedPriceOfProduct:productObject];  
  13.                     product.productID = productObject.productIdentifier;  
  14.                     product.region = [productObject.priceLocale objectForKey:NSLocaleCurrencyCode];  
  15.                     [modalProductArray addObject:product];  
  16.                       
  17.                     NSLog(@"Title : %@",product.productName);  
  18.                     NSLog(@" Description : %@",product.description);  
  19.                     NSLog(@"Price :  %@",product.productPrice);  
  20.                     NSLog(@"Region : %@",product.region);  
  21.                     NSLog(@"Object ID : %@",product.productID);  
  22.                     NSLog(@"============");  
  23.                 }  
  24.   
  25.   
  26. </code>  
Here, we are looping in products array which is a SKProduct type. And, every SKProduct has property like localizedTitle (Title Name what you have written while registering Product), localizedDescription (Description Text),

price(which is in NSDecimal type) , priceLocale (where, it is in localized price), productIdentifier (Unique Product ID).

As per your requirement you can access any of them. But here, I’m storing all the information of the product. So, instead of storing in a local variable we’ll create a Modal Class which has the same property as SKProduct has.

Add a new class in your project and all four properties in your (.h) file. As, I have written in my ModalProduct.h file,
  1. <code>  
  2.  
  3. #import <Foundation/Foundation.h>  
  4.   
  5. @interface ModalProduct : NSObject  
  6.   
  7. @property(nonatomic) NSString *productName;  
  8. @property(nonatomic) NSString *productPrice;  
  9. @property(nonatomic) NSString *productID;  
  10. @property(nonatomic) NSLocale *region;  
  11.   
  12. @end  
  13.   
  14. </code>   
Back there, I've created an instance of model class and passed all the SKProduct value to model’s property.
  1. <code>   
  2. // Create Modal Object Instance  
  3.  product = [[ModalProduct alloc]init];  
  4. </code>  
And, setting value in property,
  1. <code>  
  2. //Step2  
  3.   
  4. product.productName = productObject.localizedTitle;  
  5. product.productPrice = [RMStore localizedPriceOfProduct:productObject];  
  6. product.productID = productObject.productIdentifier;  
  7. product.region = [productObject.priceLocale objectForKey:NSLocaleCurrencyCode];  
  8.   
  9. </code>  
Finally, we store the Model Product’s object in an array called modalProductArray.
  1. <code>  
  2. // Step 3  
  3.   
  4. [modalProductArray addObject:product];  
  5.  </code>  
Till now, we have retrieved data from iTunes and stored in an array. In Further steps, we will use this array as a data source and populate a Tableview Controller cell.



Create a Table View Controller class and make your Table View Cell as Subtitle type. And, configure your Table View Controller which looks something alike,


Now, we configure all the delegate methods. All you need is a data source (i.e. modalProjectArray) from where we will be retrieving data and populating the table’s cell.

 

  • NumberOfSectionsInTableView
    1. <code>  
    2.   
    3. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {  
    4.     // Return the number of sections.  
    5.     return 1;  
    6. }  
    7.   
    8.   
    9. </code>  
  • NumberOfRowsInSection
    1. <code>  
    2. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {  
    3.     // Return the number of rows in the section.  
    4.     return modalProductArray.count;  
    5. }  
    6. </code>  
  • cellForRowAtIndexPath
    1. <code>  
    2. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {  
    3.       
    4.     static NSString *simpleTableIdentifier = @"product";  
    5.       
    6.     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];  
    7.       
    8.     if (cell == nil) {  
    9.         cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:simpleTableIdentifier];  
    10.     }  
    11.       
    12.     // Format Detail TextLabel  
    13.     NSString *priceLabel = [NSString stringWithFormat:@"You 'll Pay Rs. %@",[[modalProductArray objectAtIndex:indexPath.row] productPrice]];  
    14.       
    15.     // Populate Data in cell  
    16.     cell.textLabel.text = [[modalProductArray objectAtIndex:indexPath.row] productName];  
    17.     cell.detailTextLabel.text = priceLabel;  
    18.       
    19.     return cell;  
    20.       
    21.     }  
    22.   
    23. </code>  
  • didSelectRowAtIndexPath
    1. <code>  
    2. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath  
    3. {  
    4.     NSString *selectedProduct = [[modalProductArray objectAtIndex:indexPath.row] productID];  
    5.   
    6.     NSLog(@"You have selected : %@",selectedProduct);  
    7.       
    8.     // Purchase Method  
    9.     [self purchaseInAppProduct:selectedProduct];  
    10.       
    11. }  
    12.   
    13. </code>  

It lets user choose the product and pass selectedProduct as parameter to purchaseInAppProduct method.

This method belongs to the second phase where we are paying for the selected product. For purchasing there is a custom method called purchaseInAppProduct.

  1. <code>  
  2.   
  3. -(void)purchaseInAppProduct:(NSString *)productName{  
  4.       
  5.     [[RMStore defaultStore] addPayment:productName success:^(SKPaymentTransaction *transaction) {  
  6.           
  7.         [activityView stopAnimating];  
  8.         [[UIApplication sharedApplication] endIgnoringInteractionEvents];  
  9.           
  10.         // Transaction Log  
  11.         [self inAppTransactionLog:transaction];  
  12.           
  13.         // Home Screen  
  14.         [self.navigationController popToRootViewControllerAnimated:YES];  
  15.           
  16.     } failure:^(SKPaymentTransaction *transaction, NSError *error) {  
  17.           
  18.         //Run if the purchases fails  
  19.         [activityView stopAnimating];  
  20.         [[UIApplication sharedApplication] endIgnoringInteractionEvents];  
  21.           
  22.         // Transaction Log  
  23.         [self inAppTransactionLog:transaction];  
  24.            
  25.     }];  
  26.       
  27. }  
  28.   
  29. -(void)inAppTransactionLog:(SKPaymentTransaction *)transaction{  
  30.       
  31.     // Transaction Log  
  32.     NSLog(@"Transaction Date : %@", transaction.transactionDate);  
  33.     NSLog(@"Transaction ID : %@", transaction.transactionIdentifier);  
  34.       
  35.     switch (transaction.transactionState) {  
  36.         case SKPaymentTransactionStatePurchased:  
  37.             NSLog(@"You Have Successfully Purchased.");  
  38.             break;  
  39.               
  40.         case SKPaymentTransactionStateFailed:  
  41.             NSLog(@"Sorry, There is some issue in InApp Payment.");  
  42.             break;  
  43.               
  44.         case SKPaymentTransactionStatePurchasing:  
  45.             break;  
  46.               
  47.         case SKPaymentTransactionStateDeferred:  
  48.             break;  
  49.               
  50.         case SKPaymentTransactionStateRestored:  
  51.             break;  
  52.               
  53.         default:  
  54.             NSLog(@"Unknown Error");  
  55.             break;  
  56.     }  
  57.           
  58. }  
  59.   
  60. </code>  
RMStore gives us a addPayment: method which passes two blocks as parameters (success and failure). And, both the block has SKPaymentTransaction type instance as parameter which holds the transaction’s information like purchasing date and purchasing transaction ID.

Now, you are ready to test your code with the sandbox account what you have created in the beginning of this blog. Before doing this, make sure you have added your Sandbox account in your Device’s iTunes.

If everything goes right, your flow of app would be like the following.

When Page successfully loads and fills the TableView cell.




When you select any of the cells, then you will move to InApp Payment transaction. Finally, there is a Thank You message .





Conclusion

In this article, we dealt with InApp Payment where we did basic setups and then, wrote the code for Product Request for displaying Product Details and their payment. Moreover, you can store some NSUserDefault value on successful payment to be aware that you have paid for the product or you can use the restoration technique of RMStore.