Verifying Apple App Store Receipts For In App Purchases With PHP and cURL
One of the features available to iOS developers when creating iPhone or iPad applications is the ability to sell items within their application (called In App Purchases).
In many cases, the In App Purchase will be fulfilled by downloading data from a third-party server. Before the iOS application can download the purchased item, the third-party server must check with Apple to ensure the item has been successfully purchased.
The following diagram demonstrates how this works in principle.
In this PhpRiot snippet I will show you how to verify a purchase receipt that was submitted by the iOS application with the Apple receipt verification service. We will achieve this using PHP and cURL. Referring to the above diagram, the code in this article will deal with steps 3, 4 and 5.
To verify a receipt, we will define a function called getReceiptData(). This function will return information about the transaction being verified if the receipt is valid, and it will throw an exception if the receipt is invalid or if the receipt cannot be verified.
The following listing demonstrates the basic skeleton for this script. The first argument ($receipt) is the base-64 encoded receipt data exactly as supplied from the iOS application.
The second argument ($isSandbox) is a boolean indicating whether the receipt being verified is from a real transaction or a transaction from a test user.
function getReceiptData($receipt, $isSandbox = false) { } $receipt = $_POST['receipt']; $isSandbox = (bool) $_POST['sandbox']; try { $info = getReceiptData($receipt, $isSandbox); // receipt is valid } catch (Exception $ex) { // unable to verify receipt, or receipt is not valid }
receipt and sandbox.
If you're testing a receipt for the sandbox, the endpoint URL to check with is https://sandbox.itunes.apple.com/verifyReceipt. The endpoint URL for real transactions is https://buy.itunes.apple.com/verifyReceipt.
As such, we can set the URL based on the value of $isSandbox.
function getReceiptData($receipt, $isSandbox = false) { if ($isSandbox) { $endpoint = 'https://sandbox.itunes.apple.com/verifyReceipt'; } else { $endpoint = 'https://buy.itunes.apple.com/verifyReceipt'; } }
The verifyReceipt web service requires a post request consisting only of a JSON-encoded string. This string should correspond to a JavaScript object with a single key called receipt-data. This can be defined in PHP using array('receipt-data' => $receipt).
Once this array has been defined, json_encode() can be used to build the JSON string.
function getReceiptData($receipt, $isSandbox = false) { // ... previous code $postData = json_encode( array('receipt-data' => $receipt) ); }
Next we build the cURL request using the $endpoint and $postData variables we have defined.
In addition to setting the request post data (including setting the request to use POST instead of GET), we must
also instruct cURL to return the response. This is achieved by setting the CURLOPT_RETURNTRANSFER to
true. If you don't do this, the response from the HTTP request will be output directly and you will be
unable to parse it.
The listing below shows how we build the cURL request. Additionally, this code also performs the request (and assigns the response data to $response), retrieves any error codes or messages and finally closes the connection. We will make use of this data shortly.
function getReceiptData($receipt, $isSandbox = false) { // ... previous code $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); $response = curl_exec($ch); $errno = curl_errno($ch); $errmsg = curl_error($ch); curl_close($ch); }
Finally, we must check the response data. If the request failed for some reason, the $errno
value will be non-zero. In this case, we throw an exception that includes the error number and message. This
will be handled by the code that calls getReceiptData().
function getReceiptData($receipt, $isSandbox = false) { // ... previous code if ($errno != 0) { throw new Exception($errmsg, $errno); } }
At this point in the code we know we know the HTTP request was successful, so we must parse the response and ensure the submitted receipt was successful. This web service returns a JSON object with two keys, status and receipt.
We can turn the JSON string into a PHP object using json_decode(). This function will return null if the string wasn't a valid JSON string.
function getReceiptData($receipt, $isSandbox = false) { // ... previous code $data = json_decode($response); if (!is_object($data)) { throw new Exception('Invalid response data'); } }
If the status value is 0, the receipt was valid, otherwise it was not valid. As such, we throw an exception for non-zero values. If the receipt was valid, we build an array of data to return.
function getReceiptData($receipt, $isSandbox = false) { // ... previous code if (!isset($data->status) || $data->status != 0) { throw new Exception('Invalid receipt'); } return array( 'quantity' => $data->receipt->quantity, 'product_id' => $data->receipt->product_id, 'transaction_id' => $data->receipt->transaction_id, 'purchase_date' => $data->receipt->purchase_date, 'app_item_id' => $data->receipt->app_item_id, 'bid' => $data->receipt->bid, 'bvrs' => $data->receipt->bvrs ); }
The following listing shows the entire code. You may want to move getReceiptData() into its own
class or file so it can easily be reused.
/** * Verify a receipt and return receipt data * * @param string $receipt Base-64 encoded data * @param bool $isSandbox Optional. True if verifying a test receipt * @throws Exception If the receipt is invalid or cannot be verified * @return array Receipt info (including product ID and quantity) */ function getReceiptData($receipt, $isSandbox = false) { // determine which endpoint to use for verifying the receipt if ($isSandbox) { $endpoint = 'https://sandbox.itunes.apple.com/verifyReceipt'; } else { $endpoint = 'https://buy.itunes.apple.com/verifyReceipt'; } // build the post data $postData = json_encode( array('receipt-data' => $receipt) ); // create the cURL request $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); // execute the cURL request and fetch response data $response = curl_exec($ch); $errno = curl_errno($ch); $errmsg = curl_error($ch); curl_close($ch); // ensure the request succeeded if ($errno != 0) { throw new Exception($errmsg, $errno); } // parse the response data $data = json_decode($response); // ensure response data was a valid JSON string if (!is_object($data)) { throw new Exception('Invalid response data'); } // ensure the expected data is present if (!isset($data->status) || $data->status != 0) { throw new Exception('Invalid receipt'); } // build the response array with the returned data return array( 'quantity' => $data->receipt->quantity, 'product_id' => $data->receipt->product_id, 'transaction_id' => $data->receipt->transaction_id, 'purchase_date' => $data->receipt->purchase_date, 'app_item_id' => $data->receipt->app_item_id, 'bid' => $data->receipt->bid, 'bvrs' => $data->receipt->bvrs ); } // fetch the receipt data and sandbox indicator from the post data $receipt = $_POST['receipt']; $isSandbox = (bool) $_POST['sandbox']; // verify the receipt try { $info = getReceiptData($receipt, $isSandbox); // receipt is valid, now do something with $info } catch (Exception $ex) { // unable to verify receipt, or receipt is not valid }
Now that you can verify the receipt data, you can fulfill the user's request. If you deem the receipt to be valid you can send back the requested data, otherwise you can send back an error message so your iOS application can communicate the problem back to the user.
Further Reading
Other Options
- Download a PDF version of this article
- Put your PHP knowledge to the test with our online and iPad/iPhone quizzes
- View or post comments for this article
- Browse similar articles by tag: cURL, JSON, PHP
- Read related articles:




