CommissionService.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. <?php
  2. namespace app\common\service;
  3. use think\facade\Db;
  4. use think\facade\Log;
  5. class CommissionService
  6. {
  7. /**
  8. * 获取分销设置
  9. */
  10. public function getDistributionSetting()
  11. {
  12. $setting = Db::table('ba_distribution_setting')
  13. ->order('id', 'desc')
  14. ->find();
  15. // 如果没有设置,返回默认值
  16. if (!$setting) {
  17. $setting = [
  18. 'default_rate' => 0.10, // 10%
  19. 'min_withdrawal' => 10.00,
  20. 'max_withdrawal' => 10000.00,
  21. 'withdrawal_fee_rate' => 0.01, // 1%
  22. ];
  23. }
  24. return $setting;
  25. }
  26. /**
  27. * 计算佣金
  28. */
  29. public function calculateCommission($orderAmount, $distributorId = 0)
  30. {
  31. if ($distributorId <= 0) {
  32. return [
  33. 'has_commission' => false,
  34. 'commission_rate' => 0,
  35. 'commission_amount' => 0
  36. ];
  37. }
  38. $setting = $this->getDistributionSetting();
  39. $commissionRate = ($setting['default_rate']/100) ?? 0.10;
  40. // 计算佣金金额
  41. $commissionAmount = bcmul($orderAmount, $commissionRate, 2);
  42. return [
  43. 'has_commission' => true,
  44. 'commission_rate' => $commissionRate,
  45. 'commission_amount' => $commissionAmount,
  46. 'setting' => $setting
  47. ];
  48. }
  49. /**
  50. * 创建分销记录
  51. */
  52. public function createDistributionRecord($data)
  53. {
  54. $recordData = [
  55. 'order_id' => $data['order_id'] ?? 0,
  56. 'product_id' => $data['product_id'] ?? 0,
  57. 'buyer_id' => $data['buyer_id'] ?? 0,
  58. 'distributor_id' => $data['distributor_id'] ?? 0,
  59. 'commission_rate' => $data['commission_rate'] ?? 0,
  60. 'commission_amount' => $data['commission_amount'] ?? 0,
  61. 'status' => 1, // 1=待结算
  62. 'create_time' => date('Y-m-d H:i:s')
  63. ];
  64. return Db::table('ba_distribution_record')->insertGetId($recordData);
  65. }
  66. /**
  67. * 更新分销记录状态
  68. */
  69. public function updateDistributionRecord($orderId, $status, $settleTime = null)
  70. {
  71. $updateData = [
  72. 'status' => $status,
  73. 'update_time' => date('Y-m-d H:i:s')
  74. ];
  75. if ($settleTime) {
  76. $updateData['settle_time'] = $settleTime;
  77. }
  78. return Db::table('ba_distribution_record')
  79. ->where('order_id', $orderId)
  80. ->update($updateData);
  81. }
  82. /**
  83. * 获取用户佣金统计
  84. */
  85. public function getUserCommissionSummary($userId)
  86. {
  87. $summary = Db::table('ba_distribution_record')
  88. ->where('distributor_id', $userId)
  89. ->field('
  90. COUNT(*) as total_orders,
  91. SUM(CASE WHEN status = 1 THEN commission_amount ELSE 0 END) as pending_amount,
  92. SUM(CASE WHEN status = 2 THEN commission_amount ELSE 0 END) as settled_amount,
  93. SUM(CASE WHEN status = 3 THEN commission_amount ELSE 0 END) as canceled_amount
  94. ')
  95. ->find();
  96. return $summary ?: [
  97. 'total_orders' => 0,
  98. 'pending_amount' => 0,
  99. 'settled_amount' => 0,
  100. 'canceled_amount' => 0
  101. ];
  102. }
  103. /**
  104. * 更新用户佣金表
  105. */
  106. public function updateUserCommission($userId)
  107. {
  108. $summary = $this->getUserCommissionSummary($userId);
  109. // 已结算的佣金可以作为当前佣金(可提现)
  110. $currentCommission = $summary['settled_amount'] ?? 0;
  111. $totalCommission = $summary['settled_amount'] + $summary['pending_amount'];
  112. // 检查用户佣金表是否有记录
  113. $userCommission = Db::table('ba_user_commission')
  114. ->where('user_id', $userId)
  115. ->find();
  116. $data = [
  117. 'total_commission' => $totalCommission,
  118. 'current_commission' => $currentCommission,
  119. 'update_time' => date('Y-m-d H:i:s')
  120. ];
  121. if ($userCommission) {
  122. // 更新
  123. Db::table('ba_user_commission')
  124. ->where('user_id', $userId)
  125. ->update($data);
  126. } else {
  127. // 创建
  128. $data['user_id'] = $userId;
  129. $data['promotion_link'] = $this->generatePromotionLink($userId);
  130. Db::table('ba_user_commission')->insert($data);
  131. }
  132. }
  133. /**
  134. * 处理订单佣金
  135. */
  136. public function processOrderCommission(int $orderId): bool
  137. {
  138. try {
  139. // 获取订单信息
  140. $order = Db::name('order')
  141. ->where('id', $orderId)
  142. ->where('pay_status', 1) // 已支付
  143. ->where('order_status', 'IN', [1, 3]) // 已确认或已完成
  144. ->where('is_commission', 0) // 未处理佣金
  145. ->find();
  146. if (!$order) {
  147. Log::info("订单不满足佣金处理条件: {$orderId}");
  148. return false;
  149. }
  150. // 如果有分销人,处理佣金
  151. if (!empty($order['distributor_id']) && $order['commission_amount'] > 0) {
  152. $distributorId = $order['distributor_id'];
  153. $commissionAmount = $order['commission_amount'];
  154. // 更新分销人佣金
  155. $this->addCommission($distributorId, $commissionAmount, [
  156. 'type' => 2, // 订单佣金
  157. 'order_id' => $orderId,
  158. 'from_user_id' => $order['user_id'],
  159. 'remark' => '订单佣金:' . $order['order_no'] . ',金额:¥' . $commissionAmount
  160. ]);
  161. // 标记订单佣金已处理
  162. Db::name('order')
  163. ->where('id', $orderId)
  164. ->update([
  165. 'is_commission' => 1,
  166. 'update_time' => date('Y-m-d H:i:s')
  167. ]);
  168. Log::info("订单佣金处理成功: 订单ID={$orderId}, 分销人ID={$distributorId}, 佣金={$commissionAmount}");
  169. }
  170. return true;
  171. } catch (\Throwable $e) {
  172. Log::error("处理订单佣金失败: " . $e->getMessage());
  173. return false;
  174. }
  175. }
  176. /**
  177. * 增加佣金
  178. */
  179. public function addCommission(int $userId, float $amount, array $extra = []): bool
  180. {
  181. try {
  182. if ($amount <= 0) {
  183. return false;
  184. }
  185. Db::startTrans();
  186. // 获取当前佣金
  187. $commission = Db::name('user_commission')
  188. ->where('user_id', $userId)
  189. ->lock(true)
  190. ->find();
  191. $balanceBefore = $commission ? $commission['current_commission'] : 0;
  192. $balanceAfter = $balanceBefore + $amount;
  193. if ($commission) {
  194. // 更新现有记录
  195. Db::name('user_commission')
  196. ->where('user_id', $userId)
  197. ->update([
  198. 'total_commission' => Db::raw('total_commission + ' . $amount),
  199. 'current_commission' => Db::raw('current_commission + ' . $amount),
  200. 'update_time' => date('Y-m-d H:i:s')
  201. ]);
  202. } else {
  203. // 创建新记录
  204. $promotionLink = $this->generatePromotionLink($userId);
  205. Db::name('user_commission')->insert([
  206. 'user_id' => $userId,
  207. 'total_commission' => $amount,
  208. 'current_commission' => $amount,
  209. 'withdrawn_commission' => 0.00,
  210. 'frozen_commission' => 0.00,
  211. 'promotion_link' => $promotionLink,
  212. 'invitation_count' => 0,
  213. 'total_invitation' => 0,
  214. 'update_time' => date('Y-m-d H:i:s')
  215. ]);
  216. }
  217. // 记录佣金流水
  218. Db::name('commission_flow')->insert([
  219. 'user_id' => $userId,
  220. 'order_id' => $extra['order_id'] ?? null,
  221. 'from_user_id' => $extra['from_user_id'] ?? null,
  222. 'type' => $extra['type'] ?? 1,
  223. 'amount' => $amount,
  224. 'balance_before' => $balanceBefore,
  225. 'balance_after' => $balanceAfter,
  226. 'remark' => $extra['remark'] ?? ('佣金增加:¥' . $amount),
  227. 'create_time' => date('Y-m-d H:i:s')
  228. ]);
  229. Db::commit();
  230. return true;
  231. } catch (\Throwable $e) {
  232. Db::rollback();
  233. Log::error("增加佣金失败: " . $e->getMessage());
  234. return false;
  235. }
  236. }
  237. /**
  238. * 处理提现
  239. */
  240. public function processWithdrawal(int $withdrawalId): bool
  241. {
  242. try {
  243. $withdrawal = Db::name('withdrawal_record')
  244. ->where('id', $withdrawalId)
  245. ->where('status', 0) // 待审核
  246. ->lock(true)
  247. ->find();
  248. if (!$withdrawal) {
  249. Log::warning("提现记录不存在或状态不符: {$withdrawalId}");
  250. return false;
  251. }
  252. $userId = $withdrawal['user_id'];
  253. $amount = $withdrawal['amount'];
  254. // 检查用户佣金是否足够
  255. $commission = Db::name('user_commission')
  256. ->where('user_id', $userId)
  257. ->find();
  258. if (!$commission || $commission['current_commission'] < $amount) {
  259. Log::warning("用户佣金不足: 用户ID={$userId}, 需要={$amount}, 当前={$commission['current_commission']}");
  260. return false;
  261. }
  262. Db::startTrans();
  263. // 扣除佣金
  264. $newCurrent = $commission['current_commission'] - $amount;
  265. $newWithdrawn = $commission['withdrawn_commission'] + $amount;
  266. Db::name('user_commission')
  267. ->where('user_id', $userId)
  268. ->update([
  269. 'current_commission' => $newCurrent,
  270. 'withdrawn_commission' => $newWithdrawn,
  271. 'update_time' => date('Y-m-d H:i:s')
  272. ]);
  273. // 记录佣金流水
  274. Db::name('commission_flow')->insert([
  275. 'user_id' => $userId,
  276. 'type' => 3, // 提现扣除
  277. 'amount' => -$amount,
  278. 'balance_before' => $commission['current_commission'],
  279. 'balance_after' => $newCurrent,
  280. 'remark' => '提现扣除:' . $withdrawal['withdrawal_no'] . ',金额:¥' . $amount,
  281. 'create_time' => date('Y-m-d H:i:s')
  282. ]);
  283. // 更新提现记录状态为审核通过(实际打款需要另外处理)
  284. Db::name('withdrawal_record')
  285. ->where('id', $withdrawalId)
  286. ->update([
  287. 'status' => 1, // 审核通过
  288. 'audit_time' => date('Y-m-d H:i:s'),
  289. 'current_balance' => $commission['current_commission'],
  290. 'balance_after' => $newCurrent,
  291. 'update_time' => date('Y-m-d H:i:s')
  292. ]);
  293. Db::commit();
  294. Log::info("提现处理成功: 提现ID={$withdrawalId}, 用户ID={$userId}, 金额={$amount}");
  295. return true;
  296. } catch (\Throwable $e) {
  297. Db::rollback();
  298. Log::error("处理提现失败: " . $e->getMessage());
  299. return false;
  300. }
  301. }
  302. /**
  303. * 获取用户佣金详情
  304. */
  305. public function getUserCommissionDetail(int $userId): array
  306. {
  307. try {
  308. // 佣金信息
  309. $commission = Db::name('user_commission')
  310. ->where('user_id', $userId)
  311. ->find();
  312. if (!$commission) {
  313. $commission = [
  314. 'total_commission' => 0.00,
  315. 'current_commission' => 0.00,
  316. 'withdrawn_commission' => 0.00,
  317. 'frozen_commission' => 0.00,
  318. 'promotion_link' => $this->generatePromotionLink($userId),
  319. 'invitation_count' => 0,
  320. 'total_invitation' => 0
  321. ];
  322. }
  323. // 最近佣金流水
  324. $flows = Db::name('commission_flow')
  325. ->where('user_id', $userId)
  326. ->order('create_time', 'desc')
  327. ->limit(50)
  328. ->select()
  329. ->toArray();
  330. // 统计今日/本月佣金
  331. $todayStart = date('Y-m-d 00:00:00');
  332. $monthStart = date('Y-m-01 00:00:00');
  333. $todayIncome = Db::name('commission_flow')
  334. ->where('user_id', $userId)
  335. ->where('amount', '>', 0)
  336. ->where('create_time', '>=', $todayStart)
  337. ->sum('amount');
  338. $monthIncome = Db::name('commission_flow')
  339. ->where('user_id', $userId)
  340. ->where('amount', '>', 0)
  341. ->where('create_time', '>=', $monthStart)
  342. ->sum('amount');
  343. return [
  344. 'commission' => $commission,
  345. 'flows' => $flows,
  346. 'stats' => [
  347. 'today_income' => $todayIncome ?: 0,
  348. 'month_income' => $monthIncome ?: 0
  349. ]
  350. ];
  351. } catch (\Throwable $e) {
  352. Log::error("获取用户佣金详情失败: " . $e->getMessage());
  353. return [
  354. 'commission' => [
  355. 'total_commission' => 0.00,
  356. 'current_commission' => 0.00,
  357. 'withdrawn_commission' => 0.00,
  358. 'frozen_commission' => 0.00,
  359. 'promotion_link' => '',
  360. 'invitation_count' => 0,
  361. 'total_invitation' => 0
  362. ],
  363. 'flows' => [],
  364. 'stats' => [
  365. 'today_income' => 0,
  366. 'month_income' => 0
  367. ]
  368. ];
  369. }
  370. }
  371. /**
  372. * 生成唯一邀请码
  373. */
  374. public function generateInvitationCode(): string
  375. {
  376. $maxAttempts = 10;
  377. $attempts = 0;
  378. while ($attempts < $maxAttempts) {
  379. // 方法1:使用数字+字母组合
  380. $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // 去掉容易混淆的字符
  381. $code = '';
  382. for ($i = 0; $i < 6; $i++) {
  383. $code .= $chars[mt_rand(0, strlen($chars) - 1)];
  384. }
  385. // 方法2:或者使用更短的邀请码
  386. // $code = strtoupper(substr(md5(uniqid()), 0, 6));
  387. // 检查邀请码是否已存在
  388. $exists = Db::table('ba_user')
  389. ->where('invitation_code', $code)
  390. ->count();
  391. if (!$exists) {
  392. return $code;
  393. }
  394. $attempts++;
  395. // 如果冲突,尝试不同的算法
  396. if ($attempts >= 5) {
  397. $code = 'INV' . mt_rand(1000, 9999);
  398. $exists = Db::table('ba_user')
  399. ->where('invitation_code', $code)
  400. ->count();
  401. if (!$exists) {
  402. return $code;
  403. }
  404. }
  405. }
  406. // 如果还是冲突,使用时间戳+随机数
  407. return 'INV' . time() . mt_rand(10, 99);
  408. }
  409. /**
  410. * 生成推广链接
  411. */
  412. private function generatePromotionLink(int $userId): string
  413. {
  414. // 获取用户的邀请码
  415. $user = Db::table('ba_user')
  416. ->where('id', $userId)
  417. ->field('invitation_code')
  418. ->find();
  419. $invitationCode = $user['invitation_code'] ?? '';
  420. if (empty($invitationCode)) {
  421. // 如果用户还没有邀请码,先生成一个
  422. $invitationCode = $this->generateInvitationCode();
  423. Db::table('ba_user')
  424. ->where('id', $userId)
  425. ->update(['invitation_code' => $invitationCode]);
  426. }
  427. $domain = request()->domain();
  428. return $domain . '/#/user/register?invite=' . $invitationCode;
  429. }
  430. }