公司做的app需要做IAP订阅支付,开始觉得和微信的支付流程差不多,做起来还是有点麻烦,主要是网上的文章很少,不能拿来主义。自己做完总结一下,希望对小伙伴们有帮助我就很欣慰了。代码写的不好 不要喷我。。。
首先讲一下我的业务逻辑,也就是php服务端需要做什么事情。
先上图:
下面我详细的讲一下每一步做些什么,贴出来相应的代码供大家参考。
第一步: app 调用创建订单接口,创建订单信息,保存下来。
第二步: app 调用sdk 发起支付,传苹果给的 receipt(票据)和 订单号 给服务端,服务端通过验证receipt 获取订单信息保存
数据库。至于票据长什么样子,怎么验证,后面贴代码出来。
第三步: 订阅模式支付,首次支付苹果服务器会异步发送两次通知到服务端,之后你在app端操作的时候也会有通知,比如更 改套餐,取消订阅等操作都会有通知,这个就比较坑了,如果异步处理的时候没弄清楚。
很容易出问题,不能每次接收到通知就处理,沙盒模式下通知来的很奇怪,通知有很多状态,要区别开来处理。
接下来是代码处理流程:php框架+tp 5.02
首先是同步完成订单时候的验证:
/**
* @title 验证支付票据 完成订单接口
*/
public function verifyReceipt()
{
$receipt = Request::instance()->param('receipt'); //票据
$orderSn = Request::instance()->param('orderSn'); /订单号
//判断订单是否存在 检查状态
//写入日志 查看票据格式 记录日志的方法 这个方法不贴出来了
Tool::callAddLog('request-param',json_encode(['receipt'=>$receipt,'orderSn'=>$orderSn]));
//password 是验证秘钥
$jsonItem = json_encode(['receipt-data'=>$receipt, 'password'=>'XXXXXXXXXXXXXXXX']);
//$url= 'https://buy.itunes.apple.com/verifyReceipt'; //正式
$url= 'https://sandbox.itunes.apple.com/verifyReceipt'; //测试
//模拟post提交 下面会贴出来
$result = Tool::http_post_json($jsonItem,$url);
if($result['status'] !== 0){
//验证失败 返回app错误状态
}
//验证完成加入日志
Tool::callAddLog('verifyReceipt_finish',json_encode($result));
//找出时间最大的数组
$receiptitem = $result['latest_receipt_info'];
//这个是排序的方法 下面回帖出来 苹果会把这个会员往期的订单信息全部返回,需要找出来最近的那一组信息
$item = Tool::arraySort($receiptitem,'purchase_date','desc');
//判断一下过期时间 延长会员时间
$orderThird = $item['transaction_id']; //本次订阅的订单号
$orderThirdFirst = $item['original_transaction_id']; //这个是原始订单号
if($orderThird == $orderThirdFirst){ //首次订阅 两个相等
}else{
//判断过期时间和当前时间比较 expires_date_ms是毫秒单位
if($item['expires_date_ms']/1000 > time()){
}else{
//过期处理 票据失效
}
}
//接下来处理订单业务逻辑,处理订单,修改订单信息,original_transaction_id 把这个单号和存入订单信息, 在异步回调的时候根据这个单号来查找订单信息来判断是哪个用户,苹果票据里面也有产品product_id,是app_store的产品id,不是自己业务的产品id,可以在后台把自己的产品ID和 product_id 关联,处理业务逻辑。
}
//模拟post提交
public static function http_post_json($json,$url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); //这两行一定要加,不加会报SSL 错误
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$errmsg = curl_error($ch);
curl_close($ch);
$data = json_decode($response, true);
return $data;
}
//查找最新数据的方法
public static function arraySort($arr,$key,$type='asc'){
$keyArr = []; // 初始化存放数组将要排序的字段值
foreach ($arr as $k=>$v){
$keyArr[$k] = $v[$key]; // 循环获取到将要排序的字段值
}
if($type == 'asc'){
asort($keyArr); // 排序方式,将一维数组进行相应排序
}else{
arsort($keyArr);
}
foreach ($keyArr as $k=>$v){
$newArray[$k] = $arr[$k]; // 循环将配置的值放入响应的下标下
}
$newArray = array_merge($newArray); // 重置下标
return $newArray[0]; // 数据返回
}
public static function http_post_json($json,$url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); //这两行一定要加,不加会报SSL 错误
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$errmsg = curl_error($ch);
curl_close($ch);
$data = json_decode($response, true);
return $data;
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
验证之后返回的状态
验证之前的票据是一个字符串:就是一个字符串
验证之后的信息:这个是重点
到了 这同步主动验证这边就结束了。
下面是异步通知这块:
异步通知的票据可以验证发送到苹果服务器验证一下有效性,也可以不要验证,它本身的信息就够用了。
通知类型: notification_type 有很多种情况,有些通知接收存入日志,不用处理,
需要处理的类型:RENEWAL DID_CHANGE_RENEWAL_STATUS
但是 DID_CHANGE_RENEWAL_STATUS 这种通知 在首次订阅的时候 也会发一个过来,这个时候不处理,也就是当参数 auto_renew_status 等于 true 的时候 不处理,其余的时候都是需要处理的通知。
public function applePayReceive(){
$str = file_get_contents('php://input');
//写入通知日志
$jsonItem = json_decode($str,true);
if($jsonItem['notification_type'] == 'RENEWAL' || $jsonItem['notification_type'] == 'DID_CHANGE_RENEWAL_STATUS'){
if($jsonItem['notification_type'] == 'DID_CHANGE_RENEWAL_STATUS'){
if($jsonItem['auto_renew_status'] == 'true'){
//第一次够买的时候 不处理通知
die;
}
$expires_date = $jsonItem['latest_receipt_info']['expires_date'];
$expires_date = $expires_date/1000;
Tool::asynAddLog('request-param',$expires_date.'----'.time());
if($expires_date > time()){
}else{
//时间不对 不处理
}
}
//处理订阅产品的业务逻辑
echo '完成!';die;
}
好了,上面是我写的苹果支付所有流程和代码,业务代码部分我去掉了,大家自己写自己的业务逻辑就可以。
重点:异步通知有可能收不到,这种情况下,需要把用户首次订阅的凭证存下来,等到用户到期的时候 再去验证一下这个凭证,然后取出订阅相关信息,找到最近的一条,更新会员时间,会员时间用苹果提供的过期时间就可以。
沙盒模式下苹果的通知是乱的,请大家注意,不一定按照文档上面说的时间有规则的发送通知,所以异步通知不能保证业务的完整性,处理业务时一定要把用户首次支付的凭证持久化,以便后续通知收不到的情况,会员到期服务端主动去查询会员是否续费 情况。