PHP服务端 苹果支付(IAP)处理

 公司做的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;
}

好了,上面是我写的苹果支付所有流程和代码,业务代码部分我去掉了,大家自己写自己的业务逻辑就可以。

看到最后的同志们,我上面有个地方写错了 ,苹果正式环境,第一次购买的时候,异步的两个通知,notification_type 等于DID_CHANGE_RENEWAL_STATUS 这个的时候,auto_renew_status 值有可能不是ture,我开始没弄懂意思,现在我的理解是

等于true是自动续费的意思,就是说用户选择的是连续订阅,而等于false的时候是用户买了产品取消了订阅模式。才会出现这种情况。不是苹果坑,而是自己没理解,

 

其实根本不能用通知的类型来判断是否给用户重置,正确保险的方案是在主动通知的时候把苹果的原始订单号和本次交易的订单号存入订单,异步的时候判断订单是否完成。然后再写业务逻辑。

 

重点:异步通知有可能收不到,这种情况下,需要把用户首次订阅的凭证存下来,等到用户到期的时候 再去验证一下这个凭证,然后取出订阅相关信息,找到最近的一条,更新会员时间,会员时间用苹果提供的过期时间就可以。

沙盒模式下苹果的通知是乱的,请大家注意,不一定按照文档上面说的时间有规则的发送通知,所以异步通知不能保证业务的完整性,处理业务时一定要把用户首次支付的凭证持久化,以便后续通知收不到的情况,会员到期服务端主动去查询会员是否续费 情况。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章