* @link http://www.wanlshop.com * * @careful 非FastAdmin购买授权,未经版权所有权人书面许可,不得自行复制到第三方系统使用、二次开发、分支、复制和商业使用! * @creationtime 2020年8月7日23:46:12 * @lasttime 2021年9月27日00:48:32 * @version V2.x https://pay.yansongda.cn/docs/v2/ **/ class WanlPay { private $type; private $method; private $code; public function __construct($type = '' ,$method = '', $code = '') { $auth = Auth::instance(); // 方式 $this->wanlchat = new WanlChat(); $this->type = $type; // 类型 $this->method = $method; // 方式 $this->user_id = $auth->isLogin() ? $auth->id : 0; // 用户ID $this->request = \think\Request::instance(); $this->code = $code; // 小程序code } /** * 支付 */ public function pay($order_id, $order_type) { if($this->user_id == 0){ return ['code' => 10001 ,'msg' => '用户ID不存在']; } // 获取支付信息 $pay = model('app\api\model\wanlshop\Pay') ->where('order_id', 'in', $order_id) ->where(['user_id' => $this->user_id, 'type' => $order_type == 'groups' ? 'groups' : 'goods']) ->select(); // 1.0.8 升级 $price = 0; // 付款金额 $order_no = []; // 订单号 $pay_id = []; // 交易号 foreach($pay as $row){ $price = bcadd($price, $row['price'], 2); // 总价格 $order_no[] = $row['order_no']; // 订单集 $pay_id[] = $row['id']; // 交易集 } // 第三方支付订单 1.0.8 if($this->type != 'balance'){ $payOutTrade = model('app\api\model\wanlshop\PayOutTrade'); $payOutTrade->out_trade_no = date('YmdHis'). rand(10000000,99999999). substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8); $payOutTrade->pay_id = $pay_id; $payOutTrade->order_id = $order_id; $payOutTrade->price = $price; $payOutTrade->save(); } // 1.0.8升级 拼团订单 if($order_type == 'groups'){ $title = '拼团订单-' . implode("_", $order_no); $model_order = model('app\api\model\wanlshop\groups\Order'); $model_order_goods = model('app\api\model\wanlshop\groups\OrderGoods'); $model_goods = model('app\api\model\wanlshop\groups\Goods'); }else{ $title = '商城订单-' . implode("_", $order_no); $model_order = model('app\api\model\wanlshop\Order'); $model_order_goods = model('app\api\model\wanlshop\OrderGoods'); $model_goods = model('app\api\model\wanlshop\Goods'); } // 支付方式 if ($this->type == 'balance') { // 支付列表 $pay_list = []; // 订单列表 $order_list = []; // 拼团列表 $groups_list = []; // 拼团列表 $groups_team_list = []; // 判断金额 $user = model('app\common\model\User')->get($this->user_id); if (!$user || $user['money'] < $price) { return ['code' => 500 ,'msg' => '余额不足本次支付']; } $result = false; $balance_no = date('YmdHis') . rand(10000000,99999999); Db::startTrans(); try { foreach($pay as $row){ $isAlone = false; // 拼团订单一般是单订单,暂可以这样操作 $groups_state = 'start'; // 拼团状态 foreach($model_order_goods->where('order_id', $row['order_id'])->select() as $goods){ if($order_type == 'groups' && !empty($goods['group_type'])){ if($goods['group_type'] == 'alone'){ $isAlone = true; }else{ // 查询团ID $groups = model('app\api\model\wanlshop\groups\Groups') ->where(['group_no' => $goods['group_no']]) ->find(); // 判断是否超团 $groups_team = model('app\api\model\wanlshop\groups\Team') ->where(['group_no' => $goods['group_no']]) ->select(); // 已拼团总数量 $groups_team_count = count($groups_team); if($groups_team_count >= $groups['people_num'] || $groups['join_num'] >= $groups['people_num']){ $this->error(__('参与拼单失败,拼团已完成')); } // 判断是否具备成团条件 if(($groups['people_num'] - $groups_team_count) <= 1 || ($groups['people_num'] - $groups['join_num']) <= 1){ $groups_state = 'success'; //调整其他拼团订单 // 订单状态:1=待支付,2=待成团,3=待发货,4=待收货,5=待评论,6=已完成,7=已取消 foreach($groups_team as $team){ $order_list[] = ['id' => $team['order_id'], 'state' => 3, 'groupstime' => time()]; } } // 拼团状态: ready=准备中,start=拼团中,success=已成团,fail=拼团失败,auto=自动成团 $groups_list[] = [ 'id' => $groups['id'], 'join_num' => $groups['join_num'] + 1, 'state' => $groups_state, 'grouptime' => $groups_state === 'success' ? time() : NULL ]; $groups_team_list[] = [ 'user_id' => $user['id'], 'shop_id' => $goods['shop_id'], 'group_no' => $goods['group_no'], 'order_id' => $goods['order_id'], 'order_goods_id' => $goods['id'] ]; } } // 新增付款人数、新增销量 $model_goods->where('id', $goods['goods_id'])->inc('payment')->inc('sales', $goods['number'])->update(); } // 订单列表 if($groups_state === 'success'){ $order_list[] = ['id' => $row['order_id'], 'state' => 3, 'paymenttime' => time(), 'groupstime' => time()]; }else{ $order_list[] = ['id' => $row['order_id'], 'state' => $isAlone ? 3 : 2, 'paymenttime' => time()]; } // 支付列表 $pay_list[] = [ 'id' => $row['id'], 'trade_no' => $balance_no, // 第三方交易号 'pay_type' => 0, // 支付类型:0=余额支付,1=微信支付,2=支付宝支付 'pay_state' => 1, // 支付状态 (支付回调):0=未支付,1=已支付,2=已退款 'total_amount' => $price, // 总金额 'actual_payment' => $row['price'], // 实际支付 'notice' => json_encode([ 'type' => $this->type, 'user_id' => $user['id'], 'trade_no' => $balance_no, 'out_trade_no' => $row['pay_no'], 'amount' => $row['price'], 'total_amount' => $price, 'order_id' => $row['order_id'], 'trade_type' => 'BALANCE', 'version' => '1.0.0' ]) ]; } // 更新支付列表 $result = model('app\api\model\wanlshop\Pay')->saveAll($pay_list); // 更新订单列表 $result = $model_order->saveAll($order_list); // 修改用户金额 if(count($order_no) == 1){ $result = self::money(-$price, $user['id'], '余额支付'.$order_type == 'groups' ? '拼团':'商品'.'订单', $order_type == 'groups' ? 'groups':'pay', implode(",",$order_no)); }else{ $result = self::money(-$price, $user['id'], '余额合并支付'.$order_type == 'groups' ? '拼团':'商品'.'订单', $order_type == 'groups' ? 'groups':'pay', implode(",",$order_no)); } if($order_type == 'groups'){ model('app\api\model\wanlshop\groups\Groups')->isUpdate()->saveAll($groups_list); model('app\api\model\wanlshop\groups\Team')->saveAll($groups_team_list); } Db::commit(); } catch (Exception $e) { Db::rollback(); return ['code' => 10002 ,'msg' => $e->getMessage()]; } // 返回结果 if ($result !== false) { return ['code' => 200 ,'msg' => '成功', 'data' => []]; } else { return ['code' => 10003 ,'msg' => '支付异常']; } // 支付宝支付、更新数据均在回调中执行 } else if($this->type == 'alipay'){ $data = [ 'out_trade_no' => $payOutTrade->out_trade_no, 'total_amount' => $price, 'subject' => $title ]; try{ $alipay = Pay::alipay(self::getConfig($this->type))->{$this->method}($data); if($this->method == 'app' || $this->method == 'wap'){ return ['code' => 200 ,'msg' => '成功', 'data' => $alipay->getContent()]; }else{ return ['code' => 200 ,'msg' => '成功', 'data' => $alipay]; } } catch (\Exception $e) { return ['code' => 10006 ,'msg' => $this->type.':'.$e->getMessage()]; } // 微信支付 } else if($this->type == 'wechat'){ $data = [ 'out_trade_no' => $payOutTrade->out_trade_no, // 订单号 'body' => $title, // 标题 'total_fee' => $price * 100 //付款金额 单位分 ]; if($this->method == 'miniapp'){ // 1.1.9升级 改为Easywechat try{ $auth = Easywechat::app() ->auth ->session($this->code); } catch (\Exception $e) { return ['code' => 10005 ,'msg' => $e->getMessage()]; } if(isset($auth['errcode'])){ return ['code' => 10005 ,'msg' => $auth['errmsg']]; } $time = time(); $third = model('app\api\model\wanlshop\Third') ->get(['platform' => 'weixin_open', 'openid' => $auth['openid']]); if(!$third){ $third = model('app\api\model\wanlshop\Third'); // array_key_exists("unionid",$auth) if(isset($auth['unionid'])){ $third->unionid = $auth['unionid']; $third->openid = $auth['openid']; }else{ $third->openid = $auth['openid']; } $third->access_token = $auth['session_key']; $third->expires_in = 7776000; $third->logintime = $time; $third->expiretime = $time + 7776000; $third->user_id = $this->user_id; $third->save(); } $data['openid'] = $auth['openid']; }else if($this->method == 'mp'){ // 微信公众平台支付必有OPENID 1.1.2升级 $third = model('app\api\model\wanlshop\Third') ->where(['platform' => 'weixin_h5', 'user_id' => $this->user_id]) ->find(); if ($third) { $data['openid'] = $third['openid']; }else{ return ['code' => 10005 ,'msg' => '获取微信openid失败,无法支付']; } } // 开始支付 try{ $wechat = Pay::wechat(self::getConfig($this->type))->{$this->method}($data); if($this->method == 'app'){ return ['code' => 200 ,'msg' => '成功', 'data' => $wechat->getContent()]; }else if($this->method == 'wap'){ return ['code' => 200 ,'msg' => '成功', 'data' => $wechat->getTargetUrl()]; }else{ return ['code' => 200 ,'msg' => '成功', 'data' => $wechat]; } } catch (\Exception $e) { return ['code' => 10006 ,'msg' => $this->type.':'.$e->getMessage()]; } // 百度支付 } else if($this->type == 'baidu'){ try{ } catch (\Exception $e) { return ['code' => 10006 ,'msg' => $this->type.':'.$e->getMessage()]; } // QQ支付 } else if($this->type == 'qq'){ try{ } catch (\Exception $e) { return ['code' => 10006 ,'msg' => $this->type.':'.$e->getMessage()]; } // 苹果支付 } else if($this->type == 'apple'){ try{ } catch (\Exception $e) { return ['code' => 10006 ,'msg' => $this->type.':'.$e->getMessage()]; } } else { return ['code' => 10010 ,'msg' => '未找到任何有效支付']; } } /** * 用户充值 */ public function recharge($price) { if($this->user_id == 0){ return ['code' => 10001 ,'msg' => '用户ID不存在']; } if($price <= 0){ return ['code' => 10002 ,'msg' => '充值金额不合法']; } // 充值订单号 $pay_no = date("Ymdhis") . sprintf("%08d", $this->user_id) . mt_rand(1000, 9999); // 支付标题 $title = '充值-' . $pay_no; // 生成一个订单 $order = \app\api\model\wanlshop\RechargeOrder::create([ 'orderid' => $pay_no, 'user_id' => $this->user_id, 'amount' => $price, 'payamount' => 0, 'paytype' => $this->type, 'ip' => $this->request->ip(), 'useragent' => substr($this->request->server('HTTP_USER_AGENT'), 0, 255), 'status' => 'created' ]); if($this->type == 'alipay'){ // 获取配置 $payConfig = self::getConfig($this->type); $payConfig['notify_url'] = str_replace("notify", "notify_recharge", $payConfig['notify_url']); $data = [ 'out_trade_no' => $pay_no, 'total_amount' => $price, 'subject' => $title ]; try{ $alipay = Pay::alipay($payConfig)->{$this->method}($data); if($this->method == 'app' || $this->method == 'wap'){ return ['code' => 200 ,'msg' => '成功', 'data' => $alipay->getContent()]; }else{ return ['code' => 200 ,'msg' => '成功', 'data' => $alipay]; } // 1.1.5升级 捕获异常 } catch (\Exception $e) { return ['code' => 10006 ,'msg' => $this->type.':'.$e->getMessage()]; } // 微信支付 } else if($this->type == 'wechat'){ // 获取配置 $payConfig = self::getConfig($this->type); $payConfig['notify_url'] = str_replace("notify", "notify_recharge", $payConfig['notify_url']); $data = [ 'out_trade_no' => $pay_no, // 订单号 'body' => $title, // 标题 'total_fee' => $price * 100 //付款金额 单位分 ]; if($this->method == 'miniapp'){ // 1.1.9升级 获取微信openid改为Easywechat try{ $auth = Easywechat::app() ->auth ->session($this->code); } catch (\Exception $e) { return ['code' => 10005 ,'msg' => $e->getMessage()]; } if(isset($auth['errcode'])){ return ['code' => 10005 ,'msg' => $auth['errmsg']]; } $time = time(); $third = model('app\api\model\wanlshop\Third') ->get(['platform' => 'weixin_open', 'openid' => $auth['openid']]); if(!$third){ $third = model('app\api\model\wanlshop\Third'); if(isset($auth['unionid'])){ $third->unionid = $auth['unionid']; $third->openid = $auth['openid']; }else{ $third->openid = $auth['openid']; } $third->access_token = $auth['session_key']; $third->expires_in = 7776000; $third->logintime = $time; $third->expiretime = $time + 7776000; $third->user_id = $this->user_id; $third->save(); } $data['openid'] = $auth['openid']; }else if($this->method == 'mp'){ // 微信公众平台支付必有OPENID 1.1.2升级 $third = model('app\api\model\wanlshop\Third') ->where(['platform' => 'weixin_h5', 'user_id' => $this->user_id]) ->find(); if ($third) { $data['openid'] = $third['openid']; }else{ return ['code' => 10005 ,'msg' => '获取微信openid失败,无法支付']; } } // 开始支付 try{ $wechat = Pay::wechat($payConfig)->{$this->method}($data); if($this->method == 'app'){ return ['code' => 200 ,'msg' => '成功', 'data' => $wechat->getContent()]; }else if($this->method == 'wap'){ return ['code' => 200 ,'msg' => '成功', 'data' => $wechat->getTargetUrl()]; }else{ return ['code' => 200 ,'msg' => '成功', 'data' => $wechat]; } // 1.1.5升级 捕获异常 } catch (\Exception $e) { return ['code' => 10006 ,'msg' => $this->type.':'.$e->getMessage()]; } // 百度支付 } else if($this->type == 'baidu'){ try{ } catch (\Exception $e) { return ['code' => 10006 ,'msg' => $this->type.':'.$e->getMessage()]; } // QQ支付 } else if($this->type == 'qq'){ try{ } catch (\Exception $e) { return ['code' => 10006 ,'msg' => $this->type.':'.$e->getMessage()]; } // 苹果支付 } else if($this->type == 'apple'){ try{ } catch (\Exception $e) { return ['code' => 10006 ,'msg' => $this->type.':'.$e->getMessage()]; } } else { return ['code' => 10010 ,'msg' => '未找到任何有效支付']; } } /** * 支付回调 */ public function notify() { $wanlpay = Pay::{$this->type}(self::getConfig($this->type)); try{ $result = $wanlpay->verify(); // 查询支付集 $payOutTrade = model('app\api\model\wanlshop\PayOutTrade') ->where(['out_trade_no' => $result['out_trade_no']]) ->find(); // 查询订单是否存在 $pay = model('app\api\model\wanlshop\Pay') ->where('id', 'in', $payOutTrade['pay_id']) ->where('pay_state', 'neq', '1') ->select(); if(!$pay){ return ['code' => 10001 ,'msg' => '网络异常']; } // 1.0.8升级 拼团订单 $order_type = $pay[0]['type']; if($order_type == 'groups'){ $model_order = model('app\api\model\wanlshop\groups\Order'); $model_order_goods = model('app\api\model\wanlshop\groups\OrderGoods'); $model_goods = model('app\api\model\wanlshop\groups\Goods'); }else{ $model_order = model('app\api\model\wanlshop\Order'); $model_order_goods = model('app\api\model\wanlshop\OrderGoods'); $model_goods = model('app\api\model\wanlshop\Goods'); } $trade_no = ''; // 支付类型 $pay_type = 8; $user_id = 0; $order_no = []; // 总价格 $price = 0; // 主要用于日志 foreach($pay as $row){ // $price += $row['price']; 1.0.8 升级 $price = bcadd($price, $row['price'], 2); // 总价格 // 订单集 $order_no[] = $row['order_no']; $user_id = $row['user_id']; } // -----------------------------判断订单是否合法----------------------------- $config = get_addon_config('wanlshop'); // 支付宝 if ($this->type == 'alipay') { // 判断状态 if (in_array($result['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) { // 判断金额 if($price != $result['total_amount']){ return ['code' => 10002 ,'msg' => '支付金额不合法']; } // 判断appid if($config['sdk_alipay']['app_id'] != $result['app_id']){ return ['code' => 10003 ,'msg' => 'APPID不合法']; } }else{ return ['code' => 500 ,'msg' => '支付回调失败']; } // 回调支付 $pay_type = 2; // 支付类型:0=余额支付,1=微信支付,2=支付宝支付 $pay_name = '支付宝'; $trade_no = $result['trade_no']; } else if($this->type == 'wechat'){ // 判断状态 if ($result['result_code'] == 'SUCCESS') { // 判断金额 if($price != ($result['total_fee'] / 100)){ return ['code' => 10002 ,'msg' => '支付金额不合法']; } // 判断商家ID if($config['sdk_qq']['mch_id'] != $result['mch_id']){ return ['code' => 10004 ,'msg' => '商户不合法']; } // H5微信支付 if($result['trade_type'] == 'MWEB'){ if($config['sdk_qq']['gz_appid'] != $result['appid']){ return ['code' => 10005 ,'msg' => '支付类型 '.$result['trade_type'] .' 不合法']; } } // 小程序支付 if($result['trade_type'] == 'JSAPI'){ if($config['mp_weixin']['appid'] != $result['appid']){ if($config['sdk_qq']['gz_appid'] != $result['appid']){ return ['code' => 10006 ,'msg' => '支付APPID不合法']; } } } // App支付 if($result['trade_type'] == 'APP'){ if($config['sdk_qq']['wx_appid'] != $result['appid']){ return ['code' => 10007 ,'msg' => '支付类型 '.$result['trade_type'] .' 不合法']; } } }else{ return ['code' => 500 ,'msg' => '支付回调失败']; } // 回调支付 $pay_type = 1; // 支付类型:0=余额支付,1=微信支付,2=支付宝支付 $pay_name = '微信'; $trade_no = $result['transaction_id']; } // -----------------------------支付成功,修改订单----------------------------- $order_list = []; $pay_list = []; // 拼团列表 $groups_list = []; // 拼团列表 $groups_team_list = []; foreach($pay as $row){ $isAlone = false; // 拼团订单一般是单订单,暂可以这样操作 $groups_state = 'start'; // 拼团状态 foreach($model_order_goods->where('order_id', $row['order_id'])->select() as $goods){ if($order_type == 'groups' && !empty($goods['group_type'])){ if($goods['group_type'] == 'alone'){ $isAlone = true; }else{ // 查询团ID $groups = model('app\api\model\wanlshop\groups\Groups') ->where(['group_no' => $goods['group_no']]) ->find(); // 判断是否超团 $groups_team = model('app\api\model\wanlshop\groups\Team') ->where(['group_no' => $goods['group_no']]) ->select(); // 已拼团总数量 $groups_team_count = count($groups_team); if($groups_team_count >= $groups['people_num'] || $groups['join_num'] >= $groups['people_num']){ $this->error(__('参与拼单失败,拼团已完成')); } // 判断是否具备成团条件 if(($groups['people_num'] - $groups_team_count) <= 1 || ($groups['people_num'] - $groups['join_num']) <= 1){ $groups_state = 'success'; //调整其他拼团订单 // 订单状态:1=待支付,2=待成团,3=待发货,4=待收货,5=待评论,6=已完成,7=已取消 foreach($groups_team as $team){ $order_list[] = ['id' => $team['order_id'], 'state' => 3, 'groupstime' => time()]; } } // 拼团状态: ready=准备中,start=拼团中,success=已成团,fail=拼团失败,auto=自动成团 $groups_list[] = [ 'id' => $groups['id'], 'join_num' => $groups['join_num'] + 1, 'state' => $groups_state ]; $groups_team_list[] = [ 'user_id' => $user_id, // 1.0.9升级 'shop_id' => $goods['shop_id'], 'group_no' => $goods['group_no'], 'order_id' => $goods['order_id'], 'order_goods_id' => $goods['id'] ]; } } // 新增付款人数、新增销量 $model_goods->where('id', $goods['goods_id'])->inc('payment')->inc('sales', $goods['number'])->update(); } // 订单列表 if($groups_state === 'success'){ $order_list[] = ['id' => $row['order_id'], 'state' => 3, 'paymenttime' => time(), 'groupstime' => time()]; }else{ $order_list[] = ['id' => $row['order_id'], 'state' => $isAlone ? 3 : 2, 'paymenttime' => time()]; } // 支付列表 1.0.9升级 $pay_list[] = [ 'id' => $row['id'], 'trade_no' => $trade_no, // 第三方交易号 'pay_type' => $pay_type, // 支付类型:0=余额支付,1=微信支付,2=支付宝支付 'pay_state' => 1, // 支付状态 (支付回调):0=未支付,1=已支付,2=已退款 'total_amount' => $price, // 总金额 'actual_payment' => $row['price'], // 实际支付 'notice' => json_encode($result) ]; } // 更新支付列表 model('app\api\model\wanlshop\Pay')->saveAll($pay_list); // 更新订单列表 $model_order->saveAll($order_list); // 支付日志 model('app\common\model\MoneyLog')->create([ 'user_id' => $user_id, 'money' => -$price, // 操作金额 'memo' => $pay_name.'支付' . $order_type == 'groups' ? '拼团' : '商城' . '订单', // 备注 'type' => $order_type == 'groups' ? 'groups' : 'pay', // 类型 'service_ids' => implode(",",$order_no) // 业务ID ]); Log::debug('Wanlpay notify', $result->all()); if($order_type == 'groups'){ model('app\api\model\wanlshop\groups\Groups')->isUpdate()->saveAll($groups_list); model('app\api\model\wanlshop\groups\Team')->saveAll($groups_team_list); } // 1.1.5升级 捕获异常 } catch (\Exception $e) { return ['code' => 10008 ,'msg' => $e->getMessage()]; } // 返回给支付接口 return ['code' => 200 ,'msg' => $wanlpay->success()->send()]; } /** * 充值支付回调 */ public function notify_recharge() { $wanlpay = Pay::{$this->type}(self::getConfig($this->type)); try{ $result = $wanlpay->verify(); // 查询订单是否存在 $order = model('app\api\model\wanlshop\RechargeOrder') ->where(['orderid' => $result['out_trade_no']]) ->find(); if (!$order) { return ['code' => 10001 ,'msg' => '支付订单不存在']; }else{ if($order['status'] == 'paid'){ return ['code' => 10007 ,'msg' => '订单已经支付过']; } } $memo = ''; $trade_no = ''; // -----------------------------判断订单是否合法----------------------------- $config = get_addon_config('wanlshop'); // 支付宝 if ($this->type == 'alipay') { // 判断状态 if (in_array($result['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) { // 判断金额 if($order['amount'] != $result['total_amount']){ return ['code' => 10002 ,'msg' => '支付金额不合法']; } // 判断appid if($config['sdk_alipay']['app_id'] != $result['app_id']){ return ['code' => 10003 ,'msg' => 'APPID不合法']; } }else{ return ['code' => 500 ,'msg' => '支付回调失败']; } $memo = '支付宝充值'; $trade_no = $result['trade_no']; } else if($this->type == 'wechat'){ // 判断状态 if ($result['result_code'] == 'SUCCESS') { // 判断金额 if($order['amount'] != ($result['total_fee'] / 100)){ return ['code' => 10002 ,'msg' => '支付金额不合法']; } // 判断商家ID if($config['sdk_qq']['mch_id'] != $result['mch_id']){ return ['code' => 10004 ,'msg' => '商户不合法']; } // H5微信支付 if($result['trade_type'] == 'MWEB'){ if($config['sdk_qq']['gz_appid'] != $result['appid']){ return ['code' => 10005 ,'msg' => '支付类型 '.$result['trade_type'] .' 不合法']; } } // 小程序支付 if($result['trade_type'] == 'JSAPI'){ if($config['mp_weixin']['appid'] != $result['appid']){ if($config['sdk_qq']['gz_appid'] != $result['appid']){ return ['code' => 10006 ,'msg' => '支付APPID不合法']; } } } // App支付 if($result['trade_type'] == 'APP'){ if($config['sdk_qq']['wx_appid'] != $result['appid']){ return ['code' => 10007 ,'msg' => '支付类型 '.$result['trade_type'] .' 不合法']; } } }else{ return ['code' => 500 ,'msg' => '支付回调失败']; } $memo = '微信充值'; $trade_no = $result['transaction_id']; } // -----------------------------支付成功,修改订单----------------------------- if ($order['status'] == 'created') { $order->memo = $trade_no; $order->payamount = $order['amount']; // 上面已经判断过金额,可以直接使用 $order->paytime = time(); $order->status = 'paid'; $order->save(); // 更新用户金额 self::money(+$order['amount'], $order['user_id'], $memo, 'recharge', $order['id']); } Log::debug('Wanlpay notify', $result->all()); // 1.1.5升级 捕获异常 } catch (\Exception $e) { return ['code' => 10008 ,'msg' => $e->getMessage()]; } // 返回给支付接口 return ['code' => 200 ,'msg' => $wanlpay->success()->send()]; } /** * 支付成功 */ public function return() { $wanlpay = Pay::{$this->type}(self::getConfig($this->type)); try{ return $wanlpay->verify(); // 1.1.5升级 捕获异常 } catch (\Exception $e) { return __($e->getMessage()); } } /** * 退款回调 */ public function notify_refund() { $wanlpay = Pay::{$this->type}(self::getConfig($this->type)); try{ $result = $wanlpay->verify(null, true); // 支付宝 if ($this->type == 'wechat') { $type_text = '微信支付'; $money = bcdiv($result['refund_fee'], 100, 2); $pay_no = $result['out_refund_no']; } else if($this->type == 'alipay'){ $type_text = '支付宝支付'; $money = $result['refund_amount']; $pay_no = $result['body']; } // 查询支付集 // $payOutTrade = model('app\api\model\wanlshop\PayOutTrade') // ->where('out_trade_no', 'eq', $result['out_trade_no']) // ->find(); // 查询订单是否存在 $pay = model('app\api\model\wanlshop\Pay') ->where('pay_no', 'eq', $pay_no) ->where('pay_state', 'eq', '1') ->find(); if($pay){ $refund = model('app\api\model\wanlshop\Refund') ->where([ 'user_id' => $pay['user_id'], 'order_pay_id' => $pay['id'], 'order_id' => $pay['order_id'], 'order_type' => $pay['type'] ]) ->find(); // 判断退款是否存在,退款状态是否第三方退款中状态 if($refund){ if($refund['state'] == '3' || $refund['state'] == '7'){ // 判断退款金额 if(bccomp($money, $refund['price'], 4) == 0){ // 更新商品状态 $this->setOrderGoodsState(3, $refund['goods_ids'], $refund['order_type']); // 更新订单状态 $this->setRefundState($refund['order_id'], $refund['order_type']); // 写入日志 $this->refundLog($pay['user_id'], $refund['id'], $type_text.'退款处理成功,订单金额¥'.$refund['price'].'元已到账'); // 推送开始 $this->pushRefund($refund['id'], $refund['order_id'], $refund['goods_ids'], '退款成功', $refund['order_type']); // 更新退款 $refund->allowField(true)->save(['state' => 4,'notice' => json_encode($result),'completetime' => time()]); }else{ // 写入日志 $this->refundLog($pay['user_id'], $refund['id'], $type_text.'退款订单异常,订单金额¥'.$refund['price'].'元,实际退款金额¥'.$money.'元'); // 推送开始 $this->pushRefund($refund['id'], $refund['order_id'], $refund['goods_ids'], $type_text.'退款金额不匹配,退款失败', $refund['order_type']); // 更新退款 $refund->allowField(true)->save(['state' => 8,'notice' => json_encode($result),'refuse_content' => $type_text.'退款失败,金额不匹配,请联系管理员']); } }else{ // 写入日志 $this->refundLog($pay['user_id'], $refund['id'], $type_text.'退款订单异常'); // 推送开始 $this->pushRefund($refund['id'], $refund['order_id'], $refund['goods_ids'], $type_text.'退款订单异常,退款失败', $refund['order_type']); // 更新退款 $refund->allowField(true)->save(['state' => 8,'notice' => json_encode($result),'refuse_content' => $type_text.'退款订单不存在,请联系管理员']); } } }else{ return ['code' => 10001 ,'msg' => '支付订单不存在']; } } catch (\Exception $e) { // 错误逻辑 return ['code' => 10008 ,'msg' => $e->getMessage()]; } // 返回给支付接口 return ['code' => 200 ,'msg' => $wanlpay->success()->send()]; } /** * 第三方退款 * @param int $money 金额 * @param int $pay_id 会员ID * @1.1.2升级 ~ 1.1.5升级 */ public function refund($refund_id, $money, $pay_id) { $type = ''; $pay = model('app\api\model\wanlshop\Pay')->get($pay_id); if($pay && $pay['pay_state'] == 1 && $pay['pay_type'] != 0){ // 1.1.3升级 $trade = model('app\api\model\wanlshop\PayOutTrade') ->where([['EXP', Db::raw('FIND_IN_SET('.$pay['id'].', pay_id)')]]) ->find(); if(!$trade){ return ['code' => 10009 ,'msg' => '没有找到商户订单号']; } if($pay['pay_type'] == 1){ $type = 'wechat'; $type_text = '微信'; // 退款订单 $order['out_trade_no'] = $trade['out_trade_no']; $order['out_refund_no'] = $pay['pay_no']; //商家订单号 // 分批退款 if(count(explode(',', $trade['pay_id'])) > 1){ // 总金额 $order['total_fee'] = bcmul(model('app\api\model\wanlshop\Pay') ->where('id', 'in', $trade['pay_id']) ->sum('price'), 100, 0); }else{ $order['total_fee'] = bcmul($pay['price'], 100, 0); // 订单金额 } $order['refund_fee'] = bcmul($money, 100, 0); $order['refund_desc'] = '商城订单(订单号:'.$pay['order_no'].')退款'.$type_text.'账户¥'.$money.'元。'; $notice = json_decode($pay['notice'], true); switch ($notice['trade_type']){ case 'JSAPI': $order['type'] = 'miniapp'; break; case 'APP': $order['type'] = 'app'; break; } }else if($pay['pay_type'] == 2){ $type = 'alipay'; $type_text = '支付宝'; // 1.1.3升级 $order['out_trade_no'] = $trade['out_trade_no']; $order['refund_amount'] = $money; //退款金额 $order['refund_reason'] = '商城订单(订单号:'.$pay['order_no'].')退款买家'.$type_text.'账户¥'.$money.'元。'; //退款原因说明 // 分批退款 if(count(explode(',', $trade['pay_id'])) > 1){ $order['out_request_no'] = $refund_id; //退款请求号 } } // 退款 1.1.5升级 try{ $payConfig = self::getConfig($type); $payConfig['notify_url'] = str_replace("notify", "notify_refund", $payConfig['notify_url']); $wanlpay = Pay::{$type}($payConfig)->refund($order); $result = false; // 注意,接口中result_code=SUCCESS,仅代表本次退款请求成功,不代表退款成功 if($type == 'wechat' && $wanlpay->result_code == 'SUCCESS'){ // 关于微信退款: // 微信退款不提供退款结果,仅返回退款请求成功,所以必须$result=true // 通过回调来获取结果 $result = true; } // 注意,接口中code=10000,仅代表本次退款请求成功,不代表退款成功 if($type == 'alipay' && $wanlpay->code == '10000'){ // fund_change = Y,代表退款成功,支付宝文档《如何判断退款是否成功》,https://opendocs.alipay.com/support/01rawa if($wanlpay->fund_change == 'Y'){ return ['code' => 0,'msg' => 'OK','data' => [ 'money' => $money, 'user_id' => $pay['user_id'], 'type_text' => $type_text, 'refund_id' => $refund_id ]]; }else{ // 通过回调来获取结果 // $result = true; // 关于支付宝退款: // 因测试订单时请求的均为 fund_change=Y 状态,即代表退款成功,可同步退款成功,以上注释支付宝文档《如何判断退款是否成功》 // 返回结果中的notify_url为配置的参数 也为notify_refund()方法, // 但,实际服务器接收到异步回调为 notify()方法 且notify()中接受到的 TRADE_CLOSED交易状态为 "交易关闭" // 目前等待支付宝进一步反馈,我们判断因为退款已成功fund_change=Y,所以返回notify() 为交易关闭 // 即使可以通过回调,可没有 同步fund_change=Y 状态安全可靠,已下 // 当前最佳最合理安全方案: // 1.当 提交退款请求,且fund_change=Y同步完成退款(否则会出现已经给用户支付宝退款,但没有扣用户本地余额,会存在一定风险) // 2.如果 极少数订单出现fund_change=N 时 code=10010中断第三方支付,使金额转到商城余额在手动提现,可避免任何风险产生 return ['code' => 10010 ,'msg' => '提交退款未同步退款成功']; } } if($result){ return ['code' => 200,'msg' => 'OK','data' => [ 'money' => $money, 'user_id' => $pay['user_id'], 'type_text' => $type_text, 'refund_id' => $refund_id ]]; }else{ return ['code' => 10009 ,'msg' => '退款失败']; } } catch (\Exception $e) { return ['code' => 10008 ,'msg' => $e->getMessage()]; } }else{ return ['code' => 1 ,'msg' => '余额实时退款']; } } /** * 变更会员余额 * @param int $money 余额 * @param int $user_id 会员ID * @param string $memo 备注 * @param string $type 类型 * @param string $ids 业务ID */ public static function money($money, $user_id, $memo, $type = '', $ids = '') { $user = model('app\common\model\User')->get($user_id); if ($user && $money != 0) { $before = $user->money; $after = function_exists('bcadd') ? bcadd($user->money, $money, 2) : $user->money + $money; //更新会员信息 $user->save(['money' => $after]); //写入日志 $row = model('app\common\model\MoneyLog')->create([ 'user_id' => $user_id, 'money' => $money, // 操作金额 'before' => $before, // 原金额 'after' => $after, // 增加后金额 'memo' => $memo, // 备注 'type' => $type, // 类型 'service_ids' => $ids // 业务ID ]); return $row; }else{ return ['code' => 500 ,'msg' => '变更金额失败']; } } /** * 推送退款消息(方法内使用) * * @param string refund_id 订单ID * @param string order_id 订单ID * @param string goods_id 订单ID * @param string title 标题 */ private function pushRefund($refund_id = 0, $order_id = 0, $goods_id = 0, $title = '', $order_type = 'goods') { if($order_type === 'groups'){ $orderModel = model('app\index\model\wanlshop\groups\Order'); $orderGoodsModel = model('app\index\model\wanlshop\groups\OrderGoods'); }else{ $orderModel = model('app\index\model\wanlshop\Order'); $orderGoodsModel = model('app\index\model\wanlshop\OrderGoods'); } $order = $orderModel->get($order_id); $goods = $orderGoodsModel->get($goods_id); $msg = [ 'user_id' => $order['user_id'], // 推送目标用户 'shop_id' => $order['shop_id'], 'title' => $title, // 推送标题 'image' => $goods['image'], // 推送图片 'content' => '您申请退款的商品 '.(mb_strlen($goods['title'],'utf8') >= 25 ? mb_substr($goods['title'],0,25,'utf-8').'...' : $goods['title']).' '.$title, 'type' => 'order', // 推送类型 'modules' => $order_type === 'groups' ? 'groupsrefund' : 'refund', // 模块类型 'modules_id' => $refund_id, // 模块ID 'come' => '订单'.$order['order_no'] // 来自 ]; $this->wanlchat->send($order['user_id'], $msg); $notice = model('app\index\model\wanlshop\Notice'); $notice->data($msg); $notice->allowField(true)->save(); } /** * 更新订单商品状态(方法内使用) * * @ApiSummary (WanlShop 更新订单商品状态) * @ApiMethod (POST) * * @param string $status 状态 * @param string $goods_id 商品ID */ private function setOrderGoodsState($status = 0, $goods_id = 0, $order_type = 'goods') { if($order_type === 'groups'){ $orderGoodsModel = model('app\index\model\wanlshop\groups\OrderGoods'); }else{ $orderGoodsModel = model('app\index\model\wanlshop\OrderGoods'); } return $orderGoodsModel->save(['refund_status' => $status],['id' => $goods_id]); } /** * 修改订单状态(方法内使用) 1.0.5升级 * * @ApiSummary (WanlShop 修改订单状态) * @ApiMethod (POST) * * @param string $id 订单ID */ private function setRefundState($order_id = 0, $order_type = 'goods') { if($order_type === 'groups'){ $orderModel = model('app\index\model\wanlshop\groups\Order'); $orderGoodsModel = model('app\index\model\wanlshop\groups\OrderGoods'); }else{ $orderModel = model('app\index\model\wanlshop\Order'); $orderGoodsModel = model('app\index\model\wanlshop\OrderGoods'); } $list = $orderGoodsModel ->where(['order_id' => $order_id]) ->select(); $refundStatusCount = 0; foreach($list as $row){ if($row['refund_status'] == 3) $refundStatusCount += 1; } // 如果订单下所有商品全部退款完毕则关闭订单 if(count($list) == $refundStatusCount){ $orderModel->save(['state' => 7],['id' => $order_id]); return true; } return false; } /** * 退款日志(方法内使用) * * @ApiSummary (WanlShop 退款日志) * @ApiMethod (POST) * * @param string $user_id 用户ID * @param string $refund_id 退款ID * @param string $content 日志内容 * @param string $type 退款状态:0=买家,1=卖家,2=官方,3=系统 */ private function refundLog($user_id = 0, $refund_id = 0, $content = '', $type = 3) { return model('app\index\model\wanlshop\RefundLog')->allowField(true)->save([ 'user_id' => $user_id, 'refund_id' => $refund_id, 'type' => $type, 'content' => $content ]); } /** * 获取配置 1.1.2升级 * @param string $type 支付类型 * @return array|mixed */ private function getConfig($type) { $config = get_addon_config('wanlshop'); $pay_config = []; if($type == 'alipay'){ $pay_config = [ 'app_id' => $config['sdk_alipay']['app_id'], 'notify_url' => $config['ini']['appurl'].$config['sdk_alipay']['notify_url'], 'return_url' => $config['ini']['appurl'].$config['sdk_alipay']['return_url'], 'private_key' => $config['sdk_alipay']['private_key'], 'log' => [ 'file' => LOG_PATH.'wanlpay'.DS.$type.'-'.date("Y-m-d").'.log', 'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug 'type' => 'single', // optional, 可选 daily. 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 ], 'http' => [ 'timeout' => 5.0, 'connect_timeout' => 5.0 ], ]; if (isset($config['sdk_alipay']['app_cert_public_key']) && substr($config['sdk_alipay']['app_cert_public_key'], 0, 8) == '/addons/') { $pay_config['app_cert_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['sdk_alipay']['app_cert_public_key'], 1)); } if (isset($config['sdk_alipay']['alipay_root_cert']) && substr($config['sdk_alipay']['alipay_root_cert'], 0, 8) == '/addons/') { $pay_config['alipay_root_cert'] = ROOT_PATH . str_replace('/', DS, substr($config['sdk_alipay']['alipay_root_cert'], 1)); } if (isset($config['sdk_alipay']['ali_public_key']) && (Str::endsWith($config['sdk_alipay']['ali_public_key'], '.crt') || Str::endsWith($config['sdk_alipay']['ali_public_key'], '.pem'))) { $pay_config['ali_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['sdk_alipay']['ali_public_key'], 1)); } }else if($type == 'wechat'){ $pay_config = [ 'appid' => $config['sdk_qq']['wx_appid'], // APP APPID 'app_id' => $config['sdk_qq']['gz_appid'], // 公众号 APPID 'miniapp_id' => $config['mp_weixin']['appid'], // 小程序 APPID 'mch_id' => $config['sdk_qq']['mch_id'], 'key' => $config['sdk_qq']['key'], 'notify_url' => $config['ini']['appurl']. $config['sdk_qq']['notify_url'], // 1.0.8升级 回调 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4 'return_url' => $config['h5']['domain'].($config['h5']['router_mode'] == 'hash' ? '/#':'') . '/pages/page/success?type=pay', 'log' => [ 'file' => LOG_PATH.'wanlpay'.DS.$type.'-'.date("Y-m-d").'.log', 'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug 'type' => 'single', // optional, 可选 daily. 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 ], 'http' => [ 'timeout' => 5.0, 'connect_timeout' => 5.0, // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html) ], ]; if (isset($config['sdk_qq']['cert_client']) && substr($config['sdk_qq']['cert_client'], 0, 8) == '/addons/') { $pay_config['cert_client'] = ROOT_PATH . str_replace('/', DS, substr($config['sdk_qq']['cert_client'], 1)); } if (isset($config['sdk_qq']['cert_key']) && substr($config['sdk_qq']['cert_key'], 0, 8) == '/addons/') { $pay_config['cert_key'] = ROOT_PATH . str_replace('/', DS, substr($config['sdk_qq']['cert_key'], 1)); } } return $pay_config; } }