UserWalletService.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. <?php
  2. namespace services\common;
  3. use common\components\Service;
  4. use common\enums\UserWalletEnum;
  5. use common\events\census\ShareEvent;
  6. use common\events\user\UserWalletEvent;
  7. use common\helpers\TaskHelper;
  8. use common\helpers\LockHelper;
  9. use common\logic\wechat\WechatMessageLogic;
  10. use common\models\common\CapitalErrorLog;
  11. use common\models\common\CapitalLogRelation;
  12. use common\models\common\FrozenCommissionLog;
  13. use common\models\user\UserWallet;
  14. use common\models\common\CapitalLog;
  15. use services\common\RabbitMqService;
  16. use common\enums\RedisKeyEnum;
  17. use common\enums\RabbitMqEnum;
  18. use common\enums\StatusEnum;
  19. use common\models\user\UserFreezeCommission;
  20. use common\models\user\UserInfo;
  21. use common\traits\ErrorTrait;
  22. use common\models\user\UserWithdrawLog;
  23. use Exception;
  24. use Yii;
  25. use yii\helpers\Json;
  26. /**
  27. *
  28. * 用户资金服务类
  29. * Date: 2021/10/08
  30. * Time: 14:56
  31. */
  32. class UserWalletService extends Service
  33. {
  34. use ErrorTrait;
  35. //钱包操作类型
  36. const WALLET_TYPE_SCORE = 'score';
  37. const WALLET_TYPE_FROZEN_SCORE = 'frozen_score';
  38. const WALLET_TYPE_BALANCE = 'balance';
  39. const WALLET_TYPE_FROZEN_BALANCE = 'frozen_balance';
  40. const WALLET_TYPE_COMMISSION = 'commission';
  41. const WALLET_TYPE_FROZEN_COMMISSION = 'frozen_commission';
  42. /**
  43. * 获取资金类型
  44. * @return array
  45. */
  46. public static function getWalletType()
  47. {
  48. return [
  49. self::WALLET_TYPE_SCORE,
  50. self::WALLET_TYPE_BALANCE,
  51. self::WALLET_TYPE_COMMISSION
  52. ];
  53. }
  54. /**
  55. * 获取资金类型描述
  56. * @param $type
  57. * @return mixed|string
  58. */
  59. public static function getWalletTypeMsg($type)
  60. {
  61. $arr = [
  62. self::WALLET_TYPE_SCORE => '积分',
  63. self::WALLET_TYPE_FROZEN_SCORE => '冻结积分',
  64. self::WALLET_TYPE_BALANCE => '余额',
  65. self::WALLET_TYPE_FROZEN_BALANCE => '冻结余额',
  66. self::WALLET_TYPE_COMMISSION => '佣金',
  67. self::WALLET_TYPE_FROZEN_COMMISSION => '冻结佣金',
  68. ];
  69. return $arr[$type] ?? '';
  70. }
  71. /**
  72. * 异步队列操作用户钱包
  73. * @param int $mall_id
  74. * @param int $user_id
  75. * @param string $wallet_type 操作类型:score,balance,commission
  76. * @param float $money 操作金额,负数:减少/正数:增加
  77. * @param bool $is_frozen 是否操作冻结资金
  78. * @param string $desc 操作描述
  79. * @param string $source 来源
  80. * @param int $source_id 来源业务ID
  81. * @param bool $wait_result 是否阻塞,等待结果
  82. * @throws
  83. * @return bool
  84. */
  85. public static function handleByQueue($mall_id, $user_id, $wallet_type, $money, $is_frozen = false, $desc = '', $source = '', $source_id = 0, $wait_result = true)
  86. {
  87. if (!in_array($wallet_type, [self::WALLET_TYPE_SCORE, self::WALLET_TYPE_BALANCE, self::WALLET_TYPE_COMMISSION])) {
  88. throw new Exception('操作类型不合法');
  89. }
  90. $task_id = TaskHelper::pushTaskQueue(
  91. RabbitMqEnum::EXCHANGE_TASK_USER_WALLET,
  92. RabbitMqEnum::USER_WALLET_QUEUE,
  93. ['mall_id' => $mall_id, 'user_id' => $user_id, 'wallet_type' => $wallet_type, 'money' => $money, 'is_frozen' => $is_frozen, 'desc' => $desc, 'source' => $source, 'source_id' => $source_id],
  94. __CLASS__,
  95. 'handle'
  96. );
  97. if (!$task_id) {
  98. throw new Exception('操作失败');
  99. }
  100. $result = true;
  101. //block for result
  102. if ($wait_result) {
  103. $hasWait = 0; //已经等待的时间:毫秒
  104. $max_wait_time = 3 * 1000; //最长等待 3s
  105. while (true) {
  106. $result = self::getHandleResult($task_id);
  107. if ($result) {
  108. break;
  109. }
  110. usleep(30 * 1000);//等待30ms
  111. $hasWait += 30;
  112. if ($hasWait > $max_wait_time) {
  113. //等待超时
  114. $result = false;
  115. break;
  116. }
  117. }
  118. }
  119. //注意:返回false 表示正在等待处理
  120. return $result;
  121. }
  122. /**
  123. * 异步队列-批量操作用户钱包
  124. * @param $data
  125. * [
  126. * ['mall_id' => $mall_id,'user_id'=>1,'is_frozen'=>true,'not_add_total'=>true,'wallet_type'=>'balance','money'=>1,'desc'=> '操作描述','source_table' => $source,'source_table_id' => $source_id,'from_type'=>$from_type','capital_type'=>$capital_type'],
  127. * ['mall_id' => $mall_id,'user_id'=>2,'is_frozen'=>false,'not_add_total'=>false,'wallet_type'=>'balance','money'=>1,'desc'=> '操作描述','source_table' => $source,'source_table_id' => $source_id,'from_type'=>$from_type','capital_type'=>$capital_type'],
  128. * ]
  129. * @return bool
  130. * @throws Exception
  131. */
  132. public static function batchHandleByQueue($data)
  133. {
  134. $capital_types = self::getWalletType();
  135. $function = 'batchHandle';
  136. //判断data 数据格式
  137. foreach ($data as $val) {
  138. if (empty($val['user_id'])) {
  139. throw new Exception('参数不合法 ,user_id 不能为空');
  140. }
  141. if (empty($val['mall_id'])) {
  142. throw new Exception('参数不合法 ,mall_id 不能为空');
  143. }
  144. if (!in_array($val['wallet_type'], $capital_types)) {
  145. throw new Exception('操作类型不合法');
  146. }
  147. $change_type[] = $val['money'] > 0 ? 'add' : 'sub';
  148. }
  149. //操作类型不一致
  150. $change_type = array_unique($change_type);
  151. if(count($change_type) > 1) $function = 'batchHandleByTransaction';
  152. $task_id = TaskHelper::pushTaskQueue(
  153. RabbitMqEnum::EXCHANGE_TASK_USER_WALLET,
  154. RabbitMqEnum::USER_WALLET_QUEUE,
  155. $data,
  156. __CLASS__,
  157. $function
  158. );
  159. if (!$task_id) {
  160. throw new Exception('操作失败');
  161. }
  162. return true;
  163. }
  164. /**
  165. * 批量操作用户钱包
  166. * @param int $task_id mq消费message_id
  167. * @param array $data
  168. * [
  169. * ['mall_id' => $mall_id,'user_id'=>1,'is_frozen'=>true,'not_add_total'=>true,'wallet_type'=>'balance','money'=>1,'desc'=> '操作描述','source_table' => $source,'source_table_id' => $source_id,'from_type'=>$from_type','capital_type'=>$capital_type','order_no'=>$order_no,'contributor_name'=>$contributor_name,'contributor_id'=>$contributor_id,'contributor_money'=>$contributor_money],
  170. * ['mall_id' => $mall_id,'user_id'=>2,'is_frozen'=>false,'not_add_total'=>false,'wallet_type'=>'balance','money'=>1,'desc'=> '操作描述','source_table' => $source,'source_table_id' => $source_id,'from_type'=>$from_type','capital_type'=>$capital_type','order_no'=>$order_no,'contributor_name'=>$contributor_name,'contributor_id'=>$contributor_id,'contributor_money'=>$contributor_money],
  171. *
  172. * ]
  173. * @throws
  174. * @return bool
  175. *
  176. */
  177. public static function batchHandle($data)
  178. {
  179. $task_id = 0;
  180. if(!empty($data['message_id'])){
  181. $task_id = $data['message_id'];
  182. unset($data['message_id']);
  183. }
  184. $user_handles = [];
  185. $capital_log_list = [];// 提交数据记录类型
  186. foreach ($data as $val) {
  187. $wallet_type = empty($val['is_frozen']) ? $val['wallet_type'] : 'frozen_'.$val['wallet_type'];
  188. $capital_log_type = empty($val['is_frozen']) ? $val['wallet_type'] : $val['wallet_type'] . '_frozen';
  189. if (!in_array($capital_log_type, $capital_log_list)) {
  190. $capital_log_list[] = $capital_log_type;
  191. }
  192. $user_handles[$val['user_id']][$wallet_type][] = $val;
  193. }
  194. $error = [];
  195. $time = time();
  196. $user_wallet_list = [];
  197. $mall_id = $data[0]['mall_id'];
  198. foreach ($user_handles as $user_id => $handle) {
  199. $lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_USER_WALLET , $user_id);
  200. $identification = uniqid();
  201. if (!LockHelper::lock($lock_key, $identification, 100, 100)) {
  202. Yii::error(__CLASS__ . ' batchHandle error : 抢不到锁,task_id:' . $task_id);
  203. continue;
  204. }
  205. $user_wallet_model = UserWallet::findOne(['user_id' => $user_id, 'status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]]);
  206. if (empty($user_wallet_model)) {
  207. $error[] = ['user_id' => $user_id, 'type' => '', 'params' => json_encode($handle), 'error_msg' => '用户钱包不存在'];
  208. continue;
  209. }
  210. foreach ($handle as $wallet_type => $logs) {
  211. $is_frozen = false;
  212. if (strpos($wallet_type, 'frozen') !== false) {
  213. $is_frozen = true;
  214. }
  215. //求和
  216. $money = array_sum(array_column($logs, 'money'));
  217. if (!is_numeric($money) || empty($money)) {
  218. continue;
  219. }
  220. $current_num = $user_wallet_model->$wallet_type;
  221. /*if ($current_num + $money < 0) {
  222. $error[] = ['user_id' => $user_id, 'type' => $wallet_type, 'params' => json_encode($logs), 'error_msg' => '用户' . self::getWalletTypeMsg($wallet_type) . '不足'];
  223. continue;
  224. }*/
  225. foreach ($logs as $key => $log) {
  226. //操作非冻结资金
  227. $wallet_type = $log['wallet_type'];
  228. $frozen_field = 'frozen_'.$wallet_type;
  229. $total_field = 'total_' . $wallet_type;
  230. $total_use_field = 'total_use_' . $wallet_type;
  231. if ($log['is_frozen']) {
  232. $user_wallet_model->$frozen_field += $log['money'];//冻结
  233. }else{
  234. $user_wallet_model->$wallet_type += $log['money'];//非冻结
  235. }
  236. if ($log['is_frozen']==false&&empty($log['not_add_total'])) {
  237. $log['money'] > 0 && $user_wallet_model->$total_field += $log['money'];//总数
  238. }
  239. if ($log['capital_type'] != UserWalletEnum::WITHDRAWAL_DECR) {
  240. $log['is_frozen'] == false && $log['money'] < 0 && $user_wallet_model->$total_use_field += abs($log['money']);//已使用
  241. }
  242. }
  243. $trans = Yii::$app->db->beginTransaction();
  244. if (!$user_wallet_model->save()) {
  245. $error[] = ['user_id' => $user_id, 'wallet_type' => $wallet_type, 'logs' => $logs, 'error_msg' => $user_wallet_model->getErrorMessage()];
  246. $trans->rollBack();
  247. continue;
  248. }
  249. $values_arr = [];
  250. foreach ($logs as $key => $log) {
  251. if($log['is_frozen']) $is_frozen = true;
  252. $info = [
  253. $log['mall_id'],
  254. $log['user_id'],
  255. $log['money'] >= 0 ? CapitalLog::CHANGE_TYPE_ADD : CapitalLog::CHANGE_TYPE_SUB,
  256. abs($log['money']),
  257. $current_num,
  258. $log['desc'],
  259. $log['source_table'],
  260. $log['source_table_id'],
  261. $log['from_type'],
  262. $log['capital_type'],
  263. $time,
  264. $time,
  265. $log['order_no']??'',
  266. $log['contributor_name']??'',
  267. $log['contributor_money']??0,
  268. $log['contributor_id']??0,
  269. ];
  270. $is_frozen && $info[] = CapitalLog::STATUS_WAIT_SEND;
  271. if(isset($log['service_charge'])){
  272. $info[] = $log['service_charge'];
  273. }
  274. $values_arr[] = $info;
  275. $current_num = $current_num + $log['money'];
  276. }
  277. $fields = ['mall_id', 'user_id', 'change_type', 'change_num', 'current_num', 'desc', 'source_table', 'source_table_id','from_type', 'capital_type','created_at', 'updated_at','order_no','contributor_name','contributor_money','contributor_id'];
  278. if ($is_frozen) {
  279. //操作冻结资金
  280. CapitalLog::$capitalType = str_replace('frozen_', '', $wallet_type) . '_frozen';
  281. $fields[] = 'status';
  282. } else {
  283. CapitalLog::$capitalType = $wallet_type;
  284. }
  285. if(isset($log['service_charge'])){
  286. $fields[] = 'service_charge';
  287. }
  288. Yii::$app->db->createCommand()->batchInsert(
  289. CapitalLog::tableName(),
  290. $fields,
  291. $values_arr
  292. )->execute();
  293. if(UserFreezeCommission::setLogData($user_id, $logs) === false) {
  294. $error[] = ['user_id' => $user_id, 'wallet_type' => $wallet_type, 'logs' => $logs, 'error_msg' => 'from qimall_user_freeze_commission err: '.UserFreezeCommission::getStaticError()];
  295. }
  296. // 重新编写用户金额管理
  297. $user_wallet_list[] = ['user_id' => $user_id, 'wallet_type' => $wallet_type, 'logs' => $logs];
  298. $trans->commit();
  299. }
  300. LockHelper::uLock($lock_key, $identification);
  301. }
  302. // 操作积分-余额-佣金 日志
  303. foreach ($capital_log_list as $item){
  304. $log_value = ['time' => $time, 'capitalType' => $item];
  305. \Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_TASK, RabbitMqEnum::TASK_QUEUE, $log_value, CapitalLogRelation::class, 'addLog');
  306. }
  307. $event = new UserWalletEvent($mall_id);
  308. \Yii::$app->services->events->dispatch($event, ['mall_id' => $mall_id, 'user_wallet_list' => $user_wallet_list], $event->listeners);
  309. if ($error) {
  310. //记录错误信息
  311. foreach ($error as $err) {
  312. $log = new CapitalErrorLog();
  313. $log->attributes = $err;
  314. $log->save();
  315. }
  316. }
  317. return true;
  318. }
  319. /**
  320. * 批量操作用户钱包 强一致性【在一个事务内完成 所有操作】
  321. * @param int $task_id mq消费message_id
  322. * @param array $data
  323. * [
  324. * ['mall_id' => $mall_id,'user_id'=>1,'is_frozen'=>true,'not_add_total'=>true,'wallet_type'=>'balance','money'=>1,'desc'=> '操作描述','source_table' => $source,'source_table_id' => $source_id,'from_type'=>$from_type','capital_type'=>$capital_type','order_no'=>$order_no,'contributor_name'=>$contributor_name,'contributor_id'=>$contributor_id,'contributor_money'=>$contributor_money],
  325. * ['mall_id' => $mall_id,'user_id'=>2,'is_frozen'=>false,'not_add_total'=>false,'wallet_type'=>'balance','money'=>1,'desc'=> '操作描述','source_table' => $source,'source_table_id' => $source_id,'from_type'=>$from_type','capital_type'=>$capital_type','order_no'=>$order_no,'contributor_name'=>$contributor_name,'contributor_id'=>$contributor_id,'contributor_money'=>$contributor_money],
  326. *
  327. * ]
  328. * @throws
  329. * @return bool
  330. *
  331. */
  332. public static function batchHandleByTransaction($data)
  333. {
  334. $trans = Yii::$app->db->beginTransaction();
  335. try{
  336. $task_id = 0;
  337. if(!empty($data['message_id'])){
  338. $task_id = $data['message_id'];
  339. unset($data['message_id']);
  340. }
  341. Yii::error(__CLASS__ . ' batchHandleByTransaction task_id : ,task_id:' . $task_id);
  342. $user_handles = [];
  343. $capital_log_list = [];// 提交数据记录类型
  344. foreach ($data as $val) {
  345. $wallet_type = empty($val['is_frozen']) ? $val['wallet_type'] : 'frozen_'.$val['wallet_type'];
  346. $capital_log_type = empty($val['is_frozen']) ? $val['wallet_type'] : $val['wallet_type'] . '_frozen';
  347. if (!in_array($capital_log_type, $capital_log_list)) {
  348. $capital_log_list[] = $capital_log_type;
  349. }
  350. $user_handles[$val['user_id']][$wallet_type][] = $val;
  351. }
  352. // 因为所有传来列表mall_id相同 所以拿取第一个数组的mall_id赋值
  353. $mall_id = $data[0]['mall_id'];
  354. $user_wallet_list = [];
  355. $time = time();
  356. foreach ($user_handles as $user_id => $handle) {
  357. $lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_USER_WALLET , $user_id);
  358. $identification = uniqid();
  359. if (!LockHelper::lock($lock_key, $identification, 100, 100)) {
  360. throw new Exception(__CLASS__ . ' batchHandle error : 抢不到锁,task_id:' . $task_id);
  361. }
  362. $user_wallet_model = UserWallet::findOne(['user_id' => $user_id, 'status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]]);
  363. if (empty($user_wallet_model)) {
  364. LockHelper::uLock($lock_key, $identification);
  365. throw new Exception("{$user_id}用户钱包不存在");
  366. }
  367. foreach ($handle as $wallet_type => $logs) {
  368. $is_frozen = false;
  369. if (strpos($wallet_type, 'frozen') !== false) {
  370. $is_frozen = true;
  371. }
  372. //求和
  373. $money = array_sum(array_column($logs, 'money'));
  374. if (!is_numeric($money) || empty($money)) {
  375. continue;
  376. }
  377. $current_num = $user_wallet_model->$wallet_type;
  378. /*因为新需求可以导入负数所以注释此段代码
  379. if ($current_num + $money < 0) {
  380. LockHelper::uLock($lock_key, $identification);
  381. throw new Exception("{$user_id}用户".self::getWalletTypeMsg($wallet_type)."不足");
  382. //$error[] = ['user_id' => $user_id, 'type' => $wallet_type, 'params' => json_encode($logs), 'error_msg' => '用户' . self::getWalletTypeMsg($wallet_type) . '不足'];
  383. }*/
  384. foreach ($logs as $key => $log) {
  385. //操作非冻结资金
  386. $wallet_type = $log['wallet_type'];
  387. $frozen_field = 'frozen_'.$wallet_type;
  388. $total_field = 'total_' . $wallet_type;
  389. $total_use_field = 'total_use_' . $wallet_type;
  390. if ($log['is_frozen']) {
  391. $user_wallet_model->$frozen_field += $log['money'];//冻结
  392. }else{
  393. $user_wallet_model->$wallet_type += $log['money'];//非冻结
  394. }
  395. if ($log['is_frozen']==false&&empty($log['not_add_total'])) {
  396. $log['money'] > 0 && $user_wallet_model->$total_field += $log['money'];//总数
  397. }
  398. if ($log['capital_type'] != UserWalletEnum::WITHDRAWAL_DECR) {
  399. $log['is_frozen'] == false && $log['money'] < 0 && $user_wallet_model->$total_use_field += abs($log['money']);//已使用
  400. }
  401. }
  402. if (!$user_wallet_model->save()) {
  403. LockHelper::uLock($lock_key, $identification);
  404. throw new Exception("{$user_id}用户".self::getWalletTypeMsg($wallet_type)." error".$user_wallet_model->getErrorMessage());
  405. }
  406. $values_arr = [];
  407. foreach ($logs as $key => $log) {
  408. if($log['is_frozen']) $is_frozen = true;
  409. $info = [
  410. $log['mall_id'],
  411. $log['user_id'],
  412. $log['money'] >= 0 ? CapitalLog::CHANGE_TYPE_ADD : CapitalLog::CHANGE_TYPE_SUB,
  413. abs($log['money']),
  414. $current_num,
  415. $log['desc'],
  416. $log['source_table'],
  417. $log['source_table_id'],
  418. $log['from_type'],
  419. $log['capital_type'],
  420. $time,
  421. $time,
  422. $log['order_no']??'',
  423. $log['contributor_name']??'',
  424. $log['contributor_money']??0,
  425. $log['contributor_id']??0,
  426. ];
  427. $is_frozen && $info[] = CapitalLog::STATUS_WAIT_SEND;
  428. if(isset($log['service_charge'])){
  429. $info[] = $log['service_charge'];
  430. }
  431. $values_arr[] = $info;
  432. $current_num = $current_num + $log['money'];
  433. }
  434. $fields = ['mall_id', 'user_id', 'change_type', 'change_num', 'current_num', 'desc', 'source_table', 'source_table_id','from_type', 'capital_type','created_at', 'updated_at','order_no','contributor_name','contributor_money','contributor_id'];
  435. if ($is_frozen) {
  436. //操作冻结资金
  437. CapitalLog::$capitalType = str_replace('frozen_', '', $wallet_type) . '_frozen';
  438. $fields[] = 'status';
  439. } else {
  440. CapitalLog::$capitalType = $wallet_type;
  441. }
  442. if(isset($log['service_charge'])){
  443. $fields[] = 'service_charge';
  444. }
  445. Yii::$app->db->createCommand()->batchInsert(
  446. CapitalLog::tableName(),
  447. $fields,
  448. $values_arr
  449. )->execute();
  450. if(UserFreezeCommission::setLogData($user_id, $logs) === false) {
  451. \Yii::error(__METHOD__.' from qimall_user_freeze_commission err: '.UserFreezeCommission::getStaticError());
  452. }
  453. $user_wallet_list[] = ['user_id' => $user_id, 'wallet_type' => $wallet_type, 'logs' => $logs];
  454. }
  455. LockHelper::uLock($lock_key, $identification);
  456. }
  457. $trans->commit();
  458. // 操作积分-余额-佣金 日志
  459. foreach ($capital_log_list as $item){
  460. $log_value = ['time' => $time, 'capitalType' => $item];
  461. \Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_TASK, RabbitMqEnum::TASK_QUEUE, $log_value, CapitalLogRelation::class, 'addLog');
  462. }
  463. // 修改事务请求
  464. $event = new UserWalletEvent($mall_id);
  465. \Yii::$app->services->events->dispatch($event, ['mall_id'=>$mall_id, 'user_wallet_list' => $user_wallet_list], $event->listeners);
  466. }catch(Exception $e){
  467. $trans->rollBack();
  468. Yii::error(__CLASS__ . ' batchHandleByTransaction exception : ' . $e->getMessage() . ',task_id:' . $task_id);
  469. self::$static_error = $e->getMessage();
  470. return ;
  471. }
  472. return true;
  473. }
  474. /**
  475. * 直接操作用户钱包
  476. *
  477. * @param int $task_id
  478. * @param array $data
  479. * ['mall_id' => $mall_id,'user_id'=>1,'is_frozen'=>true,'not_add_total'=>true,'wallet_type'=>'balance','money'=>1,'desc'=> '操作描述','source_table' => $source,'source_table_id' => $source_id,'from_type'=>$from_type','capital_type'=>$capital_type','order_no'=>$order_no,'contributor_name'=>$contributor_name,'contributor_id'=>$contributor_id,'contributor_money'=>$contributor_money],
  480. * @param boolean $is_trans 是否启用事务
  481. * @return bool
  482. */
  483. public static function handle($task_id, $data, bool $is_trans = true)
  484. {
  485. $task_id = 0;
  486. if(!empty($data['message_id'])){
  487. $task_id = $data['message_id'];
  488. unset($data['message_id']);
  489. }
  490. $user_id = $data['user_id'];
  491. $wallet_type = $data['wallet_type'];
  492. $is_frozen = $data['is_frozen'] ?? false; //是否操作冻结资金
  493. $unfrozen = $data['unfrozen'] ?? false; //是否操作解冻资金
  494. $capital_types = self::getWalletType();
  495. if (!in_array($wallet_type, $capital_types)) {
  496. self::$static_error = '操作类型有误';
  497. return false;
  498. }
  499. //lock
  500. $lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_USER_WALLET , $user_id);
  501. $identification = uniqid();
  502. if (LockHelper::lock($lock_key, $identification, 100, 100)) {
  503. $is_trans && $t = \Yii::$app->db->beginTransaction();
  504. try {
  505. $edit_field = empty($is_frozen) ? $wallet_type : 'frozen_' . $wallet_type;
  506. $edit_use_field = 'total_use_' . $wallet_type;
  507. //修改用户钱包表
  508. $user_wallet_model = UserWallet::findOne(['user_id' => $user_id, 'status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]]);
  509. if (empty($user_wallet_model)) throw new Exception('服务不存在或已删除');
  510. $current_num = $user_wallet_model->$edit_field;
  511. if ($data['money'] < 0 && bcadd($current_num, $data['money'],2) < 0) {
  512. throw new Exception('用户' . self::getWalletTypeMsg($edit_field) . '不足');
  513. }
  514. $frozen_field = 'frozen_' . $edit_field;
  515. if($unfrozen && abs($user_wallet_model->$frozen_field) < $data['money']){
  516. throw new Exception('用户' . self::getWalletTypeMsg($frozen_field) . '不足');
  517. }
  518. $user_wallet_model->$edit_field += $data['money'];
  519. if ($data['capital_type'] != UserWalletEnum::WITHDRAWAL_DECR) {
  520. empty($is_frozen) && $data['money'] < 0 && $user_wallet_model->$edit_use_field += abs($data['money']);
  521. }
  522. if (strpos($edit_field, 'frozen') === false && $data['money'] > 0) {
  523. $total_field = 'total_' . $edit_field;
  524. if (empty($data['no_change_field']) || !in_array($total_field, $data['no_change_field'])){
  525. $user_wallet_model->$total_field += $data['money'];
  526. }
  527. if($unfrozen){
  528. $frozen_field = 'frozen_' . $edit_field;
  529. $user_wallet_model->$frozen_field -= $data['money'];
  530. }
  531. }
  532. if (!$user_wallet_model->save()) throw new Exception($user_wallet_model->getErrorMessage());
  533. $setData = [
  534. 'mall_id' => $data['mall_id'],
  535. 'user_id' => $data['user_id'],
  536. 'change_type' => $data['money'] >= 0 ? CapitalLog::CHANGE_TYPE_ADD : CapitalLog::CHANGE_TYPE_SUB,
  537. 'change_num' => abs($data['money']),
  538. 'current_num' => $current_num,
  539. 'desc' => $data['desc'] ?? '',
  540. 'source_table' => $data['source_table'] ?? '',
  541. 'source_table_id' => $data['source_table_id'] ?? 0,
  542. 'from_type' => $data['from_type'] ?? '',
  543. 'capital_type' => $data['capital_type'] ?? '',
  544. 'order_no' => $data['order_no'] ?? '',
  545. 'contributor_name' => $data['contributor_name'] ?? '',
  546. 'contributor_id' => $data['contributor_id'] ?? 0,
  547. 'contributor_money' => $data['contributor_money'] ?? 0,
  548. ];
  549. if(isset($data['change_type'])) {
  550. $setData['change_type'] = $data['change_type'];
  551. }
  552. if ($is_frozen) {
  553. //操作冻结资金
  554. CapitalLog::$capitalType = $wallet_type . '_frozen';
  555. $setData['status'] = CapitalLog::STATUS_WAIT_SEND;
  556. } else {
  557. CapitalLog::$capitalType = $wallet_type;
  558. }
  559. $is_send_message = false;
  560. if ($is_frozen && $data['money'] < 0 && !$unfrozen) {
  561. //取消冻结资金 不添加记录 add by zmz
  562. }else{
  563. $is_send_message = true;
  564. $log_res = CapitalLog::setData($setData);
  565. if (!$log_res) {
  566. throw new Exception(CapitalLog::getStaticError());
  567. }
  568. if(isset($data['service_charge'])){
  569. $log_res->service_charge = $data['service_charge'];
  570. $log_res->save();
  571. }
  572. }
  573. if(UserFreezeCommission::setLogData($user_id, [$data]) === false) {
  574. \Yii::error(__METHOD__.' from qimall_user_freeze_commission err: '.UserFreezeCommission::getStaticError());
  575. }
  576. $is_trans && $t->commit();
  577. //佣金到账微信公众号消息通知
  578. if (CapitalLog::$capitalType == 'commission' && $data['money'] >= 0) {
  579. $obj = new WechatMessageLogic();
  580. $obj->OrderCommissionSettle(['mall_id' => $data['mall_id'], 'user_id' => $data['user_id'], 'money' => abs($data['money'])]);
  581. }
  582. //unlock
  583. LockHelper::uLock($lock_key, $identification);
  584. $task_id && self::setHandleResult($task_id, true);
  585. if($is_send_message||!$is_frozen){
  586. $send_message_data = [
  587. 'user_id'=>$user_id,
  588. 'money'=>$data['money'],
  589. 'wallet_type'=>$wallet_type,
  590. 'mall_id' => $data['mall_id'],
  591. 'change_type'=>$setData['change_type'],
  592. ];
  593. \Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_TASK, RabbitMqEnum::TASK_QUEUE, $send_message_data, WechatMessageLogic::class, 'receiptSendMessage');
  594. }
  595. return true;
  596. } catch (Exception $e) {
  597. Yii::error(__CLASS__ . ' handle exception : ' . $e->getMessage() . ',task_id:' . $task_id);
  598. $is_trans && $t->rollBack();
  599. LockHelper::uLock($lock_key, $identification);
  600. self::$static_error = $e->getMessage();
  601. return false;
  602. }
  603. } else {
  604. //抢不到锁
  605. Yii::error(__CLASS__ . ' handle error : 抢不到锁,task_id:' . $task_id);
  606. self::$static_error = '操作太频繁';
  607. return false;
  608. }
  609. }
  610. /**
  611. * 获取处理结果
  612. * @param $task_id
  613. * @return array|bool|null|string
  614. * @throws \yii\db\Exception
  615. */
  616. public static function getHandleResult($task_id)
  617. {
  618. $result_key = RabbitMqEnum::USER_WALLET_RESUALT_KEY_PREFIX . $task_id;
  619. $redis = \Yii::$app->redis;
  620. return $redis->executeCommand('GET', [$result_key]);
  621. }
  622. /**
  623. * 获取处理结果
  624. * @param $task_id
  625. * @param $result
  626. * @return array|bool|null|string
  627. * @throws \yii\db\Exception
  628. */
  629. public static function setHandleResult($task_id, $result)
  630. {
  631. $result_key = RabbitMqEnum::USER_WALLET_RESUALT_KEY_PREFIX . $task_id;
  632. $redis = \Yii::$app->redis;
  633. return $redis->executeCommand('SET', [$result_key, $result, 'EX', 86400]);
  634. }
  635. /**
  636. * 解冻资金:将冻结金额 转换成 正常金额
  637. * @param $log_id
  638. * @param $wallet_type
  639. * @param bool $is_send 是否发放
  640. * @return bool
  641. */
  642. public static function unfrozen($log_id, $wallet_type, $is_send = true)
  643. {
  644. try {
  645. $capital_types = self::getWalletType();
  646. if (!in_array($wallet_type, $capital_types)) {
  647. throw new Exception('操作类型有误');
  648. }
  649. CapitalLog::$capitalType = $wallet_type . '_frozen';
  650. $log = CapitalLog::findOne(['id' => $log_id, 'status' => CapitalLog::STATUS_WAIT_SEND]);
  651. if (empty($log)) {
  652. throw new Exception('操作记录不存在');
  653. }
  654. if ($is_send) {
  655. $log_arr = $log->toArray();
  656. $log_arr['wallet_type'] = $wallet_type;
  657. $log_arr['is_frozen'] = false;
  658. if ($log['change_type'] == 'sub') {
  659. $log_arr['money'] = bcmul($log_arr['change_num'], -1, 2);
  660. } else {
  661. $log_arr['money'] = $log_arr['change_num'];
  662. }
  663. $log_arr['unfrozen'] = true;
  664. $res = self::handle(0, $log_arr);
  665. if (!$res) {
  666. throw new Exception(self::$static_error);
  667. }
  668. $status = CapitalLog::STATUS_SEND;
  669. } else {
  670. $log_arr = $log->toArray();
  671. $log_arr['wallet_type'] = $wallet_type;
  672. $log_arr['is_frozen'] = true; //操作冻结
  673. $log_arr['money'] = $log_arr['change_num'] * -1;
  674. $log_arr['unfrozen'] = false; //非解冻
  675. $res = self::handle(0, $log_arr);
  676. $status = CapitalLog::STATUS_FAIL;
  677. }
  678. //修改记录状态
  679. CapitalLog::$capitalType = $wallet_type . '_frozen';
  680. if(!CapitalLog::updateAll(['status' =>$status ],['id' => $log_id])){
  681. throw new Exception($log->getErrorMessage());
  682. }
  683. return true;
  684. } catch (Exception $e) {
  685. Yii::error(__CLASS__ . ' unfrozen exception : ' . $e->getMessage());
  686. self::$static_error = $e->getMessage();
  687. return false;
  688. }
  689. }
  690. //获取用户上一次填写的提现信息
  691. public function getPrevInfo($mall_id, $user_id, $params)
  692. {
  693. try {
  694. $where = [
  695. ['mall_id' => $mall_id],
  696. ['user_id' => $user_id],
  697. ['from' => $params['from']],
  698. ['type' => $params['type']],
  699. ];
  700. if (!empty($params['source'])) $where[] = ['source' => $params['source']];
  701. // print_r($where);exit;
  702. $info = UserWithdrawLog::getOne($where, true, [], 'id desc');
  703. if (empty($info)) return [];
  704. $info['extra'] = json_decode($info['extra'], true);
  705. if (empty($info['extra']['bank_account_type'])) $info['extra']['bank_account_type'] = 1;
  706. return $info['extra'];
  707. } catch (\Exception $e) {
  708. throw new Exception($e->getMessage());
  709. }
  710. }
  711. }