SettlementService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <?php
  2. namespace App\Services\Settlement;
  3. use App\Cache\FinanceCache;
  4. use App\Consts\BaseConst;
  5. use App\Consts\ErrorConst;
  6. use App\Consts\FinanceConsts;
  7. use App\Dao\Bank\BankDao;
  8. use App\Dao\Bank\WithdrawDao;
  9. use App\Dao\Settlement\BillDao;
  10. use App\Dao\Settlement\FinancialDao;
  11. use App\Facade\Site;
  12. use App\Libs\Utils;
  13. use App\Exceptions\ApiException;
  14. use App\Models\Settlement\FinancialStats;
  15. use Illuminate\Support\Facades\DB;
  16. class SettlementService
  17. {
  18. private $billDao;
  19. private $bankDao;
  20. private $financialDao;
  21. private $withdrawDao;
  22. public function __construct(
  23. BillDao $billDao,
  24. BankDao $bankDao,
  25. FinancialDao $financialDao,
  26. WithdrawDao $withdrawDao
  27. )
  28. {
  29. $this->billDao = $billDao;
  30. $this->bankDao = $bankDao;
  31. $this->financialDao = $financialDao;
  32. $this->withdrawDao = $withdrawDao;
  33. }
  34. /**
  35. * 结算列表
  36. *
  37. * @param $params
  38. * @return mixed
  39. */
  40. public function bills($params)
  41. {
  42. // 站点信息
  43. $channelId = Site::getCurrentChannelId();
  44. $params['channel_id'] = $channelId;
  45. // 结算列表
  46. $bills = $this->billDao->getBills($params);
  47. // 导出逻辑
  48. $export = (int)getProp($params, 'export');
  49. if ($export) {
  50. $this->exportBills($bills);
  51. }
  52. return $bills;
  53. }
  54. /**
  55. * 结算单订单明细
  56. *
  57. * @param $date
  58. * @return mixed
  59. */
  60. public function billOrders($date)
  61. {
  62. // 站点信息
  63. $channelId = Site::getCurrentChannelId();
  64. // 查询订单
  65. return $this->billDao->billOrders($channelId, $date);
  66. }
  67. /**
  68. * 结算信息-汇总数据
  69. *
  70. * @return array
  71. */
  72. public function billsStat(): array
  73. {
  74. // 站点id
  75. $channelId = Site::getCurrentChannelId();
  76. // 站点提现信息
  77. $financialStat = $this->financialDao->getChannelFinancialStat($channelId);
  78. $enableWithdrawalAmount = getProp($financialStat, 'enable_withdrawal_amount_7day', 0.00); // t-7可提现余额
  79. $accumulativeWithdrawalAmount = getProp($financialStat, 'accumulative_withdrawal_amount', 0.00);
  80. $latestWithdrawalAmount = getProp($financialStat, 'latest_withdrawal_amount', 0.00);
  81. $withdrawPendingAmount = getProp($financialStat, 'withdraw_pending_amount', 0.00);
  82. $latestWithdrawalTime = getProp($financialStat, 'latest_withdraw_time');
  83. // 充值总额
  84. // $totalRechargeAmount = $this->orderDao->getChannelRechargeAmount($channelId);
  85. // 充值总额(默认7日前数据)
  86. [$startDate, $endDate] = ['', date('Y-m-d', strtotime('-' . BaseConst::DATE_RANGE_DAYS . ' days'))];
  87. $totalRechargeAmountBefore7Days = $this->billDao->getBillsRechargeAmount($channelId, $startDate, $endDate);
  88. // 月充值总额(前7日数据不显示)
  89. $monthRechargeAmountWithout7Days = 0.00;
  90. $startDate = date('Y-m-01');
  91. if (date('j') > BaseConst::DATE_RANGE_DAYS) {
  92. $monthRechargeAmountWithout7Days = $this->billDao->getBillsRechargeAmount($channelId, $startDate, $endDate);
  93. }
  94. // 最近一笔提现中
  95. $latestWithdrawLog = $this->withdrawDao->getLatestWithdrawingCash($channelId);
  96. $withdrawingAmount = getProp($latestWithdrawLog, 'amount', 0.00);
  97. // 结算总额
  98. // $totalSettlementAmount = $accumulativeWithdrawalAmount + $enableWithdrawalAmount + $withdrawPendingAmount;
  99. // 站点统计
  100. return [
  101. 'total_recharge_amount' => 0.00, // floor($totalRechargeAmount * 100) / 100, // 充值总额
  102. 'total_recharge_amount_without_today' => 0.00, // 充值总额(不含当日)
  103. 'month_recharge_amount_without_today' => 0.00, // 月充值总额(不含当日)
  104. 'total_recharge_amount_without_7day' => floor($totalRechargeAmountBefore7Days * 100) / 100, // 充值总额(不含当日)
  105. 'month_recharge_amount_without_7day' => floor($monthRechargeAmountWithout7Days * 100) / 100, // 月充值总额(不含当日)
  106. 'enable_withdrawal_amount' => floor($enableWithdrawalAmount * 100) / 100, // 可提现总余额
  107. 'total_withdraw_amount' => floor($accumulativeWithdrawalAmount * 100) / 100, // 累计提现金额
  108. 'total_settlement_amount' => 0.00, // floor($totalSettlementAmount * 100) / 100, // 结算总额
  109. 'latest_withdrawal_amount' => floor($latestWithdrawalAmount * 100) / 100, // 最近一笔提现金额
  110. 'latest_withdraw_time' => $latestWithdrawalTime ?: '', // 最近一笔提现时间
  111. 'withdrawing_amount' => floor($withdrawingAmount * 100) / 100, // 提现中金额
  112. ];
  113. }
  114. /**
  115. * 提现列表
  116. *
  117. * @param $params
  118. * @return mixed
  119. */
  120. public function withdrawCashes($params)
  121. {
  122. // 站点id
  123. $channelId = Site::getCurrentChannelId();
  124. $params['channel_id'] = $channelId;
  125. // 获取提现记录
  126. return $this->withdrawDao->getWithdrawCashes($params);
  127. }
  128. /**
  129. * 提现
  130. *
  131. * @param $params
  132. * @return array
  133. * @throws ApiException
  134. */
  135. public function applyWithDraw($params): array
  136. {
  137. dLog('withdraw')->info('applyWithDraw-1', $params);
  138. // 相关参数
  139. $channelId = Site::getCurrentChannelId();
  140. $channelUserId = Site::getUid();
  141. $amount = (float)getProp($params, 'amount');
  142. $cardId = (int)getProp($params, 'card_id');
  143. $isCompany = (int)getProp($params, 'is_company');
  144. // 金额不小于1
  145. if (!is_numeric($amount) || $amount < 1 || $cardId < 1) {
  146. Utils::throwError(ErrorConst::PARAM_ERROR_CODE);
  147. }
  148. // 单笔提现下限
  149. if ($amount < FinanceConsts::WITH_DRAW_PRIVATE_MIN) {
  150. Utils::throwError(ErrorConst::WITHDRAW_CASH_AMOUNT);
  151. }
  152. // 单笔提现上限
  153. if ($amount >= FinanceConsts::WITH_DRAW_PRIVATE_MAX) {
  154. Utils::throwError(ErrorConst::WITHDRAW_CASH_AMOUNT_MORE);
  155. }
  156. // 核对银行卡信息
  157. $bankAccount = $this->bankDao->getValidCashAccountById($cardId);
  158. if (empty($bankAccount)) {
  159. Utils::throwError(ErrorConst::WITHDRAW_CASH_AMOUNT_ACCOUNT);
  160. }
  161. if ($bankAccount['is_company'] != $isCompany) {
  162. Utils::throwError(ErrorConst::WITHDRAW_CASH_AMOUNT_NOT_MATCH);
  163. }
  164. if ((int)$bankAccount['channel_user_id'] != $channelUserId) {
  165. Utils::throwError(ErrorConst::PRIVATE_ACCOUNT_NO_ACCESS);
  166. }
  167. dLog('withdraw')->info('applyWithDraw-2', compact('bankAccount'));
  168. // 判断当前渠道今日是否已有提现
  169. $isWithDrawn = $this->checkChannelIsWithDrawByChannelId($channelId);
  170. if ($isWithDrawn) {
  171. Utils::throwError(ErrorConst::WITHDRAW_CASH_TODAY_USE);
  172. }
  173. dLog('withdraw')->info('applyWithDraw-3', compact('isWithDrawn'));
  174. // 判断账户可提现余额是否满足
  175. $financialStat = $this->financialDao->getChannelFinancialStat($channelId);
  176. $enableWithDrawAmount = getProp($financialStat, 'enable_withdrawal_amount_7day'); // t-7可提现余额
  177. dLog('withdraw')->info('applyWithDraw-4', compact('financialStat', 'enableWithDrawAmount'));
  178. // 余额判断(-1 小于, 0 等于, 1 大于, 第3个参数必须带上)
  179. if (bccomp($amount, $enableWithDrawAmount, 2) >= 1) {
  180. Utils::throwError(ErrorConst::WITHDRAW_NOT_ENOUGH);
  181. }
  182. // 执行提现
  183. $result = $this->runWithDraw($channelId, $amount, $bankAccount, $financialStat);
  184. if (!$result) {
  185. Utils::throwError(ErrorConst::WITHDRAW_CASHES_FAILED);
  186. }
  187. return [
  188. 'enable_amount' => $enableWithDrawAmount - $amount,
  189. 'withdraw_pending_amount' => $amount
  190. ];
  191. }
  192. /**
  193. * 执行提现
  194. *
  195. * @param $channelId
  196. * @param $amount
  197. * @param $bankAccount
  198. * @param FinancialStats $financialStat
  199. * @return bool
  200. */
  201. private function runWithDraw($channelId, $amount, $bankAccount, FinancialStats $financialStat): bool
  202. {
  203. try {
  204. DB::beginTransaction();
  205. // 更新渠道可提现金额
  206. $financialStat->latest_withdrawal_amount = $amount; //最近提现金额
  207. $financialStat->enable_withdrawal_amount -= $amount; //修改可提现金额
  208. $financialStat->enable_withdrawal_amount_7day -= $amount; //修改可提现金额
  209. $financialStat->withdraw_pending_amount += $amount; //修改提现中金额
  210. $financialStat->latest_withdraw_time = date('Y-m-d H:i:s'); //修改提现时间
  211. $financialStat->save();
  212. // 创建提现单
  213. $this->withdrawDao->addWithDrawCashes([
  214. 'distribution_channel_id' => $channelId,
  215. 'distribution_channel_name' => Site::getCurrentChannelName(),
  216. 'tallage' => 0,
  217. 'amount' => $amount,
  218. 'status' => FinanceConsts::CHECK_PENDING,
  219. 'is_company' => (int)getProp($bankAccount, 'is_company'),
  220. 'account_bank' => getProp($bankAccount, 'account_bank'),
  221. 'bank_account' => getProp($bankAccount, 'card_number'),
  222. 'account_name' => getProp($bankAccount, 'account_name'),
  223. 'pay_merchant_company_id' => 1, // 目前只有一个公司主体
  224. 'bank' => getProp($bankAccount, 'bank'),
  225. ]);
  226. DB::commit();
  227. // 添加渠道到今日提现队列中
  228. FinanceCache::sAddChannelIdToWithDraw($channelId, date('Ymd'));
  229. return true;
  230. } catch (\Exception $e) {
  231. dLog('withdraw')->info('runWithDraw-exception', [$e->getMessage()]);
  232. $msg = sprintf('异常-站点 %s 提现 %s 元失败', $channelId, $amount);
  233. sendNotice($msg);
  234. DB::rollBack();
  235. return false;
  236. }
  237. }
  238. /**
  239. * 校验渠道当日是否已有提现
  240. *
  241. * @param $channelId
  242. * @return bool
  243. */
  244. private function checkChannelIsWithDrawByChannelId($channelId): bool
  245. {
  246. // 缓存层判断
  247. $ymd = date('Ymd');
  248. if (FinanceCache::checkChannelIdIsInWithDraw($channelId, $ymd)) {
  249. return true;
  250. }
  251. // 数据库层判断
  252. [$startTime, $endTime] = [date('Y-m-d'), date('Y-m-d') . ' 23:59:59'];
  253. $checkRes = $this->withdrawDao->checkChannelIdIsInWithDraw($channelId, $startTime, $endTime);
  254. if (!$checkRes) {
  255. return false;
  256. }
  257. // 更新缓存
  258. FinanceCache::sAddChannelIdToWithDraw($channelId, $ymd);
  259. return true;
  260. }
  261. /**
  262. * 结算导出
  263. *
  264. * @param $bills
  265. * @return void
  266. */
  267. private function exportBills($bills)
  268. {
  269. // 设置超时时间
  270. set_time_limit(0);
  271. // 组装下载数据
  272. $data = [];
  273. if ($bills->count()) {
  274. foreach ($bills as $bill) {
  275. $data[] = [
  276. getProp($bill, 'date'),
  277. getProp($bill, 'recharge_amount'),
  278. getProp($bill, 'rate') * 100 . '%',
  279. getProp($bill, 'service_amount'),
  280. getProp($bill, 'settlement_price'),
  281. ];
  282. }
  283. }
  284. $title = ['结算日期', '充值金额', '佣金比例', '支付通道费', '结算后金额'];
  285. exportExcel($data, $title, '结算信息-' . date("YmdHis"));
  286. exit();
  287. }
  288. }