_apple_product_ids = json_decode($settingModel::getSetting('APPLE_PRODUCT_IDS'),true); //json解码没有错误发生时 if (JSON_ERROR_NONE == json_last_error()) { } else{ $this->jsonResult('',0,'苹果ProductID,json解码错误,请到后台确认配置是否正确'); } $this->redis = \think\Cache::store('default')->handler(); } /** * 新增订单 * * @return array */ public function index() { die(); $gameid = $this->input('gameid'); //游戏ID $member_id = $this->input('userid'); //用户ID $serverid = $this->input('serverid'); //服务器ID $servername = $this->input('servername'); //区服名称 $amount = $this->input('amount'); //充值金额 $roleid = $this->input('roleid'); //角色ID $attach = $this->input('attach'); //上一级的扩展参数,多数是订单编号[麻花网络自己聚合自己时,会加个aoyou###前缀] $imeil = trim($this->input('imeil')); //手机imeil码 $productname= $this->input('productname'); //消费的商品名称 $rolename = $this->input('rolename',''); //角色名 $rolelevel = $this->input('rolelevel',''); //角色角色等级 $orderid = makeOrderid(); $pay_status = 0; //cy_pay表的支付状态 $cp_payflag = 0; //cy_paycpinfo表的支付状态 $memberModel = new Members; $frozenModel = new ChannelFrozen; $cpurlModel = new Cpurl; $gameModel = new Game; $payModel = new PayModel; $payCpinfoModel = new PayCpinfo; $memberChannelGameModel = new MemberChannelGame; $checkResult = $this->validate($this->input, [ ['gameid', 'require|integer', '游戏id不能为空|游戏ID必须为整型'], ['userid', 'require|integer', '用户ID不能为空|用户ID必须为整型'], ['serverid', 'require|max:50', '游戏服务器id不能为空|游戏服务器id不能超过50个字符'], ['servername', 'require|max:255', '游戏区服名称不能为空|游戏区服名称不能超过255个字符'], ['amount', 'require|integer|gt:0', '充值金额不能为空|充值金额必须为正整型|充值金额必须大于0'], ['roleid', 'require', '角色id不能为空'], ['attach', 'require', '游戏合作方的订单参数不能为空'], ['channel_id', 'require|integer', '渠道ID不能为空|渠道ID必须为整型'], ['productname', 'require', '消费的商品名称不能为空'], ]); if (true !== $checkResult) { $this->jsonResult('', 0, $checkResult); } $memberInfo = $memberModel->field('username')->where(['id'=>$member_id])->find(); if(empty($memberInfo)){ $this->jsonResult('', 0, '用户不存在'); } //玩家在这款游戏的归属渠道 $channel_id = $memberChannelGameModel->where(['member_id'=>$member_id,'game_id'=>$gameid])->value('channel_id'); if (empty($channel_id)) { $this->jsonResult('', 0, '无关联的归属渠道信息'); } //当前游戏的渠道是否禁止消费(非归属渠道) if (isFrozenOption($this->input('channel_id'),$gameid,'consume')) { $this->jsonResult('', 0, '您所在渠道已经被禁止在此游戏消费'); } //充值回调地址 $cpurl = $cpurlModel->where(['appid'=>$this->appInfo['id']])->value('url'); if(empty($cpurl)){ $this->jsonResult('', 0, '没有回调地址,请通知我方配置'); } //游戏信息 $gameInfo = $gameModel->field('order_recheck')->where(['id' => $gameid])->find(); if(empty($gameInfo)){ $this->jsonResult('', 0, '游戏id参数错误'); } //指定时间内,禁止重复下单 if(!requestDuplicateCheck('pay_duplicate_'.$member_id.'_'.$gameid,$this->payRequestLimit)){ $this->jsonResult('', 0, '充值请求过多,请于'.$this->payRequestLimit.'s以后,再次进行充值操作'); } if($payModel->where(['attach'=>$attach,'gameid'=>$gameid])->find()){ $this->redis->del('pay_duplicate_'.$member_id.'_'.$gameid); $this->jsonResult('', 0, '游戏合作方的订单参数不能重复'); } //游戏防沉迷限制 $restrictInfo = model('GameRestrict')->where(['game_id'=>$gameid,'restrict_type'=>'preventhook'])->find(); if($restrictInfo){ $isPreventHook = intval($restrictInfo['restrict_status']); } else{ $settingModel = new Setting; $isPreventHook = intval($settingModel::getSetting('CHILD_LIMIT')); } if($isPreventHook == 2){ // 已实名认证用户做防沉迷判断 $membersTwoInfo = model('MembersTwo')->where(['userid' => $member_id])->find(); if ($membersTwoInfo && !empty($membersTwoInfo['realname']) && !empty($membersTwoInfo['idcard']) && $membersTwoInfo['realname']<>-1 && $membersTwoInfo['idcard']<>-1) { $totalMonthRechargeAmt = model('Pay')->where(['userid' => $member_id,'status'=>1,'create_time'=>['egt',strtotime(date('Y-m-01'))]])->sum('amount'); if (isMeetAgeByIDCard($membersTwoInfo['idcard'],0,8)){ $this->jsonResult('', 0, '未满8岁周岁无法充值'); } else if(isMeetAgeByIDCard($membersTwoInfo['idcard'],8,16)){ if($amount>50){ $this->jsonResult('', 0, '未满16周岁,单次充值不可超过50元'); } if(($totalMonthRechargeAmt+$amount) > 200){ $this->jsonResult('', 0, '未满16周岁,每月累充不可超过200元'); } } else if(isMeetAgeByIDCard($membersTwoInfo['idcard'],16,18)){ if($amount>100){ $this->jsonResult('', 0, '未满18周岁,单次充值不可超过100元'); } if(($totalMonthRechargeAmt+$amount) > 400){ $this->jsonResult('', 0, '未满18周岁,每月累充不可超过400元'); } } } } $payData['orderid'] = $orderid; $payData['use_coin_sn'] = ''; //平台币劵订单号 $payData['amount'] = $amount; $payData['real_amount'] = $amount; $payData['real_ptb'] = 0; $payData['real_ptb_amount'] = 0; $payData['userid'] = $member_id; $payData['username'] = $memberInfo['username']; $payData['roleid'] = $roleid; $payData['rolename'] = $rolename; $payData['rolelevel'] = $rolelevel; $payData['paytype'] = 'ios-iap'; $payData['productname'] = $productname; $payData['serverid'] = $serverid; $payData['gameid'] = $gameid; $payData['status'] = 0; $payData['ip'] = request()->ip(); $payData['imeil'] = $imeil; $payData['create_time'] = NOW_TIMESTAMP; $payData['channel_id'] = $channel_id; $payData['recheck_status'] = $gameInfo['order_recheck']; $payData['attach'] = $attach; // 启动事务 Db::startTrans(); try{ $result = ''; //返回值 $payModel->insert($payData); //给Cp的用户名用子账号名 $paycp_username = (!empty($this->input('sub_username')) ? $this->input('sub_username') : $memberInfo['username']); $str = "orderid=".urlencode($orderid)."&username=".urlencode($paycp_username)."&gameid=".$gameid."&roleid=".urlencode($roleid)."&serverid=".urlencode($serverid)."&paytype=".urlencode('ios-iap')."&amount=".$amount."&paytime=".NOW_TIMESTAMP."&attach=".urlencode($attach); $param = $str."&appkey=".urlencode($this->appInfo['appkey']); $md5params = md5($param); $params = $str . "&sign=".urlencode($md5params); $paycpData['orderid'] = $orderid; $paycpData['fcallbackurl'] = $cpurl; $paycpData['params'] = $params; $paycpData['create_time'] = NOW_TIMESTAMP; $paycpData['payflag'] = $cp_payflag; //写入cy_paycpinfo表数据 $payCpinfoModel->insert($paycpData); $this->redis->del('pay_duplicate_'.$member_id.'_'.$gameid); // 提交事务 Db::commit(); } catch (\Exception $e) { // 回滚事务 Db::rollback(); $this->jsonResult('', 0, '订单生成失败'.$e->getMessage()); } $result['orderid'] = $orderid; $this->jsonResult($result, 1, '订单生成成功',false); //先响应客户端请求,返回后,下面代码继续执行 fastcgi_finish_request(); //收集设备信息 $platform = $this->input('platform'); $this->saveDevices($gameid,$imeil,$platform); //更新游戏区服信息 $this->saveGameServer($member_id,$gameid,$imeil,'pay'); exit; } // 接收通知 public function notify() { $receipt_data = $this->input('receipt_data'); $orderid = $this->input('orderid'); $transaction_id = $this->input('transaction_id'); $product_id = $this->input('product_id'); //写入日志 log_message('apple receipt-data: '.$receipt_data,'error',LOG_PATH . '../paylog/'); $payModel = new PayModel; $payCpinfoModel = new PayCpinfo; $membersModel = new Members; if ( empty($orderid) ) { $this->jsonResult('', 0, '订单编号不能为空'); } if ( empty($receipt_data) ) { $this->jsonResult('', 0, '苹果购买凭证不能为空'); } if ( empty($transaction_id) ) { $this->jsonResult('', 0, '苹果购买记录的唯一标识不能为空'); } if ( empty($product_id) ) { $this->jsonResult('', 0, '苹果购买记录的ProductID不能为空'); } $payInfo = $payModel->field('id,status,amount,real_amount,userid,gameid,channel_id')->where(['orderid'=>$orderid,'userid'=>$this->input('userid')])->find(); //消费订单信息 if(!$payInfo) { $this->jsonResult('', 0, '订单记录不存在'); } elseif($payInfo['status']!=0){ $this->jsonResult('', 0, '该订单记录交易状态已结束'); } //log_message('apple base64_decode receipt-data: '.base64_decode($receipt_data),'error',LOG_PATH . '../paylog/'); //苹果返回的记录 $response = $this->_getReceiptData($receipt_data,$transaction_id,$product_id); // 判断购买是否成功 if ( ! isset($response['status']) || $response['status'] != 0 ) { $this->jsonResult('', 0, '支付失败,Status Code:'.$response['status']); } // 判断购买数量 if (! isset($response['quantity']) || 0 > $response['quantity'] ) { $this->jsonResult('', 0, '购买的数量不能小于1'); } // 判断产品ID 是否在白名单里 if (! isset($this->_apple_product_ids[$response['product_id']]) ) { $this->jsonResult('', 0, '非法的ProductID'); } // 判断产品ID的金额是否一致 if ($this->_apple_product_ids[$response['product_id']]!=$payInfo['amount'] ) { $this->jsonResult('', 0, '订单金额不匹配'); } // 判断服务端产品ID和客户端的产品ID是否一致 elseif($response['product_id']!=$product_id) { $this->jsonResult('', 0, '苹果购买记录的ProductID不匹配'); } // 启动事务 Db::startTrans(); try{ $payModel->where(['id'=>$payInfo['id']])->update(['status'=>1,'pay_time'=>NOW_TIMESTAMP]); //cy_paycpinfo表记录状态更新 $payCpinfoModel->where(['orderid'=>$orderid])->update(['payflag'=>1]); $result = true; // 提交事务 Db::commit(); } catch (\Exception $e) { // 回滚事务 Db::rollback(); $result = false; //写入日志 log_message('订单编号:'.$orderid.'订单状态更新失败'.$e->getMessage(),'error',LOG_PATH . '../paylog/'); } if($result==false){ $this->jsonResult('', 0, '订单状态更新失败'); } // 通知CP,支付成功 (new PayCallback())->callBackToCp($orderid); $this->jsonResult('', 1, '订单支付成功',false); //先响应客户端请求,返回后,下面代码继续执行 fastcgi_finish_request(); //更新member表的total_pay_amount $membersModel->where(['id'=>$payInfo['userid']])->update(['total_pay_amount'=>Db::raw('total_pay_amount+'.$payInfo['amount'])]); //预警邮件 (new PayLogic())->payWarning($payInfo['userid'], $payInfo['channel_id'], $payInfo['gameid'], $payInfo['amount']); } /** * 发起苹果验证请求 * * @param string $receipt 苹果回调数据 * @param $transaction_id 苹果购买记录的唯一标识 * @param $product_id 苹果的ProductID * @param bool $isSandbox 是否沙盒模式 * * @return array */ private function _getReceiptData($receipt,$transaction_id,$product_id, $isSandbox = false) { if ( $isSandbox ) { $endpoint = 'https://sandbox.itunes.apple.com/verifyReceipt'; } else { $endpoint = 'https://buy.itunes.apple.com/verifyReceipt'; } $postData = json_encode(['receipt-data' => $receipt]); $curl = curl_init($endpoint); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $postData); curl_setopt($curl, CURLOPT_TIMEOUT, 5); // 设置超时限制防止死循环 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); //这两行一定要加,不加会报SSL 错误 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); $response = curl_exec($curl); $errno = curl_errno($curl); $errmsg = curl_error($curl); curl_close($curl); // 判断时候出错,抛出异常 if ( $errno != 0 ) { $this->jsonResult('', 0, 'curl 请求错误 '.$errno); } //写入日志 log_message('apple response: '.$response,'error',LOG_PATH . '../paylog/'); $data = json_decode($response, true); // 判断返回的数据是否数组 if ( ! is_array($data) ) { $this->jsonResult('', 0, 'Invalid response data'); } //支付验证成功时 if($data['status']==0){ $in_app = []; if(!isset($data['receipt']['in_app']) || count($data['receipt']['in_app'])==0){ $this->jsonResult('', 0, 'response in_app is An empty array'); } else{ foreach($data['receipt']['in_app'] as $value) { if($value['transaction_id']==$transaction_id) { $in_app = $value; break; } } } if(empty($in_app)){ $this->jsonResult('', 0, '没有匹配的交易记录'); } return [ 'status' => $data['status'], 'quantity' => $in_app['quantity'], 'product_id' => $in_app['product_id'], 'transaction_id' => $in_app['transaction_id'], 'purchase_date' => $in_app['purchase_date'], ]; } // 如果是沙盒数据 则验证沙盒模式 elseif($data['status']=='21007'){ // 请求验证 return $this->_getReceiptData($receipt,$transaction_id,$product_id,true); } else{ $this->jsonResult('', 0, 'apple response status:'.$data['status']); } } }