IN - App Purchase Issue

Hello Sir/Madam


I have a app named Project Planning Pro and it Offeres In-App . This Project was on Non-Arc but I converted it into ARC and after that My In-App is wprking fine in Sandbox Mode but when it goes live Restore/Upgrade process take too time to respond and our customers are getting affected.

Below are my code snippet Please let me know what I am doing wrong. Some time while purchasing a product in live redirect to Appstore and all purchasing thing done in App Store only it will no Redirect to My App so App is not getting any callback so that I can refresh it but it only happens in Live Environment. Please help me on this.

@property (nonatomic,retain)InAppPurchase* appPurchase;//This is my In App Purchase Class

//In - App Calls
-(void)restorepurchase:(NSString*)fetchprodID//restoring
{
    appPurchase=[[InAppPurchase alloc]initWithCaller:appDelegate.navController andProductIdentifier:fetchprodID];
    [appPurchase checkforCompletedStatusAndProceedForPurchasing];
  
  
}
-(void)callupgrade:(NSString*)fetchprodID//Upgrade
{
    appPurchase=[[InAppPurchase alloc]initWithCaller:appDelegate.navController andProductIdentifier:fetchprodID];
    [appPurchase requestProUpgradeProductData];
}

//Following are In-App Purchase class.m Implementation

/
/
/
/
/
/
/ 
#import "InAppPurchase.h"
#import "Projects.h"
#import "ProjectDetails.h"
#import "GAITracker.h"
#import "GAIDictionaryBuilder.h"
#import "LocalizationSystem.h"
#import "UpgradeView.h"
/
#import <AdSupport/AdSupport.h>
#define  KPLANNINGPROSELECTEDSURVEYUSEROPTION @"KPLANNINGPROSELECTEDSURVEYUSEROPTION"
#define  KPLANNINGPROUPGRADERESPONSETIME @"KPLANNINGPROUPGRADERESPONSETIME"
static BOOL subscriptionPurchase;
@implementation InAppPurchase
@synthesize callerViewController;
@synthesize productsRequest;
@synthesize productUpgradeProductId;
/Crashlytic Issue fix Integrate in PPP*/
/
#pragma mark - IAP  Methods
-(NSString*)base64forData:(NSData*)theData {
    const uint8_t* input = (const uint8_t*)[theData bytes];
    NSInteger length = [theData length];
   
    static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
   
    NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
    uint8_t* output = (uint8_t*)data.mutableBytes;
   
    NSInteger i;
    for (i=0; i < length; i += 3) {
        NSInteger value = 0;
        NSInteger j;
        for (j = i; j < (i + 3); j++) {
            value <<= 8;
           
            if (j < length) {
                value |= (0xFF & input[j]);
            }
        }
       
        NSInteger theIndex = (i / 3) * 4;
        output[theIndex + 0] =                    table[(value >> 18) & 0x3F];
        output[theIndex + 1] =                    table[(value >> 12) & 0x3F];
        output[theIndex + 2] = (i + 1) < length ? table[(value >> 6)  & 0x3F] : '=';
        output[theIndex + 3] = (i + 2) < length ? table[(value >> 0)  & 0x3F] : '=';
    }
   
    return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
}
-(NSString*)base64StringWithData:(NSData*)data
{
    if([data respondsToSelector:@selector(base64EncodedStringWithOptions:)])
    {
        return [data base64EncodedStringWithOptions:0];
    }
    else
    {
        return [self base64forData:data];
    }
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if(buttonIndex==1)
    {
        [appDelegate rateApplication:nil];
        [appDelegate trackGAEventWithCategeory:@"Rate App" andEventName:@"Rate Click" andSource:@"InApp Success" andValue:nil];
    }
}
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    responded=TRUE;
    if([[queue transactions] count])
    {
        if(!restoreMessageShown)
        {
            [busyIndicator removeFromSuperview];
            [[[callerViewController navigationController] navigationBar]
             setUserInteractionEnabled:YES];
            [[callerViewController view]setUserInteractionEnabled:YES];
           
            if(subscriptionRestored == TRUE)
            {
                UIAlertView* alertview=[[UIAlertView alloc]initWithTitle:appDelegate.appName message:AMLocalizedString(@"Your active subscriptions have been restored successfully", nil) delegate:nil cancelButtonTitle:AMLocalizedString(@"OK",nil) otherButtonTitles:nil];
                [alertview show];
            }
            else if(purchaseRestored == TRUE)
            {
                UIAlertView* alertview=[[UIAlertView alloc]initWithTitle:appDelegate.appName message:AMLocalizedString(@"Your previous In-App purchases have been restored successfully", nil) delegate:nil cancelButtonTitle:AMLocalizedString(@"OK",nil) otherButtonTitles:nil];
                [alertview show];
            }
            restoreMessageShown=TRUE;
        }
    }
    else if(restoredTransaction && !subscriptionRestored){
        restoredTransaction=false;
        [self requestProUpgradeProductData]; 
    }
}
-(void)checkforCompletedStatusAndProceedForPurchasing
{
    if([self canMakePurchases])
    {
        restoredTransaction=TRUE;
        restoreMessageShown=FALSE;
        responded=FALSE;
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
    }
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
    [busyIndicator removeFromSuperview];
    [[[callerViewController navigationController] navigationBar]
     setUserInteractionEnabled:YES];
    [[callerViewController view]setUserInteractionEnabled:YES];
}
-(id)initWithCaller:(id)caller andProductIdentifier:(NSString*)productIdentifier
{
    self=[super init];
    appDelegate = [[UIApplication sharedApplication]delegate];
    self.productUpgradeProductId = productIdentifier;
    subscriptionPurchase = FALSE;
    self.callerViewController=caller;
    [[[callerViewController navigationController] navigationBar] setUserInteractionEnabled:NO];
    [[callerViewController view]setUserInteractionEnabled:NO];
    busyIndicator=[[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    [busyIndicator startAnimating];
    busyIndicator.frame=CGRectMake([[[appDelegate.navController viewControllers]lastObject] view].frame.size.width/2-10, [[[appDelegate.navController viewControllers]lastObject]view].frame.size.height/2-10, 20, 20);
    [[[[appDelegate.navController viewControllers]lastObject] view] addSubview:busyIndicator];
   
/
     fetchTimer = [NSTimer scheduledTimerWithTimeInterval:120.0 target:self selector:@selector(showConnectionError)userInfo:nil repeats:NO];
   
    return self;
}

-(void)dealloc
{
    NSLog(@"InApp Deallocated");
    [self invalidateFetchTimer];
    self.callerViewController = nil;
  
    /
    self.productsRequest.delegate = nil;
    [self.productsRequest cancel];
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
    /
   
}
-(void)showConnectionError
{
    UIAlertView* alert=[[UIAlertView alloc]initWithTitle:AMLocalizedString(@"Error", nil) message:AMLocalizedString(@"The server is taking too long to respond. Please try again later.", nil) delegate:nil cancelButtonTitle:AMLocalizedString(@"OK", nil) otherButtonTitles:nil, nil];
    [alert show];
    if(productsRequest)
    {
        [productsRequest cancel];
    }
    [busyIndicator removeFromSuperview];
    [[[callerViewController navigationController]navigationBar]setUserInteractionEnabled:YES];
    [[callerViewController view]setUserInteractionEnabled:YES];
}
- (void)requestProUpgradeProductData
{
    if ([self canMakePurchases]==YES)
    {
        NSSet *productIdentifiers = nil;
        if(subscriptionPurchase)
        {
            if(isMonthlySubscription)
            {
                productIdentifiers = [NSSet setWithObjects:kInAppMonthlySubscriptionProductId,nil];
            }
            else
            {
               productIdentifiers = [NSSet setWithObjects:kInAppYearlySubscriptionProductId,nil];
            }
        }
        else
        {
            productIdentifiers = [NSSet setWithObjects:productUpgradeProductId,nil];
        }
        productsRequest=[[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
        productsRequest.delegate = self;
        responded=FALSE;
        [productsRequest start];
    }
    /
}
#pragma mark SKProductsRequestDelegate methods
-(void)invalidateFetchTimer/Crashlytic Issue fix Integrate in PPP*/
{

        if(fetchTimer != nil && [fetchTimer isKindOfClass:[NSTimer class]])
        {
            if ([fetchTimer isValid])/
                  [fetchTimer invalidate];
            fetchTimer = nil;
        }
}
-(void)requestDidFinish:(SKRequest *)request{
    NSLog(@"finished");
}
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error{
    NSLog(@"fail");
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    [self invalidateFetchTimer];
    responded=YES;
    NSArray *products = response.products;
   
    if(subscriptionPurchase)
    {
        proUpgradeProduct = [products lastObject];
    }
    else
    {
        if (products.count != 0)/
        proUpgradeProduct = [products objectAtIndex:0];
    }
   
    if (proUpgradeProduct)
    {
        [self purchaseProUpgrade];
    }
    for (NSString *invalidProductId in response.invalidProductIdentifiers)
    {
        NSLog(@"Invalid product id: %@" , invalidProductId);
    }

    self.productsRequest = nil;/
    [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerProductsFetchedNotification object:self userInfo:nil];
}
/
- (BOOL)canMakePurchases
{
    return [SKPaymentQueue canMakePayments];
}
/
- (void)purchaseProUpgrade
{
    appDelegate = [[UIApplication sharedApplication]delegate];
    appDelegate.inAppSucceedMsgShown=NO;
    SKPayment* payment=[SKPayment paymentWithProduct:proUpgradeProduct];
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}
#pragma Purchase helpers
/
- (void)recordTransaction:(SKPaymentTransaction *)transaction
{
}
/
- (void)provideContent:(NSString *)productId
{
    appDelegate=[[UIApplication sharedApplication]delegate];
   
    if ([productId isEqualToString:kInAppPurchaseProUpgradeProductId] || [productId isEqualToString:kInAppPurchaseProUpgradeProductIdLowPrice] || [productId isEqualToString:kInAppPurchaseProUpgradeProductIdDiscount])
    {
        [[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:KPAIDAPPUPGRADESTATUSKEY ];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
    else
    {
        [ManageCounters checkSubscriptionValidity];
    }
    appDelegate.gaantPro=TRUE;
    appDelegate.showAds=FALSE;
   
    if([[appDelegate.navController viewControllers] count]==2)
    {
        [(ProjectDetails*)[appDelegate visibleViewController] dontShowAdsForSession];
    }
    else
    {
        [(Projects*)appDelegate.projectsController performSelectorOnMainThread:@selector(refreshProjects) withObject:nil waitUntilDone:NO];
    }
    [(Projects*)appDelegate.projectsController dontShowAdsForSession];
}
/
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful
{
    [appDelegate.upgradeButton setEnabled:YES];
    [busyIndicator removeFromSuperview];
   
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
   
    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:transaction, @"transaction" , nil];
    [[[callerViewController navigationController]navigationBar]setUserInteractionEnabled:YES];
    [[callerViewController view]setUserInteractionEnabled:YES];
    [callerViewController dismissViewControllerAnimated:YES completion:nil];
   
    if (wasSuccessful)
    {
        if (appDelegate.inAppSucceedMsgShown == NO)
        {
            [self provideContent:transaction.payment.productIdentifier];
            [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionSucceededNotification object:self userInfo:userInfo];
            [appDelegate hideUpgradeButton];
            if(!restoredTransaction)
            {
                NSString* title=appDelegate.appName;
                if([AMLocalizedString(@"Language", nil)isEqualToString:@"Default"])
                {
                    title=[NSString stringWithFormat:@"%@!",AMLocalizedString(@"Rate Now", nil)];
                }
               
                UIAlertView* alert=[[UIAlertView alloc]initWithTitle:title message:[NSString stringWithFormat:@"%@ %@",AMLocalizedString(@"Congratulations. Your upgrade was successful.", nil),AMLocalizedString(@"Kindly take a few moments to Rate us in the app store. Your ratings are important to us.", nil)] delegate:self cancelButtonTitle:AMLocalizedString(@"Later", nil) otherButtonTitles:AMLocalizedString(@"Yes, rate now", nil), nil];
                [alert show];
                [appDelegate trackGAEventWithCategeory:@"Rate App" andEventName:@"Shown" andSource:@"In APP Success" andValue:nil];
                [FBSDKAppEvents logPurchase:1.0f currency:@"USD"];
               
                SKProduct* product = proUpgradeProduct;
                if(product)
                {
                    int quantity = transaction.payment.quantity;
                    float unitPrice = [product.price floatValue];
                    float revenue = unitPrice * quantity;
                    NSString *currencyCode = [product.priceLocale objectForKey:NSLocaleCurrencyCode];
           
/
/
/
/
/
/
                }
            }
            appDelegate.inAppSucceedMsgShown=YES;
        }
        if(!restoredTransaction)
        {
           
            [appDelegate.gATracker  send:[[GAIDictionaryBuilder createTransactionWithId:transaction.transactionIdentifier affiliation:nil revenue:[NSNumber numberWithFloat:14.99*0.7*1000000] tax:[NSNumber numberWithFloat:14.99*0.3*1000000] shipping:nil currencyCode:@"$"]build]];
            NSDateFormatter* dateFormatter_=[[NSDateFormatter alloc] init];
            [dateFormatter_ setLocale:appDelegate.defaultLocale];
            [dateFormatter_ setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
            [dateFormatter_ setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
           
            NSString* str=[dateFormatter_ stringFromDate:[NSDate date]];
            NSDate* date1=[dateFormatter_ dateFromString:str];
            NSFileManager* manager = [NSFileManager defaultManager];
        /
            NSDictionary* attributes = [manager attributesOfItemAtPath:appDelegate.sharedDirectoryPath error:nil];
        
            NSDate *date2 = nil;
           
            if (attributes != nil)
            {
                date2=[attributes fileCreationDate];
                NSString* str=[dateFormatter_ stringFromDate:date2];
                date2=[dateFormatter_ dateFromString:str];
            }
           
            if(date1 && date2)
            {
                NSDateComponents *conversionInfo = [[NSCalendar currentCalendar] components:NSCalendarUnitDay fromDate:date2  toDate:date1  options:0];
                int days = (int)[conversionInfo day];
                [[NSUserDefaults standardUserDefaults]setValue:[NSString stringWithFormat:@"%d",days] forKey:KPLANNINGPROUPGRADERESPONSETIME];
                [[NSUserDefaults standardUserDefaults]synchronize];
            }
            [appDelegate setShowPaidCongrtsLabel:YES];
           
            if(!paidSurveyShown)
            {
                [appDelegate performSelector:@selector(showPaidSurveyView) withObject:nil
                                  afterDelay:1.6f];
                paidSurveyShown=TRUE;
            }
            /
            /
            /
           
            [ACTConversionReporter reportWithConversionID:@"995494498" label:@"D3rLCJfvvF8Q4pTY2gM" value:@"1.00" isRepeatable:YES];
            [appDelegate trackGAEventWithCategeory:@"Upgrade" andEventName:@"IAP_Success" andSource:[proUpgradeProduct productIdentifier] andValue:nil];
            [appDelegate trackGAEventWithCategeory:@"Survey" andEventName:[[NSUserDefaults standardUserDefaults]objectForKey:KPLANNINGPROSELECTEDSURVEYUSEROPTION] andSource:nil andValue:nil];
        }
        [appDelegate removeAllLocalNotifications];
    }
    else
    {
        /
        [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionFailedNotification object:self userInfo:userInfo];
       
    }
}
/
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
    [self invalidateFetchTimer];
   
    [self recordTransaction:transaction];
    [self finishTransaction:transaction wasSuccessful:YES];
}
/
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
    [self invalidateFetchTimer];
   
    [self recordTransaction:transaction.originalTransaction];
    [self finishTransaction:transaction wasSuccessful:YES];
}
/
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
    [self invalidateFetchTimer];
   
    if (transaction.error.code != SKErrorPaymentCancelled)
    {
        [self finishTransaction:transaction wasSuccessful:NO];
    }
    else
    {
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        [appDelegate.upgradeButton setEnabled:YES];
        if(callerViewController != nil)
        {
            [[[callerViewController navigationController] navigationBar] setUserInteractionEnabled:YES];
            [[callerViewController view]setUserInteractionEnabled:YES];
        }
        [busyIndicator removeFromSuperview];
       
        [appDelegate trackGAEventWithCategeory:@"Upgrade" andEventName:@"IAP_Cacelled2" andSource:([[proUpgradeProduct productIdentifier]isEqualToString:kInAppPurchaseProUpgradeProductId]?@"IAP1":@"IAP2") andValue:nil];
    }
}
#pragma mark SKPaymentTransactionObserver methods
/ 
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    [self invalidateFetchTimer];
   
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    for (SKPaymentTransaction *transaction in transactions)
    {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchased:
               
               
                /
                if([NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]] && subscriptionPurchase)/
                {
                    [userDefaults setObject:[self base64StringWithData:[NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]] forKey:KSUBSCRIBEDAPPRECIEPTKEY];
                    [userDefaults synchronize];
                }
                [self completeTransaction:transaction];
                break;
               
            case SKPaymentTransactionStateFailed:
               
                [self failedTransaction:transaction];
                 [[[UIAlertView alloc] initWithTitle:appDelegate.appName message:AMLocalizedString(@"Error: Transaction Failed.Please try again later.", nil) delegate:nil cancelButtonTitle:AMLocalizedString(@"Ok", nil) otherButtonTitles: nil] show];
                break;
               
            case SKPaymentTransactionStateRestored:
                if([NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]])
                {
                    NSString* recieptData = [self base64StringWithData:[NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]];
                    if(recieptData)
                    {
                        NSError *error = nil;
                        NSDictionary *requestContents = [NSDictionary dictionaryWithObjectsAndKeys:recieptData,@"receipt-data",@"1bb2ee24ac2d4215bdec51ac6dc3daf3",@"password",nil];
                        NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
                        NSURL *storeURL = [NSURL URLWithString:KRECEIPTVALIDATIONURL];
                        NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
                        [storeRequest setHTTPMethod:@"POST"];
                        [storeRequest setHTTPBody:requestData];
                        NSURLResponse* response = nil;
                        NSData* data_ = [NSURLConnection sendSynchronousRequest:storeRequest returningResponse:&response error:&error];
                        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data_ options:0 error:&error];
                        if(jsonResponse && [jsonResponse objectForKey:@"latest_receipt"])
                        {
                            subscriptionRestored = TRUE;
                            [userDefaults setObject:[jsonResponse objectForKey:@"latest_receipt"] forKey:KSUBSCRIBEDAPPRECIEPTKEY];
                            [userDefaults synchronize];
                            [self restoreTransaction:transaction];
                            break;
                        }
                        else if([jsonResponse objectForKey:@"latest_expired_receipt_info"] == nil && [jsonResponse objectForKey:@"latest_receipt"] == nil)
                        {
                            purchaseRestored = TRUE;
                            [self restoreTransaction:transaction];
                        }
                    }
                }
                else
                {
                    purchaseRestored = TRUE;
                    [self restoreTransaction:transaction];
                }
                break;
               
            default:
               
                break;
        }
    }
}
@end

The sandbox website returns quicker than the production website because the production website has a much larger database it needs to search.


But what you are doing is quite wierd, ineffective and deprecated multiple times over.


You are using "NSURLConnection sendSynchronousRequest:". That is deprecated. It is also a terrible user experience as they wait for a synchronous response.

And then you are 'testing' whether or not there is a field called 'latest_receipt' which is undocumented. (Others may disagree - they are wrong.) And even if it is returned for your receipt, it doesn't really indicate that it is not expired, just that it is the 'latest receipt'. And you don't need it anyway.


Use NSURLSession. Use asynchronous transmissions. And parse the receipt for the appropriate fields. Even better, learn OpenSSL decoding and do it on the device.

IN - App Purchase Issue
 
 
Q