13

I want to use appStoreReceiptURL to see which version of the app someone purchased. How can I get this into a string?

I'm testing this by downloading the app from the store, then running a new version of the app from Xcode. Here is what I've tried:

NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSLog(@"receiptUrl %@",[receiptUrl path]);
if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptUrl path]]) {
        NSLog(@"exists");
         NSError *error;
         NSString *receiptString = [[NSString alloc] initWithContentsOfFile:[receiptUrl path] encoding:NSUTF8StringEncoding error:&error];
         if (receiptString == nil) {
              NSLog(@"Error: %@", [error localizedDescription]);
         } else {
             NSLog(@"Receipt: %@",receiptString);
        }

} else {
        NSLog(@"does not exist");
}

This is what I get:

receiptUrl /var/mobile/Applications/E612F261-2D30-416E-BF82-F24xxxx8860/StoreKit/receipt
exists
Error: The operation couldn’t be completed. (Cocoa error 261.)
Tom Kincaid
  • 4,887
  • 6
  • 47
  • 72

3 Answers3

30

I figured out the trick is to read the receipt as data then base 64 encode it from the data. Here's what worked for me if it helps anyone else. Also thanks to this thread for the base 64 encoding: Converting NSData to base64

// 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];

                NSString *postData = [self getJsonStringFromDictionary:dictionary];

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



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


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


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


// from https://stackoverflow.com/questions/2197362/converting-nsdata-to-base64
- (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];
}
Community
  • 1
  • 1
Tom Kincaid
  • 4,887
  • 6
  • 47
  • 72
  • 2
    The implementation of `getJsonStringFromDictionary` is missing, but it works if you replace it with: `NSData *postData = [NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:&error];` – Nestor Oct 02 '13 at 13:13
  • 2
    To be more precise: getJsonStringFromDictionary -> NSError *error = nil; NSData *postData = [NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:&error]; NSString *postString = @""; if (! postData) { NSLog(@"Got an error: %@", error); } else { postString = [[NSString alloc] initWithData:postData encoding:NSUTF8StringEncoding]; } – Al-Noor Ladhani Apr 26 '14 at 02:45
  • Just heads up if you are getting a 21004 error, dont forget to send your application shared secret... like this `NSString *password = @"your shared secret string"; objects = [[NSArray alloc] initWithObjects:receiptString, password, nil]; keys = [[NSArray alloc] initWithObjects:@"receipt-data", @"password", nil];` – MiMo Jul 03 '14 at 15:43
  • Hello @Tom, This approach should be used if we have our server right? – Ranjit Mar 31 '15 at 06:57
11

This is the code I use:

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];

NSString *encReceipt = [receiptData base64EncodedStringWithOptions:0];

I hope this helps.

jeprubio
  • 17,312
  • 5
  • 45
  • 56
0

Cocoa error 261 is NSFileReadInapplicableStringEncodingError.

Rather than attempting to read the file as UTF8, have you tried NSASCIIStringEncoding?

NSString *receiptString =
    [[NSString alloc] initWithContentsOfFile:[receiptUrl path] 
                                    encoding:NSASCIIStringEncoding
                                       error:&error];

Also, given that you're unsure of the actual encoding of the file, you can use the following instead of guessing.:

NSStringEncoding *encoding = nil;
NSString *receiptString =
    [NSString stringWithContentsOfFile:[receiptUrl path]
                          usedEncoding:&encoding
                                 error:NULL]; 
Ben S
  • 68,394
  • 30
  • 171
  • 212
  • Ascii works but the stirng is just garbage. Nil defaults to ascii. You gave me the idea to try other encodings but they didn't work either. I think there is some special way to read the receipt, but I can't find any examples. – Tom Kincaid Sep 20 '13 at 20:45
  • https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt – Ben S Sep 20 '13 at 21:33