苹果支付二次验证是保障应用内支付安全的核心环节,开发者在收到客户端上报的支付凭证后,不能直接信任该凭证的有效性,需要调用苹果官方的验证接口进行校验,PHP作为常用的服务端开发语言,实现该流程并不复杂。

苹果支付二次验证的核心原理
苹果支付完成后,客户端会拿到一个receipt-data凭证,这个凭证是加密后的支付信息。服务端需要把这个凭证发送到苹果提供的验证接口,苹果会返回该凭证对应的支付详情,服务端通过解析返回结果判断支付是否真实有效,同时可以校验商品ID、支付金额等信息是否和预期一致。
PHP实现二次验证的完整步骤
第一步:获取客户端上报的支付凭证
首先需要从客户端请求中获取receipt-data参数,同时建议让客户端同时上报商品ID、用户ID等关联信息,方便后续校验。
<?php
// 获取客户端POST请求中的参数
$receiptData = $_POST['receipt_data'] ?? '';
$productId = $_POST['product_id'] ?? '';
$userId = $_POST['user_id'] ?? '';
// 校验参数是否为空
if (empty($receiptData) || empty($productId) || empty($userId)) {
echo json_encode(['code' => 400, 'msg' => '参数缺失']);
exit;
}
?>
第二步:调用苹果验证接口
苹果提供了两个验证接口,一个是沙箱环境用于测试,一个是正式环境用于生产,开发阶段可以先使用沙箱接口,上线后切换到正式接口。
| 环境类型 | 接口地址 |
|---|---|
| 沙箱环境 | https://sandbox.itunes.apple.com/verifyReceipt |
| 正式环境 | https://buy.itunes.apple.com/verifyReceipt |
调用接口时需要把receipt-data作为请求参数,同时可以传入password字段,该字段是你在苹果开发者后台配置的共享密钥,用于验证自动续期订阅类商品。
<?php
// 配置验证接口地址,开发阶段用沙箱,上线后替换为正式地址
$verifyUrl = 'https://sandbox.itunes.apple.com/verifyReceipt';
// 如果是自动续期订阅商品,需要配置共享密钥,普通消耗型商品可以不传
$sharedSecret = '你的苹果开发者后台共享密钥';
// 构造请求参数
$postData = json_encode([
'receipt-data' => $receiptData,
'password' => $sharedSecret
]);
// 初始化curl请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $verifyUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
// 跳过SSL证书验证,生产环境建议开启证书校验
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
// 处理请求异常
if ($error) {
echo json_encode(['code' => 500, 'msg' => '验证接口请求失败: ' . $error]);
exit;
}
?>
第三步:解析验证返回结果
苹果接口返回的是JSON格式数据,核心字段是status,当status为0时表示凭证验证通过,其他值都代表验证失败,具体状态码含义可以参考苹果官方文档。验证通过后,还需要从返回结果中提取商品信息,和客户端上报的商品ID做比对,避免客户端篡改商品信息。
<?php
$result = json_decode($response, true);
// 校验验证状态
if ($result['status'] != 0) {
echo json_encode(['code' => 400, 'msg' => '支付凭证验证失败,状态码: ' . $result['status']]);
exit;
}
// 提取支付对应的商品信息,普通商品在receipt的in_app字段中
$receipt = $result['receipt'] ?? [];
$inAppItems = $receipt['in_app'] ?? [];
// 查找匹配的商品
$validItem = null;
foreach ($inAppItems as $item) {
if ($item['product_id'] == $productId) {
$validItem = $item;
break;
}
}
if (!$validItem) {
echo json_encode(['code' => 400, 'msg' => '未找到匹配的商品信息']);
exit;
}
// 校验商品支付状态,普通商品支付状态为0表示成功
if ($validItem['purchase_state'] != 0) {
echo json_encode(['code' => 400, 'msg' => '商品支付未完成']);
exit;
}
?>
第四步:处理业务逻辑和异常
验证通过后,就可以根据业务需求处理后续逻辑,比如给用户发放对应的虚拟商品、更新用户的会员状态等。同时需要注意处理重复验证的问题,同一个支付凭证可能会被客户端多次上报,需要记录已验证的凭证标识,避免重复发放商品。
<?php
// 假设使用数据库记录已处理的支付凭证,transaction_id是苹果返回的唯一交易ID
$transactionId = $validItem['transaction_id'];
// 查询该交易ID是否已经处理过
// $db是已经初始化的数据库连接对象
$checkSql = "SELECT id FROM order_log WHERE transaction_id = '{$transactionId}' LIMIT 1";
$checkRes = $db->query($checkSql);
if ($checkRes && $checkRes->num_rows > 0) {
echo json_encode(['code' => 200, 'msg' => '该订单已处理']);
exit;
}
// 处理业务逻辑,比如给用户发放商品
$updateSql = "UPDATE user SET coin = coin + 100 WHERE id = {$userId}";
$db->query($updateSql);
// 记录订单日志
$logSql = "INSERT INTO order_log (user_id, product_id, transaction_id, create_time) VALUES ({$userId}, '{$productId}', '{$transactionId}', " . time() . ")";
$db->query($logSql);
echo json_encode(['code' => 200, 'msg' => '支付验证成功,商品已发放']);
?>
常见注意事项
- 沙箱环境和正式环境的凭证不能混用,沙箱凭证发送到正式接口会返回21007状态码,此时需要切换到沙箱接口重新验证。
- 自动续期订阅类商品的验证逻辑更复杂,需要定期轮询验证接口检查订阅状态,处理续期、取消等场景。
- 生产环境必须开启SSL证书校验,避免请求被中间人篡改。
- 支付凭证属于敏感信息,不要明文存储在日志中,避免信息泄露。
苹果支付二次验证的核心是不信任客户端上报的任何信息,所有支付状态都以苹果官方接口返回的结果为准,这样才能最大程度保障支付安全。