UserRechargeServices.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2023 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. declare (strict_types=1);
  12. namespace app\services\user;
  13. use app\dao\system\config\SystemConfigDao;
  14. use app\dao\user\UserRechargeDao;
  15. use app\services\BaseServices;
  16. use app\services\order\StoreOrderCreateServices;
  17. use app\services\pay\PayServices;
  18. use app\services\pay\RechargeServices;
  19. use app\services\statistic\CapitalFlowServices;
  20. use app\services\system\config\SystemGroupDataServices;
  21. use app\services\wechat\WechatUserServices;
  22. use crmeb\exceptions\AdminException;
  23. use crmeb\exceptions\ApiException;
  24. use crmeb\services\FormBuilder as Form;
  25. use crmeb\services\pay\Pay;
  26. use think\facade\Route as Url;
  27. /**
  28. *
  29. * Class UserRechargeServices
  30. * @package app\services\user
  31. * @method be($map, string $field = '') 查询一条数据是否存在
  32. * @method getDistinctCount(array $where, $field, ?bool $search = true)
  33. * @method getTrendData($time, $type, $timeType)
  34. */
  35. class UserRechargeServices extends BaseServices
  36. {
  37. /**
  38. * UserRechargeServices constructor.
  39. * @param UserRechargeDao $dao
  40. */
  41. public function __construct(UserRechargeDao $dao)
  42. {
  43. $this->dao = $dao;
  44. }
  45. /**
  46. * 获取单条数据
  47. * @param int $id
  48. * @param array $field
  49. */
  50. public function getRecharge(int $id, array $field = [])
  51. {
  52. return $this->dao->get($id, $field);
  53. }
  54. /**
  55. * 获取统计数据
  56. * @param array $where
  57. * @param string $field
  58. * @return float
  59. */
  60. public function getRechargeSum(array $where, string $field = '')
  61. {
  62. $whereData = [];
  63. if (isset($where['data'])) {
  64. $whereData['time'] = $where['data'];
  65. }
  66. if (isset($where['paid']) && $where['paid'] != '') {
  67. $whereData['paid'] = $where['paid'];
  68. }
  69. if (isset($where['nickname']) && $where['nickname']) {
  70. $whereData['like'] = $where['nickname'];
  71. }
  72. if (isset($where['recharge_type']) && $where['recharge_type']) {
  73. $whereData['recharge_type'] = $where['recharge_type'];
  74. }
  75. return $this->dao->getWhereSumField($whereData, $field);
  76. }
  77. /**
  78. * 获取充值列表
  79. * @param array $where
  80. * @param string $field
  81. * @return array
  82. */
  83. public function getRechargeList(array $where, string $field = '*', $is_page = true)
  84. {
  85. $whereData = [];
  86. if (isset($where['data'])) {
  87. $whereData['time'] = $where['data'];
  88. }
  89. if (isset($where['paid']) && $where['paid'] != '') {
  90. $whereData['paid'] = $where['paid'];
  91. }
  92. if (isset($where['nickname']) && $where['nickname']) {
  93. $whereData['like'] = $where['nickname'];
  94. }
  95. [$page, $limit] = $this->getPageValue($is_page);
  96. $list = $this->dao->getList($whereData, $field, $page, $limit);
  97. $count = $this->dao->count($whereData);
  98. foreach ($list as &$item) {
  99. switch ($item['recharge_type']) {
  100. case PayServices::WEIXIN_PAY:
  101. $item['_recharge_type'] = '微信充值';
  102. break;
  103. case 'system':
  104. $item['_recharge_type'] = '系统充值';
  105. break;
  106. case PayServices::ALIAPY_PAY:
  107. $item['_recharge_type'] = '支付宝充值';
  108. break;
  109. default:
  110. $item['_recharge_type'] = '其他充值';
  111. break;
  112. }
  113. $item['_pay_time'] = $item['pay_time'] ? date('Y-m-d H:i:s', $item['pay_time']) : '暂无';
  114. $item['_add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', $item['add_time']) : '暂无';
  115. $item['paid_type'] = $item['paid'] ? '已支付' : '未支付';
  116. $item['avatar'] = strpos($item['avatar'] ?? '', 'http') === false ? (sys_config('site_url') . $item['avatar']) : $item['avatar'];
  117. unset($item['user']);
  118. }
  119. return compact('list', 'count');
  120. }
  121. /**
  122. * 获取用户充值数据
  123. * @return array
  124. */
  125. public function user_recharge(array $where)
  126. {
  127. $data = [];
  128. $data['sumPrice'] = $this->getRechargeSum($where, 'price');
  129. $data['sumRefundPrice'] = $this->getRechargeSum($where, 'refund_price');
  130. $where['recharge_type'] = 'alipay';
  131. $data['sumAlipayPrice'] = $this->getRechargeSum($where, 'price');
  132. $where['recharge_type'] = 'weixin';
  133. $data['sumWeixinPrice'] = $this->getRechargeSum($where, 'price');
  134. return [
  135. [
  136. 'name' => '充值总金额',
  137. 'field' => '元',
  138. 'count' => $data['sumPrice'],
  139. 'className' => 'iconjiaoyijine',
  140. 'col' => 6,
  141. ],
  142. [
  143. 'name' => '充值退款金额',
  144. 'field' => '元',
  145. 'count' => $data['sumRefundPrice'],
  146. 'className' => 'iconshangpintuikuanjine',
  147. 'col' => 6,
  148. ],
  149. [
  150. 'name' => '支付宝充值金额',
  151. 'field' => '元',
  152. 'count' => $data['sumAlipayPrice'],
  153. 'className' => 'iconzhifubao',
  154. 'col' => 6,
  155. ],
  156. [
  157. 'name' => '微信充值金额',
  158. 'field' => '元',
  159. 'count' => $data['sumWeixinPrice'],
  160. 'className' => 'iconweixinzhifu',
  161. 'col' => 6,
  162. ],
  163. ];
  164. }
  165. /**
  166. * 退款表单
  167. * @param int $id
  168. * @return array
  169. * @throws \FormBuilder\Exception\FormBuilderException
  170. * @author 吴汐
  171. * @email 442384644@qq.com
  172. * @date 2023/03/24
  173. */
  174. public function refund_edit(int $id)
  175. {
  176. $UserRecharge = $this->getRecharge($id);
  177. if (!$UserRecharge) {
  178. throw new AdminException(100026);
  179. }
  180. if ($UserRecharge['paid'] != 1) {
  181. throw new AdminException(400677);
  182. }
  183. if ($UserRecharge['price'] == $UserRecharge['refund_price']) {
  184. throw new AdminException(400147);
  185. }
  186. if ($UserRecharge['recharge_type'] == 'balance') {
  187. throw new AdminException(400678);
  188. }
  189. $f = array();
  190. $f[] = Form::input('order_id', '退款单号', $UserRecharge->getData('order_id'))->disabled(true);
  191. $f[] = Form::radio('refund_price', '状态', 1)->options([['label' => '本金(扣赠送余额)', 'value' => 1], ['label' => '仅本金', 'value' => 0]]);
  192. return create_form('编辑', $f, Url::buildUrl('/finance/recharge/' . $id), 'PUT');
  193. }
  194. /**
  195. * 退款操作
  196. * @param int $id
  197. * @param string $refund_price
  198. * @return mixed
  199. * @throws \think\db\exception\DataNotFoundException
  200. * @throws \think\db\exception\DbException
  201. * @throws \think\db\exception\ModelNotFoundException
  202. */
  203. public function refund_update(int $id, string $refund_price)
  204. {
  205. $UserRecharge = $this->getRecharge($id);
  206. if (!$UserRecharge) {
  207. throw new AdminException(100026);
  208. }
  209. if ($UserRecharge['price'] == $UserRecharge['refund_price']) {
  210. throw new AdminException(400147);
  211. }
  212. if ($UserRecharge['recharge_type'] == 'balance') {
  213. throw new AdminException(400678);
  214. }
  215. $data['refund_price'] = $UserRecharge['price'];
  216. $refund_data['pay_price'] = $UserRecharge['price'];
  217. $refund_data['refund_price'] = $UserRecharge['price'];
  218. if ($refund_price == 1) {
  219. $number = bcadd($UserRecharge['price'], $UserRecharge['give_price'], 2);
  220. } else {
  221. $number = $UserRecharge['price'];
  222. }
  223. try {
  224. $recharge_type = $UserRecharge['recharge_type'];
  225. if ($recharge_type == 'weixin') {
  226. $refund_data['wechat'] = true;
  227. } else {
  228. $refund_data['trade_no'] = $UserRecharge['trade_no'];
  229. $refund_data['order_id'] = $UserRecharge['order_id'];
  230. /** @var WechatUserServices $wechatUserServices */
  231. $wechatUserServices = app()->make(WechatUserServices::class);
  232. $refund_data['open_id'] = $wechatUserServices->uidToOpenid((int)$UserRecharge['uid'], 'routine') ?? '';
  233. $refund_data['pay_new_weixin_open'] = sys_config('pay_new_weixin_open');
  234. /** @var StoreOrderCreateServices $storeOrderCreateServices */
  235. $storeOrderCreateServices = app()->make(StoreOrderCreateServices::class);
  236. $refund_data['refund_no'] = $storeOrderCreateServices->getNewOrderId('tk');
  237. }
  238. if ($recharge_type == 'allinpay') {
  239. $drivers = 'allin_pay';
  240. $trade_no = $UserRecharge['trade_no'];
  241. } elseif (sys_config('pay_wechat_type')) {
  242. $drivers = 'v3_wechat_pay';
  243. $trade_no = $UserRecharge['trade_no'];
  244. } else {
  245. $drivers = 'wechat_pay';
  246. $trade_no = $UserRecharge['order_id'];
  247. }
  248. /** @var Pay $pay */
  249. $pay = app()->make(Pay::class, [$drivers]);
  250. $pay->refund($trade_no, $refund_data);
  251. } catch (\Exception $e) {
  252. throw new AdminException($e->getMessage());
  253. }
  254. if (!$this->dao->update($id, $data)) {
  255. throw new AdminException(100007);
  256. }
  257. //修改用户余额
  258. /** @var UserServices $userServices */
  259. $userServices = app()->make(UserServices::class);
  260. $userInfo = $userServices->getUserInfo($UserRecharge['uid']);
  261. if ($userInfo['now_money'] > $number) {
  262. $now_money = bcsub((string)$userInfo['now_money'], $number, 2);
  263. } else {
  264. $number = $userInfo['now_money'];
  265. $now_money = 0;
  266. }
  267. $userServices->update((int)$UserRecharge['uid'], ['now_money' => $now_money], 'uid');
  268. //写入资金流水
  269. /** @var CapitalFlowServices $capitalFlowServices */
  270. $capitalFlowServices = app()->make(CapitalFlowServices::class);
  271. $UserRecharge['nickname'] = $userInfo['nickname'];
  272. $UserRecharge['phone'] = $userInfo['phone'];
  273. $capitalFlowServices->setFlow($UserRecharge, 'refund_recharge');
  274. //保存余额记录
  275. /** @var UserMoneyServices $userMoneyServices */
  276. $userMoneyServices = app()->make(UserMoneyServices::class);
  277. $userMoneyServices->income('user_recharge_refund', $UserRecharge['uid'], $number, $now_money, $id);
  278. //提醒推送
  279. event('NoticeListener', [['user_type' => strtolower($userInfo['user_type']), 'data' => $data, 'UserRecharge' => $UserRecharge, 'now_money' => $refund_price], 'recharge_order_refund_status']);
  280. //自定义通知-充值退款
  281. $UserRecharge['now_money'] = $now_money;
  282. $UserRecharge['time'] = date('Y-m-d H:i:s');
  283. event('NoticeListener', [$UserRecharge['uid'], $UserRecharge, 'recharge_refund']);
  284. //自定义事件-后台充值退款
  285. event('CustomEventListener', ['admin_recharge_refund', [
  286. 'uid' => $UserRecharge['uid'],
  287. 'refund_price' => $UserRecharge['price'],
  288. 'now_money' => $now_money,
  289. 'nickname' => $UserRecharge['price'],
  290. 'phone' => $UserRecharge['phone'],
  291. 'refund_time' => date('Y-m-d H:i:s')
  292. ]]);
  293. return true;
  294. }
  295. /**
  296. * 删除
  297. * @param int $id
  298. * @return bool
  299. */
  300. public function delRecharge(int $id)
  301. {
  302. $rechargInfo = $this->getRecharge($id);
  303. if (!$rechargInfo) throw new AdminException(100026);
  304. if ($rechargInfo->paid) {
  305. throw new AdminException(400679);
  306. }
  307. if ($this->dao->delete($id))
  308. return true;
  309. else
  310. throw new AdminException(100008);
  311. }
  312. /**
  313. * 生成充值订单号
  314. * @return bool|string
  315. */
  316. public function getOrderId()
  317. {
  318. return 'wx' . date('YmdHis', time()) . substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
  319. }
  320. /**
  321. * 导入佣金到余额
  322. * @param int $uid
  323. * @param $price
  324. * @return bool
  325. * @throws \think\db\exception\DataNotFoundException
  326. * @throws \think\db\exception\DbException
  327. * @throws \think\db\exception\ModelNotFoundException
  328. */
  329. public function importNowMoney(int $uid, $price)
  330. {
  331. /** @var UserServices $userServices */
  332. $userServices = app()->make(UserServices::class);
  333. $user = $userServices->getUserInfo($uid);
  334. if (!$user) {
  335. throw new ApiException(100100);
  336. }
  337. /** @var UserBrokerageServices $frozenPrices */
  338. $frozenPrices = app()->make(UserBrokerageServices::class);
  339. $broken_commission = $frozenPrices->getUserFrozenPrice($uid);
  340. $commissionCount = bcsub((string)$user['brokerage_price'], (string)$broken_commission, 2);
  341. if ($price > $commissionCount) {
  342. throw new ApiException(400680);
  343. }
  344. // 计算手续费 - 直接从数据库查询,不使用缓存
  345. /** @var SystemConfigDao $configDao */
  346. $configDao = app()->make(SystemConfigDao::class);
  347. $transferFeeRate = (string)($configDao->getConfigValue('yue_transfer_fee') ?: 0);
  348. $transferFee = bcmul((string)$price, bcdiv((string)$transferFeeRate, '100', 4), 2);
  349. $actualAmount = bcsub((string)$price, $transferFee, 2);
  350. $edit_data = [];
  351. $edit_data['now_money'] = bcadd((string)$user['now_money'], $actualAmount, 2);
  352. $edit_data['brokerage_price'] = $user['brokerage_price'] > $price ? bcsub((string)$user['brokerage_price'], (string)$price, 2) : 0;
  353. if (!$userServices->update($uid, $edit_data, 'uid')) {
  354. throw new ApiException(100007);
  355. }
  356. //写入充值记录
  357. $rechargeInfo = [
  358. 'uid' => $uid,
  359. 'order_id' => app()->make(StoreOrderCreateServices::class)->getNewOrderId('cz'),
  360. 'recharge_type' => 'balance',
  361. 'price' => $actualAmount,
  362. 'give_price' => 0,
  363. 'paid' => 1,
  364. 'pay_time' => time(),
  365. 'add_time' => time()
  366. ];
  367. if (!$re = $this->dao->save($rechargeInfo)) {
  368. throw new ApiException(400681);
  369. }
  370. //余额记录
  371. /** @var UserMoneyServices $userMoneyServices */
  372. $userMoneyServices = app()->make(UserMoneyServices::class);
  373. $userMoneyServices->income('brokerage_to_nowMoney', $uid, $actualAmount, $edit_data['now_money'], $re['id']);
  374. //写入提现记录
  375. $extractInfo = [
  376. 'uid' => $uid,
  377. 'real_name' => $user['nickname'],
  378. 'extract_type' => 'balance',
  379. 'extract_price' => $price,
  380. 'balance' => $user['brokerage_price'],
  381. 'add_time' => time(),
  382. 'status' => 1
  383. ];
  384. /** @var UserExtractServices $userExtract */
  385. $userExtract = app()->make(UserExtractServices::class);
  386. $userExtract->save($extractInfo);
  387. //佣金提现记录
  388. /** @var UserBrokerageServices $userBrokerageServices */
  389. $userBrokerageServices = app()->make(UserBrokerageServices::class);
  390. $userBrokerageServices->income('brokerage_to_nowMoney', $uid, ['number' => $price, 'fee' => $transferFee], $edit_data['brokerage_price'], $re['id']);
  391. return true;
  392. }
  393. /**
  394. * 申请充值
  395. * @param int $uid
  396. * @param $price
  397. * @param $recharId
  398. * @param $type
  399. * @param $from
  400. * @param bool $renten
  401. * @return mixed
  402. * @throws \think\db\exception\DataNotFoundException
  403. * @throws \think\db\exception\DbException
  404. * @throws \think\db\exception\ModelNotFoundException
  405. */
  406. public function recharge(int $uid, $price, $recharId, $type, $from, bool $renten = false)
  407. {
  408. /** @var UserServices $userServices */
  409. $userServices = app()->make(UserServices::class);
  410. $user = $userServices->getUserInfo($uid);
  411. if (!$user) {
  412. throw new ApiException(400214);
  413. }
  414. switch ((int)$type) {
  415. case 0: //支付充值余额
  416. $paid_price = 0;
  417. if ($recharId) {
  418. /** @var SystemGroupDataServices $systemGroupData */
  419. $systemGroupData = app()->make(SystemGroupDataServices::class);
  420. $data = $systemGroupData->getDateValue($recharId);
  421. if ($data === false) {
  422. throw new ApiException(400682);
  423. } else {
  424. $paid_price = $data['give_money'] ?? 0;
  425. $price = $data['price'] ?? 0;
  426. }
  427. }
  428. $recharge_data = [];
  429. $recharge_data['order_id'] = app()->make(StoreOrderCreateServices::class)->getNewOrderId('cz');
  430. $recharge_data['uid'] = $uid;
  431. $recharge_data['price'] = $price;
  432. $recharge_data['recharge_type'] = $from;
  433. $recharge_data['paid'] = 0;
  434. $recharge_data['add_time'] = time();
  435. $recharge_data['give_price'] = $paid_price;
  436. $recharge_data['channel_type'] = $user['user_type'];
  437. if (!$rechargeOrder = $this->dao->save($recharge_data)) {
  438. throw new ApiException(400683);
  439. }
  440. try {
  441. /** @var RechargeServices $recharge */
  442. $recharge = app()->make(RechargeServices::class);
  443. $order_info = $recharge->recharge($rechargeOrder);
  444. } catch (\Exception $e) {
  445. throw new ApiException($e->getMessage());
  446. }
  447. if ($renten) {
  448. return $order_info;
  449. }
  450. return ['msg' => '', 'type' => $from, 'data' => $order_info];
  451. case 1: //佣金转入余额
  452. $this->importNowMoney($uid, $price);
  453. return ['msg' => '转入余额成功', 'type' => $from, 'data' => []];
  454. default:
  455. throw new ApiException(100100);
  456. }
  457. }
  458. /**
  459. * 用户充值成功后
  460. * @param $orderId
  461. * @param array $other
  462. * @return bool
  463. * @throws \think\db\exception\DataNotFoundException
  464. * @throws \think\db\exception\DbException
  465. * @throws \think\db\exception\ModelNotFoundException
  466. */
  467. public function rechargeSuccess($orderId, array $other = [])
  468. {
  469. $order = $this->dao->getOne(['order_id' => $orderId, 'paid' => 0]);
  470. if (!$order) {
  471. throw new ApiException(410173);
  472. }
  473. /** @var UserServices $userServices */
  474. $userServices = app()->make(UserServices::class);
  475. $user = $userServices->getUserInfo((int)$order['uid']);
  476. if (!$user) {
  477. throw new ApiException(410032);
  478. }
  479. $price = bcadd((string)$order['price'], (string)$order['give_price'], 2);
  480. if (!$this->dao->update($order['id'], ['paid' => 1, 'recharge_type' => $other['pay_type'], 'pay_time' => time(), 'trade_no' => $other['trade_no'] ?? ''], 'id')) {
  481. throw new ApiException(410286);
  482. }
  483. $now_money = bcadd((string)$user['now_money'], (string)$price, 2);
  484. /** @var UserMoneyServices $userMoneyServices */
  485. $userMoneyServices = app()->make(UserMoneyServices::class);
  486. $userMoneyServices->income('user_recharge', $user['uid'], ['number' => $price, 'price' => $order['price'], 'give_price' => $order['give_price']], $now_money, $order['id']);
  487. if (!$userServices->update((int)$order['uid'], ['now_money' => $now_money], 'uid')) {
  488. throw new ApiException(410287);
  489. }
  490. /** @var CapitalFlowServices $capitalFlowServices */
  491. $capitalFlowServices = app()->make(CapitalFlowServices::class);
  492. $order['nickname'] = $user['nickname'];
  493. $order['phone'] = $user['phone'];
  494. $capitalFlowServices->setFlow($order, 'recharge');
  495. //提醒推送
  496. event('NoticeListener', [['order' => $order, 'now_money' => $now_money], 'recharge_success']);
  497. //自定义消息-订单拒绝退款
  498. $order['now_money'] = $now_money;
  499. $order['time'] = date('Y-m-d H:i:s');
  500. event('CustomNoticeListener', [$order['uid'], $order, 'recharge_success']);
  501. $order['pay_type'] = $other['pay_type'];
  502. // 小程序订单服务
  503. event('OrderShippingListener', ['recharge', $order, 3, '', '']);
  504. //自定义事件-用户充值
  505. event('CustomEventListener', ['user_recharge', [
  506. 'uid' => $order['uid'],
  507. 'id' => (int)$order['id'],
  508. 'order_id' => $orderId,
  509. 'nickname' => $order['nickname'],
  510. 'phone' => $order['phone'],
  511. 'price' => $order['price'],
  512. 'give_price' => $order['give_price'],
  513. 'now_money' => $order['now_money'],
  514. 'recharge_time' => date('Y-m-d H:i:s'),
  515. ]]);
  516. return true;
  517. }
  518. /**
  519. * 根据查询用户充值金额
  520. * @param array $where
  521. * @param string $rechargeSumField
  522. * @param string $selectType
  523. * @param string $group
  524. * @return float|int
  525. */
  526. public function getRechargeMoneyByWhere(array $where, string $rechargeSumField, string $selectType, string $group = "")
  527. {
  528. switch ($selectType) {
  529. case "sum" :
  530. return $this->dao->getWhereSumField($where, $rechargeSumField);
  531. case "group" :
  532. return $this->dao->getGroupField($where, $rechargeSumField, $group);
  533. }
  534. }
  535. }