| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- <?php
- /**
- * iOS应用内支付(苹果内购支付)
- *
- */
- namespace app\api\controller\v1;
- use app\api\controller\Api;
- use app\common\model\Members;
- use app\common\model\Game;
- use app\common\model\ChannelFrozen;
- use app\common\model\Cpurl;
- use app\common\model\Pay as PayModel;
- use app\common\model\PayCpinfo;
- use app\common\model\MemberChannelGame;
- use app\common\model\Setting;
- use app\common\logic\PayCallback;
- use app\common\logic\Pay as PayLogic;
- use think\Db;
- class IosPay extends Api {
-
- protected $payRequestLimit = 15; //重复下单的限制时间
- protected $redis; //redis的句柄对象
-
- //苹果后台设置产品对应的ProductID
- private $_apple_product_ids = [];
-
-
- /**
- * 不进行父类的登录验证,所以增加构造方法重写了父类的初始化方法
- */
- public function _initialize()
- {
- parent::_initialize();
-
- $settingModel = new Setting;
-
- $this->_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']);
- }
- }
- }
|