Sunday, 10 April 2016

Validate your INAPP purchase with APPLE server



InApp purchase validation IOS


Fraud, happened to so many app developers where in-app purchase never occurs with server and request was redirected to dummy server so that user can unlock in-app for free. Mostly happened in jailbroken devices that let app developer frustrated as in-app is the way to get some bucks in his/her pocket as app revenue.

                                    There is no way to correct this apart from implementing a server validation of the receipt that is returned after successful in-app purchase. If you tried to do validation within the app then you will never broke this fraud as hackers can change DNS server settings within your devices. In this post i am going to post code that let you verify your in-app purchase with in the app.

We will call below given method, getStoreReceipt in method

- (void) completedPurchaseTransaction: (SKPaymentTransaction *) transaction


getStoreReceipt takes a bool to determine wether you are testing in-app against a SANDBOX environment or LIVE server. Below is the code , you can copy paste whole code and paste into the class where you implemented completedPurchaseTransaction method.

Method 1: 

// this returns an NSDictionary of the app's store receipt, status=0 for good, -1 for bad
- (NSDictionary *)getStoreReceipt:(BOOL)sandbox {
    
    NSArray *objects;
    NSArray *keys;
    NSDictionary *dictionary;
    
    BOOL gotreceipt = false;
    
    @try {
        
        NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
        
        if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptUrl path]]) {
            
            NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
            
            NSString *receiptString = [self base64forData:receiptData];
            
            if (receiptString != nil) {
                
                objects = [[NSArray alloc] initWithObjects:receiptString, nil];
                keys = [[NSArray alloc] initWithObjects:@"receipt-data", nil];
                dictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
                
               NSData *postData1  = [NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:nil];
                NSString *postData =[[NSString alloc] initWithData:postData1 encoding:NSUTF8StringEncoding];
                NSString *urlSting = @"https://buy.itunes.apple.com/verifyReceipt";
                if (sandbox) urlSting = @"https://sandbox.itunes.apple.com/verifyReceipt";
                
                dictionary = [self getJsonDictionaryWithPostFromUrlString:urlSting andDataString:postData];
                
                if ([dictionary objectForKey:@"status"] != nil) {
                    
                    if ([[dictionary objectForKey:@"status"] intValue] == 0) {
                        
                        gotreceipt = true;
                        
                    }
                }
                
            }
            
        }
        
    } @catch (NSException * e) {
        gotreceipt = false;
    }
    
    if (!gotreceipt) {
        objects = [[NSArray alloc] initWithObjects:@"-1", nil];
        keys = [[NSArray alloc] initWithObjects:@"status", nil];
        dictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
    }
    
    return dictionary;
}


Method 2: 

- (NSDictionary *) getJsonDictionaryWithPostFromUrlString:(NSString *)urlString andDataString:(NSString *)dataString {
    NSString *jsonString = [self getStringWithPostFromUrlString:urlString andDataString:dataString];
    NSLog(@"%@", jsonString); // see what the response looks like
    return [self getDictionaryFromJsonString:jsonString];
}

Method 3: 

- (NSDictionary *) getDictionaryFromJsonString:(NSString *)jsonstring {
    NSError *jsonError;
    NSDictionary *dictionary = (NSDictionary *) [NSJSONSerialization JSONObjectWithData:[jsonstring dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&jsonError];
    if (jsonError) {
        dictionary = [[NSDictionary alloc] init];
    }
    return dictionary;
}

Method 4: 

- (NSString *) getStringWithPostFromUrlString:(NSString *)urlString andDataString:(NSString *)dataString {
    NSString *s = @"";
    @try {
        NSData *postdata = [dataString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
        NSString *postlength = [NSString stringWithFormat:@"%d", [postdata length]];
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
        [request setURL:[NSURL URLWithString:urlString]];
        [request setTimeoutInterval:60];
        [request setHTTPMethod:@"POST"];
        [request setValue:postlength forHTTPHeaderField:@"Content-Length"];
        [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        [request setHTTPBody:postdata];
        NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
        if (data != nil) {
            s = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        }
    }
    @catch (NSException *exception) {
        s = @"";
    }
    return s;
}

Method 5: 

- (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];
}


Hope above code helps you, but to stop fraud in-app you must verify reciept by sending it to your own server for verification with APPLE server.

5 comments:
Write comments
  1. Hi aman,

    I have a question about receipt validation in apple

    ReplyDelete
    Replies
    1. Yes please ask what ever your doubts/question. I will definitely answer that as soon as i am available

      Delete
  2. actually am doing the InAppPurchase Application. it is working fine with Deployment Target 6.0 ,now i changed my target into 7. Transaction.TransactionReceipt is deprecated in iOS7. so that am using the [[NSBundle mainBundle] appStoreReceiptURL] . in that am receiving all the receipts.

    ReplyDelete
  3. my problem is, The dictionaryFromPlistData: method returns proper NSDictionary object for data returned by transactionReceipt, but it returns nil using data returned using appStoreReceiptURL -- and consequetly I don't get valid receipt! . can you please help me to reslove this issue.

    ReplyDelete
  4. i refering this url
    http://stackoverflow.com/questions/19955352/invalid-transaction-receipt-returned-by-appstorereceipturl-nsdata-in-ios-7 ..

    ReplyDelete