Skip to content

Instantly share code, notes, and snippets.

@elmodos
Last active December 6, 2016 12:58
Show Gist options
  • Save elmodos/d7332149875f890c3e1b79a5f32e69a4 to your computer and use it in GitHub Desktop.
Save elmodos/d7332149875f890c3e1b79a5f32e69a4 to your computer and use it in GitHub Desktop.
Check iOS auto-renewable subscriptions if they are still valid
- (void)validateRenewablePurchases:(NSArray<NSString *> *)products callback:(void (^)(NSDictionary *values, NSString *errorMessage))callback
{
// Sending into background
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ @autoreleasepool{
void (^callbackOnMainThread)(NSDictionary *values, NSString *errorMessage) = ^(NSDictionary *values, NSString *errorMessage) {
dispatch_async(dispatch_get_main_queue(), ^{
callback(values, errorMessage);
});
};
// Load the receipt from the app bundle.
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
//
if (receiptData)
{
BOOL isSandbox = [[receiptURL lastPathComponent] isEqualToString:@"sandboxReceipt"];
// Make JSON object that describes the request
NSDictionary *requestContents = @{
@"receipt-data" : [receiptData base64EncodedStringWithOptions:0],
@"password" : kStoreKitReceiptValidationSharedSecred
};
//
NSError *error = nil;
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:nil];
if (requestData)
{
// Create a POST request with the receipt data.
NSURL *storeURL = isSandbox ?
[NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"] :
[NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
//
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
// Sending synchronous request in place
error = nil;
NSURLResponse *response = nil;
NSData *resData = [NSURLConnection sendSynchronousRequest:storeRequest returningResponse:&response error:&error];
if (error)
{
callbackOnMainThread(nil, error.localizedDescription);
}
else
{
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:resData options:0 error:&error];
if (jsonResponse == nil)
{
callbackOnMainThread(nil, error.localizedDescription);
}
else
{
NSLog(@"jsonResponse:%@", jsonResponse);
//
NSArray *latestReceipts = jsonResponse[@"latest_receipt_info"];
if ([latestReceipts isKindOfClass:[NSArray class]])
{
//
NSMutableDictionary *result = [NSMutableDictionary dictionary];
// get every requested product
for (NSString *product in products)
{
// Filter out items with requested product
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
return [evaluatedObject[@"product_id"] isEqual:product];
}];
NSArray *filteredReceipts = [latestReceipts filteredArrayUsingPredicate:predicate];
NSArray *sortedFiltered = [filteredReceipts sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1[@"expires_date_ms"] compare:obj2[@"expires_date_ms"]];
}];
// Get maximum value of expiration date
NSString *maxTimeStringMs = sortedFiltered.lastObject[@"expires_date_ms"];
if ([maxTimeStringMs isKindOfClass:[NSString class]])
{
// crap starts here
// Was "expires_date_ms": "1479951889000"
NSString *maxTimeStringSeconds = [maxTimeStringMs substringToIndex:maxTimeStringMs.length - 3];
// Now "expires_date_ms": "1479951889"
// Now we can fit double
NSTimeInterval doubleValue = [maxTimeStringSeconds doubleValue];
NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:doubleValue];
NSDate *now = [NSDate date];
BOOL isValid = [expirationDate compare:now] == NSOrderedDescending;
result[product] = [NSNumber numberWithBool:isValid];
}
}
callbackOnMainThread(result, nil);
}
else
{
callbackOnMainThread(nil, nil);
}
}
}
}
else
{
// smth went wrong with JSON encoding
callbackOnMainThread(nil, error.localizedDescription);
}
}
else
{
// no receipt, no message, be silent
callbackOnMainThread(nil, nil);
}
}});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment