RewardLogic.php 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
  1. <?php
  2. /*
  3. * @Descripttion: 触发推三返一奖励
  4. * @copyright: Copyright (c) 2021 广东七件事集团
  5. * @Author: lun
  6. * @Date: 2022-01-01 20:16:12
  7. */
  8. namespace addons\Rebate\common\logic;
  9. use addons\Coupon\common\enums\AddonsCouponEnum;
  10. use addons\Coupon\common\models\AddonsCoupon;
  11. use addons\Coupon\common\models\AddonsCouponUserRelate;
  12. use addons\Rebate\common\enums\RebateEnum;
  13. use addons\Rebate\common\models\Rebate;
  14. use addons\Rebate\common\models\RebateBuy;
  15. use addons\Rebate\common\models\RebateRecommendNum;
  16. use addons\Rebate\common\models\RebateRewardLog;
  17. use addons\Rebate\common\models\RebateStatistics;
  18. use addons\Rebate\common\models\RebateVirtualOrderLog;
  19. use addons\Rebate\common\services\SettingService;
  20. use common\enums\AddonsEnum;
  21. use common\enums\RabbitMqEnum;
  22. use common\enums\StatusEnum;
  23. use common\enums\UserWalletEnum;
  24. use common\models\common\AddonsSetting;
  25. use common\models\common\CapitalLog;
  26. use common\models\goods\Goods;
  27. use common\models\mall\MallAddons;
  28. use common\models\order\Order;
  29. use common\models\order\OrderDetail;
  30. use common\models\user\User;
  31. use common\traits\ErrorTrait;
  32. use services\common\UserWalletService;
  33. use yii\db\Exception;
  34. use Yii;
  35. use yii\helpers\Json;
  36. class RewardLogic
  37. {
  38. use ErrorTrait;
  39. /**
  40. * 奖励处理
  41. * @Author: lun
  42. * @DateTime: 2022/1/15 0015 9:51
  43. * @Copyright: copyright (c) 2021 广东七件事集团
  44. * @param $order_id
  45. * @param $settlement_type // 结算类型,1=支付完成,2=订单完成
  46. * @return bool
  47. */
  48. public static function execute($order_id, $settlement_type)
  49. {
  50. $t = Yii::$app->db->beginTransaction();
  51. try {
  52. // 获取订单内容
  53. $where = ['id' => $order_id];
  54. $order = Order::find()->select("id,user_id,mall_id,order_no,pay_time")
  55. ->where($where)
  56. ->with(['detail' => function($query){
  57. $query->select("id, order_id,goods_id,num,goods_price,goods_name,price,pic_url");
  58. }])
  59. ->asArray()->one();
  60. if(empty($order)) throw new Exception("订单不存在");
  61. $mall_id = $order['mall_id'];
  62. $user_id = $order['user_id'];
  63. // 获取推三返一基础配置内容
  64. $settingService = new SettingService(['mall_id' => $mall_id]);
  65. $config = $settingService->getSetting();
  66. $settlement_method = $config['settlement_method'];
  67. $addons_name = $config['addons_name'];
  68. // 获取推三返一的商品
  69. $rebate_list = Rebate::getRebateAllGoodsId($mall_id);
  70. $goods_arr = [];
  71. foreach ($rebate_list as $item) {
  72. $goods_arr[$item['item_id']][] = $item;
  73. }
  74. $order_settlement_status = 0;// 订单结算状态[0:未达成;1:已达成]
  75. if($settlement_method == $settlement_type) $order_settlement_status = 1;
  76. $reward_commission = [];
  77. $unfozenRebateBuys = [];
  78. foreach($order['detail'] as $order_detail) {
  79. // 检查该商品是否参与了推三返一活动
  80. if(!isset($goods_arr[$order_detail['goods_id']])) continue;
  81. foreach ($goods_arr[$order_detail['goods_id']] as $rebate) {
  82. // 获取消费者的直推上级
  83. $parent_id = User::find()->select('parent_id')->where(['id' => $user_id, 'mall_id' => $mall_id, 'status' => StatusEnum::ENABLED])->asArray()->scalar();
  84. if(empty($parent_id)) {
  85. continue;
  86. }
  87. if(empty($rebate['join_type'])){
  88. // 判断上级是否有权限
  89. if($rebate['initiate_type'] == RebateEnum::INITIATE_TYPE_NEW && Rebate::checkUserHasOrder($mall_id, $parent_id) !== false) {
  90. continue;
  91. }
  92. if($rebate['initiate_type'] == RebateEnum::INITIATE_TYPE_LEVEL && Rebate::checkUserLevel($parent_id, $rebate['initiate_level']) == false) {
  93. continue;
  94. }
  95. // 判断消费者是否符合权限
  96. if($rebate['consume_type'] == RebateEnum::CONSUME_TYPE_NEW) {
  97. $order_num = Rebate::checkUserHasOrder($mall_id, $user_id);
  98. if (!empty($order_num) && $order_num > 1) {
  99. continue;
  100. }
  101. }
  102. if($rebate['consume_type'] == RebateEnum::CONSUME_TYPE_LEVEL && Rebate::checkUserLevel($user_id, $rebate['consume_level']) == false) {
  103. continue;
  104. }
  105. }else{
  106. // 判断上级是否有权限
  107. if($rebate['initiate_type'] == RebateEnum::INITIATE_TYPE_NEW && Rebate::checkNewRegisterUser($mall_id, $parent_id) == false) {
  108. continue;
  109. }
  110. if($rebate['initiate_type'] == RebateEnum::INITIATE_TYPE_LEVEL && Rebate::checkRebateUserLevel($parent_id, $rebate['initiate_level']) == false) {
  111. continue;
  112. }
  113. // 判断消费者是否符合权限
  114. if($rebate['consume_type'] == RebateEnum::CONSUME_TYPE_NEW && Rebate::checkNewRegisterUser($mall_id, $user_id)==false) {
  115. continue;
  116. }
  117. if($rebate['consume_type'] == RebateEnum::CONSUME_TYPE_LEVEL && Rebate::checkRebateUserLevel($user_id, $rebate['consume_level']) == false) {
  118. continue;
  119. }
  120. }
  121. // 初始化参与活动
  122. $res = RebateRecommendNum::initRecommendNum($mall_id, ['r_id' => $rebate['id'], 'user_id' => $parent_id]);
  123. if ($res === false) throw new \Exception(RebateRecommendNum::getStaticError());
  124. // 检测购买此商品是否有使用优惠或抵扣(随机减不算)
  125. if (!empty($rebate['is_grant']) && ($order_detail['price'] * $order_detail['num']) != $order_detail['goods_price']) continue;
  126. // 保存消费者数据
  127. $rabate_buy = self::saveRebateBuy($mall_id, $rebate['id'], $user_id, $parent_id, $order, $order_detail, $order_settlement_status);
  128. if ($rabate_buy && $rabate_buy['is_settlement'] == 1) {// 如果此订单已经结算过则不再触发
  129. continue;
  130. }
  131. if($settlement_method != $settlement_type) {
  132. continue;
  133. }
  134. // 获取未结算的ids
  135. $rebate_buy_ids = RebateBuy::getNoSettlementList($mall_id, $rebate['id'], $parent_id);
  136. // 奖励的内容
  137. $rebate_info = Json::decode($rebate['reward_info'], true);
  138. // 获取用户上级已有的订单数
  139. $recommend_num = RebateRecommendNum::setData($mall_id, ['r_id' => $rebate['id'], 'user_id' => $parent_id, 'pay_num' => 1]);
  140. // 活动统计
  141. RebateStatistics::setData($mall_id, ['r_id' => $rebate['id'], 'pay_money' => $order_detail['goods_price']]);
  142. // 判断奖励方式
  143. $reward_title = $content = '';
  144. if($rebate['reward_type'] == RebateEnum::REWARD_TYPE_COND) {// 条件奖励
  145. if($recommend_num['pay_num'] % $rebate_info[0]['num'] == 0) {// 取余=0则代表可的奖励
  146. $reward_title = "每推荐" . $rebate_info[0]['num']. "人购买";
  147. $content = self::giveParentReward($mall_id, $rebate_info[0]['data'], $parent_id, $order_id, $order_detail['id'], $addons_name,$reward_commission);
  148. $reward_key = 0;
  149. }
  150. } else {
  151. // 阶梯奖励
  152. if (empty($rebate['is_repeat'])) {
  153. foreach($rebate_info as $k => $item){
  154. if($item['num'] == $recommend_num['pay_num']) {
  155. $reward_title = "推荐" . $item['num']. "人购买";
  156. $is_frozen = $k === 0 && $rebate['is_freeze_first_reward'] ? RebateEnum::FROZEN : RebateEnum::NOT_FROZEN;
  157. $content = self::giveParentReward($mall_id, $item['data'], $parent_id, $order_id, $order_detail['id'], $addons_name,$reward_commission, $is_frozen, $rebate);
  158. $reward_key = $k;
  159. break;
  160. }
  161. }
  162. } else {
  163. // 重复发奖处理
  164. $last_value = $rebate_info[count($rebate_info) - 1]['num'];// 获取数组中最后一个的数值
  165. $multiple = empty($recommend_num['pay_num']) ? 0 : ceil($recommend_num['pay_num'] / $last_value) - 1;// 向上取整再减1,获取倍数
  166. // 设置循环奖励,从第二轮开始
  167. if ($recommend_num['pay_num'] > $last_value && !empty($rebate['is_reward_after_n']) && !empty($rebate['reward_threshold'])) {
  168. if (!is_numeric($offset = array_search($rebate['reward_threshold'], array_column($rebate_info, 'num')))) {
  169. continue;
  170. }
  171. // 得到 $rebate['reward_threshold'] 的奖励
  172. $rebate_info_by_threshold = array_slice($rebate_info, $offset);
  173. if (empty($rebate_info_by_threshold)) {
  174. continue;
  175. }
  176. $rebate_info_by_threshold_count = count($rebate_info_by_threshold);
  177. $rewardIndex = (($recommend_num['pay_num'] - $last_value) % $rebate_info_by_threshold_count) - 1;
  178. $rewardIndex < 0 && $rewardIndex = $rebate_info_by_threshold_count - 1;
  179. $item = $rebate_info_by_threshold[$rewardIndex];
  180. $reward_title = "推荐" . $recommend_num['pay_num']. "人购买";
  181. $content = self::giveParentReward($mall_id, $item['data'], $parent_id, $order_id, $order_detail['id'], $addons_name,$reward_commission);
  182. $reward_key = $offset + $rewardIndex; // 在$rebate_info数组的key
  183. $isRepeat = true; // 标识这是循环奖励
  184. } else {
  185. foreach($rebate_info as $k => $item) {
  186. $item['num'] = $item['num'] + $last_value * $multiple;// 奖励个数计算=阶梯个数 + 最后数值 * 向下取整倍数
  187. if($item['num'] == $recommend_num['pay_num']) {
  188. $is_frozen = $k === 0 && $rebate['is_freeze_first_reward'] ? RebateEnum::FROZEN : RebateEnum::NOT_FROZEN;
  189. $reward_title = "推荐" . $item['num']. "人购买";
  190. $content = self::giveParentReward($mall_id, $item['data'], $parent_id, $order_id, $order_detail['id'], $addons_name,$reward_commission, $is_frozen, $rebate);
  191. $item['num'] = $rebate_info[$k]['num'];
  192. $reward_key = $k;
  193. break;
  194. }
  195. }
  196. }
  197. }
  198. // 如果当前获取第二项奖励,并且第一项是冻结的,需要解冻第一项
  199. if (isset($reward_key) && $reward_key == 1 && !isset($isRepeat)) {
  200. $unfozenRebateBuys[] = $rabate_buy;
  201. }
  202. }
  203. if($content) {
  204. // 保存数据时的配置
  205. $setting = [
  206. 'title' => $rebate['title'],
  207. 'item_id' => $rebate['id'],
  208. 'settlement_method' => $settlement_method,
  209. 'reward_type' => $rebate['reward_type'],
  210. 'reward_info' => $rebate_info,
  211. 'log_ids' => implode(',', $rebate_buy_ids),
  212. 'reward_key' => $reward_key ?? -1,
  213. 'order_detail_id' => $order_detail['id'],
  214. 'freeze_first_reward_type' => $rebate['freeze_first_reward_type'],
  215. 'freeze_first_reward_percentage' => $rebate['freeze_first_reward_percentage']
  216. ];
  217. // 保存奖励数据
  218. $log = [
  219. 'r_id' => $rebate['id'],
  220. 'user_id' => $parent_id,
  221. 'reward_title' => $reward_title,
  222. 'content' => $content,
  223. 'setting' => Json::encode($setting),
  224. 'is_frozen' => $is_frozen ?? RebateEnum::NOT_FROZEN,
  225. ];
  226. $reward = RebateRewardLog::setData($mall_id, $log);
  227. if($reward == false) throw new Exception(RebateRewardLog::getStaticError());
  228. // 将购买记录改为已结算
  229. RebateBuy::updateAll(['is_reward_trigger' => 1, 'updated_at' => time()], ['id' => $rabate_buy['id'], 'order_settlement_status' => 1]);
  230. RebateBuy::updateAll(['is_settlement' => 1, 'reward_id' => $reward['id'], 'updated_at' => time()], ['id' => $rebate_buy_ids, 'order_settlement_status' => 1]);
  231. }
  232. }
  233. }
  234. self::unfrozenFirstReward2Queue($unfozenRebateBuys);
  235. $t->commit();
  236. LevelUpLogic::execute($order,$settlement_type);
  237. // 推三返一订单完成-触发的奖励
  238. if ($settlement_type==2 && MallAddons::isInstall($order['mall_id'], 'BonusPool')) {
  239. $handle_class = \addons\BonusPool\common\listeners\order\OrderComputeCommissionListener::class;
  240. $data = [
  241. 'rebate_commission_reward_arr' => $reward_commission,
  242. 'order_id' => $order_id,
  243. ];
  244. \Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_ORDER, RabbitMqEnum::ORDER_QUEUE, $data, $handle_class, 'handleSync');
  245. }
  246. return true;
  247. } catch (Exception $e) {
  248. $t->rollBack();
  249. self::setStaticError($e->getMessage());
  250. return false;
  251. }
  252. }
  253. /**
  254. * 保存购买者信息
  255. * @Author: lun
  256. * @DateTime: 2022/1/15 0015 9:47
  257. * @Copyright: copyright (c) 2021 广东七件事集团
  258. * @param $mall_id
  259. * @param $r_id
  260. * @param $user_id
  261. * @param $parent_id
  262. * @param $order
  263. * @param $order_detail
  264. * @param $order_settlement_status
  265. * @return array| bool
  266. */
  267. private static function saveRebateBuy($mall_id, $r_id, $user_id, $parent_id, $order, $order_detail, $order_settlement_status = 0, $is_virtual = 0, $is_settlement = 0)
  268. {
  269. $params = [
  270. 'mall_id' => $mall_id,
  271. 'r_id' => $r_id,
  272. 'pid' => $parent_id,
  273. 'order_no' => $order['order_no'],
  274. 'pay_at' => $order['pay_time'],
  275. 'pay_money' => $order_detail['goods_price'],
  276. 'goods_info' => Json::encode([
  277. 'id' => $order_detail['goods_id'],
  278. 'goods_name' => $order_detail['goods_name'],
  279. 'cover_pic' => $order_detail['pic_url'],
  280. 'price' => $order_detail['price'],
  281. ]),
  282. 'user_id' => $user_id,
  283. 'order_settlement_status' => $order_settlement_status,
  284. 'is_virtual' => $is_virtual,
  285. 'is_settlement' => $is_settlement,
  286. 'order_detail_id' => $order_detail['id']
  287. ];
  288. // 检测是否已经生成过数据
  289. $has_buy = RebateBuy::find()->where(['r_id' => $r_id, 'pid' => $parent_id, 'user_id' => $user_id, 'order_no' => $order['order_no']])->one();
  290. if ($has_buy) {
  291. if (empty($has_buy->order_settlement_status) && $order_settlement_status == 1) {
  292. $has_buy->order_settlement_status = 1;
  293. $has_buy->settlement_at = time();
  294. $has_buy->position = self::getPosition($mall_id, $r_id, $parent_id);
  295. if (!$has_buy->save()) throw new Exception($has_buy->getErrorMessage());
  296. }
  297. return $has_buy->toArray();
  298. }
  299. if ($params['order_settlement_status']) {
  300. $params['settlement_at'] = time();
  301. $params['position'] = self::getPosition($mall_id, $r_id, $parent_id);
  302. }
  303. return RebateBuy::setData($params);
  304. }
  305. /**
  306. * 得到position
  307. *
  308. * @param int $mallId
  309. * @param int $rId
  310. * @param int $parentId
  311. *
  312. * @return int
  313. */
  314. public static function getPosition(int $mallId, int $rId, int $parentId): int
  315. {
  316. $position = RebateBuy::find()
  317. ->where([
  318. 'mall_id' => $mallId,
  319. 'r_id' => $rId,
  320. 'pid' => $parentId,
  321. 'order_settlement_status' => StatusEnum::ENABLED
  322. ])
  323. ->orderBy('position desc')
  324. ->select('position')
  325. ->scalar() ?: 0;
  326. return ++$position;
  327. }
  328. /**
  329. * 赠送活动奖励
  330. * @Author: lun
  331. * @DateTime: 2022/1/15 0015 9:47
  332. * @Copyright: copyright (c) 2021 广东七件事集团
  333. * @param $mall_id
  334. * @param $rewards
  335. * @param $user_id
  336. * @param $order_id
  337. * @param string $desc
  338. * @param string $rebate 推三返一活动数据
  339. * @return bool|string
  340. */
  341. private static function giveParentReward($mall_id, $rewards, $user_id, $order_id, $order_detail_id, $desc = '推三返一',&$reward_commission = [], bool $is_frozen = false, array $rebate = [])
  342. {
  343. try {
  344. $orderInfo = OrderDetail::find()->where(['mall_id' => $mall_id, 'status' => StatusEnum::ENABLED, 'id' => $order_detail_id])
  345. ->select('id, goods_price as pay_money, original_goods_price, cost_price, num')
  346. ->asArray()->one();
  347. $reward_text = "";
  348. //判断是否安装红包拓客插件
  349. $has_redpack = MallAddons::isInstall($mall_id, AddonsEnum::ADDONS_NAME_REDPACK);
  350. $insert_wallet = [];
  351. foreach ($rewards as $item) {
  352. // 注意:冻结奖励时,商城资产 积分 佣金 余额 冻结时会转为冻结资产。优惠券奖励则订单完成后再发奖励。积分拓客则发未冻结的,已冻结的订单完成再发送
  353. switch ($item['type']) {
  354. case RebateEnum::REWARD_SCORE:// 积分
  355. if ($item['ratio']) {
  356. $item['data'] = self::getPercentage($mall_id, $orderInfo, $item);
  357. }
  358. // 当赠送金额小于等于0,不赠送
  359. if ($item['data'] <= 0) break;
  360. $reward_text.= ",积分:" . $item['data'];
  361. $walletItem = [
  362. 'mall_id' => $mall_id,
  363. 'user_id' => $user_id,
  364. 'wallet_type' => $item['type'],
  365. 'desc' => $desc . '赠送',
  366. 'source_table' => Order::tableName(),
  367. 'source_table_id' => $order_id,
  368. 'from_type' => AddonsEnum::ADDONS_NAME_REBATE,
  369. 'capital_type' => UserWalletEnum::GIVE,
  370. ];
  371. self::computeAndInsertWallet($is_frozen, $item['data'], $rebate, $walletItem, $insert_wallet, $reward_text);
  372. break;
  373. case RebateEnum::REWARD_BALANCE:// 余额
  374. if ($item['ratio']) {
  375. $item['data'] = self::getPercentage($mall_id, $orderInfo, $item);
  376. }
  377. if ($item['data'] <= 0) break;
  378. $reward_text.= ",余额:¥" . $item['data'];
  379. $walletItem = [
  380. 'mall_id' => $mall_id,
  381. 'user_id' => $user_id,
  382. 'wallet_type' => $item['type'],
  383. 'desc' => $desc . '赠送',
  384. 'source_table' => Order::tableName(),
  385. 'source_table_id' => $order_id,
  386. 'from_type' => AddonsEnum::ADDONS_NAME_REBATE,
  387. 'capital_type' => UserWalletEnum::GIVE,
  388. ];
  389. self::computeAndInsertWallet($is_frozen, $item['data'], $rebate, $walletItem, $insert_wallet, $reward_text);
  390. break;
  391. case RebateEnum::REWARD_COMMISSION:// 佣金
  392. if ($item['ratio']) {
  393. $item['data'] = self::getPercentage($mall_id, $orderInfo, $item);
  394. }
  395. if ($item['data'] <= 0) break;
  396. $reward_text.= ",佣金:¥" . $item['data'];
  397. $walletItem = [
  398. 'mall_id' => $mall_id,
  399. 'user_id' => $user_id,
  400. 'wallet_type' => $item['type'],
  401. 'desc' => $desc . '赠送',
  402. 'source_table' => OrderDetail::tableName(),
  403. 'source_table_id' => $order_detail_id,
  404. 'from_type' => AddonsEnum::ADDONS_NAME_REBATE,
  405. 'capital_type' => UserWalletEnum::GIVE,
  406. ];
  407. self::computeAndInsertWallet($is_frozen, $item['data'], $rebate, $walletItem, $insert_wallet, $reward_text);
  408. if(isset($reward_commission[$order_detail_id])){
  409. $reward_commission[$order_detail_id]['money'] += $item['data'];
  410. }else{
  411. $reward_commission[$order_detail_id]['money'] = $item['data'];
  412. }
  413. break;
  414. case RebateEnum::REWARD_COUPON:// 优惠券
  415. // 赠送优惠券
  416. foreach ($item['data'] as $coupon) {
  417. $give_coupon_arr[$coupon['relevant_id']] = $coupon['num'];
  418. }
  419. if (empty($give_coupon_arr)) {
  420. break;
  421. }
  422. if ($is_frozen) {
  423. $coupon_arr = AddonsCoupon::find()
  424. ->select('name')
  425. ->where(['mall_id' => $mall_id, 'id' => array_keys($give_coupon_arr), 'status' => StatusEnum::ENABLED, 'coupon_status' => AddonsCouponEnum::STATUS_RECEIVING])
  426. ->asArray()
  427. ->all();
  428. } else {
  429. $coupon_arr = AddonsCouponUserRelate::giveCoupon($mall_id, $user_id, $give_coupon_arr, AddonsCouponEnum::SEND_FROM14);
  430. }
  431. if ($coupon_arr) {
  432. foreach ($coupon_arr as $info) {
  433. $coupon_text[] = $info['name'];
  434. }
  435. $reward_text.= ",优惠券:[" . implode(',', $coupon_text) .']';
  436. }
  437. break;
  438. case RebateEnum::REWARD_REDPACK://激活红包
  439. case RebateEnum::REWARD_FROZEN_REDPACK:
  440. //未安装红包拓客插件,则跳出
  441. if (!$has_redpack) break;
  442. if ($item['ratio']) {
  443. $item['data'] = self::getPercentage($mall_id, $orderInfo, $item);
  444. }
  445. // 当赠送金额小于等于0,不赠送
  446. if ($item['data'] <= 0) break;
  447. ['frozen_money' => $frozen_money, 'money' => $money] = self::computeRewardMoney(
  448. $is_frozen,
  449. $item['data'],
  450. $rebate['freeze_first_reward_type'] ?? RebateEnum::FREEZE_FIRST_REWARD_TYPE_ALL,
  451. $rebate['freeze_first_reward_percentage'] ?? 0
  452. );
  453. $redpack_data = [
  454. 'mall_id' => $mall_id,
  455. 'user_id' => $user_id,
  456. 'is_frozen' => false,
  457. 'money' => $money,
  458. 'desc' => '订单('.$order_id.')推三返一赠送红包',
  459. 'source_table' => OrderDetail::tableName(),
  460. 'source_table_id' => $order_detail_id,
  461. 'from_type' => \addons\Redpack\common\enums\RedpackEnum::FROM_REBATE_REWARD,
  462. ];
  463. if ($item['type'] == 'frozen_redpack') {
  464. $reward_text.= ",冻结红包:" . $item['data'];
  465. $redpack_data['is_frozen'] = true;
  466. } else {
  467. $reward_text.= ",激活红包:" . $item['data'];
  468. }
  469. if ($frozen_money > 0) {
  470. $reward_text .= '(冻结 ' . $frozen_money . ')';
  471. }
  472. if ($money > 0) {
  473. $res = \addons\Redpack\common\models\RedpackWallet::setData($redpack_data);
  474. if (!$res) throw new Exception(\addons\Redpack\common\models\RedpackWallet::$static_error);
  475. }
  476. break;
  477. default:
  478. break;
  479. }
  480. }
  481. if (!empty($insert_wallet)) {
  482. $res = UserWalletService::batchHandleByQueue($insert_wallet);
  483. if (empty($res)) throw new \Exception(UserWalletService::getStaticError());
  484. }
  485. return ltrim($reward_text, ',');
  486. } catch (\Exception $e) {
  487. Yii::error("推三返一奖励报错=" . $e->getMessage());
  488. return false;
  489. }
  490. }
  491. /**
  492. * 计算冻结奖励和非冻结奖励
  493. *
  494. * @return void
  495. */
  496. private static function computeRewardMoney(bool $is_frozen, float $rewardMoney, int $freezeFirstRewardType, float $freezeFirstRewardPercentage): array
  497. {
  498. if (
  499. !$is_frozen ||
  500. (
  501. $freezeFirstRewardType == RebateEnum::FREEZE_FIRST_REWARD_TYPE_PERCENTAGE &&
  502. $freezeFirstRewardPercentage <= 0
  503. )
  504. ) {
  505. return [
  506. 'frozen_money' => 0,
  507. 'money' => $rewardMoney,
  508. ];
  509. }
  510. // 全部冻结
  511. if (
  512. $freezeFirstRewardType == RebateEnum::FREEZE_FIRST_REWARD_TYPE_ALL ||
  513. (
  514. $freezeFirstRewardType == RebateEnum::FREEZE_FIRST_REWARD_TYPE_PERCENTAGE &&
  515. $freezeFirstRewardPercentage >= 100
  516. )
  517. ) {
  518. return [
  519. 'frozen_money' => $rewardMoney,
  520. 'money' => 0,
  521. ];
  522. }
  523. // 按百分比冻结
  524. $frozen_money = bcmul($rewardMoney, $freezeFirstRewardPercentage / 100, 2);
  525. return [
  526. 'frozen_money' => $frozen_money,
  527. 'money' => bcsub($rewardMoney, $frozen_money, 2),
  528. ];
  529. }
  530. /**
  531. * 计算并插入奖励
  532. *
  533. * @param bool $is_frozen
  534. * @param float $rewardMoney
  535. * @param array $rebate
  536. * @param array $walletItem
  537. * @param array $insert_wallet
  538. * @param string $reward_text
  539. *
  540. * @return void
  541. */
  542. private static function computeAndInsertWallet(bool $is_frozen, float $rewardMoney, array $rebate, array $walletItem, array &$insert_wallet, string &$reward_text)
  543. {
  544. ['frozen_money' => $frozen_money, 'money' => $money] = self::computeRewardMoney(
  545. $is_frozen,
  546. $rewardMoney,
  547. $rebate['freeze_first_reward_type'] ?? RebateEnum::FREEZE_FIRST_REWARD_TYPE_ALL,
  548. $rebate['freeze_first_reward_percentage'] ?? 0
  549. );
  550. if ($money > 0) {
  551. $insert_wallet[] = array_merge($walletItem, ['is_frozen' => false, 'money' => $money]);
  552. }
  553. $rewardFlag = in_array($walletItem['wallet_type'], [RebateEnum::REWARD_BALANCE, RebateEnum::REWARD_COMMISSION]) ? '¥' : '';
  554. if ($frozen_money > 0) {
  555. $insert_wallet[] = array_merge($walletItem, ['is_frozen' => true, 'money' => $frozen_money]);
  556. $reward_text .= '(冻结 ' . $rewardFlag . $frozen_money . ')';
  557. }
  558. }
  559. /**
  560. * 奖励(余额-佣金-积分)百分比计算
  561. * @param $mall_id //商户id
  562. * @param $order //商品信息
  563. * @param $item //占比信息
  564. */
  565. public static function getPercentage($mall_id, $order, $item)
  566. {
  567. $ratio_money = 0; //返回比率计算价格
  568. // 获取配置字段
  569. $settingService = new SettingService(['mall_id' => $mall_id]);
  570. $system = $settingService->getSetting()['computing_method'];
  571. //计算比率
  572. $ratio = $item['data'] / 100;
  573. switch ($system)
  574. {
  575. case RebateEnum::COMPUTING_METHOD_PAY_PRICE:// 支付价格计算
  576. //计算金额
  577. $ratio_money = bcmul($order['pay_money'], $ratio,2);
  578. break;
  579. case RebateEnum::COMPUTING_METHOD_GOODS_PRICE:// 商品售价计算
  580. $ratio_money = bcmul($order['original_goods_price'], $ratio, 2);
  581. break;
  582. case RebateEnum::COMPUTING_METHOD_GOODS_PROFIT:// 商品利润计算
  583. //商品利润=商品支付价格-成本
  584. $profit_money = $order['pay_money'] - ($order['cost_price'] * $order['num']);
  585. $ratio_money = bcmul($profit_money, $ratio, 2);
  586. break;
  587. default:
  588. break;
  589. }
  590. return $ratio_money;
  591. }
  592. /**
  593. * 奖励判断
  594. * @return bool
  595. */
  596. public static function checkOrder($order_id,$settlement_type){
  597. // 获取订单内容
  598. $where = ['id' => $order_id];
  599. $order = Order::find()->select("id,user_id,mall_id,order_no,pay_time")
  600. ->where($where)
  601. ->with(['detail' => function($query){
  602. $query->select("order_id,goods_id,num,goods_price,goods_name,price,pic_url");
  603. }])
  604. ->asArray()->one();
  605. if(empty($order)) return true;
  606. $mall_id = $order['mall_id'];
  607. $user_id = $order['user_id'];
  608. // 获取推三返一基础配置内容
  609. $settingService = new SettingService(['mall_id' => $mall_id]);
  610. $config = $settingService->getSetting();
  611. $settlement_method = $config['settlement_method'];
  612. //结算状态与订单状态不一致时不判断
  613. if($settlement_method != $settlement_type) {
  614. return true;
  615. }
  616. // 获取消费者的直推上级
  617. $parent_id = User::find()->select('parent_id')->where(['id' => $user_id, 'mall_id' => $mall_id, 'status' => StatusEnum::ENABLED])->asArray()->scalar();
  618. if(empty($parent_id)) {
  619. return true;
  620. }
  621. // 获取推三返一的商品
  622. $rebate_list = Rebate::getRebateAllGoodsId($mall_id);
  623. if(empty($rebate_list)) return true;
  624. // 获取所有商品id
  625. $goods_arr = [];
  626. foreach ($rebate_list as $item) {
  627. $goods_arr[$item['item_id']][] = $item;
  628. }
  629. $result = true;
  630. foreach($order['detail'] as $order_detail) {
  631. // 检查该商品是否参与了推三返一活动
  632. if(!isset($goods_arr[$order_detail['goods_id']])) continue;
  633. foreach ($goods_arr[$order_detail['goods_id']] as $rebate) {
  634. //检查活动是否开启触发奖励设置
  635. if(empty($rebate['repeat_setting']) || empty($rebate['agent_limit_num'])) continue;
  636. //条件奖励不判断
  637. if($rebate['reward_type'] == RebateEnum::REWARD_TYPE_COND) continue;
  638. if(empty($rebate['join_type'])){
  639. // 判断上级是否有权限
  640. if($rebate['initiate_type'] == RebateEnum::INITIATE_TYPE_NEW && Rebate::checkUserHasOrder($mall_id, $parent_id) !== false) {
  641. continue;
  642. }
  643. if($rebate['initiate_type'] == RebateEnum::INITIATE_TYPE_LEVEL && Rebate::checkUserLevel($parent_id, $rebate['initiate_level']) == false) {
  644. continue;
  645. }
  646. // 判断消费者是否符合权限
  647. if($rebate['consume_type'] == RebateEnum::CONSUME_TYPE_NEW) {
  648. $order_num = Rebate::checkUserHasOrder($mall_id, $user_id);
  649. if (!empty($order_num) && $order_num > 1) {
  650. continue;
  651. }
  652. }
  653. if($rebate['consume_type'] == RebateEnum::CONSUME_TYPE_LEVEL && Rebate::checkUserLevel($user_id, $rebate['consume_level']) == false) {
  654. continue;
  655. }
  656. }else{
  657. // 判断上级是否有权限
  658. if($rebate['initiate_type'] == RebateEnum::INITIATE_TYPE_NEW && Rebate::checkNewRegisterUser($mall_id, $parent_id) == false) {
  659. continue;
  660. }
  661. if($rebate['initiate_type'] == RebateEnum::INITIATE_TYPE_LEVEL && Rebate::checkRebateUserLevel($parent_id, $rebate['initiate_level']) == false) {
  662. continue;
  663. }
  664. // 判断消费者是否符合权限
  665. if($rebate['consume_type'] == RebateEnum::CONSUME_TYPE_NEW && Rebate::checkNewRegisterUser($mall_id, $user_id)==false) {
  666. continue;
  667. }
  668. if($rebate['consume_type'] == RebateEnum::CONSUME_TYPE_LEVEL && Rebate::checkRebateUserLevel($user_id, $rebate['consume_level']) == false) {
  669. continue;
  670. }
  671. }
  672. // 奖励的内容
  673. $rebate_info = Json::decode($rebate['reward_info'], true);
  674. // 获取用户上级已有的订单数
  675. $recommend_num = RebateRecommendNum::find()
  676. ->where(['r_id'=>$rebate['id'],'user_id'=>$parent_id])
  677. ->select('pay_num')
  678. ->scalar() ?? 0;
  679. //获取当前订单是否已统计
  680. $has_buy = RebateBuy::find()->where(['r_id' => $rebate['id'], 'pid' => $parent_id, 'user_id' => $user_id, 'order_no' => $order['order_no'],'is_settlement'=>1])->one();
  681. if(empty($has_buy)) $recommend_num += 1;
  682. //触发奖励判断
  683. if($recommend_num % $rebate['agent_limit_num'] == 0){
  684. $result = false;
  685. }
  686. }
  687. }
  688. return $result;
  689. }
  690. /**
  691. * 创建虚拟订单
  692. *
  693. * @param $mall_id
  694. * @param $user_id
  695. * @param $rebate_id
  696. *
  697. * @return bool
  698. */
  699. public static function createVirtualOrder($mall_id, $user_id, $rebate_id)
  700. {
  701. $parent_id = User::find()->select('parent_id')->where(['id' => $user_id, 'mall_id' => $mall_id, 'status' => StatusEnum::ENABLED])->asArray()->scalar();
  702. if (empty($parent_id)) {
  703. self::$static_error = '上级用户不存在';
  704. return false;
  705. }
  706. $t = Yii::$app->db->beginTransaction();
  707. try {
  708. // 初始化参与活动
  709. $res = RebateRecommendNum::initRecommendNum($mall_id, ['r_id' => $rebate_id, 'user_id' => $parent_id]);
  710. if ($res === false) {
  711. throw new \Exception(RebateRecommendNum::getStaticError());
  712. }
  713. // 找到参与活动的商品
  714. $goods = Goods::findOne(['id' => Rebate::find()->where(['id' => $rebate_id])->select('item_id')->scalar()]);
  715. if (empty($goods)) {
  716. throw new \Exception('商品不存在');
  717. }
  718. $order_detail = [
  719. 'id' => 0,
  720. 'goods_price' => 0,
  721. 'goods_id' => $goods->id,
  722. 'goods_name' => $goods->goods_name,
  723. 'pic_url' => $goods->cover_pic,
  724. 'price' => $goods->price,
  725. ];
  726. // 保存消费者数据
  727. $rabate_buy = self::saveRebateBuy(
  728. $mall_id,
  729. $rebate_id,
  730. $user_id,
  731. $parent_id,
  732. ['order_no' => Order::generateOrderNo('s'), 'pay_time' => time()],
  733. $order_detail,
  734. 1,
  735. 1,
  736. 1
  737. );
  738. if ($rabate_buy === false) {
  739. throw new \Exception(RebateRecommendNum::getStaticError());
  740. }
  741. // 增加推荐数量
  742. if (RebateRecommendNum::setData($mall_id, ['r_id' => $rebate_id, 'user_id' => $parent_id, 'pay_num' => 1]) === false) {
  743. throw new \Exception(RebateRecommendNum::getStaticError());
  744. }
  745. // 活动统计
  746. if (RebateStatistics::setData($mall_id, ['r_id' => $rebate_id, 'pay_money' => 0]) === false) {
  747. throw new \Exception(RebateStatistics::getStaticError());
  748. }
  749. // 记录手动生成订单
  750. $setRebateVirtualOrderLogRes = RebateVirtualOrderLog::setData($mall_id, [
  751. 'user_id' => $user_id,
  752. 'user_parent_id' => $parent_id,
  753. 'rebate_id' => $rebate_id,
  754. 'rebate_buy_id' => $rabate_buy['id']
  755. ]);
  756. if ($setRebateVirtualOrderLogRes === false) {
  757. throw new \Exception(RebateVirtualOrderLog::getStaticError());
  758. }
  759. $t->commit();
  760. }catch (\Exception $e) {
  761. $t->rollBack();
  762. Yii::error(Json::encode([
  763. 'msg' => $e->getMessage(),
  764. 'line' => $e->getLine(),
  765. 'file' => $e->getFile()
  766. ]));
  767. self::$static_error = '系统错误';
  768. return false;
  769. }
  770. return true;
  771. }
  772. /**
  773. * 如果当前获取的奖励是第二项,则检查需要解冻第一项
  774. *
  775. * @param array $rebate_buy
  776. *
  777. * @return void
  778. */
  779. public static function unfrozenFirstReward(array $rebate_buy)
  780. {
  781. $rewardLog = RebateRewardLog::find()
  782. ->where([
  783. 'mall_id' => $rebate_buy['mall_id'],
  784. 'r_id' => $rebate_buy['r_id'],
  785. 'user_id' => $rebate_buy['pid'],
  786. 'is_frozen' => RebateEnum::FROZEN,
  787. 'status' => StatusEnum::ENABLED,
  788. ])
  789. ->one();
  790. if (empty($rewardLog)) {
  791. return;
  792. }
  793. $rebateBuy = RebateBuy::find()
  794. ->where([
  795. 'reward_id' => $rewardLog->id,
  796. 'status' => StatusEnum::ENABLED,
  797. 'mall_id' => $rebate_buy['mall_id'],
  798. ])
  799. ->select('reward_id, order_no, order_detail_id')
  800. ->asArray()
  801. ->one();
  802. if (empty($rebateBuy)) {
  803. return;
  804. }
  805. // 循环需要解冻的类型
  806. $setting = json_decode($rewardLog->setting, true);
  807. if (isset($setting['reward_key'], $setting['reward_info'][$setting['reward_key']])) {
  808. // 查询订单
  809. $orderId = Order::find()
  810. ->where([
  811. 'order_no' => $rebateBuy['order_no'],
  812. 'mall_id' => $rewardLog->mall_id,
  813. 'status' => StatusEnum::ENABLED
  814. ])
  815. ->select('id')
  816. ->scalar();
  817. $orderInfo = OrderDetail::find()
  818. ->where(['mall_id' => $rewardLog->mall_id, 'status' => StatusEnum::ENABLED, 'id' => $rebateBuy['order_detail_id']])
  819. ->select('id, order_id, goods_price as pay_money, original_goods_price, cost_price, num')
  820. ->asArray()
  821. ->one();
  822. //判断是否安装红包拓客插件
  823. $has_redpack = MallAddons::isInstall($rewardLog->mall_id, AddonsEnum::ADDONS_NAME_REDPACK);
  824. foreach ($setting['reward_info'][$setting['reward_key']]['data'] as $item) {
  825. switch ($item['type']) {
  826. case RebateEnum::REWARD_SCORE:
  827. case RebateEnum::REWARD_BALANCE:
  828. case RebateEnum::REWARD_COMMISSION:
  829. $wallet_type = $item['type'];
  830. CapitalLog::$capitalType = "{$wallet_type}_frozen";
  831. $commission_list = CapitalLog::lists([
  832. 'where' => [
  833. [
  834. 'or',
  835. ['source_table' => Order::tableName(), 'source_table_id' => $orderId],
  836. ['source_table' => OrderDetail::tableName(), 'source_table_id' => $rebateBuy['order_detail_id']],
  837. ],
  838. ['from_type' => AddonsEnum::ADDONS_NAME_REBATE],
  839. ['capital_type' => UserWalletEnum::GIVE],
  840. ['status' => CapitalLog::STATUS_WAIT_SEND]
  841. ]
  842. ]);
  843. foreach ($commission_list as $commission) {
  844. UserWalletService::unfrozen($commission['id'], $item['type']);
  845. }
  846. break;
  847. case RebateEnum::REWARD_COUPON:
  848. foreach ($item['data'] as $coupon) {
  849. $give_coupon_arr[$coupon['relevant_id']] = $coupon['num'];
  850. }
  851. if (empty($give_coupon_arr)) {
  852. break;
  853. }
  854. AddonsCouponUserRelate::giveCoupon($rewardLog->mall_id, $rewardLog->user_id, $give_coupon_arr, AddonsCouponEnum::SEND_FROM14);
  855. break;
  856. case RebateEnum::REWARD_REDPACK://激活红包
  857. case RebateEnum::REWARD_FROZEN_REDPACK:
  858. //未安装红包拓客插件,则跳出
  859. if (!$has_redpack) break;
  860. if ($item['ratio']) {
  861. $item['data'] = self::getPercentage($rewardLog->mall_id, $orderInfo, $item);
  862. }
  863. // 当赠送金额小于等于0,不赠送
  864. if ($item['data'] <= 0) break;
  865. ['frozen_money' => $frozen_money] = self::computeRewardMoney(
  866. true,
  867. $item['data'],
  868. $setting['freeze_first_reward_type'] ?? RebateEnum::FREEZE_FIRST_REWARD_TYPE_ALL,
  869. $setting['freeze_first_reward_percentage'] ?? 0
  870. );
  871. if ($frozen_money <= 0) {
  872. break;
  873. }
  874. $redpack_data = [
  875. 'mall_id' => $rewardLog->mall_id,
  876. 'user_id' => $rewardLog->user_id,
  877. 'is_frozen' => false,
  878. 'money' => $frozen_money,
  879. 'desc' => '订单('.$orderInfo['order_id'].')推三返一赠送红包',
  880. 'source_table' => OrderDetail::tableName(),
  881. 'source_table_id' => $orderInfo['id'],
  882. 'from_type' => \addons\Redpack\common\enums\RedpackEnum::FROM_REBATE_REWARD,
  883. ];
  884. if ($item['type'] == 'frozen_redpack') {
  885. $redpack_data['is_frozen'] = true;
  886. }
  887. $res = \addons\Redpack\common\models\RedpackWallet::setData($redpack_data);
  888. if (!$res) throw new Exception(\addons\Redpack\common\models\RedpackWallet::$static_error);
  889. break;
  890. }
  891. }
  892. }
  893. $rewardLog->is_frozen = RebateEnum::UNFREEZE;
  894. $rewardLog->save();
  895. }
  896. public static function unfrozenFirstReward2Queue(array $unfozenRebateBuys)
  897. {
  898. foreach ($unfozenRebateBuys as $unfozenRebateBuy) {
  899. Yii::$app->services->rabbitMq->push(
  900. RabbitMqEnum::EXCHANGE_TASK_USER_WALLET,
  901. RabbitMqEnum::USER_WALLET_QUEUE,
  902. $unfozenRebateBuy,
  903. self::class,
  904. 'unfrozenFirstReward'
  905. );
  906. }
  907. }
  908. }