PHP (Laravel) 实现 iOS 内购服务端验证
IOS内购的流程大致如下:
- 通过产品ID获取产品信息列表(客户端的事,我们不用管)
- 添加监听(客户端的事,我们不用管)
- 把产品包装成SKPayment(支付)发送给苹果服务器(客户端的事,我们不用管)
- 苹果服务器购买成功后会回调监听方法,根据苹果服务器返回信息判断是否购买成功。
- 购买失败或已经购买过该商品则注销交易。如果购买成功,此时可以向自家服务器发送购买成功的消息,并通过后台向苹果服务器发送验证,然后注销交易。(这一步,客户端会得到苹果返回的支付凭证数据,然后用此数据发到我们服务端,服务端进行验证)
代码如下
/**
* @Author woann <www.woann.cn>
* @param Request $request
* @return \Illuminate\Http\JsonResponse
* @des ios内购支付回调
*/
public function iosNotify(Request $request)
{
$this->validate(
$request,
[
'order_id' => 'required|digits_between:1,9|integer',
'apple_receipt' => 'required|min:20',
]
);
//苹果内购的验证收据
$receipt_data = $request->input('apple_receipt');
$order_id = $request->input('order_id');
$order = LevelOrder::find($order_id);
if (!$order || $order->state != 0) {
return returnApi(500,'订单状态异常');
}
$ios_sandBox = env('IOS_SANDBOX',true);//判断生产环境,开发环境
if(empty($receipt_data)){
return json(500, '参数不正确');
}
// 获取校验结果
$result=$this->validate_apple_pay($receipt_data,$ios_sandBox);
if (!$result || !is_array($result) || !isset($result['status'])) {
return returnApi(500,'获取数据失败,请重试');
}
//如果校验失败
if ($result['status'] != 0) {
return returnApi(500,'miss [apple_receipt]');
}
if ($result['status'] == 0) {
$transaction_ids = [];//用来收集需要关闭的transaction_id
//遍历未结束交易的列表(in_app)
foreach ($result['receipt']['in_app'] as $v){
$transaction_ids[] = $v['transaction_id'];//不管当前交易是否已经处理过,都要返回给客户端结束交易
$is_exist = DB::table('ios_transaction_id')->where('transaction_id', $v['transaction_id'])->first();
if ($is_exist) {
//如果已经处理过该订单 直接跳过
continue;
}
//否则在此处理业务逻辑
//...
//然后记录下此transaction_id标记为已处理过
DB::table('ios_transaction_id')->insert(['transaction_id' => $v['transaction_id']]);
}
//返回给客户端需要结束交易的transaction_id列表
return returnApi(200,'SUCCESS',['transaction_ids' => $transaction_ids]);
}
}
/**
* 验证AppStore内付
* @param string $receipt_data 付款后凭证
* @return array 验证是否成功
*/
protected function validate_apple_pay($receipt_data,$ios_sandBox)
{
/**
* 21000 App Store不能读取你提供的JSON对象
* 21002 receipt-data域的数据有问题
* 21003 receipt无法通过验证
* 21004 提供的shared secret不匹配你账号中的shared secret
* 21005 receipt服务器当前不可用
* 21006 receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送
* 21007 receipt是Sandbox receipt,但却发送至生产系统的验证服务
* 21008 receipt是生产receipt,但却发送至Sandbox环境的验证服务
*/
$POSTFIELDS = '{"receipt-data":"'. $receipt_data .'"}';
if($ios_sandBox){
// 请求验证
$data = httpRequest('https://sandbox.itunes.apple.com/verifyReceipt', $POSTFIELDS);
}else{
// 请求验证
$data = httpRequest('https://buy.itunes.apple.com/verifyReceipt', $POSTFIELDS);
}
return $data;
}
protected function httpRequest($url, $postData = array(), $json = true)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($postData) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
}
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
$info = curl_getinfo($ch);
$data = curl_exec($ch);
curl_close($ch);
if ($json) {
return json_decode($data, true);
} else {
return $data;
}
}
```
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: