How to Verify Apple In‑App Purchases with PHP: A Step‑by‑Step Guide
This article explains how to implement Apple In‑App Purchase receipt verification in a PHP backend, covering required Apple documentation, the verification workflow, detailed PHP code for handling production and sandbox environments, and how to record successful transactions in the database.
Apple In‑App Purchase receipt verification
Apple requires the server to send the base64‑encoded receipt received from an iOS app to the verifyReceipt endpoint and interpret the JSON response. Two endpoints are provided:
Production: https://buy.itunes.apple.com/verifyReceipt Sandbox: https://sandbox.itunes.apple.com/verifyReceipt The HTTP POST body must be a JSON object containing the key "receipt-data" with the receipt string. Apple returns a JSON object that always includes a status field. A status of 0 means the receipt is valid. A status of 21007 indicates that a sandbox receipt was sent to the production endpoint; the request should be retried using the sandbox URL.
PHP (Laravel) implementation
The following controller demonstrates the complete workflow: receiving the receipt from the iOS client, verifying it with Apple (including sandbox fallback), extracting the latest in‑app purchase record, updating the user's virtual balance, recording the transaction, and returning a standardized result.
<?php
namespace App\Http\Controllers\Main;
use App\Models\ReferInfo;
use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class AppleController extends Controller
{
const FLAG = true; // true = sandbox mode, false = production
/**
* Verify an Apple receipt.
* @param string $receipt_data Base64‑encoded receipt
* @return array Result with status, message, product_id and transaction_id
*/
public function appleValidatePay($receipt_data)
{
if (strlen($receipt_data) < 20) {
return array_format('Invalid parameter', 414);
}
$html = $this->acurl($receipt_data);
$data = json_decode($html, true);
// Retry with sandbox endpoint if needed
if ($data['status'] == '21007') {
$html = $this->acurl($receipt_data, 1);
$data = json_decode($html, true);
$data['sandbox'] = '1';
}
if (intval($data['status']) === 0) {
$order = $data['receipt']['in_app'];
$k = count($order) - 1;
$need = $order[$k];
$result = [
'status' => true,
'message' => 'Purchase successful',
'product_id' => substr($need['product_id'], 19),
'transaction_id' => $need['transaction_id'],
];
} else {
$result = [
'status' => false,
'message' => 'Purchase failed status:' . $data['status'],
];
}
return $result;
}
/**
* Endpoint called by the iOS client.
* Expects POST parameters: receipt_data (string) and user_inte (int).
*/
public function paypalApp(Request $request)
{
$receipt_data = $request->input('receipt_data', '');
$user_inte = $request->user_inte; // points / coins to add
$uid = \Auth::user()->uid;
$user = User::lockForUpdate()->where('uid', $uid)->first();
if (!$user) {
return array_format('Failed to get payment object', 414);
}
if (self::FLAG) {
$result = $this->appleValidatePay($receipt_data);
if (!$result['status']) {
return array_format('Verification failed', 414);
}
$transaction_id = $result['transaction_id'];
} else {
$transaction_id = date('ymdHis') . rand_num(10);
}
$order_id = order_id();
DB::beginTransaction();
$user->user_inte += $user_inte;
if (!$user->save()) {
DB::rollBack();
return array_format('Failed to update user info', 414);
}
$data = [
'uid' => $uid,
'order_id' => $order_id,
'user_inte' => $user_inte,
'device' => constant('DF_DEVICE'),
'status' => 1,
'trade_no' => $transaction_id,
'created_at' => date('Y-m-d H:i:s'),
];
$temp = DB::table('pay_info')->insert($data);
if (!$temp) {
DB::rollBack();
return array_format('Order failed', 414);
}
DB::commit();
return array_format('Payment successful', 200);
}
/**
* Send the receipt to Apple for verification.
* @param string $receipt_data
* @param int $sandbox 0 = production, 1 = sandbox
* @return string Raw JSON response from Apple
*/
public function acurl($receipt_data, $sandbox = 0)
{
$postFields = json_encode(['receipt-data' => $receipt_data]);
$urlBuy = "https://buy.itunes.apple.com/verifyReceipt";
$urlSandbox = "https://sandbox.itunes.apple.com/verifyReceipt";
$url = $sandbox ? $urlSandbox : $urlBuy;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
}Testing the integration
The iOS client should POST the base64 receipt as the receipt_data parameter to the paypalApp endpoint. The server will verify the receipt with Apple, update the user's virtual balance, record the transaction in the pay_info table, and return a JSON response indicating success (HTTP 200) or failure (HTTP 414).
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Laravel Tech Community
Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
