In-App Purchases in iOS 6 Tutorial: Consumables and Receipt Validation

Ray Wenderlich
Learn how to implement consumable In-App Purchases and validate receipts!

Learn how to implement consumable In-App Purchases and validate receipts!

This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer.

A while back on the weekly tutorial poll on the sidebar, you guys voted for a tutorial on In-App Purchases: consumables and receipt validation. As always, your wish is my command! :]

This tutorial picks up where we left things off in our previous In-App Purchases tutorial. As a reminder, in that tutorial we developed an app called “In App Rage” where customers could purchase rage face comics as non-consumable In-App purchases.

In this tutorial, you will extend the app to add a new consumable In-App Purchase – after all, those are often way more lucrative than non-consumables, since customers can purchase them more than once! You will also add receipt validation to add some extra security into the app.

Note that this tutorial (and the previous) are focused on the “simplest and easiest” way to do things, not the most “robust and flexible” way of doing things. If you’re more interested in the latter, check out our book iOS 6 by Tutorials, where you’ll learn how to develop an extremely flexible server-based In-App Purchases system, with downloadable content hosted on Apple’s servers!

So without further ado, let’s dive into some more In-App Purchases info – you’ll see it’s pretty easy, and there’s no need to rage about it! :]


Getting Started

Start by downloading the starter project for this tutorial.

Note that the starter project is not the same as where we left things off in the last tutorial – I’ve added a few extra things for you:

  • Added the ability to see a rage comic after purchasing it (which was left as an exercise at the end of last tutorial).
  • Added the skeleton for the “random rage face” feature. Right now you can do this an unlimited amount of times for testing.
  • Gratuitous icons and iPhone 5 screen size support! :]

You’ll have to make a few changes to this project for it to work for you:

  • Open RageIAPHelper.m and MasterViewController.m and search for “com.razeware” – you’ll see the product identifiers I set up. Replace those with your own product identifiers you created last time in iTunes Connect.
  • Open In App Rage-Info.plist and update your Bundle Identifier to your own bundle identifier.

Once you’re done, try out the app on your device and purchase a comic and make sure the purchase succeeds, and that you can view the comic after you buy it. Also, try out the new random rage face feature!

Screenshots of In-App Purchases starter project

Creating a Consumable In-App Purchase

You have an sneaky plan to make money – instead of allowing the user to see cool random rage faces as often as they’d like, instead you’ll add an arbitrary limit to it, and charge users to purchase more, mwuhahaha!

This way, there’s no limit to how much money users can spend on your app, hopefully keeping the bucks rolling in!

Note: This is just the beginning of ways you can use consumable In-App Purchases to (arguably sneakily) increase your app’s revenue. To learn more, check out the many blogs and talks about freemium game design, gamer psychology, or any games by Zynga ;]

Just like you did with consumable In-App Purchases, the first step to make this all happen is to create a new In-App Purchase entry in iTunes Connect.

So log onto iTunes Connect, and click Manage Your Applications. Click your entry for In-App Rage, click Manage In-App Purchases, and Create New. Underneath the Consumable option, click Select.

Selecting a consumable In-App Purchase in iTunes Connect

Next, fill out the screen that appears as follows (but replace razeware with your own name/company name):

Entering details for a consumable In-App Purchase

Finally, scroll down to the Language section and click Add Language. Fill in the dialog that appears as follows:

Entering language details for a consumable In-App purchase

Click Save, then Save again, and you’re done! Time to try this out in your app.

Implementing a Consumable In-App Purchase

To begin, open RageIAPHelper.m and add the new product identifier you jsut created to the bottom of the list of productIdentifiers, similar to the following:

NSSet * productIdentifiers = [NSSet setWithObjects:
                              @"com.razeware.inapprage.drummerrage",
                              @"com.razeware.inapprage.itunesconnectrage",
                              @"com.razeware.inapprage.nightlyrage",
                              @"com.razeware.inapprage.studylikeaboss",
                              @"com.razeware.inapprage.updogsadness",
                              @"com.razeware.inapprage.randomrageface",
                              nil];

Next, open IAPHelper.m and modify your provideContentForProductIdentifier: method as follows:

- (void)provideContentForProductIdentifier:(NSString *)productIdentifier {
 
    if ([productIdentifier isEqualToString:@"com.razeware.inapprage.randomrageface"]) {
 
        int currentValue = [[NSUserDefaults standardUserDefaults] 
            integerForKey:@"com.razeware.inapprage.randomrageface"];
        currentValue += 5;
        [[NSUserDefaults standardUserDefaults] setInteger:currentValue 
            forKey:@"com.razeware.inapprage.randomrageface"];
        [[NSUserDefaults standardUserDefaults] synchronize];
 
    } else {
        [_purchasedProductIdentifiers addObject:productIdentifier];
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
 
    [[NSNotificationCenter defaultCenter] 
        postNotificationName:IAPHelperProductPurchasedNotification 
        object:productIdentifier userInfo:nil];
 
}

Here you add some special case behavior for the consumable In-App Purchase you added. When a customer purchases this, instead of simply setting a flag indicating whether it’s purchased or not, you increment a value that keeps track of how many rolls the user currently has.

Note: This works but isn’t the ideal way of doing things, because now your IAPHelper class has hard-coded logic for a particular In-App Purchase so isn’t as reusable. For a more reusable way of doing things, check out iOS 6 by Tutorials.

Next open MasterViewController.m and replace your tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: methods as follows:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _products.count;
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" 
        forIndexPath:indexPath];
 
    SKProduct * product = (SKProduct *) _products[indexPath.row];
    cell.textLabel.text = product.localizedTitle;
    [_priceFormatter setLocale:product.priceLocale];
    cell.detailTextLabel.text = [_priceFormatter stringFromNumber:product.price];
 
    if (![product.productIdentifier isEqualToString:@"com.razeware.inapprage.randomrageface"] &&
        [[RageIAPHelper sharedInstance] productPurchased:product.productIdentifier]) {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
        cell.accessoryView = nil;
    } else {
        UIButton *buyButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        buyButton.frame = CGRectMake(0, 0, 72, 37);
        [buyButton setTitle:@"Buy" forState:UIControlStateNormal];
        buyButton.tag = indexPath.row;
        [buyButton addTarget:self action:@selector(buyButtonTapped:) 
            forControlEvents:UIControlEventTouchUpInside];
        cell.accessoryType = UITableViewCellAccessoryNone;
        cell.accessoryView = buyButton;
    }
 
    return cell;
}

Here you have removed the “test” row that the sample project had earlier, since now you have a new consumable In-App Purchase to display instead. You also add some logic so that you always display the “Buy” button for the consumable IAP.

Finally, open RandomFaceViewController.m and replace the buttonTapped: method with this (and these additional two methods):

- (void)refresh {
    int currentValue = [[NSUserDefaults standardUserDefaults] 
        integerForKey:@"com.razeware.inapprage.randomrageface"];
    self.label.text = [NSString stringWithFormat:@"Times Remaining: %d", currentValue];
}
 
- (void)viewWillAppear:(BOOL)animated {
    [self refresh];
}
 
- (IBAction)buttonTapped:(id)sender {
 
    int currentValue = [[NSUserDefaults standardUserDefaults] 
        integerForKey:@"com.razeware.inapprage.randomrageface"];
    if (currentValue <= 0) return;
 
    currentValue--;
    [[NSUserDefaults standardUserDefaults] setInteger:currentValue 
        forKey:@"com.razeware.inapprage.randomrageface"];
    [self refresh];
 
    int randomIdx = (arc4random() % 4) + 1;
    NSString * randomName = [NSString stringWithFormat:@"random%d.png", randomIdx];
    self.imageView.image = [UIImage imageNamed:randomName];
}

This is some basic logic to replace the unlimited button taps with using the new value you’re using to track the user’s spins.

And that’s it! Build and run, and purchase a few spins. Then test out your the random rage face button and make sure it works as expected!

Your first consumable In-App purchase!

Receipt Validation

When you make an In-App Purchase, you can’t 100% trust that the response that comes over the network saying “everything went OK” really came from Apple without using a technique called “receipt validation.”

When you make an In-App Purchase, Apple sends you back a special piece of data called a “receipt.” This is a private piece of data that records cryptographically-signed information about the transaction. The idea is that for your app to be secure, you shouldn’t blindly trust that a purchase completed – you should send the receipt to a special “receipt validation” server that Apple has set up to double-check that everything is OK.

The dangers of not performing receipt validation were proven pretty spectacularly recently. A Russian hacker developed an easy-to-install In-App Purchase hack that allowed users to receive almost any In-App Purchase for free – at least, if the app wasn’t doing proper receipt validation.

The hack is a classic “man in the middle” attack. You configure DNS records so your devices is routed to the “hack” servers rather than Apple, and you configure a fake certificate on your device so the OS trusts the “hack server” you’re connected to. Then, whenever you make a request to make an In-App Purchase, the request goes to the “hack server” instead. The hack server will always say “done and paid for!” so the app will unlock the content without knowing it’s been had – for free!

This hack will no longer work on iOS 6. However, there are other variants hackers could employ in the future, so it’s still a good idea to use receipt validation.

Note: For more information on the In-App Purchase hack described above, check out this article.

The No-Server Solution

Apple’s official recommendation to perform receipt validation is to connect to your own server, which then connects to Apple’s servers to validate the receipts.

For a number of reasons, this is more secure than connecting to Apple directly, and this is the approach you take in iOS 6 by Tutorials.

But in this tutorial, the focus is on the simplest and easiest way to implement In-App Purchases, and it would be a major pain to have to set up your own server just to validate receipts.

A lot of other developers feel the same way, and so they wrote code to connect to Apple’s validation server directly from their apps rather than going through an intermediate server (despite Apple’s recommendations). It became so common that Apple provided some sample code demonstrating a fairly secure way to verify receipts by connecting to Apple’s servers directly.

In this section, you’re going to integrate Apple’s provided code into the app to validate receipts before unlocking the purchases.

Note: The original code was missing some pieces (such as the base64 routines) as well as some logic (like returning results to a caller), which I added in. Also, I cannot vouch 100% for the robustness of the Apple code, as it looks a little thrown together, so use at your own risk!

Go ahead and download the resources for this tutorial, and drag the folder into your project. Make sure Copy items into destination group’s folder (if needed) is checked, Create groups for any added folders is selected, and the In-App Rage target is checked, and click Finish.

The code requires the Security framework, so let’s add it to your project. To do this, click on your In App Rage project root in the Project Navigator and then the In App Rage target. Select the Build Phases tab, scroll down to the Link Binary with Libraries section, expand it if necessary, and click the + button. Select Security.framework and click Add. At this point, your list of libraries should look like the following:

Adding security framework to the project

Then switch to IAPHelper.m and add the following import to the top of the file:

#import "VerificationController.h"

And add the following method:

- (void)validateReceiptForTransaction:(SKPaymentTransaction *)transaction {
    VerificationController * verifier = [VerificationController sharedInstance];
    [verifier verifyPurchase:transaction completionHandler:^(BOOL success) {
        if (success) {
            NSLog(@"Successfully verified receipt!");
            [self provideContentForProductIdentifier:transaction.payment.productIdentifier];
        } else {
            NSLog(@"Failed to validate receipt.");            
            [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
        }
    }];
}

This method simply calls Apple’s (somewhat modified) code to verify the transactions, and either provides the content or not, based on the results.

The final step is to call this new method from completeTransaction: and restoreTransaction:, instead of providing the content right away. Replace those two methods with the following:

- (void)completeTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"completeTransaction...");
 
    [self validateReceiptForTransaction:transaction];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
 
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"restoreTransaction...");
 
    [self validateReceiptForTransaction:transaction];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

Build and run, and your app should work as usual – but now with a great deal more security!

Note: If you are going to use this verification controller in your app, remember that it is currently configured to use the sandbox server (see the verifyPurchase:completionHandler: method). You will need to switch this to production before you release your app.

Where To Go From Here?

Here is the final sample project from the tutorial series.

Congrats – you now have implemented both non-consumable and consumable In-App Purchases, added the ability for users to restore transactions, and are validating receipts!

Like I mentioned in the previous tutorials, for a lot of simple apps this is more than enough. But if you want to take things even further and learn how develop a robust and extensible server-based system, check out iOS 6 by Tutorials!

I hope you enjoyed this series, and wish you best of luck with your consumable In-App Purchases! Just don’t be too sneaky/evil, one Zynga is enough ;]

If you have any questions or comments on this tutorial or In-App Purchases in general, please join the forum discussion below!

Ray Wenderlich

Ray is an indie software developer currently focusing on iPhone and iPad development, and the administrator of this site. He’s the founder of a small iPhone development studio called Razeware, and is passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

User Comments

42 Comments

[ 1 , 2 , 3 ]
  • CocosKat wrote:Does anyone have a code snippet showing how to account for this?

    https://developer.apple.com/library/ios ... index.html

    Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive a 21007 status code. Following this approach ensures that you do not have to switch between URLs while your application is being tested or reviewed in the sandbox or is live in the App Store.

    thank you in advance


    You'll want to change the beginning of validateReceipt in validate.php to something like:

    Code: Select all
       $store = 'https://buy.itunes.apple.com/verifyReceipt';

       $postData = json_encode(array('receipt-data' => $receipt));
       
       $ch = curl_init($store);
       curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
       curl_setopt($ch, CURLOPT_POST, true);
       curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
       $encodedResponse = curl_exec($ch);
       curl_close($ch);
       
       if ($encodedResponse == false)
       {
          return result(ERROR_VERIFICATION_NO_RESPONSE,
          'Payment could not be verified (no response data).');
       }   
       $response = json_decode($encodedResponse);
       $status = $response->{'status'};
       $decodedReceipt = $response->{'receipt'};
       
       if ($status == 21007)
       {
                    $store = 'https://sandbox.itunes.apple.com/verifyReceipt';
          $ch = curl_init($sandstore);
          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
          curl_setopt($ch, CURLOPT_POST, true);
          curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
          $encodedResponse = curl_exec($ch);
          curl_close($ch);
       
          if ($encodedResponse == false)
          {
             return result(ERROR_VERIFICATION_NO_RESPONSE,
             'Payment could not be verified (no response data).');
          }   
          $response = json_decode($encodedResponse);
          $status = $response->{'status'};
          $decodedReceipt = $response->{'receipt'};
       }
    pchan126
  • Question about how to securely store which in-app purchases the user has made. I understand the receipt validation that occurs at app purchase time and on a restore of in-app purchases. However, the examples make use of storing the in-app products that were purchased as keys in the NSUserDefaults. My understanding is that anyone using a jailbroken device can simply change or add these options to the device's NSUserDefaults file to enable the in-app purchase options w/o actually purchasing them. The only way to detect or validate the purchases, particularly at app-startup, would be to call the Restore option which does the receipt validation. However, this then forces the user to have to enter their Apple credentials which doesn't seem practical. Any hints or ideas on how to securely verify in-app purchases at app startup? Thanks!
    wallyyoung
  • I tried to add this code for build target IOS7, but i get lots of deprecated messages and even one fatal error in XCode. Is there an updated VerificationController code resource somewhere? I would be very glad ....
    marcelmolenaar
  • thanks for this post.. very useful ..
    Poornima
  • Hey i replaced doesTransactionInfoMatchReceipt with below and it worked. Try it out :). Happy Coding


    - (BOOL)doesTransactionInfoMatchReceipt:(NSString*) receiptString
    {
    // Convert the responseString into a dictionary and pull out the receipt data.
    NSDictionary *verifiedReceiptDictionary = [self dictionaryFromJSONData:[receiptString dataUsingEncoding:NSUTF8StringEncoding]];

    // Check the status of the verifyReceipt call
    id status = [verifiedReceiptDictionary objectForKey:@"status"];
    if (!status)
    {
    return NO;
    }
    int verifyReceiptStatus = [status integerValue];
    // 21006 = This receipt is valid but the subscription has expired.
    if (0 != verifyReceiptStatus && 21006 != verifyReceiptStatus)
    {
    return NO;
    }

    // The receipt is valid, so checked the receipt specifics now.

    NSDictionary *verifiedReceiptReceiptDictionary = [verifiedReceiptDictionary objectForKey:@"receipt"];
    NSString *verifiedReceiptUniqueIdentifier = [verifiedReceiptReceiptDictionary objectForKey:@"unique_identifier"];
    NSString *transactionIdFromVerifiedReceipt = [verifiedReceiptReceiptDictionary objectForKey:@"transaction_id"];

    // Get the transaction's receipt data from the transactionsReceiptStorageDictionary
    NSDictionary *purchaseInfoFromTransaction = [transactionsReceiptStorageDictionary objectForKey:transactionIdFromVerifiedReceipt];

    if (!purchaseInfoFromTransaction)
    {
    // We didn't find a receipt for this transaction.
    return NO;
    }


    // NOTE: Instead of counting errors you could just return early.
    int failCount = 0;

    // Verify all the receipt specifics to ensure everything matches up as expected
    if (![[verifiedReceiptReceiptDictionary objectForKey:@"bid"]
    isEqualToString:[purchaseInfoFromTransaction objectForKey:@"bid"]])
    {
    failCount++;
    }

    if (![[verifiedReceiptReceiptDictionary objectForKey:@"product_id"]
    isEqualToString:[purchaseInfoFromTransaction objectForKey:@"product-id"]])
    {
    failCount++;
    }

    if (![[verifiedReceiptReceiptDictionary objectForKey:@"quantity"]
    isEqualToString:[purchaseInfoFromTransaction objectForKey:@"quantity"]])
    {
    failCount++;
    }

    if (![[verifiedReceiptReceiptDictionary objectForKey:@"item_id"]
    isEqualToString:[purchaseInfoFromTransaction objectForKey:@"item-id"]])
    {
    failCount++;
    }

    if ([[UIDevice currentDevice] respondsToSelector:NSSelectorFromString(@"identifierForVendor")])
    {
    // iOS 6 (or later)
    NSString *localIdentifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
    NSString *purchaseInfoUniqueVendorId = [purchaseInfoFromTransaction objectForKey:@"unique-vendor-identifier"];
    NSString *verifiedReceiptVendorIdentifier = [verifiedReceiptReceiptDictionary objectForKey:@"unique_vendor_identifier"];


    if(verifiedReceiptVendorIdentifier)
    {
    if (![purchaseInfoUniqueVendorId isEqualToString:verifiedReceiptVendorIdentifier]
    || ![purchaseInfoUniqueVendorId isEqualToString:localIdentifier])
    {
    // Comment this line out to test in the Simulator.
    failCount++;
    }
    }
    }
    // Do additional time checks for the transaction and receipt.

    if(failCount != 0)
    {
    return NO;
    }

    return YES;
    }
    McShaw
  • Hey marcelmolenaar, I replaced doesTransactionInfoMatchReceipt with below and it worked for iOS7 and later. Happy Coding

    - (BOOL)doesTransactionInfoMatchReceipt:(NSString*) receiptString
    {
    // Convert the responseString into a dictionary and pull out the receipt data.
    NSDictionary *verifiedReceiptDictionary = [self dictionaryFromJSONData:[receiptString dataUsingEncoding:NSUTF8StringEncoding]];

    // Check the status of the verifyReceipt call
    id status = [verifiedReceiptDictionary objectForKey:@"status"];
    if (!status)
    {
    return NO;
    }
    int verifyReceiptStatus = [status integerValue];
    // 21006 = This receipt is valid but the subscription has expired.
    if (0 != verifyReceiptStatus && 21006 != verifyReceiptStatus)
    {
    return NO;
    }

    // The receipt is valid, so checked the receipt specifics now.

    NSDictionary *verifiedReceiptReceiptDictionary = [verifiedReceiptDictionary objectForKey:@"receipt"];
    NSString *verifiedReceiptUniqueIdentifier = [verifiedReceiptReceiptDictionary objectForKey:@"unique_identifier"];
    NSString *transactionIdFromVerifiedReceipt = [verifiedReceiptReceiptDictionary objectForKey:@"transaction_id"];

    // Get the transaction's receipt data from the transactionsReceiptStorageDictionary
    NSDictionary *purchaseInfoFromTransaction = [transactionsReceiptStorageDictionary objectForKey:transactionIdFromVerifiedReceipt];

    if (!purchaseInfoFromTransaction)
    {
    // We didn't find a receipt for this transaction.
    return NO;
    }


    // NOTE: Instead of counting errors you could just return early.
    int failCount = 0;

    // Verify all the receipt specifics to ensure everything matches up as expected
    if (![[verifiedReceiptReceiptDictionary objectForKey:@"bid"]
    isEqualToString:[purchaseInfoFromTransaction objectForKey:@"bid"]])
    {
    failCount++;
    }

    if (![[verifiedReceiptReceiptDictionary objectForKey:@"product_id"]
    isEqualToString:[purchaseInfoFromTransaction objectForKey:@"product-id"]])
    {
    failCount++;
    }

    if (![[verifiedReceiptReceiptDictionary objectForKey:@"quantity"]
    isEqualToString:[purchaseInfoFromTransaction objectForKey:@"quantity"]])
    {
    failCount++;
    }

    if (![[verifiedReceiptReceiptDictionary objectForKey:@"item_id"]
    isEqualToString:[purchaseInfoFromTransaction objectForKey:@"item-id"]])
    {
    failCount++;
    }

    if ([[UIDevice currentDevice] respondsToSelector:NSSelectorFromString(@"identifierForVendor")])
    {
    // iOS 6 (or later)
    NSString *localIdentifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
    NSString *purchaseInfoUniqueVendorId = [purchaseInfoFromTransaction objectForKey:@"unique-vendor-identifier"];
    NSString *verifiedReceiptVendorIdentifier = [verifiedReceiptReceiptDictionary objectForKey:@"unique_vendor_identifier"];


    if(verifiedReceiptVendorIdentifier)
    {
    if (![purchaseInfoUniqueVendorId isEqualToString:verifiedReceiptVendorIdentifier]
    || ![purchaseInfoUniqueVendorId isEqualToString:localIdentifier])
    {
    // Comment this line out to test in the Simulator.
    failCount++;
    }
    }
    }
    // Do additional time checks for the transaction and receipt.

    if(failCount != 0)
    {
    return NO;
    }

    return YES;
    }
    McShaw
  • What about restoring consumable on a replacement device? Should I save the consumable purchases on the iCloud key chain? I don't want to invite the user to use different credentials than the AppleID.
    iosdroid
  • I need some help here, I am getting a whole bunch of "Goto into protected scope" errors in the checkReceiptSecurity function at most of the "require(...)" statements. I had seen many other implementations using the same set of codes, so I can only assume there is something wrong with my settings? I'm using xcode 5.1 on a cocos2d-x 3.0 project, I have implemented the IAPs (tested and working), now I just need to get the verification working....
    krashed
  • I have tried implement Receipt validation in Application.I used ray wenderlich tutorial code (Verification controller) in my app for checking receipt.But i want tom clarify about below things.

    1) what about below two fields?How can i implement in my App? is it always static?
    #define KNOWN_TRANSACTIONS_KEY @"knownIAPTransactions"
    #define ITC_CONTENT_PROVIDER_SHARED_SECRET @"8581a699d1264c9eb0cd0b1123227f04"

    2)When we upload Application on app store then I used the Production url.Remove the sandbox environment.Please clarify more about it.
    #define ITMS_PROD_VERIFY_RECEIPT_URL @"https://buy.itunes.apple.com/verifyReceipt" -used this when we upload on app store.

    Thank you,
    Renish Dadhaniya
  • I have tried implement Receipt validation in Application.I used ray wenderlich tutorial code (Verification controller) in my app for checking receipt.But i want tom clarify about below things.

    1) what about below two fields?How can i implement in my App? is it always static?
    #define KNOWN_TRANSACTIONS_KEY @"knownIAPTransactions"
    #define ITC_CONTENT_PROVIDER_SHARED_SECRET @"8581a699d1264c9eb0cd0b1123227f04"

    2)When we upload Application on app store then I used the Production url.Remove the sandbox environment.Please clarify more about it.
    #define ITMS_PROD_VERIFY_RECEIPT_URL @"https://buy.itunes.apple.com/verifyReceipt" -used this when we upload on app store.

    Thank you,
    Renish Dadhaniya
  • Apple has clearly said: :shock:
    Always verify your receipt for auto-renewable subscriptions first with the production URL; proceed to verify with the sandbox URL if you receive a 21007 status code. Following this approach ensures that you do not have to switch between URLs while your application is being tested or reviewed in the sandbox or is live in the App Store.

    Courtesy
    Frequently Asked Questions, Point 16
    https://developer.apple.com/library/ios/technotes/tn2259/_index.html#//apple_ref/doc/uid/DTS40009578-CH1-FREQUENTLY_ASKED_QUESTIONS

    And this point is true for all kind of receipt verification. While verifying a receipt use Production url i.e. https://buy.itunes.apple.com/verifyReceipt. Once you will get status = 21007, send again your receipt to https://sandbox.itunes.apple.com/verifyReceipt and then you will get status = 0 :P

    Apple verification team also tests app on sandbox environment, so if you will replace sandbox url with production url, they will get error and will disapprove app. Make verification process like you would not need to change url before you submit. :cry:
    vikysaran
  • Hi Ray am starting with swift, am trying to program In App Purchases but i don't see any topic about In-App Purchases inside the swift program how can i solve this i am finishing cookie crunch and i would like to implement the game for can't find the topic just purchased some art from Vicky site so any advices will be appreciated thanks Ray
    jurena70
[ 1 , 2 , 3 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in September: iOS 8 App Extensions!

Sign Up - September

RWDevCon Conference?

We are considering having an official raywenderlich.com conference called RWDevCon in DC in early 2015.

The conference would be focused on high quality Swift/iOS 8 technical content, and connecting as a community.

Would this be something you'd be interested in?

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Tammy Coron
  • Kirill Muzykov

... 49 total!

Update Team

Editorial Team

... 23 total!

Code Team

  • Orta Therox

... 3 total!

Translation Team

  • Heejun Han
  • Jesus Guerra
  • David Xie

... 33 total!

Subject Matter Experts

... 4 total!