Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble getting the original app version that the user installed (receipt validation)?

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?

like image 305
kash Avatar asked Aug 26 '14 14:08

kash


People also ask

How do I validate my apple receipt?

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.


2 Answers

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.

like image 134
Andrew Wei Avatar answered Jan 26 '23 00:01

Andrew Wei


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.

like image 41
Swud Avatar answered Jan 26 '23 01:01

Swud