Question

I'm trying to validate that the receipt is for this particular device using the code from a popular library for Receipt Validation called RMStore:

NSUUID * uuid = [[UIDevice currentDevice] identifierForVendor];
uuid_t uuidBytes;
[uuid getUUIDBytes:uuidBytes];

NSMutableData * data = [[NSMutableData alloc] init];
[data appendBytes:uuidBytes length:sizeof(uuidBytes)];
[data appendData:_parsedReceipt.opaqueValue];
[data appendData:_parsedReceipt.bundleIdentifierData];

NSMutableData * computedHash = [NSMutableData dataWithLength:SHA_DIGEST_LENGTH];
SHA1(data.bytes, data.length, computedHash.mutableBytes);

return [computedHash isEqualToData:_parsedReceipt.hash];

But the two hashes are not equal. Is there something wrong with the code?

Edit

    SKReceiptRefreshRequest * request = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{SKReceiptPropertyIsRevoked: @YES}];
    [request setDelegate:self];
    [request start];

After I re-fetch the receipt once, the hashes start to match. This is the most bizarre behavior I have seen. Does anyone have an idea why this may happen?

Was it helpful?

Solution

As indicated in the answer from where you took that code, Apple recommends to refresh the receipt if validation fails. This is what RMStore does to validate a receipt/transaction:

RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];
const BOOL verified = [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:nil]; // failureBlock is nil intentionally. See below.
if (verified) return;

// Apple recommends to refresh the receipt if validation fails on iOS
[[RMStore defaultStore] refreshReceiptOnSuccess:^{
    RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];
    [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:failureBlock];
} failure:^(NSError *error) {
    [self failWithBlock:failureBlock error:error];
}];

OTHER TIPS

I'll just add one thing here - took me a while to figure out why my hash is mismatching...

receipt bundleId example: ASN1 OCTET STRING (27 byte) 0C19636F6D2E706177656C6B6C61707563682E536B696E4578616D

Which is actually made of Identifier (0C), length (19) and value (63..6D).

For comparing app.bundleId == receipt.bundleId -> use value only
For generating hash -> use the whole ASN1 buffer
(otherwise SHA1 will result in different value)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top