RebateService.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. <?php
  2. namespace app\data\service;
  3. use app\data\model\BaseUserDiscount;
  4. use app\data\model\BaseUserUpgrade;
  5. use app\data\model\DataUser;
  6. use app\data\model\DataUserRebate;
  7. use app\data\model\ShopOrder;
  8. use app\data\model\ShopOrderItem;
  9. use think\admin\Exception;
  10. use think\admin\extend\CodeExtend;
  11. use think\admin\Service;
  12. /**
  13. * 系统实时返利服务
  14. * Class RebateService
  15. * @package app\data\service
  16. */
  17. class RebateService extends Service
  18. {
  19. const PRIZE_01 = 'PRIZE01';
  20. const PRIZE_02 = 'PRIZE02';
  21. const PRIZE_03 = 'PRIZE03';
  22. const PRIZE_04 = 'PRIZE04';
  23. const PRIZE_05 = 'PRIZE05';
  24. const PRIZE_06 = 'PRIZE06';
  25. const PRIZE_07 = 'PRIZE07';
  26. const PRIZE_08 = 'PRIZE08';
  27. const PRIZES = [
  28. self::PRIZE_01 => ['code' => self::PRIZE_01, 'name' => '首推奖励', 'func' => '_prize01'],
  29. self::PRIZE_02 => ['code' => self::PRIZE_02, 'name' => '复购奖励', 'func' => '_prize02'],
  30. self::PRIZE_03 => ['code' => self::PRIZE_03, 'name' => '直属团队', 'func' => '_prize03'],
  31. self::PRIZE_04 => ['code' => self::PRIZE_04, 'name' => '间接团队', 'func' => '_prize04'],
  32. self::PRIZE_05 => ['code' => self::PRIZE_05, 'name' => '差额奖励', 'func' => '_prize05'],
  33. self::PRIZE_06 => ['code' => self::PRIZE_06, 'name' => '管理奖励', 'func' => '_prize06'],
  34. self::PRIZE_07 => ['code' => self::PRIZE_07, 'name' => '升级奖励', 'func' => '_prize07'],
  35. self::PRIZE_08 => ['code' => self::PRIZE_08, 'name' => '平推返利', 'func' => '_prize08'],
  36. ];
  37. /**
  38. * 用户数据
  39. * @var array
  40. */
  41. protected $user;
  42. /**
  43. * 订单数据
  44. * @var array
  45. */
  46. protected $order;
  47. /**
  48. * 奖励到账时机
  49. * @var integer
  50. */
  51. protected $status;
  52. /**
  53. * 推荐用户
  54. * @var array
  55. */
  56. protected $from1;
  57. protected $from2;
  58. /**
  59. * 执行订单返利处理
  60. * @param string $orderNo
  61. * @throws \think\admin\Exception
  62. * @throws \think\db\exception\DataNotFoundException
  63. * @throws \think\db\exception\DbException
  64. * @throws \think\db\exception\ModelNotFoundException
  65. */
  66. public function execute(string $orderNo)
  67. {
  68. // 获取订单数据
  69. $map = ['order_no' => $orderNo, 'payment_status' => 1];
  70. $this->order = ShopOrder::mk()->where($map)->findOrEmpty();
  71. if ($this->order->isEmpty()) throw new Exception('订单不存在');
  72. if ($this->order['payment_type'] === 'balance') return;
  73. if ($this->order['amount_total'] <= 0) throw new Exception('订单金额为零');
  74. if ($this->order['rebate_amount'] <= 0) throw new Exception('订单返利为零');
  75. // 获取用户数据
  76. $map = ['id' => $this->order['uuid'], 'deleted' => 0];
  77. $this->user = DataUser::mk()->where($map)->findOrEmpty();
  78. if ($this->user->isEmpty()) throw new Exception('用户不存在');
  79. // 获取直接代理数据
  80. if ($this->order['puid1'] > 0) {
  81. $this->from1 = DataUser::mk()->find($this->order['puid1']);
  82. if (empty($this->from1)) throw new Exception('直接代理不存在');
  83. }
  84. // 获取间接代理数据
  85. if ($this->order['puid2'] > 0) {
  86. $this->from2 = DataUser::mk()->find($this->order['puid2']);
  87. if (empty($this->from2)) throw new Exception('间接代理不存在');
  88. }
  89. // 批量发放配置奖励
  90. foreach (self::PRIZES as $vo) if (method_exists($this, $vo['func'])) {
  91. $this->app->log->notice("订单 {$orderNo} 开始发放 [{$vo['func']}] {$vo['name']}");
  92. $this->{$vo['func']}();
  93. $this->app->log->notice("订单 {$orderNo} 完成发放 [{$vo['func']}] {$vo['name']}");
  94. }
  95. }
  96. /**
  97. * 返利服务初始化
  98. * @return void
  99. * @throws \think\db\exception\DataNotFoundException
  100. * @throws \think\db\exception\DbException
  101. * @throws \think\db\exception\ModelNotFoundException
  102. */
  103. protected function initialize()
  104. {
  105. // 返利奖励到账时机
  106. // settl_type 为 1 支付后立即到账
  107. // settl_type 为 2 确认后立即到账
  108. $this->status = $this->config('settl_type') > 1 ? 0 : 1;
  109. }
  110. /**
  111. * 获取配置数据
  112. * @param ?string $name 配置名称
  113. * @return array|string
  114. * @throws \think\db\exception\DataNotFoundException
  115. * @throws \think\db\exception\DbException
  116. * @throws \think\db\exception\ModelNotFoundException
  117. */
  118. public function config(?string $name = null)
  119. {
  120. static $data = [];
  121. if (empty($data)) $data = sysdata('RebateRule');
  122. return is_null($name) ? $data : ($data[$name] ?? '');
  123. }
  124. /**
  125. * 用户首推奖励
  126. * @return boolean
  127. * @throws \think\db\exception\DataNotFoundException
  128. * @throws \think\db\exception\DbException
  129. * @throws \think\db\exception\ModelNotFoundException
  130. */
  131. protected function _prize01(): bool
  132. {
  133. if (empty($this->from1)) return false;
  134. $map = ['order_uuid' => $this->user['id']];
  135. if (DataUserRebate::mk()->where($map)->count() > 0) return false;
  136. if (!$this->checkPrizeStatus(self::PRIZE_01, $this->from1['vip_code'])) return false;
  137. // 创建返利奖励记录
  138. $key = "{$this->from1['vip_code']}_{$this->user['vip_code']}";
  139. $map = ['type' => self::PRIZE_01, 'order_no' => $this->order['order_no'], 'order_uuid' => $this->order['uuid']];
  140. if ($this->config("frist_state_vip_{$key}") && DataUserRebate::mk()->where($map)->count() < 1) {
  141. $value = $this->config("frist_value_vip_{$key}");
  142. if ($this->config("frist_type_vip_{$key}") == 1) {
  143. $val = floatval($value ?: '0.00');
  144. $name = "{$this->name(self::PRIZE_01)},每单 {$val} 元";
  145. } else {
  146. $val = floatval($value * $this->order['rebate_amount'] / 100);
  147. $name = "{$this->name(self::PRIZE_01)},订单 {$value}%";
  148. }
  149. // 写入返利记录
  150. $this->writeRabate($this->from1['id'], $map, $name, $val);
  151. }
  152. return true;
  153. }
  154. /**
  155. * 用户复购奖励
  156. * @return boolean
  157. * @throws \think\db\exception\DataNotFoundException
  158. * @throws \think\db\exception\DbException
  159. * @throws \think\db\exception\ModelNotFoundException
  160. */
  161. protected function _prize02(): bool
  162. {
  163. $map = [];
  164. $map[] = ['order_uuid', '=', $this->user['id']];
  165. $map[] = ['order_no', '<>', $this->order['order_no']];
  166. if (DataUserRebate::mk()->where($map)->count() < 1) return false;
  167. // 检查上级可否奖励
  168. if (empty($this->from1) || empty($this->from1['vip_code'])) return false;
  169. if (!$this->checkPrizeStatus(self::PRIZE_02, $this->from1['vip_code'])) return false;
  170. // 创建返利奖励记录
  171. $key = "vip_{$this->from1['vip_code']}_{$this->user['vip_code']}";
  172. $map = ['type' => self::PRIZE_02, 'order_no' => $this->order['order_no'], 'order_uuid' => $this->order['uuid']];
  173. if ($this->config("repeat_state_{$key}") && DataUserRebate::mk()->where($map)->count() < 1) {
  174. $value = $this->config("repeat_value_{$key}");
  175. if ($this->config("repeat_type_{$key}") == 1) {
  176. $val = floatval($value ?: '0.00');
  177. $name = "{$this->name(self::PRIZE_02)},每人 {$val} 元";
  178. } else {
  179. $val = floatval($value * $this->order['rebate_amount'] / 100);
  180. $name = "{$this->name(self::PRIZE_02)},订单 {$value}%";
  181. }
  182. // 写入返利记录
  183. $this->writeRabate($this->from1['id'], $map, $name, $val);
  184. }
  185. return true;
  186. }
  187. /**
  188. * 用户直属团队
  189. * @return boolean
  190. * @throws \think\db\exception\DataNotFoundException
  191. * @throws \think\db\exception\DbException
  192. * @throws \think\db\exception\ModelNotFoundException
  193. */
  194. private function _prize03(): bool
  195. {
  196. if (empty($this->from1)) return false;
  197. if (!$this->checkPrizeStatus(self::PRIZE_03, $this->from1['vip_code'])) return false;
  198. // 创建返利奖励记录
  199. $key = "{$this->user['vip_code']}";
  200. $map = ['type' => self::PRIZE_03, 'order_no' => $this->order['order_no'], 'order_uuid' => $this->order['uuid']];
  201. if ($this->config("direct_state_vip_{$key}") && DataUserRebate::mk()->where($map)->count() < 1) {
  202. $value = $this->config("direct_value_vip_{$key}");
  203. if ($this->config("direct_type_vip_{$key}") == 1) {
  204. $val = floatval($value ?: '0.00');
  205. $name = "{$this->name(self::PRIZE_03)},每人 {$val} 元";
  206. } else {
  207. $val = floatval($value * $this->order['rebate_amount'] / 100);
  208. $name = "{$this->name(self::PRIZE_03)},订单 {$value}%";
  209. }
  210. // 写入返利记录
  211. $this->writeRabate($this->from1['id'], $map, $name, $val);
  212. }
  213. return true;
  214. }
  215. /**
  216. * 用户间接团队
  217. * @return boolean
  218. * @throws \think\db\exception\DataNotFoundException
  219. * @throws \think\db\exception\DbException
  220. * @throws \think\db\exception\ModelNotFoundException
  221. */
  222. private function _prize04(): bool
  223. {
  224. if (empty($this->from2)) return false;
  225. if (!$this->checkPrizeStatus(self::PRIZE_04, $this->from2['vip_code'])) return false;
  226. $key = "{$this->user['vip_code']}";
  227. $map = ['type' => self::PRIZE_04, 'order_no' => $this->order['order_no'], 'order_uuid' => $this->order['uuid']];
  228. if ($this->config("indirect_state_vip_{$key}") && DataUserRebate::mk()->where($map)->count() < 1) {
  229. $value = $this->config("indirect_value_vip_{$key}");
  230. if ($this->config("indirect_type_vip_{$key}") == 1) {
  231. $val = floatval($value ?: '0.00');
  232. $name = "{$this->name(self::PRIZE_03)},每人 {$val} 元";
  233. } else {
  234. $val = floatval($value * $this->order['rebate_amount'] / 100);
  235. $name = "{$this->name(self::PRIZE_03)},订单 {$value}%";
  236. }
  237. // 写入返利记录
  238. $this->writeRabate($this->from2['id'], $map, $name, $val);
  239. }
  240. return true;
  241. }
  242. /**
  243. * 用户差额奖励
  244. * @return false
  245. * @throws \think\db\exception\DataNotFoundException
  246. * @throws \think\db\exception\DbException
  247. * @throws \think\db\exception\ModelNotFoundException
  248. */
  249. private function _prize05(): bool
  250. {
  251. $puids = array_reverse(str2arr($this->user['path'], '-'));
  252. if (empty($puids) || $this->order['amount_total'] <= 0) return false;
  253. // 获取可以参与奖励的代理
  254. $vips = BaseUserUpgrade::mk()->whereLike('rebate_rule', '%,' . self::PRIZE_05 . ',%')->column('number');
  255. $users = DataUser::mk()->whereIn('vip_code', $vips)->whereIn('id', $puids)->orderField('id', $puids)->select()->toArray();
  256. if (empty($vips) || empty($users)) return true;
  257. // 查询需要计算奖励的商品
  258. foreach (ShopOrderItem::mk()->where(['order_no' => $this->order['order_no']])->cursor() as $item) {
  259. if ($item['discount_id'] > 0 && $item['rebate_type'] === 1) {
  260. [$tVip, $tRate] = [$item['vip_code'], $item['discount_rate']];
  261. $map = ['id' => $item['discount_id'], 'status' => 1, 'deleted' => 0];
  262. $rules = json_decode(BaseUserDiscount::mk()->where($map)->value('items', '[]'), true);
  263. foreach ($users as $user) if (isset($rules[$user['vip_code']]) && $user['vip_code'] > $tVip) {
  264. if (($rule = $rules[$user['vip_code']]) && $tRate > $rule['discount']) {
  265. $map = ['uuid' => $user['id'], 'type' => self::PRIZE_05, 'order_no' => $this->order['order_no']];
  266. if (DataUserRebate::mk()->where($map)->count() < 1) {
  267. $dRate = ($rate = $tRate - $rule['discount']) / 100;
  268. $name = "等级差额奖励{$tVip}#{$user['vip_code']}商品原价{$item['total_selling']}元的{$rate}%";
  269. $amount = $dRate * $item['total_selling'];
  270. // 写入用户返利记录
  271. $this->writeRabate($user['id'], $map, $name, $amount);
  272. }
  273. [$tVip, $tRate] = [$user['vip_code'], $rule['discount']];
  274. }
  275. }
  276. }
  277. }
  278. return true;
  279. }
  280. /**
  281. * 用户管理奖励发放
  282. * @return boolean
  283. * @throws \think\db\exception\DataNotFoundException
  284. * @throws \think\db\exception\DbException
  285. * @throws \think\db\exception\ModelNotFoundException
  286. */
  287. private function _prize06(): bool
  288. {
  289. $puids = array_reverse(str2arr($this->user['path'], '-'));
  290. if (empty($puids) || $this->order['amount_total'] <= 0) return false;
  291. // 记录用户原始等级
  292. $prevLevel = $this->user['vip_code'];
  293. // 获取参与奖励的代理
  294. $vips = BaseUserUpgrade::mk()->whereLike('rebate_rule', '%,' . self::PRIZE_06 . ',%')->column('number');
  295. foreach (DataUser::mk()->whereIn('vip_code', $vips)->whereIn('id', $puids)->orderField('id', $puids)->cursor() as $user) {
  296. if ($user['vip_code'] > $prevLevel) {
  297. if (($amount = $this->_prize06amount($prevLevel + 1, $user['vip_code'])) > 0.00) {
  298. $map = ['uuid' => $user['id'], 'type' => self::PRIZE_06, 'order_no' => $this->order['order_no']];
  299. if (DataUserRebate::mk()->where($map)->count() < 1) {
  300. $name = "{$this->name(self::PRIZE_06)},[ VIP{$prevLevel} > VIP{$user['vip_code']} ] 每单 {$amount} 元";
  301. $this->writeRabate($user['id'], $map, $name, $amount);
  302. }
  303. }
  304. $prevLevel = $user['vip_code'];
  305. }
  306. }
  307. return true;
  308. }
  309. /**
  310. * 计算两等级之间的管理奖差异
  311. * @param integer $prevLevel 上个等级
  312. * @param integer $nextLevel 下个等级
  313. * @return float
  314. * @throws \think\db\exception\DataNotFoundException
  315. * @throws \think\db\exception\DbException
  316. * @throws \think\db\exception\ModelNotFoundException
  317. */
  318. private function _prize06amount(int $prevLevel, int $nextLevel): float
  319. {
  320. if ($this->config("manage_type_vip_{$nextLevel}") == 2) {
  321. $amount = 0.00;
  322. foreach (range($prevLevel, $nextLevel) as $level) {
  323. [$state, $value] = [$this->config("manage_state_vip_{$level}"), $this->config("manage_value_vip_{$level}")];
  324. if ($state && $value > 0) $amount += $value;
  325. }
  326. return floatval($amount);
  327. } elseif ($this->config("manage_state_vip_{$nextLevel}")) {
  328. return floatval($this->config("manage_value_vip_{$nextLevel}"));
  329. } else {
  330. return floatval(0);
  331. }
  332. }
  333. /**
  334. * 用户升级奖励发放
  335. * @return boolean
  336. * @throws \think\db\exception\DataNotFoundException
  337. * @throws \think\db\exception\DbException
  338. * @throws \think\db\exception\ModelNotFoundException
  339. */
  340. private function _prize07(): bool
  341. {
  342. if (empty($this->from1)) return false;
  343. if ($this->order['order_no'] !== $this->user['vip_order']) return false;
  344. if (!$this->checkPrizeStatus(self::PRIZE_07, $this->from1['vip_code'])) return false;
  345. // 创建返利奖励记录
  346. $vip = "{$this->user['vip_code']}";
  347. $map = ['type' => self::PRIZE_07, 'order_no' => $this->order['order_no'], 'order_uuid' => $this->order['uuid']];
  348. if ($this->config("upgrade_state_vip_{$vip}") && DataUserRebate::mk()->where($map)->count() < 1) {
  349. $value = $this->config("upgrade_value_vip_{$vip}");
  350. if ($this->config("upgrade_type_vip_{$vip}") == 1) {
  351. $val = floatval($value ?: '0.00');
  352. $name = "{$this->name(self::PRIZE_07)},每人 {$val} 元";
  353. } else {
  354. $val = floatval($value * $this->order['rebate_amount'] / 100);
  355. $name = "{$this->name(self::PRIZE_07)},订单 {$value}%";
  356. }
  357. // 写入返利记录
  358. $this->writeRabate($this->from1['id'], $map, $name, $val);
  359. }
  360. return true;
  361. }
  362. /**
  363. * 用户平推奖励发放
  364. * @return boolean
  365. */
  366. private function _prize08(): bool
  367. {
  368. if (empty($this->from1)) return false;
  369. $map = ['vip_code' => $this->user['vip_code']];
  370. $uuids = array_reverse(str2arr(trim($this->user['path'], '-'), '-'));
  371. $puids = DataUser::mk()->whereIn('id', $uuids)->orderField('id', $uuids)->where($map)->column('id');
  372. if (count($puids) < 2) return false;
  373. $this->app->db->transaction(function () use ($puids, $map) {
  374. foreach ($puids as $key => $puid) {
  375. // 最多两层
  376. if (($layer = $key + 1) > 2) break;
  377. // 检查重复
  378. $map = ['uuid' => $puid, 'type' => self::PRIZE_08, 'order_no' => $this->order['order_no']];
  379. if (DataUserRebate::mk()->where($map)->count() < 1) {
  380. // 返利比例
  381. $rate = $this->config("equal_value_vip_{$layer}_{$this->user['vip_code']}");
  382. // 返利金额
  383. $money = floatval($rate * $this->order['rebate_amount'] / 100);
  384. $name = "{$this->name(self::PRIZE_08)}, 返回订单的 {$rate}%";
  385. // 写入返利
  386. $this->writeRabate($puid, $map, $name, $money);
  387. }
  388. }
  389. });
  390. return true;
  391. }
  392. /**
  393. * 获取奖励名称
  394. * @param string $prize
  395. * @return string
  396. */
  397. public static function name(string $prize): string
  398. {
  399. return self::PRIZES[$prize]['name'] ?? $prize;
  400. }
  401. /**
  402. * 检查等级是否有奖励
  403. * @param string $prize 奖励规则
  404. * @param integer $level 用户等级
  405. * @return boolean
  406. */
  407. private function checkPrizeStatus(string $prize, int $level): bool
  408. {
  409. $query = BaseUserUpgrade::mk()->where(['number' => $level]);
  410. return $query->whereLike('rebate_rule', "%,{$prize},%")->count() > 0;
  411. }
  412. /**
  413. * 写返利记录
  414. * @param int $uuid 奖励用户
  415. * @param array $map 查询条件
  416. * @param string $name 奖励名称
  417. * @param float $amount 奖励金额
  418. */
  419. private function writeRabate(int $uuid, array $map, string $name, float $amount)
  420. {
  421. DataUserRebate::mk()->insert(array_merge($map, [
  422. 'uuid' => $uuid,
  423. 'date' => date('Y-m-d'),
  424. 'code' => CodeExtend::uniqidDate(20, 'R'),
  425. 'name' => $name,
  426. 'amount' => $amount,
  427. 'status' => $this->status,
  428. 'order_no' => $this->order['order_no'],
  429. 'order_uuid' => $this->order['uuid'],
  430. 'order_amount' => $this->order['amount_total'],
  431. ]));
  432. // 刷新用户返利统计
  433. UserRebateService::amount($uuid);
  434. }
  435. }