I have an app that I recently updated to work with in app purchases. The previous version (paid but with no in app purchases) was 1.0 and the current version is 1.1.
As the in app purchase essentially unlocks all features (which were included in the paid version 1.0), I wanted a way for users that had originally downloaded version 1.0 to be upgraded if they pressed my restore purchases button.
To do this, I first try to restore purchases and if the response to:
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
If this provides me with a queue with a transactions count of 0, I check the receipt to see if the original version installed was 1.0.
The code to get the receipt is as per Apple's documentation
- (void)tryRestoreFromOriginalPurchase
{
// Load the receipt from the app bundle
NSError *error;
NSData *receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
if (receipt == nil) {
[self restoreFromOriginalVersionWithReceipt:nil];
return;
}
// Create the JSON object that describes the request
NSDictionary *requestContents = @{@"receipt-data": [receipt base64EncodedStringWithOptions:0]};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
if (!requestData) {
[self restoreFromOriginalVersionWithReceipt:nil];
return;
}
// Create a POST request with the receipt data
NSURL *storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
// Make a connection to the iTunes Store on a background queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (!connectionError) {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (jsonResponse) [self restoreFromOriginalVersionWithReceipt:jsonResponse];
else [self restoreFromOriginalVersionWithReceipt:nil];
} else {
[self restoreFromOriginalVersionWithReceipt:nil];
}
}];
}
This then calls the following method:
- (void)restoreFromOriginalVersionWithReceipt:(NSDictionary *)receipt
{
if (receipt == nil) {
// CALL METHOD TO HANDLE FAILED RESTORE
} else {
NSInteger status = [[receipt valueForKey:@"status"] integerValue];
if (status == 0) {
NSString *originalApplicationVersion = [[receipt valueForKey:@"receipt"] valueForKey:@"original_application_version"];
if (originalApplicationVersion != nil && [originalApplicationVersion isEqualToString:@"1.0"]) {
// CALL METHOD TO HANDLE SUCCESSFUL RESTORE
} else {
// CALL METHOD TO HANDLE FAILED RESTORE
}
} else {
// CALL METHOD TO HANDLE FAILED RESTORE
}
}
}
Now this doesn't work. When someone installs version 1.1 and taps restore purchases, it restores successfully when it shouldn't.
I've just realized that in my Info.plist, my CFBundleShortVersionString is 1.1 but my CFBundleVersion is 1.0.
This may be a really daft question, but is the receipt providing an original_application_version of 1.0 (due to the wrong CFBundleVersion) even though the update is versioned 1.1?
So if I release a new update with this corrected to say version 1.2 (for both the CFBundleShortVersionString and CFBundleVersion), will the problem be resolved?
-- UPDATE --
So I just uploaded a new version to the app store with both the CGBundleVersion and CFBundleShortVersionString equal to 1.2. However, I'm still facing the same problem - users downloading version 1.2 for the first time and tapping on restore purchases are being upgraded for free (due to the receipt check outlined above). It seems the original_application_version is always coming to 1.0.
Note: I am downloading the app under a new iTunes account that has not previously downloaded the app.
Here is the receipt I have if I install from the app store and then try to get the receipt through Xcode
2014-08-27 08:46:42.858 AppName[4138:1803] {
environment = Production;
receipt = {
"adam_id" = AppID;
"application_version" = "1.0";
"bundle_id" = "com.CompanyName.AppName";
"download_id" = 94004873536255;
"in_app" = (
);
"original_application_version" = "1.0";
"original_purchase_date" = "2014-08-26 22:30:49 Etc/GMT";
"original_purchase_date_ms" = 1409092249000;
"original_purchase_date_pst" = "2014-08-26 15:30:49 America/Los_Angeles";
"receipt_type" = Production;
"request_date" = "2014-08-26 22:46:42 Etc/GMT";
"request_date_ms" = 1409093202544;
"request_date_pst" = "2014-08-26 15:46:42 America/Los_Angeles";
};
status = 0;
}
Any thoughts?
Use the production URL https://buy.itunes.apple.com/verifyReceipt when your app is live in the App Store. For more information on these endpoints, see verifyReceipt. Verify your receipt first with the production URL; then verify with the sandbox URL if you receive a 21007 status code.
I stumbled upon the same issue - I converted my app from paid to freemium and tried to use original_application_version
in the app receipt to decide who to unlock the new freemium features for. I, too, was unsuccessful.
However, what I found out was that I was using original_application_version
incorrectly. The name misled me into thinking that this string corresponds to the version number of the app. On iOS, it does not. original_application_version
is actually the build number of the app.
Original Application Version
This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in macOS) in the Info.plist file when the purchase was originally made. In the sandbox environment, the value of this field is always “1.0”.
Source: https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
I think this could be the reason why you are getting a number you are not expecting.
Using original_purchase_date
, as you did in the end, is a reliable alternative though.
It's been a while since this question was asked, but it raises some very important points:
The original app version field in the receipt corresponds to CFBundleVersion, not CFBundleShortVersionString. In a sandbox (developer build) environment, the value string is always "1.0".
Note that when converting from a paid app to freemium, if an original (paid version) user uninstalls the app, then reinstalls from iTunes, there will be no local receipt. Calling SKPaymentQueue restoreCompletedTransactions
will not cause a new receipt to be downloaded if that user has never made an in-app purchase. In this situation, you need ask for a receipt refresh using SKReceiptRefreshRequest
and not rely on restore functionality.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With