RebateService.php 17 KB

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