Last active
August 5, 2021 08:49
-
-
Save jamesstout/5073237 to your computer and use it in GitHub Desktop.
Verify iOS in-app purchase receipts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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