FinancialRepository.php 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2024 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\common\repositories\system\financial;
  12. use app\common\dao\system\financial\FinancialDao;
  13. use app\common\repositories\BaseRepository;
  14. use app\common\repositories\system\merchant\MerchantRepository;
  15. use app\common\repositories\system\serve\ServeOrderRepository;
  16. use app\common\repositories\user\UserBillRepository;
  17. use crmeb\jobs\ChangeMerchantStatusJob;
  18. use crmeb\jobs\SendSmsJob;
  19. use crmeb\services\WechatService;
  20. use FormBuilder\Factory\Elm;
  21. use think\exception\ValidateException;
  22. use think\facade\Db;
  23. use think\facade\Queue;
  24. use think\facade\Route;
  25. /**
  26. * 商户财务申请提现
  27. */
  28. class FinancialRepository extends BaseRepository
  29. {
  30. public function __construct(FinancialDao $dao)
  31. {
  32. $this->dao = $dao;
  33. }
  34. /**
  35. * 商户财务账号表单生成方法
  36. *
  37. * 本方法用于根据给定的商户ID生成一个财务账号编辑表单。
  38. * 表单中包括银行卡、微信、支付宝三种转账方式的选项,根据商户当前设置的转账类型
  39. * 显示相应的编辑字段。商户可以填写或修改收款人的姓名、账号信息,并上传收款二维码。
  40. *
  41. * @param int $id 商户ID,用于查询商户当前的财务账号信息。
  42. * @return \FormBuilder\Form|\LaravelAdminPanel\Form
  43. */
  44. public function financialAccountForm($id)
  45. {
  46. // 根据商户ID查询商户及其当前财务账号信息
  47. $merchant = app()->make(MerchantRepository::class)->search(['mer_id' => $id])->find();
  48. // 创建表单对象,并设置表单提交的URL
  49. $form = Elm::createForm(Route::buildUrl('merchantFinancialAccountSave', ['id' => $id])->build());
  50. // 设置表单验证规则,根据转账类型动态显示相应的输入字段
  51. $form->setRule([
  52. // 创建选择器,用于选择转账类型:银行卡、微信、支付宝
  53. Elm::radio('financial_type', '转账类型:', $merchant->financial_type)
  54. ->setOptions([
  55. ['value' => 1, 'label' => '银行卡'],
  56. ['value' => 2, 'label' => '微信'],
  57. ['value' => 3, 'label' => '支付宝'],
  58. ])
  59. // 根据选择的转账类型,显示不同的输入字段
  60. ->control([
  61. [
  62. 'value' => 1,
  63. 'rule' => [
  64. // 银行卡收款人的姓名
  65. Elm::input('name', '姓名:')->value($merchant->financial_bank->name ?? '')->placeholder('请输入姓名')->required(),
  66. // 银行开户名称
  67. Elm::input('bank', '开户银行:')->value($merchant->financial_bank->bank ?? '')->placeholder('请输入开户银行')->required(),
  68. // 银行卡号
  69. Elm::input('bank_code', '银行卡号:')->value($merchant->financial_bank->bank_code ?? '')->placeholder('请输入银行卡号')->required(),
  70. ]
  71. ],
  72. [
  73. 'value' => 2,
  74. 'rule' => [
  75. // 微信收款人的姓名
  76. Elm::input('name', '姓名:')->value($merchant->financial_wechat->name ?? '')->placeholder('请输入姓名')->required(),
  77. // 微信号
  78. Elm::input('wechat', '微信号:')->value($merchant->financial_wechat->wechat ?? '')->placeholder('请输入微信号')->required(),
  79. // 微信收款二维码
  80. Elm::frameImage('wechat_code', '收款二维码:', '/' . config('admin.merchant_prefix') . '/setting/uploadPicture?field=wechat_code&type=1')->value($merchant->financial_wechat->wechat_code ?? '')->icon('el-icon-camera')->modal(['modal' => false])->width('1000px')->height('600px'),
  81. ]
  82. ],
  83. [
  84. 'value' => 3,
  85. 'rule' => [
  86. // 支付宝收款人的姓名
  87. Elm::input('name', '姓名:')->value($merchant->financial_alipay->name ?? '')->placeholder('请输入姓名')->required(),
  88. // 支付宝账号
  89. Elm::input('alipay', '支付宝账号:')->value($merchant->financial_alipay->alipay ?? '')->placeholder('请输入支付宝账号')->required(),
  90. // 支付宝收款二维码
  91. Elm::frameImage('alipay_code', '收款二维码:', '/' . config('admin.merchant_prefix') . '/setting/uploadPicture?field=alipay_code&type=1')->value($merchant->financial_alipay->alipay_code ?? '')->icon('el-icon-camera')->modal(['modal' => false])->width('1000px')->height('600px'),
  92. ]
  93. ],
  94. ]),
  95. ]);
  96. // 设置表单标题
  97. return $form->setTitle('转账信息');
  98. }
  99. /**
  100. * 保存转账信息
  101. * @param int $merId
  102. * @param array $data
  103. * @author Qinii
  104. * @day 3/18/21
  105. */
  106. public function saveAccount(int $merId, array $data)
  107. {
  108. switch ($data['financial_type']) {
  109. case 1:
  110. $key = 'financial_bank';
  111. $update = [
  112. 'name' => $data['name'],
  113. 'bank' => $data['bank'],
  114. 'bank_code' => $data['bank_code'],
  115. ];
  116. break;
  117. case 2:
  118. $key = 'financial_wechat';
  119. $update = [
  120. 'name' => $data['name'],
  121. //'idcard' => $data['idcard'],
  122. 'wechat' => $data['wechat'],
  123. 'wechat_code' => $data['wechat_code'],
  124. ];
  125. break;
  126. case 3:
  127. $key = 'financial_alipay';
  128. $update = [
  129. 'name' => $data['name'],
  130. //'idcard' => $data['idcard'],
  131. 'alipay' => $data['alipay'],
  132. 'alipay_code' => $data['alipay_code'],
  133. ];
  134. break;
  135. }
  136. return app()->make(MerchantRepository::class)->update($merId, [$key => json_encode($update), 'financial_type' => $data['financial_type']]);
  137. }
  138. /**
  139. * 商户申请提现表单的生成方法
  140. *
  141. * 本方法用于根据商户ID生成商户提现申请的表单。表单中包含了商户的基本信息、可提现金额、转账类型选择
  142. * 以及必要的转账信息展示(如银行卡、微信、支付宝信息)。商户在填写提现金额后,可提交表单进行提现申请。
  143. *
  144. * @param int $merId 商户ID,用于查询商户信息和提现配置。
  145. * @return \FormBuilder\Form|\think\response\View
  146. */
  147. public function applyForm(int $merId)
  148. {
  149. $merchant = app()->make(MerchantRepository::class)->search(['mer_id' => $merId])->field('mer_id,mer_name,mer_money,financial_bank,financial_wechat,financial_alipay,financial_type')->find();
  150. $extract_minimum_line = systemConfig('extract_minimum_line') ?: 0;
  151. $extract_minimum_num = systemConfig('extract_minimum_num');
  152. $_line = bcsub($merchant->mer_money, $extract_minimum_line, 2);
  153. $_extract = ($_line < 0) ? 0 : $_line;
  154. $form = Elm::createForm(Route::buildUrl('merchantFinancialCreateSave')->build());
  155. $form->setRule([
  156. [
  157. 'type' => 'span',
  158. 'title' => '商户名称:',
  159. 'native' => false,
  160. 'children' => ["$merchant->mer_name"]
  161. ],
  162. [
  163. 'type' => 'span',
  164. 'title' => '商户ID:',
  165. 'native' => false,
  166. 'children' => ["$merId"]
  167. ],
  168. // [
  169. // 'type' => 'span',
  170. // 'title' => '',
  171. // 'children' => []
  172. // ],
  173. [
  174. 'type' => 'span',
  175. 'title' => '提示:',
  176. 'native' => false,
  177. 'children' => ['最低可提现额度:' . $extract_minimum_line . '元;最低提现金额:' . $extract_minimum_num . '元']
  178. ],
  179. [
  180. 'type' => 'span',
  181. 'title' => '商户余额:',
  182. 'native' => false,
  183. 'children' => ["$merchant->mer_money"]
  184. ],
  185. [
  186. 'type' => 'span',
  187. 'native' => false,
  188. 'title' => '商户可提现金额:',
  189. 'children' => ["$_extract"]
  190. ],
  191. Elm::radio('financial_type', '转账类型:', $merchant->financial_type)
  192. ->setOptions([
  193. ['value' => 1, 'label' => '银行卡'],
  194. ['value' => 2, 'label' => '微信'],
  195. ['value' => 3, 'label' => '支付宝'],
  196. ])->control([
  197. [
  198. 'value' => 1,
  199. 'rule' => [
  200. [
  201. 'type' => 'span',
  202. 'title' => '姓名:',
  203. 'native' => false,
  204. 'children' => [$merchant->financial_bank->name ?? '未填写']
  205. ],
  206. [
  207. 'type' => 'span',
  208. 'title' => '开户银行:',
  209. 'native' => false,
  210. 'children' => [$merchant->financial_bank->bank ?? '未填写']
  211. ],
  212. [
  213. 'type' => 'span',
  214. 'title' => '银行卡号:',
  215. 'native' => false,
  216. 'children' => [$merchant->financial_bank->bank_code ?? '未填写']
  217. ],
  218. ]
  219. ],
  220. [
  221. 'value' => 2,
  222. 'rule' => [
  223. [
  224. 'type' => 'span',
  225. 'title' => '姓名:',
  226. 'native' => false,
  227. 'children' => [$merchant->financial_wechat->name ?? '未填写']
  228. ],
  229. [
  230. 'type' => 'span',
  231. 'title' => '微信号:',
  232. 'native' => false,
  233. 'children' => [$merchant->financial_wechat->wechat ?? '未填写']
  234. ],
  235. [
  236. 'type' => 'img',
  237. 'title' => '收款二维码:',
  238. 'native' => false,
  239. 'attrs' => ['src' => $merchant->financial_wechat->wechat_code ?? ''],
  240. 'style' => ['width' => '86px', 'height' => '48px']
  241. ],
  242. ]
  243. ],
  244. [
  245. 'value' => 3,
  246. 'rule' => [
  247. [
  248. 'type' => 'span',
  249. 'title' => '姓名:',
  250. 'native' => false,
  251. 'children' => [$merchant->financial_alipay->name ?? '未填写']
  252. ],
  253. [
  254. 'type' => 'span',
  255. 'title' => '支付宝账号:',
  256. 'native' => false,
  257. 'children' => [$merchant->financial_alipay->alipay ?? '未填写']
  258. ],
  259. [
  260. 'type' => 'img',
  261. 'title' => '收款二维码:',
  262. 'native' => false,
  263. 'attrs' => ['src' => $merchant->financial_alipay->alipay_code ?? ''],
  264. 'style' => ['width' => '86px', 'height' => '48px']
  265. ],
  266. ]
  267. ],
  268. ]),
  269. Elm::number('extract_money', '申请金额:')->value($extract_minimum_num)->required(),
  270. ]);
  271. return $form->setTitle('申请转账');
  272. }
  273. /**
  274. * 保存申请
  275. * @param int $merId
  276. * @param array $data
  277. * @author Qinii
  278. * @day 3/19/21
  279. */
  280. public function saveApply(int $merId, array $data)
  281. {
  282. $make = app()->make(MerchantRepository::class);
  283. $merchant = $make->search(['mer_id' => $merId])->field('mer_id,mer_name,mer_money,financial_bank,financial_wechat,financial_alipay')->find();
  284. if ($merchant['mer_money'] <= 0) throw new ValidateException('余额不足');
  285. if ($data['financial_type'] == 1) {
  286. $financial_account = $merchant->financial_bank;
  287. } elseif ($data['financial_type'] == 2) {
  288. $financial_account = $merchant->financial_wechat;
  289. } elseif ($data['financial_type'] == 3) {
  290. $financial_account = $merchant->financial_alipay;
  291. }
  292. if (empty($financial_account)) throw new ValidateException('未填写转账信息');
  293. $extract_maxmum_num = systemConfig('extract_maxmum_num');
  294. if ($extract_maxmum_num > 0 && $data['extract_money'] > $extract_maxmum_num) throw new ValidateException('单次申请金额不得大于' . $extract_maxmum_num . '元');
  295. //最低提现额度
  296. $extract_minimum_line = systemConfig('extract_minimum_line') ? systemConfig('extract_minimum_line') : 0;
  297. $_line = bcsub($merchant->mer_money, $extract_minimum_line, 2);
  298. if ($_line < $extract_minimum_line) throw new ValidateException('余额大于' . $extract_minimum_line . '才可提现');
  299. if ($data['extract_money'] > $_line) throw new ValidateException('提现金额大于可提现金额');
  300. //最低提现金额
  301. $extract_minimum_num = systemConfig('extract_minimum_num');
  302. if ($data['extract_money'] < $extract_minimum_num) throw new ValidateException('最低提现金额' . $extract_minimum_num);
  303. //可提现金额
  304. $_line = bcsub($merchant->mer_money, $extract_minimum_line, 2);
  305. if ($_line < 0) throw new ValidateException('余额大于' . $extract_minimum_line . '才可提现');
  306. //最低提现金额
  307. if ($data['extract_money'] < $extract_minimum_num) throw new ValidateException('最低提现金额' . $extract_minimum_num);
  308. //不足提现最低金额
  309. if ($_line < $extract_minimum_num) throw new ValidateException('提现金额不足');
  310. $_money = bcsub($merchant['mer_money'], $data['extract_money'], 2);
  311. $sn = date('YmdHis' . $merId);
  312. $ret = [
  313. 'status' => 0,
  314. 'mer_id' => $merId,
  315. 'mer_money' => $_money,
  316. 'financial_sn' => $sn,
  317. 'extract_money' => $data['extract_money'],
  318. 'financial_type' => $data['financial_type'],
  319. 'financial_account' => json_encode($financial_account, JSON_UNESCAPED_UNICODE),
  320. 'financial_status' => 0,
  321. 'mer_admin_id' => $data['mer_admin_id'],
  322. 'mark' => $datap['mark'] ?? '',
  323. 'refusal' => '',
  324. ];
  325. Db::transaction(function () use ($merId, $ret, $data, $make) {
  326. $this->dao->create($ret);
  327. $make->subMoney($merId, (float)$data['extract_money']);
  328. });
  329. }
  330. /**
  331. * 申请退保证金
  332. * @param $merId
  333. * @param $adminId
  334. * @return mixed
  335. * @author Qinii
  336. * @day 2023/5/10
  337. */
  338. public function refundMargin($merId, $adminId, $account)
  339. {
  340. $merchant = app()->make(MerchantRepository::class)->get($merId);
  341. $res = $this->checkRefundMargin($merId, $adminId, true, $account);
  342. if ($res['offline'] && (!$account['type'] || !$account['name'] || !$account['code'] || !$account['pic'])) {
  343. throw new ValidateException('请填写线下收款信息');
  344. }
  345. $financial = $res['financial'];
  346. $bill = [
  347. 'title' => '申请退保证金',
  348. 'number' => $merchant->margin,
  349. 'balance' => 0,
  350. 'mark' => '【 操作者:' . request()->adminId() . '|' . request()->adminInfo()->real_name . '】',
  351. 'mer_id' => $merchant->mer_id,
  352. ];
  353. $userBillRepository = app()->make(UserBillRepository::class);
  354. return Db::transaction(function () use ($financial, $merchant, $bill, $userBillRepository) {
  355. $this->dao->insertAll($financial);
  356. $merchant->margin = 0;
  357. $merchant->is_margin = -1;
  358. $merchant->save();
  359. $userBillRepository->bill(0, 'mer_margin', 'margin_status', 0, $bill);
  360. });
  361. }
  362. /**
  363. * 检查并处理退款保证金
  364. *
  365. * 该方法用于检查商家是否有足够的保证金可以退款,并根据情况处理线上和线下的退款。
  366. * 它首先查询商家的信息以及相关的保证金订单,然后计算可退金额并分别处理线上和线下的退款操作。
  367. *
  368. * @param string $merId 商家ID
  369. * @param string $adminId 管理员ID
  370. * @param bool $crate 是否创建退款订单,默认为false
  371. * @param array $account 如果需要线下退款,传入退款账户信息
  372. * @return array 包含线上退款金额、线下退款金额、退款订单信息和退款类型信息的数组
  373. * @throws ValidateException 如果商家无法退款或重复申请,则抛出验证异常
  374. */
  375. public function checkRefundMargin($merId, $adminId, $crate = false, $account = [])
  376. {
  377. /**
  378. * 获取线上支付的订单
  379. * 检查线上支付的订单是否够退当前的保证金
  380. * 优先线上退款,剩余的金额都走线下退款
  381. */
  382. $merchant = app()->make(MerchantRepository::class)->getWhere(['mer_id' => $merId], '*', [
  383. 'marginOrder' => function ($query) {
  384. $query->where('status', 1)->where('pay_type', '<>', ServeOrderRepository::PAY_TYPE_SYS)->field('order_id,order_sn,pay_price,pay_type,mer_id');
  385. }]
  386. );
  387. if ($merchant['is_margin'] == -1) throw new ValidateException('请勿重复申请');
  388. if (!in_array($merchant['is_margin'], [10, -10]) || $merchant['margin'] <= 0)
  389. throw new ValidateException('无可退保证金');
  390. $orderList = $merchant->marginOrder;
  391. //需退金额
  392. $extract_money = $merchant->margin;
  393. $financial = $info = [];
  394. $online = $offline = 0;
  395. if ($orderList) {
  396. foreach ($orderList as $order) {
  397. $refund_price = bcsub($order->pay_price, $extract_money, 2) < 0 ? $order->pay_price : $merchant->margin;
  398. $extract_money = bcsub($extract_money, $refund_price, 2);
  399. $online = bcadd($refund_price, $online, 2);
  400. if ($crate) {
  401. $financial[] = $this->payOrderRefund($merchant, $adminId, $refund_price, $order);
  402. }
  403. if (bccomp($extract_money, 0, 2) == 0) break;
  404. }
  405. }
  406. if (bccomp($extract_money, 0, 2) == 1) {
  407. $offline = $extract_money;
  408. if ($crate) {
  409. $financial[] = $this->payOrderRefund($merchant, $adminId, $extract_money, null, $account);
  410. }
  411. $info = $this->getType($merchant);
  412. }
  413. return compact('online', 'offline', 'financial', 'info');
  414. }
  415. /**
  416. * 根据商家的财务类型获取相应的财务信息。
  417. *
  418. * 本函数通过商家的财务类型来确定商家的收款方式,并返回相应的收款信息。
  419. * 支持的收款方式包括银行、微信和支付宝。每种收款方式返回的信息包括名称、代码和图片标识。
  420. * 如果商家未设置财务类型,则默认返回空的收款信息。
  421. *
  422. * @param object $merchant 商家对象,包含财务类型及相关信息。
  423. * @return array 返回一个包含收款方式名称、代码和图片的数组。
  424. */
  425. public function getType($merchant)
  426. {
  427. switch ($merchant->financial_type) {
  428. case 1:
  429. $arr = [
  430. 'name' => $merchant->financial_bank->name ?? '',
  431. 'code' => $merchant->financial_bank->bank ?? '',
  432. 'pic' => $merchant->financial_bank->bank_code ?? '',
  433. ];
  434. break;
  435. case 2:
  436. $arr = [
  437. 'name' => $merchant->financial_wechat->name ?? '',
  438. 'code' => $merchant->financial_wechat->wechat ?? '',
  439. 'pic' => $merchant->financial_wechat->wechat_code ?? '',
  440. ];
  441. break;
  442. case 3:
  443. $arr = [
  444. 'name' => $merchant->financial_alipay->name ?? '',
  445. 'code' => $merchant->financial_alipay->alipay ?? '',
  446. 'pic' => $merchant->financial_alipay->alipay_code ?? '',
  447. ];
  448. break;
  449. default:
  450. $arr = ['name' => '', 'code' => '', 'pic' => '',];
  451. break;
  452. }
  453. $arr['type'] = $merchant->financial_type ?: 1;
  454. return $arr;
  455. }
  456. /**
  457. * 处理订单退款逻辑
  458. *
  459. * 本函数用于生成订单退款的相关信息。如果未提供订单信息或账户信息,则会根据退款金额和商家信息生成默认的订单和账户数据。
  460. * 主要用于在退款操作时,生成必要的财务记录和订单记录,以便后续的退款处理和审计。
  461. *
  462. * @param object $merchant 商家信息对象,包含商家ID等关键信息。
  463. * @param int $adminId 管理员ID,用于标识进行退款操作的管理员。
  464. * @param float $refund_price 退款金额,指定本次退款的金额。
  465. * @param array $order 可选的订单信息数组,包含订单ID、订单号、支付金额、支付类型等信息。
  466. * @param array $account 可选的账户信息数组,用于指定退款账户的相关信息。
  467. * @return array 返回一个包含退款相关信息的数组,包括状态、商家ID、商家金额、财务流水号、提取金额、财务类型、财务账户信息、财务状态、管理员ID、备注和类型等字段。
  468. */
  469. public function payOrderRefund($merchant, $adminId, $refund_price, $order = [], $account = [])
  470. {
  471. // 如果订单信息为空,则生成默认的订单信息
  472. if (!$order) {
  473. $order = [
  474. 'order_id' => 0,
  475. 'order_sn' => '',
  476. 'pay_price' => $refund_price,
  477. 'pay_type' => ServeOrderRepository::PAY_TYPE_SYS,
  478. 'mer_id' => $merchant->mer_id,
  479. 'account' => $account
  480. ];
  481. }
  482. // 生成并返回退款相关信息数组
  483. return [
  484. 'status' => 0,
  485. 'mer_id' => $merchant->mer_id,
  486. 'mer_money' => 0,
  487. 'financial_sn' => 'mm' . date('YmdHis' . $merchant->mer_id),
  488. 'extract_money' => $refund_price,
  489. 'financial_type' => $order['pay_type'],
  490. 'financial_account' => json_encode($order, JSON_UNESCAPED_UNICODE),
  491. 'financial_status' => 0,
  492. 'mer_admin_id' => $adminId,
  493. 'mark' => '',
  494. 'type' => 1
  495. ];
  496. }
  497. /**
  498. * 商户列表
  499. * @param array $where
  500. * @param int $page
  501. * @param int $limit
  502. * @return array
  503. * @author Qinii
  504. * @day 3/19/21
  505. */
  506. public function getAdminList(array $where, int $page, int $limit)
  507. {
  508. $where['is_del'] = 0;
  509. $query = $this->dao->search($where)->with([
  510. 'merchant' => function ($query) {
  511. $query->field('mer_id,mer_name,is_trader,mer_avatar,type_id,mer_phone,mer_address,is_margin,margin,real_name,ot_margin');
  512. $query->with([
  513. 'merchantType',
  514. 'marginOrder' => function ($query) {
  515. $query->field('order_id,order_sn,pay_price')->where('status', 10);
  516. }
  517. ]);
  518. }
  519. ]);
  520. $count = $query->count();
  521. $list = $query->page($page, $limit)->select();
  522. return compact('count', 'list');
  523. }
  524. /**
  525. * 取消/拒绝 变更状态返还余额
  526. * @param $merId
  527. * @param $id
  528. * @param $data
  529. * @author Qinii
  530. * @day 3/19/21
  531. */
  532. public function cancel(?int $merId, int $id, array $data)
  533. {
  534. $where = [
  535. 'financial_id' => $id,
  536. 'is_del' => 0,
  537. 'status' => 0
  538. ];
  539. if ($merId) $where['mer_id'] = $merId;
  540. $res = $this->dao->getWhere($where);
  541. if (!$res) throw new ValidateException('数据不存在');
  542. if ($res['financial_status'] == 1) throw new ValidateException('当前状态无法完成此操作');
  543. $merId = $merId ?? $res['mer_id'];
  544. Db::transaction(function () use ($merId, $res, $id, $data) {
  545. $this->dao->update($id, $data);
  546. app()->make(MerchantRepository::class)->addMoney($merId, (float)$res['extract_money']);
  547. });
  548. }
  549. /**
  550. * 标记商家财务状态的表单生成方法
  551. *
  552. * 本方法用于生成一个用于修改商家财务状态备注的表单。通过给定的商家ID,
  553. * 获取当前备注信息,并构建一个表单以允许用户输入新的备注。
  554. * 表单提交的URL是根据当前商家ID动态生成的,确保了表单提交的目标地址与商家ID的一致性。
  555. *
  556. * @param int $id 商家的唯一标识ID,用于获取商家的当前备注信息。
  557. * @return object 返回一个包含表单HTML代码的对象,该对象还包括表单的标题和验证规则。
  558. */
  559. public function markForm($id)
  560. {
  561. // 通过商家ID获取当前的备注信息
  562. $data = $this->dao->get($id);
  563. // 构建表单提交的URL,确保表单提交时会带上正确的商家ID
  564. $form = Elm::createForm(Route::buildUrl('merchantFinancialMark', ['id' => $id])->build());
  565. // 设置表单的验证规则,这里仅包括一个文本输入框用于输入备注信息
  566. $form->setRule([
  567. Elm::text('mark', '备注:', $data['mark'])->placeholder('请输入备注')->required(),
  568. ]);
  569. // 设置表单的标题为“修改备注”,明确表单的功能
  570. return $form->setTitle('修改备注');
  571. }
  572. /**
  573. * 创建管理员标记表单
  574. *
  575. * 该方法用于生成一个用于修改管理员标记的表单。通过给定的ID获取相关数据,
  576. * 并使用这些数据来预填充表单字段。表单提交的URL是根据当前ID动态生成的,
  577. * 保证了表单提交的目标地址与当前处理的数据ID一致。
  578. *
  579. * @param int $id 数据ID,用于获取特定数据并预填充表单
  580. * @return \FormBuilder\Form|\Phper6\Elm\Form
  581. */
  582. public function adminMarkForm($id)
  583. {
  584. // 根据ID获取数据,用于预填充表单
  585. $data = $this->dao->get($id);
  586. // 创建表单,并设置表单提交的URL
  587. $form = Elm::createForm(Route::buildUrl('systemFinancialMark', ['id' => $id])->build());
  588. // 设置表单的验证规则,包括一个文本字段用于输入管理员标记
  589. $form->setRule([
  590. Elm::text('admin_mark', '备注:', $data['admin_mark'])->placeholder('请输入备注')->required(),
  591. ]);
  592. // 设置表单的标题
  593. return $form->setTitle('修改备注');
  594. }
  595. /**
  596. * 创建管理员标记表单
  597. *
  598. * 该方法用于生成一个用于管理员标记的表单,表单的主要目的是允许管理员对特定记录添加备注。
  599. * 通过传入的ID获取相关数据,并基于这些数据构建表单,表单提交的目标是系统设定的备注修改路由。
  600. *
  601. * @param int $id 记录的唯一标识符,用于获取该记录的当前备注信息。
  602. * @return \FormBuilder\Form|string
  603. */
  604. public function adminMarginMarkForm($id)
  605. {
  606. // 根据ID获取记录的信息,主要用于获取当前的管理员备注。
  607. $data = $this->dao->get($id);
  608. // 构建表单的URL,指向系统中处理备注修改的路由,其中ID作为参数进行传递。
  609. $form = Elm::createForm(Route::buildUrl('systemMarginRefundMark', ['id' => $id])->build());
  610. // 设置表单的验证规则,这里主要是一个文本输入框用于管理员输入备注信息。
  611. // 表单字段的名称、标签和默认值分别设置,同时输入框设置为必填。
  612. $form->setRule([
  613. Elm::text('admin_mark', '备注:', $data['admin_mark'])->placeholder('请输入备注')->required(),
  614. ]);
  615. // 设置表单的标题为“修改备注”,明确表单的操作目的。
  616. return $form->setTitle('修改备注');
  617. }
  618. /**
  619. * 构建保证金退还审核表单
  620. *
  621. * @param int $id 保证金订单ID
  622. * @return mixed 返回表单元素
  623. *
  624. * 此函数用于生成特定保证金订单的审核表单,表单中包含商户信息、保证金相关金额信息
  625. * 以及审核选项。通过此表单,审核人员可以查看相关详情并进行同意或拒绝的审核操作。
  626. */
  627. public function statusForm($id)
  628. {
  629. $data = $this->dao->get($id);
  630. if (!$data['merchant']->marginOrder) throw new ValidateException('未查询到缴费记录');
  631. if ($data['status'] !== 0) throw new ValidateException('请勿重复审核');
  632. $form = Elm::createForm(Route::buildUrl('systemMarginRefundSwitchStatus', ['id' => $id])->build());
  633. $rule = [
  634. [
  635. 'type' => 'span',
  636. 'title' => '商户名称:',
  637. 'native' => false,
  638. 'children' => [(string)$data['merchant']->mer_name]
  639. ],
  640. [
  641. 'type' => 'span',
  642. 'title' => '商户ID:',
  643. 'native' => false,
  644. 'children' => [(string)$data['mer_id']]
  645. ],
  646. [
  647. 'type' => 'span',
  648. 'title' => '店铺类型:',
  649. 'native' => false,
  650. 'children' => [(string)$data['merchant']->merchantType->type_name]
  651. ],
  652. [
  653. 'type' => 'span',
  654. 'title' => '保证金额度:',
  655. 'native' => false,
  656. 'children' => [(string)$data['financial_account']->pay_price]
  657. ],
  658. [
  659. 'type' => 'span',
  660. 'title' => '扣费金额:',
  661. 'native' => false,
  662. 'children' => [(string)bcsub($data['financial_account']->pay_price, $data['extract_money'], 2)]
  663. ],
  664. [
  665. 'type' => 'span',
  666. 'title' => '退回金额:',
  667. 'native' => false,
  668. 'children' => [(string)$data['extract_money']]
  669. ],
  670. [
  671. 'type' => 'div',
  672. 'title' => '退回方式:',
  673. 'native' => false,
  674. 'style' => 'white-space:pre-wrap',
  675. 'children' => [(string)$data['financial_type'] == ServeOrderRepository::PAY_TYPE_SYS ? '线下转账' : '线上退回',]
  676. ],
  677. ];
  678. if ($data['financial_type'] == ServeOrderRepository::PAY_TYPE_SYS && isset($data['financial_account']->account) && $data['financial_account']->account) {
  679. $type = $data['financial_account']->account->type;
  680. $rule[] = [
  681. 'type' => 'span',
  682. 'title' => '收款人姓名:',
  683. 'native' => false,
  684. 'style' => 'white-space:pre-wrap',
  685. 'children' => [(string)$data['financial_account']->account->name ?? '']
  686. ];
  687. $rule[] = [
  688. 'type' => 'span',
  689. 'title' => ($type == 1) ? '收款银行:' : (($type == 2) ? '微信号:' : '支付宝账号:'),
  690. 'native' => false,
  691. 'children' => [(string)$data['financial_account']->account->code ?? '']
  692. ];
  693. if ($type == 1) {
  694. $rule[] = [
  695. 'type' => 'span',
  696. 'title' => '收款银行卡:',
  697. 'native' => false,
  698. 'children' => [(string)$data['financial_account']->account->pic ?? '']
  699. ];
  700. } else {
  701. $rule[] = [
  702. 'type' => 'img',
  703. 'title' => '收款二维码:',
  704. 'native' => false,
  705. 'attrs' => ['src' => $data['merchant']->financial_wechat->wechat_code ?? ''],
  706. 'style' => ['width' => '100px', 'height' => '100px']
  707. ];
  708. }
  709. }
  710. $rule[] = Elm::radio('status', '审核:', -1)->setOptions([
  711. ['value' => 1, 'label' => '同意'],
  712. ['value' => -1, 'label' => '拒绝'],
  713. ])->control([
  714. [
  715. 'value' => -1,
  716. 'rule' => [
  717. Elm::input('refusal', '拒绝原因:')->required()
  718. ]
  719. ],]);
  720. $form->setRule($rule);
  721. return $form->setTitle('退保证金审核');
  722. }
  723. /**
  724. * 详情
  725. * @param $id
  726. * @return array|\think\Model|null
  727. * @author Qinii
  728. * @day 4/22/21
  729. */
  730. public function detail($id, $merId = 0)
  731. {
  732. $where[$this->dao->getPk()] = $id;
  733. if ($merId) $where['mer_id'] = $merId;
  734. $data = $this->dao->getSearch($where)->with(['merchant' => function ($query) {
  735. $query->field('mer_id,mer_name,mer_avatar');
  736. }])->find();
  737. if (!$data) throw new ValidateException('数据不存在');
  738. return $data;
  739. }
  740. /**
  741. * 头部统计
  742. * @return array
  743. * @author Qinii
  744. * @day 4/22/21
  745. */
  746. public function getTitle($where)
  747. {
  748. $make = app()->make(MerchantRepository::class);
  749. //应付商户金额 = 所有商户的余额之和
  750. $all = $make->search(['is_del' => 0])->sum('mer_money');
  751. //商户可提现金额 = (每个商户的余额 - 平台设置的最低提现额度) 之和
  752. $extract_minimum_line = systemConfig('extract_minimum_line') ?: 0;
  753. $ret = $make->search(['is_del' => 0])->where('mer_money', '>', $extract_minimum_line)
  754. ->field("sum(mer_money - $extract_minimum_line) as money")
  755. ->find();
  756. $money = $ret->money;
  757. //申请转账的商户数 = 申请提现且未转账的商户数量
  758. $where['financial_status'] = 0;
  759. $query = $this->dao->getSearch($where)->where('status', '>', -1);
  760. $count = $query->group('mer_id')->count();
  761. //申请转账的总金额 = 申请提现已通过审核,且未转账的申请金额 之和
  762. $where['status'] = 1;
  763. $all_ = $this->dao->search($where)->sum('extract_money');
  764. $where['status'] = 0;
  765. $all_0 = $this->dao->search($where)->sum('extract_money');
  766. $merLockMoney = app()->make(UserBillRepository::class)->merchantLickMoney();
  767. $stat = [
  768. [
  769. 'className' => 'el-icon-s-goods',
  770. 'count' => $all,
  771. 'field' => '元',
  772. 'name' => '应付商户金额'
  773. ],
  774. [
  775. 'className' => 'el-icon-s-goods',
  776. 'count' => $money,
  777. 'field' => '元',
  778. 'name' => '商户可提现金额'
  779. ],
  780. [
  781. 'className' => 'el-icon-s-goods',
  782. 'count' => $count,
  783. 'field' => '个',
  784. 'name' => '申请转账的商户数'
  785. ],
  786. [
  787. 'className' => 'el-icon-s-goods',
  788. 'count' => $all_,
  789. 'field' => '元',
  790. 'name' => '申请转账的总金额'
  791. ],
  792. [
  793. 'className' => 'el-icon-s-goods',
  794. 'count' => $all_0,
  795. 'field' => '元',
  796. 'name' => '待审核的总金额'
  797. ],
  798. [
  799. 'className' => 'el-icon-s-goods',
  800. 'count' => $merLockMoney,
  801. 'field' => '元',
  802. 'name' => '商户冻结金额'
  803. ],
  804. ];
  805. return $stat;
  806. }
  807. /**
  808. * 修改状态
  809. * @param $id
  810. * @param $type
  811. * @param $data
  812. * @return int
  813. * @throws \think\db\exception\DataNotFoundException
  814. * @throws \think\db\exception\DbException
  815. * @throws \think\db\exception\ModelNotFoundException
  816. * @author wuhaotian
  817. * @email 442384644@qq.com
  818. * @date 2024/7/23
  819. */
  820. public function switchStatus($id, $type, $data)
  821. {
  822. $where = [
  823. 'financial_id' => $id,
  824. 'is_del' => 0,
  825. 'status' => 0,
  826. 'type' => $type
  827. ];
  828. $res = $this->dao->getWhere($where);
  829. if (!$res) throw new ValidateException('数据不存在');
  830. switch ($type) {
  831. case 0:
  832. if ($data['status'] == -1) $this->cancel(null, $id, $data);
  833. break;
  834. case 1:
  835. $bill['number'] = $res['extract_money'];
  836. $bill['mer_id'] = $res->merchant->mer_id;
  837. if ($data['status'] == 1) {
  838. $this->agree($res);
  839. $data['financial_status'] = 1;
  840. $tempId = 'REFUND_MARGIN_SUCCESS';
  841. $bill['title'] = '审核通过';
  842. $bill['balance'] = 0;
  843. $bill['mark'] = '【 操作者:' . request()->adminId() . '|' . request()->adminInfo()->real_name . '】';
  844. $pm = 0;
  845. } else if ($data['status'] == -1) {
  846. $number = bcadd($res->merchant->margin, $res->extract_money, 2);
  847. $res->merchant->is_margin = 10;
  848. $res->merchant->margin = $number;
  849. $res->merchant->save();
  850. $tempId = 'REFUND_MARGIN_FAIL';
  851. $bill['title'] = '审核拒绝';
  852. $bill['balance'] = $number;
  853. $bill['mark'] = $data['refusal'] . '【 操作者:' . request()->adminId() . '|' . request()->adminInfo()->real_name . '】';
  854. $pm = 1;
  855. }
  856. $userBillRepository = app()->make(UserBillRepository::class);
  857. $userBillRepository->bill(0, 'mer_margin', 'margin_status', $pm, $bill);
  858. Queue::push(SendSmsJob::class, [
  859. 'tempId' => $tempId,
  860. 'id' => [
  861. 'name' => $res->merchant->mer_name,
  862. 'time' => $res->create_time,
  863. 'phone' => $res->merchant->mer_phone
  864. ]]);
  865. break;
  866. }
  867. // Queue::push(SendSmsJob::class, ['tempId' => 'TRANSFER_ACCOUNTS_SUCCESS', 'id' => $id]);
  868. return $this->dao->update($id, $data);
  869. }
  870. /**
  871. * 根据条件查询退款信息
  872. *
  873. * 本函数用于查询特定财务id、类型和状态下的退款信息。通过传入的id,精确查询满足条件的退款记录。
  874. * 主要用于前端展示退款详情,或相关业务逻辑中对特定退款记录的查询操作。
  875. *
  876. * @param int $id 财务id,用于查询特定的退款记录
  877. * @return array 查询到的退款信息,包含所有字段及关联的merchant信息
  878. * @throws ValidateException 如果查询不到满足条件的记录,则抛出异常
  879. */
  880. public function refundShow($id)
  881. {
  882. // 定义查询条件,包括财务id、类型和状态
  883. $where = [
  884. 'financial_id' => $id,
  885. 'type' => 1,
  886. 'status' => 1
  887. ];
  888. // 根据查询条件获取满足条件的所有字段,并包含merchant的merchantType字段
  889. $res = $this->dao->getWhere($where, '*', ['merchant' => ['merchantType']]);
  890. // 如果查询结果为空,则抛出异常提示数据不存在
  891. if (!$res) throw new ValidateException('数据不存在');
  892. // 返回查询结果
  893. return $res;
  894. }
  895. /**
  896. * 同意退保证金
  897. * @param $res
  898. * @author Qinii
  899. * @day 1/27/22
  900. */
  901. public function agree($res)
  902. {
  903. $data = [];
  904. $order = null;
  905. //线上原路退回
  906. if ($res['financial_type'] !== ServeOrderRepository::PAY_TYPE_SYS) {
  907. //验证
  908. $comp = bccomp($res['financial_account']->pay_price, $res['extract_money'], 2);
  909. if ($comp == -1)
  910. throw new ValidateException('申请退款金额:' . $res['extract_money'] . ',大于支付保证金:' . $res['financial_account']->pay_price);
  911. //退款
  912. $data = [
  913. 'refund_id' => $res['financial_account']->order_sn,
  914. 'pay_price' => $res['financial_account']->pay_price,
  915. 'refund_price' => $res['extract_money']
  916. ];
  917. $order = app()->make(ServeOrderRepository::class)->get($res['financial_account']->order_id);
  918. }
  919. $has = $this->dao->getSearch([])
  920. ->where('status', 0)
  921. ->where('mer_id', $res->mer_id)
  922. ->where('type', 1)
  923. ->where('financial_id', '<>', $res->financial_id)
  924. ->count();
  925. Db::transaction(function () use ($res, $data, $order, $has) {
  926. if ($data) {
  927. WechatService::create()->payOrderRefund($res['financial_account']->order_sn, $data);
  928. //改订单
  929. $order->status = 20;
  930. $order->save();
  931. }
  932. /*
  933. * 如果存在,则有其他正在申请退款的订单;
  934. * 保证金状态不变;
  935. * 否则
  936. * 变更保证金状态&关闭店铺
  937. *
  938. */
  939. if (!$has) {
  940. $is_margin = $res['merchant']['merchantType']['is_margin'];
  941. $res->merchant->is_margin = $is_margin;
  942. $res->merchant->margin = $res['merchant']['merchantType']['margin'];
  943. $res->merchant->ot_margin = $res['merchant']['merchantType']['margin'];
  944. if ($is_margin == 1) {
  945. $res->merchant->mer_state = 0;
  946. Queue::push(ChangeMerchantStatusJob::class, $res['mer_id']);
  947. }
  948. $res->merchant->save();
  949. }
  950. });
  951. }
  952. }