如何使用PHP实现Flutter应用in_app_purchase内购订单的服务端验证
Flutter端前置处理
完成支付流程后先提取凭证上传给PHP后端,拿到核验成功结果后再解锁权益、完成交易闭环:
- 从
in_app_purchase返回的PurchaseDetails对象中提取对应平台的购买凭证:- 安卓(Google Play):取
verificationData.serverVerificationData,为包含purchaseToken、productId、packageName字段的JSON字符串 - iOS(App Store):取
verificationData.serverVerificationData,为Base64编码的交易收据字符串
- 安卓(Google Play):取
- 将上述凭证、对应商品ID、业务侧用户唯一标识通过POST请求传入PHP后端接口
PHP后端核验逻辑
1. 安卓Google Play订单核验
先在Google Play Console后台获取服务账号JSON密钥,再通过官方API完成核验:
- 用Composer安装依赖:
composer require google/apiclient:^2.0 - 参考核验代码:
<?php require __DIR__ . '/vendor/autoload.php'; $client = new Google_Client(); $client->setAuthConfig('你的服务账号密钥.json本地路径'); $client->addScope('https://www.googleapis.com/auth/androidpublisher'); $androidPublisher = new Google_Service_AndroidPublisher($client); // 取前端传入的对应参数 $packageName = '你的应用包名'; $productId = $_POST['product_id']; $purchaseToken = $_POST['purchase_token']; try { $response = $androidPublisher->purchases_products->get($packageName, $productId, $purchaseToken); // purchaseState为0代表已完成购买 if ($response->getPurchaseState() === 0) { // 此处写入业务逻辑:记录订单到数据库、给对应用户解锁权益等 echo json_encode(['code' => 200, 'msg' => '核验成功']); } else { echo json_encode(['code' => 400, 'msg' => '订单无效']); } } catch (Exception $e) { echo json_encode(['code' => 500, 'msg' => '核验失败:' . $e->getMessage()]); } ?>
2. iOS App Store订单核验
走苹果官方收据核验接口,注意区分正式和沙箱环境:
- 参考核验代码:
<?php $receipt = $_POST['receipt_data']; // 正式环境核验地址,沙箱环境地址为https://sandbox.itunes.apple.com/verifyReceipt $url = 'https://buy.itunes.apple.com/verifyReceipt'; $postData = json_encode([ 'receipt-data' => $receipt, 'password' => 'App Store Connect生成的共享密钥' // 仅自动续期订阅类商品需要,普通商品可省略 ]); $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $response = curl_exec($ch); curl_close($ch); $result = json_decode($response, true); // 返回status为21007说明是沙箱收据,切换沙箱地址重新请求 if (isset($result['status']) && $result['status'] == 21007) { $url = 'https://sandbox.itunes.apple.com/verifyReceipt'; // 此处复用上面的请求逻辑重发请求,省略重复代码 } if (isset($result['status']) && $result['status'] == 0) { // 校验商品ID匹配,防止凭证掉包 $valid = false; foreach ($result['receipt']['in_app'] as $item) { if ($item['product_id'] == $_POST['product_id']) { $valid = true; break; } } if ($valid) { // 此处写入业务逻辑:记录订单、解锁权益等 echo json_encode(['code' => 200, 'msg' => '核验成功']); } else { echo json_encode(['code' => 400, 'msg' => '商品不匹配']); } } else { echo json_encode(['code' => 400, 'msg' => '收据无效']); } ?>
注意事项
- 必须等后端返回核验成功结果后,再在Flutter端调用
completePurchase完成交易,避免出现核验失败但交易已闭环的问题 - 本地数据库要存储已核验的订单凭证做去重判断,防止同一个凭证重复请求解锁权益
- 生产环境可以给第三方接口请求加超时、重试逻辑,降低接口波动对用户体验的影响
内容的提问来源于stack exchange,提问作者Mahesh Mohotti
相关产品推荐
相关产品推荐

