db->beginTransaction(); try { // 查询订单信息 if ($event_pay == 1) { $order = StoreOrder::getOne([['id' => $order_id]], true, ['detail']); } elseif($event_pay == 2) { $order = StoreCashierOrder::getOne([['id' => $order_id]], true); } else { $order = StoreVideoOrder::getOne([['id' => $order_id]], true); } if (empty($order)) throw new \Exception('暂无用户订单信息'); $order['order_status'] = $order_status;// 订单状态赋值 $order['event_pay'] = $event_pay; if ($event_pay == 1) { $pay_money = $order['pay_money'] - $order['shipping_money'];// 支付金额-运费 } else { $pay_money = $order['pay_money'];// 实付金额 } if ($pay_money <= 0) { $trans->commit(); return true; } $percent = $start_percent = $end_percent = 0;// 获取种子金数量百分比 // 获取基础设置内容 $config = Yii::$app->services->setting->addons->get($order['mall_id'], SeedEnum::ADDONS_NAME, 'basic'); // 不开启直接退出 if ($config['seed_status'] == 0) { $trans->commit(); return true; } // 判断商家是否参与种子金活动 $no_partake_stores = empty($config['no_partake_stores']) ? [] : Json::decode($config['no_partake_stores'], true); if ($no_partake_stores && in_array($order['store_id'], $no_partake_stores)) { $trans->commit(); return true; }// 不参与直接退出 // 检查数据库中是否已存在该订单记录,存在则不插入避免资金叠加 $has_order = SeedOrder::find()->where(['order_id' => $order_id, 'event_pay' => $event_pay, 'status' => StatusEnum::ENABLED])->asArray()->one(); if ($has_order) { $trans->commit(); return true; } // 根据订单实付金额获取可发的种子金数量 $quota_setting = empty($config['quota_setting']) ? [] : Json::decode($config['quota_setting'], true); if (empty($quota_setting)) { $trans->commit(); return true; }// 无设置种子金数量额度直接退出 // 循环获取种子金比例 srand(); foreach ($quota_setting as $item) { if ($item['start_pay_money'] <= $pay_money && $item['end_pay_money'] >= $pay_money) { $start_percent = $item['start_percent']; $end_percent = $item['end_percent']; $percent = rand($start_percent, $end_percent); break; } } $seed_num = floor($pay_money * $percent / 100);// 种子金数量 if (empty($seed_num)) {// 获取的数量为0直接退出 $trans->commit(); return true; } // 检查订单是否是赊账订单 $check_order = StoreOrderCredit::find()->where(['order_id' => $order['id']])->asArray()->one(); // 计算存入能量池金额 $ad_pool_percent = $event_pay == 3 ? $config['ad_percent'] : $config['pool_percent']; $join_pool_money = sprintf("%.2f",substr(sprintf("%.3f", ($pay_money * $ad_pool_percent / 100)), 0, -1));// 不四舍五入保留两位小数 // 额外参数 $extra = [ 'join_pool_money' => $join_pool_money, 'desc' => '店铺消费', 'capital_type' => SeedEnum::LOG_CONSUME, 'setting' => Json::encode(['percent' => $percent, 'ad_pool_percent' => $ad_pool_percent, 'multiple' => $config['multiple'], 'quota_setting' => $config['quota_setting']]), ]; // 消费者获得种子金 self::ConsumeGetSeed($order['user_id'], $seed_num, $order, $config, $extra, $check_order, 1); // 获取店铺绑定的会员id $store_info = Store::find()->select('user_id')->where(['id' => $order['store_id']])->asArray()->one(); // 获取店长种子金数量、价值、单价计算 $store_seed_num = self::getSeedCalculate($order['mall_id'], $order['store_id'], $pay_money); // 店长获得种子金 - 店长发布广告不参与 if ($event_pay <= 2) { self::ConsumeGetSeed($store_info['user_id'], $store_seed_num, $order, $config, $extra, $check_order, 2); } // 种子金/能量池累计 $total_seed_num = $seed_num + $store_seed_num; $res = SeedCapitalStatistics::saveStatistics($order['mall_id'], [ 'total_seed_num' => $total_seed_num, 'total_energy_pool' => $join_pool_money, 'cumulative_energy_pool' => $join_pool_money, ]); if ($res === false) throw new \Exception(SeedUserStatistics::getStaticError()); $trans->commit(); return true; } catch (\Exception $e) { $trans->rollBack(); self::setStaticError('第'.$e->getLine().'行,原因:'.$e->getMessage()); return false; } } /** * 能量池释放 - N个小时释放一次 * @Author: lun * @DateTime: 2022/8/1 0001 16:45 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $mall_id */ public static function release($mall_id) { $trans = Yii::$app->db->beginTransaction(); $lock_key = RedisKeyEnum::suffix(SeedEnum::LOCK_POOL , $mall_id); try { $time = time(); // 获取释放的设置 $config = Yii::$app->services->setting->addons->get($mall_id, SeedEnum::ADDONS_NAME, 'basic'); if (empty($config)) { $trans->commit(); return true; } // 获取能量池数据 $pool = SeedCapitalStatistics::findOne(['mall_id' => $mall_id, 'status' => StatusEnum::ENABLED]); // 判断是否已到释放时间 - 时间误差在2分钟内 if ($pool->last_release_at + $config['release_cycle'] * 3600 > $time + 120) { $trans->commit(); return true; } // 加锁 LockHelper::lock($lock_key, SeedEnum::LOCK_POOL_UNIQID, 30, 60); // 获取未纳入能量池释放的能量池总额 $frozen_seed_num = SeedOrder::find()->where(['mall_id' => $mall_id, 'seed_status' => SeedEnum::SEED_ADD, 'status' => StatusEnum::ENABLED])->andWhere(['>', 'can_send_at', $time])->sum("seed_num") ?? 0; // 已停止增长的种子金总数 $stop_seed_num = SeedOrder::find()->where(['mall_id' => $mall_id, 'seed_status' => SeedEnum::SEED_STOP, 'status' => StatusEnum::ENABLED])->sum("seed_num") ?? 0; // 当前收取种子金数量 = 累计 - 已收取 - 退款 - 坏账 - 未到释放时间的种子金 - 已停止增长的种子金 $seed_num = $pool->total_seed_num - $pool->collect_seed_num - $pool->refund_seed_num - $pool->bad_seed_num - $frozen_seed_num - $stop_seed_num; if ($seed_num == 0) {// 没有可收取的种子金则直接退出 $trans->commit(); // 解锁 LockHelper::uLock($lock_key, SeedEnum::LOCK_POOL_UNIQID); return true; } elseif ($seed_num < 0) { throw new \Exception("释放的种子金数量小于零:总数={$pool->total_seed_num},已收取={$pool->collect_seed_num},退款={$pool->refund_seed_num},坏账={$pool->bad_seed_num},未到释放时间总数={$frozen_seed_num}"); } $total_energy_pool = $pool->total_energy_pool;// 当前能量池总额 // 可释放的单价 = 当前能量池总额 / 当前收取种子金数量;保留两位小数不四舍五入 $unit_price = sprintf("%.2f",substr(sprintf("%.3f", ($total_energy_pool / $seed_num)), 0, -1)); $release_energy_pool = $unit_price * $seed_num;// 本次释放能量 if ($release_energy_pool <= 0) $release_energy_pool = $unit_price = 0;// 本次释放的金不足则改为不释放 if ($release_energy_pool > $pool->total_energy_pool) throw new \Exception("释放的能量池额度大于现有的能量池总额:释放={$release_energy_pool},现有={$pool->total_energy_pool}"); $pool->total_energy_pool -= $release_energy_pool; $pool->release_energy_pool += $release_energy_pool; $pool->release_num += 1; $pool->last_release_at = $time; if (!$pool->save()) throw new Exception($pool->getErrorMessage()); // 增加释放记录 $pool_log = SeedEnergyPool::setData([ 'mall_id' => $pool->mall_id, 'release_num' => $pool->release_num, 'total_seed_num' => $seed_num, 'release_energy_pool' => $release_energy_pool, 'unit_price' => $unit_price, 'release_at' => $time, ]); if ($pool_log === false) throw new \Exception(SeedEnergyPool::getStaticError()); $trans->commit(); // 释放能量跟单价大于0则执行后续队列 if ($release_energy_pool > 0 && $unit_price > 0) { // 异步队列进行每个订单的能量释放 Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_TASK, RabbitMqEnum::TASK_QUEUE, [ 'pool_id' => $pool_log['id'], 'mall_id' => $mall_id, 'unit_price' => $unit_price, 'release_at' => $time, ], self::class, 'handleSync'); } return true; } catch (\Exception $e) { self::setStaticError('第'.$e->getLine().'行,原因:'.$e->getMessage()); LockHelper::uLock($lock_key, SeedEnum::LOCK_POOL_UNIQID); $trans->rollBack(); return false; } } /** * 异步处理发放种子金价值 * @Author: lun * @DateTime: 2022/8/3 0003 10:09 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $params */ public static function handleSync($params) { $trans = Yii::$app->db->beginTransaction(); $lock_key = RedisKeyEnum::suffix(SeedEnum::LOCK_POOL , $params['mall_id']); try { $part = 1; $page_size = 1000; $time = time(); // 获取基础配置信息 $config = Yii::$app->services->setting->addons->get($params['mall_id'], SeedEnum::ADDONS_NAME, 'basic'); // 获取此次发放的池记录 $pool = SeedEnergyPool::findOne(['id' => $params['pool_id']]); if (empty($pool)) { $trans->commit(); LockHelper::uLock($lock_key, SeedEnum::LOCK_POOL_UNIQID); return true; } while ($list = SeedOrder::find() ->where(['mall_id' => $params['mall_id'], 'seed_status' => SeedEnum::SEED_ADD,'status' => StatusEnum::ENABLED]) ->andWhere(['<=','can_send_at',$params['release_at']]) ->offset(($part - 1) * $page_size)->limit($page_size)->asArray()->all()) { $seed_users = $auto_collect = $handle_credit = []; foreach ($list as $item) { $now_unit_price = $item['unit_price'] + $params['unit_price']; $add_seed_cost = sprintf("%.2f",substr(sprintf("%.3f", ($params['unit_price'] * $item['seed_num'])), 0, -1));// 总价值 $seed_cost = $item['seed_cost'] + $add_seed_cost; // 修改用户的种子金价值 $seed_users[$item['user_id']] = empty($seed_users[$item['user_id']]) ? $add_seed_cost : $seed_users[$item['user_id']] + $add_seed_cost; // 修改订单的单价、总价值 SeedOrder::updateAll([ 'unit_price' => $now_unit_price, 'seed_cost' => $seed_cost, 'increase_num' => $item['increase_num'] + 1, 'last_send_at' => $time, 'updated_at' => $time, ],['id' => $item['id']]); // 增加种子金变动记录 $rows[] = [ 'seed_order_id' => $item['id'], 'mall_id' => $item['mall_id'], 'store_id' => $item['store_id'], 'user_id' => $item['user_id'], 'order_id' => $item['order_id'], 'event_pay' => $item['event_pay'], 'seed_num' => $item['seed_num'], 'seed_cost' => $seed_cost, 'change_unit_price' => $item['unit_price'], 'after_unit_price' => $now_unit_price, 'desc' => '能量池释放', 'capital_type' => SeedEnum::LOG_RELEASE, 'pool_id' => $params['pool_id'], 'created_at' => $time, 'updated_at' => $time, ]; // 检查是否是赊账订单 if ($item['is_credit'] == StatusEnum::DISABLED) { // 判断满N倍自动释放到余额 if ($now_unit_price / $item['basic_unit_price'] >= $config['multiple']) $auto_collect[$item['event_pay']][] = $item['id']; } else { // 判断增长金额 >= 赊账金额 if ($seed_cost >= $item['credit_money']) { // 消费者处理 - 全部自动收取 if ($item['identity'] == 1) { // 未还款订单无论是否达到倍数都将自动收取 if ($item['is_able_collect'] == StatusEnum::DISABLED) { $auto_collect[$item['event_pay']][] = $item['id']; } else { // 已还款,当倍数大于设定倍数是将自动收取 if ($now_unit_price / $item['basic_unit_price'] >= $config['multiple']) $auto_collect[$item['event_pay']][] = $item['id']; } } else { // 达到倍数则设置自动收取 if ($now_unit_price / $item['basic_unit_price'] >= $config['multiple']) $auto_collect[$item['event_pay']][] = $item['id']; } } else { // 增长金额 < 赊账金额 if ($item['identity'] == 1) { // 达到倍数,但是价值又不足以还款,则将此订单设为坏账(冻结)订单 if ($now_unit_price / $item['basic_unit_price'] >= $config['multiple']) { // 判断是否属于已还款类型,未还款插入冻结,已还款则自动收取 if ($item['is_able_collect'] == StatusEnum::DISABLED) { $bad_order_arr[] = $item['id']; } else { $auto_collect[$item['event_pay']][] = $item['id']; } } } else { // 达到倍数则设置自动收取 if ($now_unit_price / $item['basic_unit_price'] >= $config['multiple']) $auto_collect[$item['event_pay']][] = $item['id']; } } } } // 修改用户种子金的价值 if ($seed_users) { foreach ($seed_users as $user_id => $seed_cost) { SeedUser::updateAll([ 'total_seed_cost' => new Expression('total_seed_cost+'.$seed_cost), 'updated_at' => $time, ], ['user_id' => $user_id, 'status' => StatusEnum::ENABLED]); } } // 批量插入种子金变动记录 if (!empty($rows)) { $field = ['seed_order_id','mall_id','store_id','user_id','order_id','event_pay','seed_num','seed_cost','change_unit_price','after_unit_price','desc','capital_type','pool_id','created_at','updated_at']; Yii::$app->db->createCommand()->batchInsert(SeedLog::tableName(), $field, $rows)->execute(); } // 达到N倍后自动收取种子金 if ($auto_collect) { foreach ($auto_collect as $type => $seed_order_ids) { $handle_credit = SeedOrder::batchCollect($params['mall_id'], $seed_order_ids, $type); if ($handle_credit === false) throw new \Exception(SeedOrder::getStaticError()); } } // 坏账订单处理 if ($bad_order_arr) { $res = SeedOrder::badOrderHandle($params['mall_id'], $bad_order_arr); if ($res === false) throw new \Exception(SeedOrder::getStaticError()); } // 赊账订单处理 if ($handle_credit && MallAddons::isInstall($params['mall_id'], 'Store')) { foreach ($handle_credit as $type => $credit_order) { foreach ($credit_order as $order_id) { // 调用门店订单处理 StoreOrderCredit::repayment($order_id,$type); } } } $part++; } // 修改释放状态 $pool->release_status = SeedEnum::RELEASE_SUCCESS; if (!$pool->save()) throw new Exception($pool->getErrorMessage()); $trans->commit(); // 解锁 LockHelper::uLock($lock_key, SeedEnum::LOCK_POOL_UNIQID); return true; } catch (\Exception $e) { $trans->rollBack(); // 解锁 LockHelper::uLock($lock_key, SeedEnum::LOCK_POOL_UNIQID); SeedCapitalStatistics::handleReleaseFail($params, '第'.$e->getLine().'行,原因:'.$e->getMessage()); return true; } } /** * 用户消费获得种子金 * @Author: lun * @DateTime: 2022/8/22 0022 14:30 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $user_id * @param $seed_num * @param $order * @param $config * @param $extra * @param $check_order * @param $identity * @throws \Exception */ public static function ConsumeGetSeed($user_id, $seed_num, $order, $config, $extra, $check_order, $identity = 1) { // 保存用户的种子金数量 if ($seed_num) { $res = SeedOrder::consumeOrder([ 'mall_id' => $order['mall_id'], 'store_id' => $order['store_id'], 'user_id' => $user_id, 'identity' => $identity, 'order_id' => $order['id'], 'order_no' => $order['order_no'], 'event_pay' => $order['event_pay'], 'goods_name' => $order['event_pay'] == 1 ? $order['detail'][0]['goods_name'] : $order['event_pay'] == 2 ? '收银台消费' : '发布广告', 'seed_num' => $seed_num, 'basic_unit_price' => $config['basic_unit_price'], 'unit_price' => $config['basic_unit_price'], 'seed_status' => SeedEnum::SEED_ADD, 'join_pool_money' => $identity == 1 ? $extra['join_pool_money'] : 0,// 消费者才增加能量池金额 'can_send_at' => time() + $config['release_cycle'] * 3600, 'credit_money' => empty($check_order) ? 0 : $check_order['credit_money'], 'is_credit' => empty($check_order) ? StatusEnum::DISABLED : StatusEnum::ENABLED, 'is_able_collect' => empty($check_order) ? StatusEnum::ENABLED : $identity == 1 ? ($order['event_pay'] <= 2 ? StatusEnum::DISABLED : StatusEnum::ENABLED) : StatusEnum::ENABLED, ], $extra); if ($res === false) throw new \Exception(SeedOrder::getStaticError()); // 用户数据统计 - 为了防止数据并发,使用updateAllCounters做累加 $res = SeedUserStatistics::setData([ 'mall_id' => $order['mall_id'], 'store_id' => $order['store_id'], 'user_id' => $user_id, ], ['pay_order_num' => 1, 'seed_num' => $seed_num]); if ($res === false) throw new \Exception(SeedUserStatistics::getStaticError()); } } /** * 计算店长可得种子金 * @Author: lun * @DateTime: 2022/8/22 0022 14:23 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $mall_id * @param $store_id * @param $pay_money * @return false|float */ public static function getSeedCalculate($mall_id, $store_id, $pay_money) { // 店铺独立设置 $percent = SeedStoreSetting::find()->select('percent')->where(['mall_id' => $mall_id, 'store_id' => $store_id, 'status' => StatusEnum::ENABLED])->scalar(); if (empty($percent)) { $percent = Yii::$app->services->setting->addons->get($mall_id, SeedEnum::ADDONS_NAME, 'store.percent') ?? 0; } $seed_num = empty($percent) ? 0 : floor($pay_money * $percent / 100);// 种子金数量 return $seed_num; } }