WithdrawLogic.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | likeshop100%开源免费商用商城系统
  4. // +----------------------------------------------------------------------
  5. // | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
  6. // | 开源版本可自由商用,可去除界面版权logo
  7. // | 商业版本务必购买商业授权,以免引起法律纠纷
  8. // | 禁止对系统程序代码以任何目的,任何形式的再发布
  9. // | gitee下载:https://gitee.com/likeshop_gitee
  10. // | github下载:https://github.com/likeshop-github
  11. // | 访问官网:https://www.likeshop.cn
  12. // | 访问社区:https://home.likeshop.cn
  13. // | 访问手册:http://doc.likeshop.cn
  14. // | 微信公众号:likeshop技术社区
  15. // | likeshop团队 版权所有 拥有最终解释权
  16. // +----------------------------------------------------------------------
  17. // | author: likeshopTeam
  18. // +----------------------------------------------------------------------
  19. namespace app\adminapi\logic\withdraw;
  20. use app\common\enum\AccountLogEnum;
  21. use app\common\enum\WithdrawEnum;
  22. use app\common\logic\AccountLogLogic;
  23. use app\common\logic\BaseLogic;
  24. use app\common\model\User;
  25. use app\common\model\UserAuth;
  26. use app\common\model\WithdrawApply;
  27. use app\common\service\ConfigService;
  28. use app\common\service\FileService;
  29. use app\common\service\WeChatConfigService;
  30. use EasyWeChat\Factory;
  31. use think\facade\Db;
  32. use think\facade\Log;
  33. /**
  34. * 提现逻辑层
  35. * Class WithdrawLogic
  36. * @package app\adminapi\logic\withdraw
  37. */
  38. class WithdrawLogic extends BaseLogic
  39. {
  40. /**
  41. * @notes 查看提现详情
  42. * @param $params
  43. * @return mixed
  44. * @author Tab
  45. * @date 2021/8/6 20:48
  46. */
  47. public static function detail($params)
  48. {
  49. $field = 'wa.id,wa.money,wa.handling_fee,wa.left_money,wa.type,wa.type as type_desc,wa.create_time,wa.status,wa.status as status_desc,wa.transfer_voucher,wa.transfer_time,wa.transfer_remark,wa.account,wa.real_name,wa.bank,wa.subbank,wa.money_qr_code';
  50. $field .= ',u.sn,u.nickname,u.mobile';
  51. $withdrawApply = WithdrawApply::field($field)
  52. ->alias('wa')
  53. ->leftJoin('user u', 'u.id = wa.user_id')
  54. ->findOrEmpty($params['id'])
  55. ->toArray();
  56. return $withdrawApply;
  57. }
  58. /**
  59. * @notes 审核拒绝
  60. * @param $params
  61. * @return bool
  62. * @author Tab
  63. * @date 2021/8/9 9:58
  64. */
  65. public static function refuse($params)
  66. {
  67. Db::startTrans();
  68. try {
  69. // 修改提现申请单状态
  70. $withdrawApply = WithdrawApply::findOrEmpty($params['id']);
  71. if($withdrawApply->status != WithdrawEnum::STATUS_WAIT) {
  72. throw new \think\Exception('不是待提现状态,不允许审核');
  73. }
  74. $withdrawApply->status = WithdrawEnum::STATUS_FAIL;
  75. $withdrawApply->audit_remark = $params['audit_remark'] ?? '';
  76. $withdrawApply->save();
  77. // 回退提现金额
  78. self::fallbackMoney($withdrawApply);
  79. // 增加账户流水变动记录
  80. AccountLogLogic::add($withdrawApply['user_id'], AccountLogEnum::BW_INC_REFUSE_WITHDRAWAL, AccountLogEnum::INC, $withdrawApply['money'], $withdrawApply['sn'], '拒绝提现回退金额');
  81. Db::commit();
  82. return true;
  83. } catch(\Exception $e) {
  84. Db::rollback();
  85. self::setError($e->getMessage());
  86. return false;
  87. }
  88. }
  89. /**
  90. * @notes 回退提现金额
  91. * @param $withdrawApply
  92. * @author Tab
  93. * @date 2021/8/9 9:50
  94. */
  95. public static function fallbackMoney($withdrawApply)
  96. {
  97. $user = User::findOrEmpty($withdrawApply->user_id);
  98. $user->user_earnings = $user->user_earnings + $withdrawApply->money;
  99. $user->save();
  100. }
  101. /**
  102. * @notes 审核通过
  103. * @param $params
  104. * @return bool
  105. * @author Tab
  106. * @date 2021/8/9 10:49
  107. */
  108. public static function pass($params)
  109. {
  110. Db::startTrans();
  111. try {
  112. $withdrawApply = WithdrawApply::findOrEmpty($params['id']);
  113. if($withdrawApply->status != WithdrawEnum::STATUS_WAIT) {
  114. throw new \think\Exception('不是待提现状态,不允许审核');
  115. }
  116. switch($withdrawApply->type) {
  117. // 提现至余额
  118. case WithdrawEnum::TYPE_BALANCE:
  119. self::balance($withdrawApply, $params);
  120. break;
  121. // 提现至微信零钱
  122. case WithdrawEnum::TYPE_WECHAT_CHANGE:
  123. self::wechatChange($withdrawApply, $params);
  124. break;
  125. // 提现至银行卡
  126. case WithdrawEnum::TYPE_BANK:
  127. // 提现至微信收款码
  128. case WithdrawEnum::TYPE_WECHAT_CODE:
  129. // 提现至支付宝收款码
  130. case WithdrawEnum::TYPE_ALI_CODE:
  131. self::common($withdrawApply, $params);
  132. break;
  133. }
  134. Db::commit();
  135. return true;
  136. } catch(\Exception $e) {
  137. Db::rollback();
  138. Log::write($e->__toString(), 'withdraw_pass_error');
  139. self::setError($e->getMessage());
  140. return false;
  141. }
  142. }
  143. /**
  144. * @notes 提现至余额
  145. * @param $withdrawApply
  146. * @author Tab
  147. * @date 2021/8/9 10:48
  148. */
  149. public static function balance($withdrawApply, $params)
  150. {
  151. // 增加用户余额
  152. $user = User::findOrEmpty($withdrawApply->user_id);
  153. $user->user_money = $user->user_money + $withdrawApply->left_money;
  154. $user->save();
  155. // 记录账户流水
  156. AccountLogLogic::add($withdrawApply->user_id, AccountLogEnum::BNW_INC_WITHDRAW, AccountLogEnum::INC, $withdrawApply->left_money, $withdrawApply->sn, '提现至余额');
  157. // 更新提现状态
  158. $withdrawApply->status = WithdrawEnum::STATUS_SUCCESS;
  159. $withdrawApply->audit_remark = $params['audit_remark'] ?? '';
  160. $withdrawApply->save();
  161. }
  162. /**
  163. * @notes 提现至银行卡/微信收款码/支付宝收款
  164. * @param $withdrawApply
  165. * @param $params
  166. * @author Tab
  167. * @date 2021/8/9 11:01
  168. */
  169. public static function common($withdrawApply, $params)
  170. {
  171. $withdrawApply->status = WithdrawEnum::STATUS_ING;
  172. $withdrawApply->audit_remark = $params['audit_remark'] ?? '';
  173. $withdrawApply->save();
  174. }
  175. /**
  176. * @notes 提现至微信零钱
  177. * @param $withdrawApply
  178. * @param $params
  179. * @return bool
  180. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  181. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  182. * @throws \GuzzleHttp\Exception\GuzzleException
  183. * @throws \think\Exception
  184. * @author Tab
  185. * @date 2021/8/9 12:03
  186. */
  187. public static function wechatChange($withdrawApply, $params)
  188. {
  189. // 校验条件
  190. self::checkCondition($withdrawApply);
  191. // 用户授权信息
  192. $userAuth = UserAuth::where('user_id', $withdrawApply->user_id)->order('terminal', 'asc')->findOrEmpty();
  193. if($userAuth->isEmpty()) {
  194. throw new \think\Exception('获取不到用户的openid');
  195. }
  196. // 获取app
  197. $config = WeChatConfigService::getWechatConfigByTerminal($userAuth->terminal);
  198. //微信零钱接口:1-企业付款到零钱;2-商家转账到零钱;
  199. $transfer_way = ConfigService::get('config', 'transfer_way',1);
  200. //企业付款到零钱
  201. if ($transfer_way == WithdrawEnum::ENTERPRISE) {
  202. if ($config['interface_version'] == 'v3') {
  203. throw new \Exception('微信支付v3不支持企业付款到零钱');
  204. }
  205. $app = Factory::payment($config);
  206. // 发起企业付款
  207. $result = $app->transfer->toBalance([
  208. // 商户订单号,需保持唯一性(只能是字母或者数字,不能包含有符号)
  209. 'partner_trade_no' => $withdrawApply->sn,
  210. 'openid' => $userAuth->openid,
  211. // NO_CHECK:不校验真实姓名, FORCE_CHECK:强校验真实姓名
  212. 'check_name' => 'NO_CHECK',
  213. // 如果 check_name 设置为FORCE_CHECK,则必填用户真实姓名
  214. 're_user_name' => '',
  215. // 企业付款金额,单位为分 100分=1元
  216. 'amount' => intval(bcmul(100, $withdrawApply->left_money)),
  217. // 企业付款操作说明信息。必填
  218. 'desc' => '提现至微信零钱'
  219. ]);
  220. // 过滤敏感字段
  221. $fiterField = ['appid','mch_appid', 'mchid', 'mch_id', 'openid'];
  222. $filterResult = array_filter($result, function($key) use ($fiterField) {
  223. return !in_array($key, $fiterField);
  224. }, ARRAY_FILTER_USE_KEY);
  225. // 更新提现申请单为提现中状态
  226. $withdrawApply->status = WithdrawEnum::STATUS_ING;
  227. $withdrawApply->audit_remark = $params['audit_remark'] ?? '';
  228. $withdrawApply->pay_desc = json_encode($filterResult);
  229. $withdrawApply->save();
  230. // 通信标识 return_code && 业务结果 result_code
  231. if($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
  232. // 企业付款成功, 更新提现申请单状态为提现成功并记录支付单号及支付时间
  233. $withdrawApply->status = WithdrawEnum::STATUS_SUCCESS;
  234. $withdrawApply->payment_no = $result['payment_no'];
  235. $withdrawApply->payment_time = strtotime($result['payment_time']);
  236. $withdrawApply->save();
  237. return true;
  238. }
  239. if($result['return_code'] == 'FAIL') {
  240. // 企业付款失败,更新提现申请单为提现失败
  241. $withdrawApply->status = WithdrawEnum::STATUS_FAIL;
  242. $withdrawApply->save();
  243. // 回退提现金额
  244. self::fallbackMoney($withdrawApply);
  245. // 记录账户流水
  246. AccountLogLogic::add($withdrawApply->user_id, AccountLogEnum::BW_INC_PAYMENT_FAIL, AccountLogEnum::INC, $withdrawApply->money, $withdrawApply->sn, '付款失败回退金额');
  247. return false;
  248. }
  249. }
  250. //商家转账到零钱
  251. if ($transfer_way == WithdrawEnum::MERCHANT) {
  252. WechatMerchantTransferLogic::transfer($withdrawApply,$userAuth,$config);
  253. }
  254. }
  255. /**
  256. * @notes 校验条件(提现至微信零钱)
  257. * @param $withdrawApply
  258. * @throws \think\Exception
  259. * @author Tab
  260. * @date 2021/8/9 11:30
  261. */
  262. public static function checkCondition($withdrawApply)
  263. {
  264. if($withdrawApply->left_money < 1) {
  265. throw new \think\Exception('扣除手续费后提现金额不能小于1元');
  266. }
  267. $count = WithdrawApply::whereDay('update_time')->where([
  268. ['user_id', '=', $withdrawApply->user_id],
  269. ['type', '=', WithdrawEnum::TYPE_WECHAT_CHANGE],
  270. ['status', 'in', [WithdrawEnum::STATUS_ING,WithdrawEnum::STATUS_SUCCESS,WithdrawEnum::STATUS_FAIL]],
  271. ])->count();
  272. if($count >= 10) {
  273. throw new \think\Exception('同一天向同一个用户最多付款10次');
  274. }
  275. }
  276. /**
  277. * @notes 查询结果(提现至微信零钱)
  278. * @param $params
  279. * @return bool
  280. * @throws \GuzzleHttp\Exception\GuzzleException
  281. * @author Tab
  282. * @date 2021/8/9 15:06
  283. */
  284. public static function search($params)
  285. {
  286. Db::startTrans();
  287. try {
  288. $withdrawApply = WithdrawApply::findOrEmpty($params['id']);
  289. if($withdrawApply->status != WithdrawEnum::STATUS_ING) {
  290. throw new \think\Exception('非提现中状态无法查询结果');
  291. }
  292. if($withdrawApply->type != WithdrawEnum::TYPE_WECHAT_CHANGE) {
  293. throw new \think\Exception('非微信零钱提现方式无法查询结果');
  294. }
  295. // 用户授权信息
  296. $userAuth = UserAuth::where('user_id', $withdrawApply->user_id)->order('terminal', 'asc')->findOrEmpty();
  297. if($userAuth->isEmpty()) {
  298. throw new \think\Exception('获取不到用户的openid');
  299. }
  300. // 获取app
  301. $config = WeChatConfigService::getWechatConfigByTerminal($userAuth->terminal);
  302. //微信零钱接口:1-企业付款到零钱;2-商家转账到零钱;
  303. $transfer_way = ConfigService::get('config', 'transfer_way',1);
  304. //企业付款到零钱
  305. if ($transfer_way == WithdrawEnum::ENTERPRISE) {
  306. $app = Factory::payment($config);
  307. $result = $app->transfer->queryBalanceOrder($withdrawApply->sn);
  308. // 过滤敏感字段
  309. $fiterField = ['appid','mch_appid', 'mchid', 'mch_id', 'openid'];
  310. $filterResult = array_filter($result, function($key) use ($fiterField) {
  311. return !in_array($key, $fiterField);
  312. }, ARRAY_FILTER_USE_KEY );
  313. // 记录查询结果
  314. $withdrawApply->pay_search_result = json_encode($filterResult);
  315. $withdrawApply->save();
  316. // 查询失败
  317. if($result['return_code'] == 'FAIL') {
  318. throw new \think\Exception($result['return_msg']);
  319. }
  320. // 提示信息
  321. $tips = $result['err_code_des'] ?? '';
  322. // 查询结果:转账成功
  323. if($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS' && $result['status'] == 'SUCCESS') {
  324. // 更新提现申请单状态
  325. $withdrawApply->status = WithdrawEnum::STATUS_SUCCESS;
  326. $withdrawApply->payment_no = $result['detail_id'];
  327. $withdrawApply->payment_time = strtotime($result['payment_time']);
  328. $withdrawApply->save();
  329. $tips = '提现成功';
  330. }
  331. // 查询结果:转账失败
  332. if($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS' && $result['status'] == 'FAILED') {
  333. // 更新提现申请单状态
  334. $withdrawApply->status = WithdrawEnum::STATUS_FAIL;
  335. $withdrawApply->save();
  336. // 回退提现金额
  337. self::fallbackMoney($withdrawApply);
  338. // 记录账户流水变动
  339. AccountLogLogic::add($withdrawApply->user_id, AccountLogEnum::BW_INC_PAYMENT_FAIL, AccountLogEnum::INC, $withdrawApply->money, $withdrawApply->sn, '付款失败回退金额');
  340. $tips = '提现失败';
  341. }
  342. // 查询结果:处理中
  343. if($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS' && $result['status'] == 'PROCESSING') {
  344. $tips = '提现处理中';
  345. }
  346. }
  347. //商家转账到零钱
  348. if ($transfer_way == WithdrawEnum::MERCHANT) {
  349. $result = WechatMerchantTransferLogic::details($withdrawApply,$config);
  350. $tips = '未知';
  351. if(isset($result['detail_status'])) {
  352. if ($result['detail_status'] == 'SUCCESS') {
  353. //提现成功,更新提现申请单
  354. WithdrawApply::update([
  355. 'status' => WithdrawEnum::STATUS_SUCCESS,
  356. 'pay_search_result' => json_encode($result, JSON_UNESCAPED_UNICODE),
  357. 'payment_no' => $result['detail_id'],
  358. 'payment_time' => strtotime($result['update_time']),
  359. ],['id'=>$withdrawApply['id']]);
  360. $tips = '提现成功';
  361. }
  362. if ($result['detail_status'] == 'FAIL') {
  363. //提现失败,更新提现申请单
  364. WithdrawApply::update([
  365. 'status' => WithdrawEnum::STATUS_FAIL,
  366. 'pay_search_result' => json_encode($result, JSON_UNESCAPED_UNICODE),
  367. ],['id'=>$withdrawApply['id']]);
  368. // 回退提现金额
  369. WithdrawLogic::fallbackMoney($withdrawApply);
  370. // 记录账户流水
  371. AccountLogLogic::add($withdrawApply->user_id, AccountLogEnum::BW_INC_PAYMENT_FAIL, AccountLogEnum::INC, $withdrawApply->money, $withdrawApply->sn, '付款失败回退金额');
  372. $tips = '提现失败';
  373. }
  374. if ($result['detail_status'] == 'PROCESSING') {
  375. $tips = '正在处理中';
  376. }
  377. }else {
  378. // 查询失败
  379. throw new \think\Exception($result['message'] ?? '商家转账到零钱查询失败');
  380. }
  381. }
  382. // 提交事务
  383. Db::commit();
  384. // 返回提示消息
  385. return $tips;
  386. } catch(\Exception $e) {
  387. Db::rollback();
  388. self::setError($e->getMessage());
  389. return false;
  390. }
  391. }
  392. /**
  393. * @notes 转账成功
  394. * @param $params
  395. * @return bool
  396. * @author Tab
  397. * @date 2021/8/9 15:56
  398. */
  399. public static function transferSuccess($params)
  400. {
  401. try {
  402. if(!isset($params['transfer_voucher']) || empty($params['transfer_voucher'])) {
  403. throw new \think\Exception('请上传转账凭证');
  404. }
  405. if(!isset($params['transfer_remark']) || empty($params['transfer_remark'])) {
  406. throw new \think\Exception('请填写转账说明');
  407. }
  408. $params['transfer_voucher'] = FileService::setFileUrl($params['transfer_voucher']);
  409. $withdrawApply = WithdrawApply::findOrEmpty($params['id']);
  410. $withdrawApply->status = WithdrawEnum::STATUS_SUCCESS;
  411. $withdrawApply->transfer_voucher = $params['transfer_voucher'];
  412. $withdrawApply->transfer_remark = $params['transfer_remark'];
  413. $withdrawApply->transfer_time = time();
  414. $withdrawApply->save();
  415. return true;
  416. } catch(\Exception $e) {
  417. self::setError($e->getMessage());
  418. return false;
  419. }
  420. }
  421. /**
  422. * @notes 转账失败
  423. * @param $params
  424. * @return bool
  425. * @author Tab
  426. * @date 2021/8/9 16:13
  427. */
  428. public static function transferFail($params)
  429. {
  430. Db::startTrans();
  431. try {
  432. // if(!isset($params['transfer_remark']) || empty($params['transfer_remark'])) {
  433. // throw new \think\Exception('请填写转账说明');
  434. // }
  435. // 更新状态
  436. $withdrawApply = WithdrawApply::findOrEmpty($params['id']);
  437. $withdrawApply->status = WithdrawEnum::STATUS_FAIL;
  438. $withdrawApply->transfer_remark = $params['transfer_remark'];
  439. $withdrawApply->transfer_voucher = $params['transfer_voucher'];
  440. $withdrawApply->save();
  441. // 回退提现金额
  442. self::fallbackMoney($withdrawApply);
  443. // 记录账户流水
  444. AccountLogLogic::add($withdrawApply->user_id, AccountLogEnum::BW_INC_TRANSFER_FAIL, AccountLogEnum::INC, $withdrawApply->money, $withdrawApply->sn, '转账失败回退金额');
  445. Db::commit();
  446. return true;
  447. } catch(\Exception $e) {
  448. Db::rollback();
  449. self::setError($e->getMessage());
  450. return false;
  451. }
  452. }
  453. }