Verify iOS in-app purchase receipts
<?php | |
include("/var/www/vhosts/xxxxxcom/httpdocs/PHPUtils/dbConfig.php"); | |
include("/var/www/vhosts/xxxxxcom/httpdocs/PHPUtils/DButils.php"); | |
// verifies receipt from iOS in-app purchase | |
// returns: | |
// 0 - if params missing | |
// 1 - if receipt is valid | |
// 2 - if invalid receipt, or invalid response from verification server | |
// or bundle/in-app IDs are incorrect | |
// or if transaction ID has been used before | |
// get the parmas from the URL | |
$testing = htmlspecialchars($_GET["testing"]); | |
$receiptData = $_GET["receipt"]; | |
if ( $testing == "" or $receiptData == "" ){ | |
echo "0"; | |
exit; | |
} | |
$testing = (bool)$testing; | |
// verify the receipt | |
try { | |
// quick check | |
if(strpos($receiptData,'{') !== false){ | |
$receiptData = base64_encode($receiptData); | |
} | |
$info = getReceiptData($receiptData, $testing); | |
if(checkInfo($info) == true){ | |
echo "1"; | |
} | |
else{ | |
echo "2"; | |
} | |
} | |
catch (Exception $ex) { | |
// unable to verify receipt, or receipt is not valid | |
echo "2"; | |
} | |
exit; | |
/** | |
* 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, | |
'status' => $data->status, | |
'product_id' => $data->receipt->product_id, | |
'transaction_id' => $data->receipt->transaction_id, | |
'original_transaction_id' => $data->receipt->original_transaction_id, | |
'purchase_date' => $data->receipt->purchase_date, | |
'bid' => $data->receipt->bid, | |
'bvrs' => $data->receipt->bvrs | |
); | |
} | |
/** | |
* Check transactionID has not been used before | |
* | |
* @param string $transactionID transactionId to check | |
* @return bool true if new ID (or there is an error, for safety), false if not | |
*/ | |
function checkTransactionID($transactionID) | |
{ | |
/* | |
from PHPUtils/dbConfig.php | |
assoc array containing db connection info | |
e.g. | |
$dbConfig['xxxxxx']['server'] = 'localhost'; | |
$dbConfig['xxxxxx']['user'] = 'username'; | |
$dbConfig['xxxxxx']['password'] = 'password'; | |
$dbConfig['xxxxxx']['database'] = 'database'; | |
*/ | |
global $dbConfig; | |
/* | |
from PHPUtils/DButils.php | |
just does a mysqli_connect with the data from $dbConfig | |
with some error checking | |
*/ | |
$link = connectToDBMYSQLI($dbConfig['xxxxxx'], __FILE__, true); | |
if (!$link) { | |
// for safety, return true, in case db is down | |
return true; | |
} | |
else{ | |
// in the iOS app, store successful transactions to a db | |
$sql = "select count(*) as `tranCount` from `ReceiptsTable` where `transactionID` = '$transactionID'"; | |
$res = mysqli_query($link, $sql); | |
if ($res) { | |
$row = mysqli_fetch_assoc($res); | |
if($row[tranCount] == 0){ | |
// this is what we want | |
return true; | |
} | |
else{ | |
// transactionID already used - a pirate! | |
return false; | |
} | |
} | |
else{ | |
// for safety, return true, in case db is down | |
return true; | |
} | |
mysqli_close($link); | |
} | |
// for safety, return true, shouldn't get here | |
return true; | |
} | |
/** | |
* Check receipt for valid in-appProductID, appBundleID and unique transactionID | |
* | |
* @param array $infoArray Array returned from getReceiptData() | |
* @return bool true if all OK, false if not | |
*/ | |
function checkInfo($infoArray) | |
{ | |
$inAppPurchaseID = "com.xxxx.xxxx.inappid"; | |
$bundleID = "com.xxxxxx.appid"; | |
if($infoArray[product_id] != $inAppPurchaseID){ | |
return false; | |
} | |
if($infoArray[bid] != $bundleID){ | |
return false; | |
} | |
if(checkTransactionID($infoArray[transaction_id]) == false){ | |
return false; | |
} | |
// probably won't get here - getReceiptData throws Ex on status == 0 | |
if($infoArray[status] != 0){ | |
return false; | |
} | |
return true; | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment