iOS Life

  • Home
  • ABOUT
  • SERVICES
  • PORTFOLIO
  • BLOG
  • Contact

Tutorial: Receipt Validation in Swift

After you have processed your In-App Purchases you may want to verify the receipt with the App Store directly. You’ll get more details about the purchase so you can store that information in your database. There are two ways: first connecting to the Apple App Store directly within your app, and second send the receipt data to your own server and having your server perform the validation with the App Store server. The second is the easiest and most secure method when verifying receipts since it protects against Man-in-the-middle (MITM) attacks where its possible to get free in-app purchases in any app.

Apple recommends you use a trusted server to communicate with the App Store. Using your own server lets you design your app to recognize and trust only your server, and lets you ensure that your server connects with the App Store server. It is not possible to build a trusted connection between a user’s device and the App Store directly because you don’t control either end of that connection.

Validate In-App Purchases

To validate an in-app purchase, your application performs the following tests, in order:

  1. Parse and validate the application’s receipt, as described in the previous sections. If the receipt is not valid, none of the in-app purchases are valid.
  2. Parse the in-app purchase receipts. Each in-app purchase receipt consists of a set of attributes, like the application’s receipt does.
  3. Examine the product identifier for each in-app purchase receipt and enable the corresponding functionality or content in your app.

If validation of an in-app purchase receipt fails, your application simply does not enable the functionality or content.

To begin, be sure you already have In-App Purchases implement in your app. You can follow the steps in this article to integrate In-App Purchases: Tutorial: In-App Purchases (IAP) in Swift.

Send Receipt Data to your Server

Use the validateReceipt() method below to retrieve the receipt data, then send this data to your server. Submit the receipt object as the payload of an HTTP POST request, and encode the data using base64 encoding. Make sure you change the request URL to point to the PHP script on your server. Next, if the PHP script communicates correctly with Apple’s server you will need to parse the response with all of the receipt and transaction information.

func validateReceipt() {
    var response: NSURLResponse?
    var error: NSError?
    
    var receiptUrl = NSBundle.mainBundle().appStoreReceiptURL
    var receipt: NSData = NSData(contentsOfURL:receiptUrl!, options: nil, error: nil)!
    
    var receiptdata: NSString = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
    //println(receiptdata)
    
    var request = NSMutableURLRequest(URL: NSURL(string: "http://www.brianjcoleman.com/code/verifyReceipt.php")!)
    
    var session = NSURLSession.sharedSession()
    request.HTTPMethod = "POST"
    
    var err: NSError?
    
    request.HTTPBody = receiptdata.dataUsingEncoding(NSASCIIStringEncoding)
    
    var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
        var err: NSError?
        var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves, error: &err) as? NSDictionary
        
        if(err != nil)
        {
            println(err!.localizedDescription)
            let jsonStr = NSString(data: data, encoding: NSUTF8StringEncoding)
            println("Error could not parse JSON: '\(jsonStr)'")
        }
        else {
            if let parseJSON = json {
                println("Receipt \(parseJSON)")
            }
            else {
                let jsonStr = NSString(data: data, encoding: NSUTF8StringEncoding)
                println("Receipt Error: \(jsonStr)")
            }
        }
    })
    
    task.resume()
}

In-App Purchase Shared Secret

If your application provides Auto-Renewable Subscriptions you will need to send a password along with the receipt data to Apple during validation. If you’re validating a receipt for an auto-renewing receipt, go to iTunes Connect and get the hexadecimal shared secret for your app.

The secret is typically a 32 digit, alpha-numeric string that looks something like this e4b2dd30b7rt4a7382b5173hg790elk7 (this one is fake).

In iTunes Connect go to My Apps > (select your app) > In-App Purchases > View or generate a shared secret

Screen Shot 2015-05-24 at 8.46.05 PM2

Send Receipt Data to Apple using PHP

Use the following PHP script to take the receipt data and send it to Apple’s server to validate. The script takes two values, the receipt-data and a password (if needed for auto-renewable subscriptions). You must replace this text with the secret key that you get from iTunes Connect. If you don’t have an auto-renewable subscription, you can remove the password parameter and only send the receipt-data.

Save this file as “verifyReceipt.php”, upload it to your web server and change the permissions to 755.

$value){
	$newcontent .= $key.' '.$value;
}

$new = trim($newcontent);
$new = trim($newcontent);
$new = str_replace('_','+',$new);
$new = str_replace(' =','==',$new);

if (substr_count($new,'=') == 0){
if (strpos('=',$new) === false){
		$new .= '=';
}
}

$new = '{"receipt-data":"'.$new.'","password":""}';
$info = getReceiptData($new);
	?>

Testing

To test the receipt validation, run your application and buy one of the in-app purchases, call the validateReceipt() method to initiate the receipt-data to send to your server where the PHP will pass the receipt (and possible the shared secret password), then get the response back from Apple and send it back to the iOS app where you can parse the JSON and get the receipt information.

During testing you may see some status codes returned from Apple, here is a table that describes each of them:

Status Code Description
21000 The App Store could not read the JSON object you provided.
21002 The data in the receipt-data property was malformed or missing.
21003 The receipt could not be authenticated.
21004 The shared secret you provided does not match the shared secret on file for your account.
Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.
21005 The receipt server is not currently available.
21006 This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.
Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.
21007 This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.
21008 This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.

Receipt Data

Below is a sample of the data you will receive for the receipt. You may want to parse this JSON and store the values into a database so you can track your users status.

 "latest_receipt_info" =     (
             {
         "is_trial_period" = false;
         "original_purchase_date" = "2015-05-20 17:41:12 Etc/GMT";
         "original_purchase_date_ms" = 1432143672000;
         "original_purchase_date_pst" = "2015-05-20 10:41:12 America/Los_Angeles";
         "original_transaction_id" = 1000000156014803;
         "product_id" = "com.brianjcoleman.testiap1";
         "purchase_date" = "2015-05-20 17:41:12 Etc/GMT";
         "purchase_date_ms" = 1432143672000;
         "purchase_date_pst" = "2015-05-20 10:41:12 America/Los_Angeles";
         quantity = 1;
         "transaction_id" = 1000000156014803;
     },
             {
         "expires_date" = "2015-05-23 17:05:59 Etc/GMT";
         "expires_date_ms" = 1432400759000;
         "expires_date_pst" = "2015-05-23 10:05:59 America/Los_Angeles";
         "is_trial_period" = false;
         "original_purchase_date" = "2015-05-23 17:03:00 Etc/GMT";
         "original_purchase_date_ms" = 1432400580000;
         "original_purchase_date_pst" = "2015-05-23 10:03:00 America/Los_Angeles";
         "original_transaction_id" = 1000000156451343;
         "product_id" = "com.brianjcoleman.testiap3";
         "purchase_date" = "2015-05-23 17:02:59 Etc/GMT";
         "purchase_date_ms" = 1432400579000;
         "purchase_date_pst" = "2015-05-23 10:02:59 America/Los_Angeles";
         quantity = 1;
         "transaction_id" = 1000000156451343;
         "web_order_line_item_id" = 1000000029801713;
     }
 );
 receipt =     {
     "adam_id" = 0;
     "app_item_id" = 0;
     "application_version" = 1;
     "bundle_id" = "com.brianjcoleman.iqtest";
     "download_id" = 0;
     "in_app" =         (
                     {
             "is_trial_period" = false;
             "original_purchase_date" = "2015-05-24 01:06:58 Etc/GMT";
             "original_purchase_date_ms" = 1432429618000;
             "original_purchase_date_pst" = "2015-05-23 18:06:58 America/Los_Angeles";
             "original_transaction_id" = 1000000156455961;
             "product_id" = "com.brianjcoleman.testiap2";
             "purchase_date" = "2015-05-24 01:06:58 Etc/GMT";
             "purchase_date_ms" = 1432429618000;
             "purchase_date_pst" = "2015-05-23 18:06:58 America/Los_Angeles";
             quantity = 1;
             "transaction_id" = 1000000156455961;
         },
                     {
             "is_trial_period" = false;
             "original_purchase_date" = "2015-05-20 17:41:12 Etc/GMT";
             "original_purchase_date_ms" = 1432143672000;
             "original_purchase_date_pst" = "2015-05-20 10:41:12 America/Los_Angeles";
             "original_transaction_id" = 1000000156014803;
             "product_id" = "com.brianjcoleman.testiap1";
             "purchase_date" = "2015-05-20 17:41:12 Etc/GMT";
             "purchase_date_ms" = 1432143672000;
             "purchase_date_pst" = "2015-05-20 10:41:12 America/Los_Angeles";
             quantity = 1;
             "transaction_id" = 1000000156014803;
         }
     );
     "original_application_version" = "1.0";
     "original_purchase_date" = "2013-08-01 07:00:00 Etc/GMT";
     "original_purchase_date_ms" = 1375340400000;
     "original_purchase_date_pst" = "2013-08-01 00:00:00 America/Los_Angeles";
     "receipt_type" = ProductionSandbox;
     "request_date" = "2015-05-24 16:31:18 Etc/GMT";
     "request_date_ms" = 1432485078143;
     "request_date_pst" = "2015-05-24 09:31:18 America/Los_Angeles";
     "version_external_identifier" = 0;
 };
 status = 0;
}

You can learn more about the Receipt Fields in the Apple Validation Programming Guide.

Note that while testing auto-renewable subscriptions the “expires_date” in the sandbox will not act like it does in production. When testing auto-renewable subscriptions in the test environment, the duration times are compressed. Additionally, test subscriptions only auto-renew a maximum of six times.

Screen Shot 2015-05-26 at 8.42.24 AM

You can grab the full source code for this tutorial. Note: Created using XCode 6.3 (Swift 1.2).

May 25, 2015 Brian Coleman
Tutorial: In-App Purchases in SwiftTutorial: Deep Linking in Swift
You Might Also Like
 
Tutorial: Check for Internet Connection in Swift
 
TestFlight vs Enterprise Distribution
7 years ago Swift, Tutorialsiap, ios8, swift, tutorial31,494
Follow Me
    
Categories
  • About Me
  • Frameworks
  • My Apps
  • News
  • Strategy
  • Swift
  • Tools
  • Tutorials
  • tvOS
  • Uncategorized
  • Videos
  • Watch
Archives
  • May 2016
  • January 2016
  • October 2015
  • July 2015
  • May 2015
  • April 2015
  • March 2015
  • November 2014
  • October 2014
  • September 2014
  • July 2014
  • June 2014
  • September 2013
  • August 2013
  • July 2013
  • June 2013
  • May 2013
  • April 2013
  • March 2013
  • February 2013
brianjcoleman on Twitter
  • Classix is still holding in the top charts on Apple TV in Canada. #55 Free, #23 Top Grossing. #tvos #appletv #app https://t.co/xuEJiT4rro, Jul 14
  • New Blog Post: "Classix for iPhone, iPad & Apple TV” #iOSDev #ios #swift #swiftlang #SwiftDevs #AppleTV Read here: https://t.co/uF6w3gYOot, May 20
  • New Blog Post: "How to test your app for IPv6 compatibility” #iOSDev #ios #swift #swiftlang #SwiftDevs Read here: https://t.co/SveichSUep, May 6

Recent Posts
  • Classix for iPhone, iPad & Apple TV
  • Tutorial: How to test your app for IPv6 compatibility
  • Tutorial: Testing SSL using Charles Proxy on an iOS Device
  • Tutorial: 3D Touch – Quick Actions in Swift
  • tvOS Tutorial: Top Shelf in Swift
Featured Apps
Classix
Sportsnet
TAGS
tutorialswiftios8iosobjective-cvideostrategygamesframeworknewsappsmonitizeios7applefacebookwatchtoolstvosios9bookdesignsocialapiprovisiontutorialsbooksiapiTunes ConnectIPv6
Search
ABOUT
Brian is a Lead iOS/tvOS Developer from Toronto with over 18 years of multifaceted experience including development, design, business analysis and project management.
MOST VIEWED
Tutorial: How To Use Login in Facebook SDK 4.1.x for Swift
163,489 views
Tutorial: How to test your app for IPv6 compatibility
102,074 views
Tutorial: How to use Auto Layout in Xcode 6
89,234 views
FOLLOW ME
    
Email Subscription
Sign up for my newsletter to receive the latest news and tutorials posted.

Enter your email address:

2013-2017 © Brian Coleman