Sunday, May 3, 2015

Add iOS in App Purchase to Your Cordova Application


Recently I was working on cordova application where we have to add in app purchase in iOS. In this blog I am going to explain how to add in app purchase to cordova based application.

First of all open your MainViewController.m file and un comment following function.

- (BOOL) webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType

As we are going to use above function to pass product id to native code from JavaScript with use of this function. Here is how to do this. From your JavaScript file add following code.

window.location.href = ''+sku.toLowerCase();

This will invoke shouldStartLoadWithRequest delegate. Now in that delegate add following code.

NSURL *url = [request URL];
if([[url hostisEqual: @""]){
        NSString *queryString = url.query;
        NSArray* queryStringValues = [queryString componentsSeparatedByString: @"&"];
        NSString* productId = [[[queryStringValues objectAtIndex:0] componentsSeparatedByString: @"="] objectAtIndex:1];
       return NO;

This way we get product id in native code and since we returned NO in that delegate, webview will not invoke this url.

Now let's add required library to support in App Purchase. First select project from project explorer and select build phases tab. At bottom where we have linked libraries click on + sign and search for storekit. It will show following framework. Add this to project.

Now open MainViewController.h file and add necessary import statements and delegates. Copy following code.


@interface MainViewController : CDVViewController <SKProductsRequestDelegate, UIAlertViewDelegate, SKPaymentTransactionObserver>
@property (retain, nonatomic) SKProduct* fetchedProduct;
@interface MainCommandDelegate : CDVCommandDelegateImpl

@interface MainCommandQueue : CDVCommandQueue

Now open MainViewController.m file and add necessary callbacks.

#pragma mark -
#pragma mark SKProductsRequestDelegate methods

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    SKPayment * payment = [SKPayment paymentWithProduct:fetchedProduct];
    [[SKPaymentQueue defaultQueue] addPayment:payment];

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
    for (SKPaymentTransaction * transaction in transactions) {
        switch (transaction.transactionState)
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:transaction];
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];

- (void)completeTransaction:(SKPaymentTransaction *)transaction {

- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
    //call javascript function to consume product only
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

- (void)failedTransaction:(SKPaymentTransaction *)transaction {
    if (transaction.error.code != SKErrorPaymentCancelled)
        NSLog(@"Transaction error: %@", transaction.error.localizedDescription);
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

This are necessary functions to support transactions and product request. Now lets first request a product information. Go back to shouldStartLoadWithRequest and add following code at the end.

BOOL productPurchased = [[NSUserDefaults standardUserDefaults] boolForKey:productId];
        if (productPurchased) {
            //call javascript function to consume product
            [self.webView stringByEvaluatingJavaScriptFromString:@"consumePurchasedProduct();"];
            SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
            productsRequest.delegate = self;
            [productsRequest start];

Here we are checking if product already purchased. If already purchased simply call JavaScript function to consume it else start product request. After we get product information we have to show it to user.  Add following code to productRequest delegate.

NSArray *products = response.products;
    fetchedProduct = [products count] == 1 ? [products firstObject] : nil;
    if (fetchedProduct)
        NSLog(@"Product title: %@" , fetchedProduct.localizedTitle);
        NSLog(@"Product description: %@" , fetchedProduct.localizedDescription);
        NSLog(@"Product price: %@" , fetchedProduct.price);
        NSLog(@"Product id: %@" , fetchedProduct.productIdentifier);
        NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
        [formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
        [formatter setLocale:[NSLocale currentLocale]];
        NSString *localizedMoneyString = [formatter stringFromNumber:fetchedProduct.price];

        NSString *productPrice = @"Price : ";
        productPrice = [productPrice stringByAppendingString:localizedMoneyString];
        NSString* alertViewContent = fetchedProduct.localizedDescription;
        alertViewContent = [alertViewContent stringByAppendingString:@"\n \n"];
        alertViewContent = [alertViewContent stringByAppendingString:productPrice];
        UIAlertView * alert = [[UIAlertView alloc] initWithTitle:fetchedProduct.localizedTitle message:alertViewContent delegate:self cancelButtonTitle:@"Buy" otherButtonTitles:nil];
        [alert show];

Above function will show alert like this with product information.

As you can see we have a buy button there. When user clicks on Buy it will call clickedButtonAtIndex function added in above code and it will start payment process. One payment is done it will call completeTransaction delegate. Add following code to it.

    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:fetchedProduct.productIdentifier];
    //call javascript function to consume product
    [self.webView stringByEvaluatingJavaScriptFromString:@"consumePurchasedProduct();"];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

Here we are adding product to user defaults in case network got disconnected before user can consume product. In case of transaction failure other functions will be called.

No comments:

Post a Comment