RebateService.php 18 KB

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