Withdraw.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2017~2021 https://www.yiovo.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
  8. // +----------------------------------------------------------------------
  9. // | Author: 萤火科技 <admin@yiovo.com>
  10. // +----------------------------------------------------------------------
  11. declare (strict_types=1);
  12. namespace app\store\controller\user;
  13. use app\common\library\helper;
  14. use app\common\library\wechat\WxPay;
  15. use app\common\model\user\UserIdcards;
  16. use app\store\controller\Controller;
  17. use app\store\model\User as UserModel;
  18. use app\store\model\user\Withdraw as WithdrawModel;
  19. use think\cache\driver\Redis;
  20. use app\store\model\Wxapp as WxappModel;
  21. use app\api\model\User as UserApiModel;
  22. use app\common\service\Export as ExportService;
  23. use app\common\enum\order\orderGoods\ExportStatus as ExportStatusEnum;
  24. /**
  25. * 用户提现
  26. * Class Withdraw
  27. * @package app\store\controller\user
  28. */
  29. class Withdraw extends Controller
  30. {
  31. public function list()
  32. {
  33. $params = $this->request->param();
  34. $model = new WithdrawModel;
  35. $list = $model->getList($params);
  36. return $this->renderSuccess(compact('list'));
  37. }
  38. /**
  39. * 提现列表导出功能
  40. * @return array
  41. * @author: zq
  42. * @Time: 2022/2/22 10:43
  43. */
  44. public function orderExport(){
  45. $param = $this->request->param();
  46. /* if(isset($param['id']) && empty($param['id'])){
  47. return $this->renderError('请勾选订单商品后再导出');
  48. }*/
  49. $model = new WithdrawModel();
  50. $data = $model->listExport($param);
  51. $res = ExportService::export($data['data'],$data['header'],$data['filename'],'列表','Xls');
  52. return $this->renderSuccess($res,'导出成功');
  53. }
  54. public function dataCount()
  55. {
  56. $arr = [];
  57. $arr['all_num'] = WithdrawModel::count();
  58. $arr['audit_0'] = WithdrawModel::where("audit", 0)->count();
  59. $arr['audit_1'] = WithdrawModel::where("audit", 1)->count();
  60. $arr['audit_2'] = $arr['all_num'] - $arr['audit_0'] - $arr['audit_1'];
  61. $arr['pay_state_0'] = WithdrawModel::where("pay_state", 0)->count();
  62. $arr['pay_state_1'] = WithdrawModel::where("pay_state", 1)->count();
  63. $arr['pay_state_2'] = WithdrawModel::where("pay_state", 2)->count();
  64. $arr['pay_state_3'] = WithdrawModel::where("pay_state", 3)->count();
  65. return $this->renderSuccess(compact('arr'));
  66. }
  67. public function detail($id = 0)
  68. {
  69. $detail = WithdrawModel::with(['user'])->where('id', $id)->find();
  70. if (empty($detail)) {
  71. return $this->renderError('参数无效');
  72. }
  73. $detail->user->promote_user_count = UserApiModel::countSubordinates($detail->user_id, $detail->shop_id, $detail->role); // 直推人数
  74. $detail->user->promote_user_name = UserModel::where('user_id',$detail->user->upper_user_id)->value('nick_name') ?? '';
  75. $detail->user->idcard = UserIdcards::where('user_id',$detail->user_id)->field('real_name')->find() ?? '';
  76. return $this->renderSuccess(compact('detail'));
  77. }
  78. //审核
  79. public function audit(int $id)
  80. {
  81. $params = $this->postForm();
  82. $detail = WithdrawModel::with(['user'])->where('id', $id)->find();
  83. if (empty($detail)) {
  84. return $this->renderError('参数无效');
  85. }
  86. $user = $detail->user;
  87. if ($detail->audit == 0 && $params['audit'] != $detail->audit) {
  88. $detail->audit = $params['audit'];
  89. $detail->audit_remark = $params['audit_remark'] ?? '';
  90. $detail->audit_time = date("Y-m-d H:i:s");
  91. $detail->save();
  92. if ($detail->audit == 2) {
  93. $user->ktxyj_amount += $detail->amount;//转账失败返回提现金额给用户
  94. $user->frozen_yj_amount -= $detail->amount;//转账失败从冻结金额中扣除单笔提现金额
  95. $user->save();
  96. }
  97. // if ($audit == 2) {
  98. //// $user->wjsyj_amount = $user->wjsyj_amount - $detail->amount;
  99. //// $user->save();
  100. // $title = '审核不通过';
  101. // $content = '非常抱歉,您的提现申请审核不通过.原因:' . $audit_remark;
  102. // } else {
  103. // $title = '审核通过';
  104. // $content = '恭喜您,您的提现申请已通过,工作人员将尽快为您打款,请留意支付宝信息。';
  105. // }
  106. // $ids = $detail->user_id;
  107. // $url = config('chef.serv_cookhome') . '/api/shop_pushes?title=' . $title . '&content=' . $content . '&ids=' . $ids . '&url=' . urlencode('zd://chefPush?target=shop_push_my_withdrawal&id=' . $id);
  108. // helper::curlRequest($url);
  109. return $this->renderSuccess('审核成功');
  110. } else {
  111. return $this->renderError('已经审核过了');
  112. }
  113. }
  114. //去支付
  115. public function topay($id)
  116. {
  117. $params = $this->postForm();
  118. $detail = WithdrawModel::with(['user'])->where('id', $id)->find();
  119. $user = $detail->user;
  120. if ($detail->pay_state == 1) {
  121. return $this->renderError("已支付,请勿重复付款");
  122. }
  123. if ($detail->pay_state == 3) {
  124. return $this->renderError("打款中,请勿重复付款");
  125. }
  126. if ($detail->audit != 1) {
  127. return $this->renderError("未审核通过,不能付款");
  128. }
  129. $pay_amount = helper::bcsub($detail->amount, $detail->tax, 2);
  130. if ($detail['transaction_type'] == 1) {
  131. // 支付宝提现
  132. $ret = $this->aliWithdrawal($detail, $pay_amount);
  133. $pay_type = 1;
  134. } elseif ($detail['transaction_type'] == 2) {
  135. // 微信提现
  136. $params['remark'] = $params['remark'] ?: '佣金提现'; // 默认打款备注信息
  137. $ret = $this->wxWithdrawal($detail, $pay_amount, $params['remark']);
  138. $pay_type = 2;
  139. } else {
  140. return $this->renderError('提现方式不正确');
  141. }
  142. $storeInfo = $this->store;
  143. if ($ret['code'] == 1) {
  144. $detail->remark = $params['remark'];
  145. $detail->pay_state = 1;
  146. $detail->pay_type = $pay_type;
  147. $detail->pay_time = date("Y-m-d H:i:s");
  148. $detail->pay_transaction_no = $ret['pay_transaction_no'];
  149. //微信或支付宝支付流水号,暂时只记录了微信的流水号,支付宝流水号没保存20220413
  150. $detail->third_trans_no = $ret['payment_no']??'';
  151. $detail->admin_id = $storeInfo['user']['store_user_id'];
  152. $detail->pay_amount = $pay_amount;
  153. $user->frozen_yj_amount -= $detail->amount;//提现成功从冻结金额中扣除单笔提现金额
  154. $user->have_withdrew_money += $detail->amount;
  155. $user->save();
  156. $detail->save();
  157. return $this->renderSuccess('付款成功');
  158. } else {
  159. $reason = $ret['reason'];
  160. $detail->not_pass_resaon = $reason;
  161. $detail->pay_state = $pay_type;
  162. $detail->admin_id = $storeInfo['user']['store_user_id'];
  163. $user->ktxyj_amount += $detail->amount;//转账失败返回提现金额给用户
  164. $user->frozen_yj_amount -= $detail->amount;//转账失败从冻结金额中扣除单笔提现金额
  165. $user->save();
  166. $detail->save();
  167. return $this->renderError($reason ?? '付款失败');
  168. }
  169. }
  170. /**
  171. * 支付宝提现
  172. * @param object $detail 提现申请信息
  173. * @param string $pay_amount 付款金额
  174. * @return array
  175. */
  176. private function aliWithdrawal($detail, $pay_amount) {
  177. $redis = new Redis(config('cache.stores.redis'));
  178. $redis->set('fx_pay_' . $detail->user_id, 1, 600);
  179. $url = config('chef.serv_cookhome') . '/api/shop/pay_ali_account?account=' . $detail->tx_account . '&amount=' . $pay_amount . '&real_name=' . $detail->real_name . "&user_id=" . $detail->user_id;
  180. $res = helper::curlRequest($url);
  181. $arr = json_decode($res, true);
  182. if ($arr['ret'] == 1) {
  183. return ['code' => 1, 'msg' => '付款成功', 'pay_transaction_no' => $arr['order_no']];
  184. } else {
  185. return ['code' => 0, 'msg' => '付款失败', 'reason' => $arr['reason']];
  186. }
  187. }
  188. /**
  189. * 小程序提现
  190. * todo 定时任务轮询打款中状态的提现订单,
  191. * 调用微信查询订单接口,直到返回打款成功,
  192. * 才将状态改为已打款,并更新用户账号资金。
  193. *
  194. * @param object $detail 提现申请信息
  195. * @param string $pay_amount 付款金额
  196. * @return array
  197. */
  198. private function wxWithdrawal($detail, $pay_amount, $remark = '') {
  199. // 验证限制
  200. $this->checkPayLimit($detail->user_id, $pay_amount);
  201. $orderNo = WithdrawModel::makePayTransactionNo();
  202. $user = $detail->user;
  203. $wxPay = new WxPay(static::getWxConfig());
  204. $res = $wxPay->transfers($orderNo, $user->open_id, $pay_amount, $remark);
  205. // if ($res === true) {
  206. if (isset($res['status']) && $res['status'] === true) {
  207. //todo 先注释掉测试一下提现20220412,感觉像之前遗留的多余的一行代码
  208. //$wxPay->transfers($orderNo, $user->open_id, $pay_amount, $remark);
  209. return ['code' => 1, 'msg' => '付款成功', 'pay_transaction_no' => $orderNo,'payment_no'=>$res['payment_no']];
  210. } else {
  211. // 判断各种失败状态
  212. // 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
  213. if ($res['data']['err_code'] == 'SYSTEMERROR') { // 将当前提现单状态改为打款中,然后后台操作人可以重复进行打款操作,直到成功为止。
  214. $detail->pay_state = 3; // 3-打款中
  215. $detail->admin_id = $this->store['user']['store_user_id'];
  216. $detail->pay_transaction_no = $orderNo;
  217. $detail->save();
  218. return $this->renderError('付款失败,请稍后重试');
  219. }
  220. return ['code' => 0, 'msg' => $res['msg'] ?? '付款失败', 'reason' => $res['msg']];
  221. }
  222. }
  223. /**
  224. * 尝试重试打款
  225. * 用同一个商户订单号向微信发起打款请求
  226. * 当打款成功时
  227. */
  228. public function repay($id) {
  229. $params = $this->postForm();
  230. $detail = WithdrawModel::with(['user'])->where('id', $id)->find();
  231. $user = $detail->user;
  232. if ($detail->pay_state != 3) {
  233. return $this->renderError("非打款中状态,请问重复付款");
  234. }
  235. if ($detail->audit != 1) {
  236. return $this->renderError("未审核通过,不能付款");
  237. }
  238. $pay_amount = helper::bcsub($detail->amount, $detail->tax, 2);
  239. if ($detail['transaction_type'] == 1) {
  240. // 支付宝提现
  241. $pay_type = 1;
  242. } elseif ($detail['transaction_type'] == 2) {
  243. // 微信提现
  244. $ret = $this->wxRepay($detail, $pay_amount, $params['remark']);
  245. $pay_type = 2;
  246. } else {
  247. return $this->renderError('提现方式不正确');
  248. }
  249. $storeInfo = $this->store;
  250. if ($ret['code'] == 1) {
  251. $detail->remark = $params['remark'];
  252. $detail->pay_state = 1;
  253. $detail->pay_type = $pay_type;
  254. $detail->pay_time = date("Y-m-d H:i:s");
  255. $detail->pay_transaction_no = $ret['pay_transaction_no'];
  256. $detail->admin_id = $storeInfo['user']['store_user_id'];
  257. $detail->pay_amount = $pay_amount;
  258. $user->frozen_yj_amount -= $detail->amount;//提现成功从冻结金额中扣除单笔提现金额
  259. $user->have_withdrew_money += $detail->amount;
  260. $user->save();
  261. $detail->save();
  262. return $this->renderSuccess('付款成功');
  263. } else {
  264. return $this->renderError($reason ?? '付款失败');
  265. }
  266. }
  267. /**
  268. * 微信重试打款
  269. * todo 请勿付款和查询并发处理
  270. */
  271. private function wxRepay($detail, $pay_amount, $remark = '') {
  272. $orderNo = $detail->pay_transaction_no;
  273. if (empty($orderNo)) {
  274. return $this->renderError('提现单异常');
  275. }
  276. // 验证限制
  277. $this->checkPayLimit($detail->user_id, $pay_amount);
  278. // 查询付款状态
  279. $wxPay = new WxPay(static::getWxConfig());
  280. $queryRes = $wxPay->getTransferInfo($orderNo);
  281. if ($queryRes === true) {
  282. // 查询结果为付款成功 则更新状态
  283. return ['code' => 1, 'msg' => '付款成功', 'pay_transaction_no' => $orderNo];
  284. }
  285. $user = $detail->user;
  286. $res = $wxPay->transfers($orderNo, $user->open_id, $pay_amount, $remark);
  287. //if ($res === true) {
  288. if (isset($res['status']) && $res['status'] === true) {
  289. //todo 先注释掉测试一下提现20220412,感觉像之前遗留的多余的一行代码
  290. //$wxPay->transfers($orderNo, $user->open_id, $pay_amount, $remark);
  291. return ['code' => 1, 'msg' => '付款成功', 'pay_transaction_no' => $orderNo,'payment_no'=>$res['payment_no']];
  292. } else {
  293. // 判断各种失败状态
  294. // 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
  295. if ($res['data']['err_code'] == 'SYSTEMERROR') {
  296. return $this->renderError('付款失败,请稍后重试');
  297. }
  298. return ['code' => 0, 'msg' => $res['msg'] ?? '付款失败', 'reason' => $res['msg']];
  299. }
  300. }
  301. /**
  302. * 检查用户提现限制
  303. */
  304. private function checkPayLimit($userId, $pay_amount) {
  305. // 判断用户是否当日提现次数超过10次,大于10次提示“您今日提现过于频繁,请次日再来~”
  306. $dayCount = WithdrawModel::where('user_id', $userId)
  307. ->whereTime('pay_time', 'today')
  308. ->count();
  309. if ($dayCount > 10) {
  310. return $this->renderError('您今日提现过于频繁,请次日再来~');
  311. }
  312. // 判断用户是否当日提现金额超过5000元,当日累计申请提现(含本次)大于5000元时提示“您今日累计提现数值较大,请次日再来~”
  313. $dayAmount = WithdrawModel::where('user_id', $userId)
  314. ->whereTime('pay_time', 'today')
  315. ->sum('pay_amount');
  316. if ($dayAmount + $pay_amount > 5000) {
  317. return $this->renderError('您今日累计提现数值较大,请次日再来');
  318. }
  319. }
  320. /**
  321. * 获取微信支付配置
  322. * @return array
  323. * @throws BaseException
  324. * @throws \think\db\exception\DataNotFoundException
  325. * @throws \think\db\exception\DbException
  326. * @throws \think\db\exception\ModelNotFoundException
  327. */
  328. private static function getWxConfig()
  329. {
  330. return WxappModel::getWxappCache(getStoreId());
  331. }
  332. }