ApplyController.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. *
  5. * User: king/QQ:995265288
  6. * Date: 2018/5/31 下午2:40
  7. * Email: livsyitian@163.com
  8. */
  9. namespace app\frontend\modules\withdraw\controllers;
  10. use app\common\components\ApiController;
  11. use app\common\events\withdraw\WithdrawAppliedEvent;
  12. use app\common\events\withdraw\WithdrawApplyEvent;
  13. use app\common\events\withdraw\WithdrawApplyingEvent;
  14. use app\common\exceptions\AppException;
  15. use app\common\facades\Setting;
  16. use app\common\listeners\income\WithdrawPayedListener;
  17. use app\common\models\Income;
  18. use app\common\models\income\WithdrawIncomeApply;
  19. use app\common\services\income\IncomeService;
  20. use app\common\services\income\WithdrawIncomeApplyService;
  21. use app\common\services\income\WithdrawIncomeDeductionService;
  22. use app\frontend\modules\withdraw\models\Withdraw;
  23. use app\frontend\modules\withdraw\services\WithdrawConfig;
  24. use Illuminate\Support\Facades\DB;
  25. use Illuminate\Support\Facades\Log;
  26. use app\frontend\modules\withdraw\services\StatisticalPresentationService;
  27. use app\common\helpers\Url;
  28. use Yunshop\Commission\models\Agents;
  29. use app\common\models\WithdrawMergeServicetaxRate;
  30. use Yunshop\Love\Common\Services\CommonService;
  31. use Yunshop\WithdrawalLimit\Common\models\MemberWithdrawalLimit;
  32. use app\common\services\finance\Withdraw as WithdrawService;
  33. class ApplyController extends ApiController
  34. {
  35. private $withdraw_set;
  36. /**
  37. * @var static
  38. */
  39. private $pay_way;
  40. /**
  41. * @var double
  42. */
  43. private $amount;
  44. /**
  45. * @var double
  46. */
  47. private $poundage;
  48. /**
  49. * @var array
  50. */
  51. private $withdraw_data;
  52. private $withdraw_config;
  53. private $need_deduction_love_data;//需要扣除爱心值的参数
  54. public function __construct()
  55. {
  56. parent::__construct();
  57. $this->withdraw_set = $this->getWithdrawSet();
  58. }
  59. //提现接口
  60. public function index()
  61. {
  62. $this->withdrawLimitation();
  63. list($amount, $pay_way, $poundage, $withdraw_data) = $this->getPostValue();
  64. $this->amount = $amount;
  65. $this->pay_way = $pay_way;
  66. $this->poundage = $poundage;
  67. $this->withdraw_data = $withdraw_data;
  68. $this->validatePayWay();
  69. //提现数据验证
  70. $this->validateData();
  71. //提现限额判断
  72. $this->cashLimitation();
  73. //提现额度插件判断
  74. $this->withdrawalLimitation();
  75. //提现扣除爱心值验证
  76. $this->validateWithdrawDeductionLove();
  77. //插入提现
  78. $result = $this->withdrawStart();
  79. if ($result === true) {
  80. return $this->successJson($this->successMessage());
  81. }
  82. return $this->errorJson($result ?: $this->withdrawalName() . '申请失败');
  83. }
  84. public function validatePayWay()
  85. {
  86. $types = array_unique(array_column($this->withdraw_data,'type'));
  87. if (!$types) {
  88. return $this->errorJson('提现类型错误!');
  89. }
  90. foreach ($types as $type) {
  91. $withdrawConfig = $this->withdrawConfig($type);
  92. if (!$withdrawConfig || !$withdrawConfig->checkPayWay($this->pay_way)) {
  93. return $this->errorJson('该提现方式已关闭!');
  94. }
  95. }
  96. }
  97. /**
  98. * @param $type
  99. * @return mixed
  100. */
  101. private function withdrawConfig($type)
  102. {
  103. if (!isset($this->withdraw_config)) {
  104. $this->withdraw_config = collect(WithdrawConfig::current()->get('withdraw_apply') ? : []);
  105. }
  106. $config = $this->withdraw_config->where('income_type',$type)->first();
  107. if (!$config) {
  108. $config = $this->withdraw_config->where('income_type','common')->first();
  109. }
  110. return call_user_func($config['class']);
  111. }
  112. //如果设置赠送说明,优先使用赠送说明
  113. private function successMessage()
  114. {
  115. if ($explain = $this->withdrawalExplain()) {
  116. return str_replace('[奖励余额]', $this->awardBalance(), $explain);
  117. }
  118. return $this->withdrawalName() . '申请成功';
  119. }
  120. private function validateWithdrawDeductionLove()
  121. {
  122. $this->need_deduction_love_data = [];
  123. if (app('plugins')->isEnabled('love') && \Setting::get('love.withdraw_deduction_status')) {
  124. $withdraw_deduction_type = \Setting::get('love.withdraw_deduction_type');
  125. if ($withdraw_deduction_type == 2) {
  126. $loveName = \Setting::get('love.unable_name') ?: '冻结爱心值';
  127. $loveValue = CommonService::getMemberFrozeLove(\YunShop::app()->getMemberId());//冻结爱心值
  128. $loveSign = 'froze';
  129. } else {
  130. $loveName = \Setting::get('love.usable_name') ?: '可用爱心值';
  131. $loveValue = CommonService::getMemberUsableLove(\YunShop::app()->getMemberId());//可用爱心值
  132. $loveSign = 'usable';
  133. }
  134. $withdraw_deduction_love_rate = \Setting::get('love.withdraw_deduction_love_rate');
  135. $deductionLove = proportionMath($this->amount,$withdraw_deduction_love_rate);//需要扣除的爱心值
  136. if (bccomp($deductionLove,$loveValue,2) == 1) {
  137. return $this->errorJson('提现需扣除' . $deductionLove . $loveName . ',您的' . $loveName . '不足');
  138. }
  139. if (!empty($withdraw_deduction_love_rate)) {
  140. $this->need_deduction_love_data = [
  141. 'rate' => $withdraw_deduction_love_rate,
  142. 'love_sign' => $loveSign,
  143. ];
  144. }
  145. }
  146. }
  147. /**
  148. * 奖励余额
  149. *
  150. * @return string
  151. */
  152. private function awardBalance()
  153. {
  154. return (new WithdrawPayedListener())->awardValue($this->pay_way, $this->amount);
  155. }
  156. /**
  157. * 余额设置-》收入提现赠送说明
  158. *
  159. * @return string
  160. */
  161. private function withdrawalExplain()
  162. {
  163. return Setting::get('finance.balance.income_withdraw_award_explain');
  164. }
  165. /**
  166. * "提现" 字样自定名称
  167. *
  168. * @return string
  169. */
  170. private function withdrawalName()
  171. {
  172. $setting = Setting::get('shop.lang.zh_cn.income');
  173. return $setting['name_of_withdrawal'] ? $setting['name_of_withdrawal'] : '提现';
  174. }
  175. private function cashLimitation()
  176. {
  177. $set = Setting::get('withdraw.income');
  178. //提交提现的次数
  179. $number_of_submissions = count($this->withdraw_data);
  180. if ($this->pay_way == 'wechat') {
  181. $wechat_frequency = floor($set['wechat_frequency'] ?: 10);
  182. //统计用户今天提现的次数
  183. $statisticalPresentationService = new StatisticalPresentationService;
  184. $today_withdraw_count = $statisticalPresentationService->statisticalPresentation('wechat');
  185. if (($number_of_submissions + $today_withdraw_count) > $wechat_frequency) {
  186. \Log::debug('提现到微信失败', ['今天提现次数', $today_withdraw_count, '本次提现次数', $number_of_submissions, '每日限制次数', $wechat_frequency]);
  187. return $this->errorJson('提现失败,每日提现到微信次数不能超过' . $wechat_frequency . '次');
  188. }
  189. } elseif ($this->pay_way == 'alipay') {
  190. $alipay_frequency = floor($set['alipay_frequency'] ?: 10);
  191. //统计用户今天提现的次数 + 供应商提现的次数
  192. $statisticalPresentationService = new StatisticalPresentationService;
  193. $today_withdraw_count = $statisticalPresentationService->statisticalPresentation('alipay');
  194. if (($number_of_submissions + $today_withdraw_count) > $alipay_frequency) {
  195. \Log::debug('提现到支付宝失败', ['今天提现次数', $today_withdraw_count, '本次提现次数', $number_of_submissions, '每日限制次数', $alipay_frequency]);
  196. return $this->errorJson('提现失败,每日提现到支付宝次数不能超过' . $alipay_frequency . '次');
  197. }
  198. }
  199. }
  200. private function withdrawalLimitation()
  201. {
  202. if (app('plugins')->isEnabled('withdrawal-limit')) {
  203. $set = \Setting::get('withdrawal-limit.is_open');
  204. if ($set != 1 || !in_array($this->pay_way, MemberWithdrawalLimit::$payWays)) {
  205. return;
  206. }
  207. $ways = json_decode(\Setting::get('withdrawal-limit.way'), true);
  208. $mark = false;
  209. switch ($this->pay_way) {
  210. case Withdraw::WITHDRAW_WITH_ALIPAY:
  211. if ($ways['alipay'] == 1) {
  212. $mark = true;
  213. }
  214. break;
  215. case Withdraw::WITHDRAW_WITH_WECHAT:
  216. if ($ways['wechat'] == 1) {
  217. $mark = true;
  218. }
  219. break;
  220. case Withdraw::WITHDRAW_WITH_MANUAL:
  221. if ($ways['bankcard'] == 1) {
  222. $mark = true;
  223. }
  224. break;
  225. }
  226. if ($mark) {
  227. $memberModel = MemberWithdrawalLimit::uniacid()->where('member_id', \YunShop::app()->getMemberId())->first();
  228. if ($memberModel) {
  229. $limit = $memberModel->total_amount;
  230. } else {
  231. $limit = 0;
  232. }
  233. if ($this->amount > $limit) {
  234. return $this->errorJson('当前提现额度不足,暂不能提现');
  235. }
  236. }
  237. }
  238. if (app('plugins')->isEnabled('high-light') && in_array($this->pay_way, [
  239. Withdraw::WITHDRAW_WITH_HIGH_LIGHT_WECHAT,
  240. Withdraw::WITHDRAW_WITH_HIGH_LIGHT_ALIPAY,
  241. Withdraw::WITHDRAW_WITH_HIGH_LIGHT_BANK
  242. ])) {
  243. try {
  244. if ($this->amount < 1) {
  245. throw new \Exception('高灯提现金额必须大于等于1元');
  246. }
  247. switch ($this->pay_way) {
  248. case Withdraw::WITHDRAW_WITH_HIGH_LIGHT_WECHAT:
  249. if ($this->amount > 100000) {
  250. throw new \Exception('高灯微信单笔提现不得大于10万元!');
  251. }
  252. break;
  253. case Withdraw::WITHDRAW_WITH_HIGH_LIGHT_ALIPAY:
  254. if ($this->amount > 400000) {
  255. throw new \Exception('高灯微信单笔提现不得大于40万元!');
  256. }
  257. break;
  258. case Withdraw::WITHDRAW_WITH_HIGH_LIGHT_BANK:
  259. if ($this->amount > 100000) {
  260. throw new \Exception('高灯银行卡单笔提现不得大于10万元!');
  261. }
  262. break;
  263. }
  264. } catch (\Exception $e) {
  265. return $this->errorJson($e->getMessage());
  266. }
  267. }
  268. }
  269. private function withdrawLimitation()
  270. {
  271. if (app('plugins')->isEnabled('commission')) {
  272. $set = \Setting::get('plugin.commission');
  273. $agent = Agents::uniacid()->where('member_id', \YunShop::app()->getMemberId())->with('agentLevel')->first();
  274. if (!$agent->agent_level_id) {
  275. if ($set['no_withdraw']) {
  276. return $this->errorJson('不满足分销商等级,不可提现', ['status' => 0]);
  277. }
  278. } else {
  279. if ($agent->agentLevel->no_withdraw) {
  280. return $this->errorJson('不满足分销商等级,不可提现', ['status' => 0]);
  281. }
  282. }
  283. }
  284. $this->validateWithdrawDate();
  285. }
  286. private function withdrawStart()
  287. {
  288. try {
  289. DB::transaction(function () {
  290. $this->_withdrawStart();
  291. });
  292. return true;
  293. } catch (\Exception $exception) {
  294. throw $exception;
  295. }
  296. }
  297. /**
  298. * @return bool
  299. * @throws AppException|
  300. */
  301. private function _withdrawStart()
  302. {
  303. $amount = '0';
  304. if (count($this->withdraw_data) > 1) { // 如果同时提现几种类型的收入并且后台设置了劳务税金额梯度比例,劳务税按金额总和计算
  305. if ($this->withdraw_set['servicetax']) {
  306. $merge_servicetax_withdraw_id = []; //劳务税id
  307. $merge_servicetax_amount = 0; //劳务税计算金额
  308. }
  309. }
  310. foreach ($this->withdraw_data as $key => $item) {
  311. $withdrawModel = new Withdraw();
  312. $withdrawModel->mark = $item['key_name'];
  313. $withdrawModel->withdraw_set = $this->withdraw_set;
  314. $withdrawModel->income_set = $this->getIncomeSet($item['key_name']);
  315. $withdrawModel->fill($this->getWithdrawData($item));
  316. event(new WithdrawApplyEvent($withdrawModel));
  317. $validator = $withdrawModel->validator();
  318. if ($validator->fails()) {
  319. throw new AppException("ERROR:Data anomaly -- {$item['key_name']}::{$validator->messages()->first()}");
  320. }
  321. event(new WithdrawApplyingEvent($withdrawModel));
  322. if (!$withdrawModel->save()) {
  323. throw new AppException("ERROR:Data storage exception -- {$item['key_name']}");
  324. }
  325. //判断收入是否已提现
  326. $apply_count = WithdrawIncomeApply::whereIn('income_id', array_filter(explode(',', $item['type_id'])))->whereIn('status', [0, 1, -1])->lockForUpdate()->count();
  327. if ($apply_count > 0) {
  328. throw new AppException("ERROR:Data storage exception repeat-- {$item['key_name']}");
  329. }
  330. //插入提现收入申请表
  331. if (!WithdrawIncomeApplyService::insert($withdrawModel)) {
  332. throw new AppException("ERROR:Data storage exception -- {$item['key_name']}");
  333. }
  334. //插入提现收入扣除爱心值记录
  335. if (!empty($this->need_deduction_love_data) && !WithdrawIncomeDeductionService::insert($withdrawModel,$this->need_deduction_love_data,proportionMath($item['income'],$this->need_deduction_love_data['rate']))) {
  336. throw new AppException("ERROR:Data2 storage exception -- {$item['key_name']}");
  337. }
  338. app('plugins')->isEnabled('converge_pay') && $this->withdraw_set['free_audit'] == 1 && $this->pay_way == 'converge_pay' ? \Setting::set('plugin.convergePay_set.notifyWithdrawUrl', Url::shopSchemeUrl('payment/convergepay/notifyUrlWithdraw.php')) : null;
  339. event(new WithdrawAppliedEvent($withdrawModel));
  340. $amount = bcadd($amount, $withdrawModel->amounts, 2);
  341. if (isset($merge_servicetax_withdraw_id)
  342. && !in_array($item['key_name'], ['StoreCashier', 'StoreWithdraw', 'StoreBossWithdraw'])
  343. && ($withdrawModel->pay_way != 'balance' || !$this->withdraw_set['balance_special'])) { //统计需要劳务税的基本计算金额
  344. $merge_servicetax_withdraw_id[] = $withdrawModel->id;
  345. $this_servicetax_amount = !$this->withdraw_set['service_tax_calculation'] ? bcsub($withdrawModel->amounts, $withdrawModel->poundage, 2) : $withdrawModel->amounts;
  346. if (bccomp($this_servicetax_amount, 0, 2) != 1) $this_servicetax_amount = 0;
  347. $merge_servicetax_amount = bcadd($merge_servicetax_amount, $this_servicetax_amount, 2);
  348. }
  349. }
  350. if (!empty($merge_servicetax_withdraw_id)) {
  351. $service_tax_data = WithdrawService::getWithdrawServicetaxPercent($merge_servicetax_amount, $withdrawModel);
  352. if (bccomp($service_tax_data['servicetax_percent'], 0, 2) == 1) {
  353. $time = time();
  354. foreach ($merge_servicetax_withdraw_id as $v) {
  355. $service_tax_insert_data[] = [
  356. 'uniacid' => \YunShop::app()->uniacid,
  357. 'withdraw_id' => $v,
  358. 'servicetax_rate' => $service_tax_data['servicetax_percent'],
  359. 'created_at' => $time,
  360. 'updated_at' => $time
  361. ];
  362. }
  363. WithdrawMergeServicetaxRate::insert($service_tax_insert_data);
  364. }
  365. }
  366. if (bccomp($amount, $this->amount, 2) != 0) {
  367. throw new AppException('提现失败:提现金额错误');
  368. }
  369. return true;
  370. }
  371. /**
  372. * @param $withdraw_item
  373. * @return array
  374. * @throws AppException
  375. */
  376. private function getWithdrawData($withdraw_item)
  377. {
  378. //dd($withdraw_item);
  379. return [
  380. 'withdraw_sn' => Withdraw::createOrderSn('WS', 'withdraw_sn'),
  381. 'uniacid' => \YunShop::app()->uniacid,
  382. 'member_id' => $this->getMemberId(),
  383. 'type' => $withdraw_item['type'],
  384. 'type_name' => $withdraw_item['type_name'],
  385. 'type_id' => $withdraw_item['type_id'],
  386. 'amounts' => $withdraw_item['income'],
  387. 'poundage' => '0.00',
  388. 'poundage_rate' => '0.00',
  389. 'poundage_type' => $withdraw_item['poundage_type'] ?: 0,
  390. 'actual_poundage' => '0.00',
  391. 'actual_amounts' => '0.00',
  392. 'servicetax' => '0.00',
  393. 'servicetax_rate' => '0.00',
  394. 'actual_servicetax' => '0.00',
  395. 'pay_way' => $this->pay_way,
  396. 'manual_type' => !empty($this->withdraw_set['manual_type']) ? $this->withdraw_set['manual_type'] : 1,
  397. 'status' => Withdraw::STATUS_INITIAL,
  398. 'audit_at' => null,
  399. 'pay_at' => null,
  400. 'arrival_at' => null,
  401. 'created_at' => time(),
  402. 'updated_at' => time(),
  403. ];
  404. }
  405. /**
  406. * 提现对应收入设置
  407. *
  408. * @param $mark
  409. * @return array
  410. */
  411. private function getIncomeSet($mark)
  412. {
  413. return Setting::get('withdraw.' . $mark);
  414. }
  415. /**
  416. * 提现设置
  417. *
  418. * @return array
  419. */
  420. private function getWithdrawSet()
  421. {
  422. return Setting::get('withdraw.income');
  423. }
  424. /**
  425. * @return array
  426. * @throws AppException
  427. */
  428. private function getPostValue()
  429. {
  430. $post_data = \YunShop::request()->data;
  431. Log::info('收入提现提交数据:', [$post_data]);
  432. //$post_data = $this->testData();
  433. !is_array($post_data) && $post_data = json_decode($post_data, true);
  434. if (!$post_data) {
  435. throw new AppException('Undetected submission of data');
  436. }
  437. // 12月20号修改 提现原代码是提现金额不能小于1元
  438. if ($post_data['total']['amounts'] < 0) {
  439. throw new AppException('提现金额不能小于0元');
  440. }
  441. $amount = $post_data['total']['amounts'];
  442. $pay_way = $post_data['total']['pay_way'];
  443. $poundage = $post_data['total']['poundage'];
  444. $withdraw_data = $post_data['withdrawal'];
  445. return [$amount, $pay_way, $poundage, $withdraw_data];
  446. }
  447. /**
  448. * @return int
  449. * @throws AppException
  450. */
  451. private function getMemberId()
  452. {
  453. $member_id = \YunShop::app()->getMemberId();
  454. if (!$member_id) {
  455. throw new AppException('Please log in');
  456. }
  457. return $member_id;
  458. }
  459. private function testData()
  460. {
  461. $data = [
  462. 'total' => [
  463. 'amounts' => 1816.01,
  464. 'poundage' => 181.6,
  465. 'pay_way' => 'balance',
  466. ],
  467. 'withdrawal' => [
  468. [
  469. 'type' => 'Yunshop\ConsumeReturn\common\models\Log',
  470. 'key_name' => 'consumeReturn',
  471. 'type_name' => '消费返现',
  472. 'type_id' => '7223,7319,7408,7477,7605,7680,7808,7881,7973,8048,8137,8205,8274,8401,8535,8670,8721,8805,8877,9030,9145,9237,9325,9403,9477,9554,9755,9837,9919,10012,10101,10184,10374,10528,10650,10760,10858',
  473. 'income' => '12032.92',
  474. 'poundage' => '12.03',
  475. 'poundage_rate' => '0.1',
  476. 'servicetax' => '1202.08',
  477. 'servicetax_rate' => '10',
  478. 'can' => '1',
  479. 'roll_out_limit' => '0',
  480. 'selected' => 1,
  481. ],
  482. [
  483. 'type' => 'Yunshop\LevelReturn\models\LevelReturnModel',
  484. 'key_name' => 'levelReturn',
  485. 'type_name' => '等级返现',
  486. 'type_id' => '7426,7481,7556,7883,7884,7885,7886,8222,8223,8224,8281,8360,8552,8895,8954,8955,8956,8957,9107,9621,10598,10599,10784,10785,10786,10989',
  487. 'income' => '20241.59',
  488. 'poundage' => '20.24',
  489. 'poundage_rate' => '0.1',
  490. 'servicetax' => '2022.13',
  491. 'servicetax_rate' => '10',
  492. 'can' => '1',
  493. 'roll_out_limit' => '10',
  494. 'selected' => 1,
  495. ]
  496. ]
  497. ];
  498. return $data;
  499. }
  500. private function validateData()
  501. {
  502. $member_id = $this->getMemberId();
  503. //对比提现的收入记录是否属于该会员
  504. foreach ($this->withdraw_data as $withdraw) {
  505. $income_ids = array_filter(explode(',', $withdraw['type_id']));
  506. $income_count = Income::where('member_id', $member_id)->whereIn('id', $income_ids)->count();
  507. //判断收入是否已提现
  508. $apply_count = WithdrawIncomeApply::whereIn('income_id', $income_ids)->whereIn('status', [0, 1, -1])->count();
  509. if ($income_count != count($income_ids) || $apply_count > 0) {
  510. return $this->errorJson('提现数据错误');
  511. }
  512. }
  513. }
  514. private function validateWithdrawDate()
  515. {
  516. $income_set = \Setting::get('withdraw.income');
  517. $disable = 0;
  518. $day_msg = '无提现限制';
  519. if (is_array($income_set['withdraw_date'])) {
  520. $day = date('d');
  521. $day_msg = '可提现日期为:' . implode(',', $income_set['withdraw_date']) . '号';
  522. $disable = 1;
  523. foreach ($income_set['withdraw_date'] as $date) {
  524. if ($day == $date) {
  525. $disable = 0;
  526. break;
  527. }
  528. if ($day < $date) {
  529. $disable = 1;
  530. }
  531. }
  532. }
  533. if ($disable == 1) {
  534. return $this->errorJson($day_msg, ['status' => 0]);
  535. }
  536. }
  537. }