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.
Note: This article assumes your iOS application
already has the ability to submit the StoreKit receipt data in Base-64
encoding to your PHP web site.
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.
Listing 1 Basic skeleton for verifying receipts (listing-1.php)
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 }
Note: This code assumes your iOS application performs a POST request which includes data in
If you're testing a receipt for the sandbox, the endpoint URL to check with is receipt
and sandbox
.
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
.
Listing 2 Determining the App Store endpoint (listing-2.php)
function getReceiptData($receipt, $isSandbox = false) { if ($isSandbox) { $endpoint = 'https://sandbox.itunes.apple.com/verifyReceipt'; } else { $endpoint = 'https://buy.itunes.apple.com/verifyReceipt'; } }
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.
Listing 3 Build the cURL request POST data (listing-3.php)
function getReceiptData($receipt, $isSandbox = false) { // ... previous code $postData = json_encode( array('receipt-data' => $receipt) ); }
$endpoint
and $postData
variables we have defined.
Note: Your cURL installation must support SSL in order to communicate with the Apple web service.
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.
Listing 4 Creating and executing the cURL request (listing-4.php)
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); }
$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()
.
Note: You can see a list of cURL error codes at http://curl.haxx.se/libcurl/c/libcurl-errors.html
Listing 5 Ensuring the cURL request was successful (listing-5.php)
function getReceiptData($receipt, $isSandbox = false) { // ... previous code if ($errno != 0) { throw new Exception($errmsg, $errno); } }
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.
Listing 6 Ensuring the response data is valid (listing-6.php)
function getReceiptData($receipt, $isSandbox = false) { // ... previous code $data = json_decode($response); if (!is_object($data)) { throw new Exception('Invalid response data'); } }
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.
Listing 7 Checking if the receipt is valid and if so, building the response data (listing-7.php)
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 ); }
Note: There are other values returned by the web service. See the link below to Apple's
guide.
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.
Listing 8 The complete script for verifying a receipt (listing-8.php)
/** * 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 }
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:
No comments:
Post a Comment