SettleService.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | LARAVEL8.0 框架 [ LARAVEL ][ RXThinkCMF ]
  4. // +----------------------------------------------------------------------
  5. // | 版权所有 2017~2021 LARAVEL研发中心
  6. // +----------------------------------------------------------------------
  7. // | 官方网站: http://www.laravel.cn
  8. // +----------------------------------------------------------------------
  9. // | Author: laravel开发员 <laravel.qq.com>
  10. // +----------------------------------------------------------------------
  11. namespace App\Services\Api;
  12. use App\Models\AccountLogModel;
  13. use App\Models\CouponModel;
  14. use App\Models\MemberCouponModel;
  15. use App\Models\MemberModel;
  16. use App\Models\OrderCommissionModel;
  17. use App\Models\OrderModel;
  18. use App\Models\StoreModel;
  19. use App\Services\BaseService;
  20. use App\Services\ConfigService;
  21. use App\Services\RedisService;
  22. use Illuminate\Support\Facades\DB;
  23. /**
  24. * 结算管理-服务类
  25. * @author laravel开发员
  26. * @since 2020/11/11
  27. * @package App\Services\Api
  28. */
  29. class SettleService extends BaseService
  30. {
  31. /**
  32. * 构造函数
  33. * @author laravel开发员
  34. * @since 2020/11/11
  35. */
  36. public function __construct()
  37. {
  38. $this->model = new AccountLogModel();
  39. }
  40. /**
  41. * 静态入口
  42. * @return static|null
  43. */
  44. public static function make()
  45. {
  46. if (!self::$instance) {
  47. self::$instance = (new static());
  48. }
  49. return self::$instance;
  50. }
  51. /**
  52. * 佣金奖励计算
  53. * @param $order 订单信息
  54. * @param $orderGoods 订单商品
  55. * @param $userInfo 用户信息
  56. * @param $settle 是否直接结算
  57. */
  58. public function commissionCount($order, $orderGoods, $userInfo, $settle = false)
  59. {
  60. $userId = isset($order['user_id']) ? $order['user_id'] : 0;
  61. $storeId = isset($order['store_id']) ? $order['store_id'] : 0;
  62. $orderId = isset($order['id']) ? $order['id'] : 0;
  63. $orderNo = isset($order['order_no']) ? $order['order_no'] : '';
  64. $orderTotal = isset($order['pay_total']) ? $order['pay_total'] : 0;
  65. $data = [
  66. 'order_id' => $orderId,
  67. 'user_id' => $userId,
  68. 'order_no' => $orderNo,
  69. 'store_id' => $storeId,
  70. 'total' => $orderTotal,
  71. 'create_time' => time(),
  72. 'status' => 2,
  73. 'mark' => 1,
  74. ];
  75. $errors = [];
  76. /* TODO 计算商家佣金 */
  77. if ($storeId > 0) {
  78. $storeInfo = StoreModel::where(['id' => $storeId])->first();
  79. $bonusRate = isset($storeInfo['bonus_rate']) ? floatval($storeInfo['bonus_rate']) : 0;
  80. $storeBonusRate = ConfigService::make()->getConfigByCode('store_bonus_rate', 0);
  81. $storeBonusRate = $storeBonusRate > 0 && $storeBonusRate <= 100 ? $storeBonusRate : 0;
  82. $bonusRate = $bonusRate > 0 && $bonusRate <= 100 ? $bonusRate : $storeBonusRate;
  83. $bonus = moneyFormat($orderTotal * $bonusRate / 100, 2);
  84. $data['bonus'] = $bonus;
  85. $data['bonus_rate'] = $bonusRate;
  86. }
  87. /* TODO 计算分销奖励 */
  88. $parents = isset($userInfo['parents']) ? $userInfo['parents'] : '';
  89. $parents = $parents ? explode(',', $parents) : [];
  90. $parents = array_filter($parents);
  91. $parents = array_reverse($parents); // 由下到上1-3层
  92. $rewardLevel = ConfigService::make()->getConfigByCode('commission_level', 2);
  93. $parents = array_splice($parents, 0, $rewardLevel);
  94. var_dump($parents);
  95. // 分销奖励计算
  96. if ($parents) {
  97. $userList = MemberModel::with(['levelData'])->whereIn('id', $parents)
  98. ->select(['id', 'nickname', 'mobile', 'member_level', 'status', 'mark'])
  99. ->orderBy(DB::raw("FIELD(id, " . implode(',', $parents) . ")"))
  100. ->get();
  101. $bonusRows = ConfigService::make()->getConfigByGroup(11);
  102. foreach ($userList as $k => $user) {
  103. // 默认只分2层
  104. $layer = $k + 1;
  105. if ($layer > $rewardLevel) {
  106. continue;
  107. }
  108. // 处理奖励数据
  109. $pid = isset($point['id']) ? $point['id'] : 0;
  110. $pMark = isset($point['mark']) ? $point['mark'] : 0;
  111. $data["bonus_level{$layer}_uid"] = $pid;
  112. $bonus = isset($bonusRows['bonus_rate_level' . $layer]) ? $bonusRows['bonus_rate_level' . $layer] : 0;
  113. $bonusData = [
  114. 'id' => $pid,
  115. 'nickname' => isset($point['nickname']) ? $point['nickname'] : '',
  116. 'mobile' => isset($point['mobile']) ? $point['mobile'] : '',
  117. 'layer' => $layer,
  118. 'bonus' => 0,
  119. ];
  120. if ($pMark == 1) {
  121. $bonusData['bonus'] = $bonus;
  122. } else {
  123. $bonusData["error"] = "{$layer}级分销用户不存在";
  124. }
  125. // 数据
  126. $data["bonus_level{$layer}_data"] = json_encode($bonusData, 256);
  127. }
  128. }
  129. // 佣金数据入库
  130. if (!$cid = OrderCommissionModel::where(['order_no' => $orderNo])->value('id')) {
  131. $cid = OrderCommissionModel::insertGetId($data);
  132. } else {
  133. OrderCommissionModel::where(['id' => $cid])->update($data);
  134. }
  135. // 是否直接结算
  136. if($settle){
  137. return $this->commissionSettle($orderId);
  138. }
  139. $this->error = '佣金计算成功';
  140. $data['id'] = $cid;
  141. return $data;
  142. }
  143. /**
  144. * 订单佣金结算
  145. * @param $orderId 订单ID
  146. * @return array|false
  147. */
  148. public function commissionSettle($orderId)
  149. {
  150. try {
  151. $info = OrderModel::with(['commission', 'user'])
  152. ->where(['id' => $orderId, 'mark' => 1])
  153. ->select(['id', 'order_no', 'user_id', 'total', 'pay_total', 'pay_status', 'status', 'refund_status'])
  154. ->first();
  155. $info = $info ? $info->toArray() : [];
  156. if (empty($info)) {
  157. $this->error = '结算订单不存在';
  158. return false;
  159. }
  160. if ($info['status'] <= 1 || $info['pay_status'] == 10) {
  161. $this->error = '订单未支付';
  162. return false;
  163. }
  164. if (in_array($info['refund_status'], [1, 2, 3])) {
  165. $this->error = '订单已退款';
  166. return false;
  167. }
  168. $orderNo = isset($info['order_no']) ? $info['order_no'] : '';
  169. $orderUserId = isset($info['user_id']) ? $info['user_id'] : 0;
  170. $user = isset($info['user']) ? $info['user'] : [];
  171. $mobile = isset($user['mobile']) ? $user['mobile'] : $orderUserId;
  172. $commission = isset($info['commission']) ? $info['commission'] : [];
  173. $commissionId = isset($commission['id']) ? $commission['id'] : 0;
  174. if (empty($commission) || $commissionId <= 0) {
  175. $this->error = '结算订单收益参数错误';
  176. return false;
  177. }
  178. if ($commission['status'] == 1) {
  179. $this->error = '订单收益已结算';
  180. return false;
  181. }
  182. $cacheKey = "caches:ordersCommission:{$orderNo}_{$orderUserId}:";
  183. if (RedisService::get("{$cacheKey}lock")) {
  184. $this->error = '订单结算中';
  185. return false;
  186. }
  187. // 创业订单
  188. $logs = [];
  189. RedisService::set("{$cacheKey}lock", $info, 300);
  190. // TODO 商家结算
  191. $storeInfo = isset($commission['store']) ? $commission['store'] : [];
  192. $storeId = isset($commission['store_id']) ? $commission['store_id'] : 0;
  193. $storeBonus = isset($commission['bonus']) ? $commission['bonus'] : 0;
  194. $storeUserId = isset($storeInfo['user_id']) ? $storeInfo['user_id'] : 0;
  195. $storeBalance = isset($storeInfo['balance']) ? $storeInfo['balance'] : 0;
  196. if ($storeId > 0 && $storeInfo && $storeBonus > 0) {
  197. $updateData = [
  198. 'balance' => DB::raw("balance + {$storeBonus}"),
  199. 'income' => DB::raw("income + {$storeBonus}"),
  200. 'update_time' => time()
  201. ];
  202. if (!StoreModel::where(['id' => $storeId])->update($updateData)) {
  203. $this->error = '商家提成结算失败';
  204. RedisService::clear("{$cacheKey}lock");
  205. return false;
  206. }
  207. $storeBalance = moneyFormat($storeBalance + $storeBonus, 2);
  208. $data = [
  209. 'user_id' => $storeUserId,
  210. 'store_id' => $storeId,
  211. 'source_order_no' => $orderNo,
  212. 'user_type' => 2,
  213. 'type' => 7,
  214. 'bonus_type' => 0,
  215. 'money' => $storeBonus,
  216. 'after_money' => $storeBalance,
  217. 'date' => date('Y-m-d'),
  218. 'create_time' => time(),
  219. 'remark' => '商家结算',
  220. 'remark1' => "用户[{$mobile}]购买商品订单结算",
  221. 'status' => 1,
  222. 'mark' => 1
  223. ];
  224. $logs[] = $data;
  225. }
  226. // TODO 分销结算
  227. $layer = 3;
  228. $bonusUids = [];
  229. for ($i = 1; $i <= $layer; $i++) {
  230. $uid = isset($commission["bonus_level{$i}_uid"]) ? $commission["bonus_level{$i}_uid"] : 0;
  231. if ($uid) {
  232. $bonusUids[] = $uid;
  233. }
  234. }
  235. $bonusUsers = MemberModel::whereIn('id', $bonusUids)
  236. ->select(['id', 'openid', 'balance', 'mobile', 'nickname', 'property', 'ls_score', 'status'])
  237. ->get()
  238. ->keyBy('id');
  239. $bonusUsers = $bonusUsers ? $bonusUsers->toArray() : [];
  240. for ($i = 1; $i <= $layer; $i++) {
  241. $uid = isset($commission["bonus_level{$i}_uid"]) ? $commission["bonus_level{$i}_uid"] : 0;
  242. $bonusData = isset($commission["bonus_level{$i}_data"]) ? $commission["bonus_level{$i}_data"] : '';
  243. $bonusData = $bonusData ? json_decode($bonusData, true) : [];
  244. $bonus = isset($bonusData['bonus']) ? $bonusData['bonus'] : 0;
  245. $bonusUser = isset($bonusUsers[$uid]) ? $bonusUsers[$uid] : [];
  246. $bonusUserBalance = isset($bonusUser['balance']) ? $bonusUser['balance'] : 0;
  247. if ($uid && $bonusUser && $bonus > 0) {
  248. $updateData = [
  249. 'balance' => DB::raw("balance + {$bonus}"),
  250. 'bonus_total' => DB::raw("bonus_total + {$bonus}"),
  251. 'update_time' => time()
  252. ];
  253. if (!MemberModel::where(['id' => $uid])->update($updateData)) {
  254. $this->error = '分销收益结算错误';
  255. RedisService::clear("{$cacheKey}lock");
  256. return false;
  257. }
  258. if ($bonus > 0) {
  259. $bonusUserBalance = moneyFormat($bonusUserBalance + $bonus, 2);
  260. $data = [
  261. 'user_id' => $uid,
  262. 'source_order_no' => $orderNo,
  263. 'user_type' => 1,
  264. 'type' => 8,
  265. 'bonus_type' => 1,
  266. 'money' => $bonus,
  267. 'after_money' => $bonusUserBalance,
  268. 'date' => date('Y-m-d'),
  269. 'create_time' => time() + $i,
  270. 'remark' => "分销奖励",
  271. 'remark1' => "用户ID:{$orderUserId}购买商品{$i}级分销奖励",
  272. 'status' => 1,
  273. 'mark' => 1
  274. ];
  275. $logs[] = $data;
  276. }
  277. }
  278. }
  279. // TODO 明细入表
  280. RedisService::set("{$cacheKey}logs", $logs, 3600);
  281. if ($logs && !AccountLogModel::insert($logs)) {
  282. $this->error = '奖励明细结算处理错误';
  283. RedisService::clear("{$cacheKey}lock");
  284. return false;
  285. }
  286. // 订单结算状态更新
  287. if (!OrderCommissionModel::where(['id' => $commissionId])->update(['status' => 1, 'arrival_at' => date('Y-m-d H:i:s'), 'update_time' => time()])) {
  288. $this->error = '订单结算状态更新失败';
  289. return false;
  290. }
  291. $this->error = '订单结算完成';
  292. RedisService::clear("{$cacheKey}lock");
  293. RedisService::clear("caches:orders:settleList");
  294. RedisService::set("{$cacheKey}result", $info, 3600);
  295. $result = ['id' => $orderId, 'user_id' => $orderUserId, 'logs' => $logs, 'commission' => $commission];
  296. RedisService::set("caches:settles:{$orderId}_{$orderNo}_success", $result, 7200);
  297. return $result;
  298. } catch (\Exception $exception) {
  299. RedisService::set("caches:settles:{$orderId}_error", ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()], 7200);
  300. return false;
  301. }
  302. }
  303. /**
  304. * 新人注册奖励
  305. * @param $userId
  306. * @return array|bool
  307. */
  308. public function registerReward($userId)
  309. {
  310. $rewardOpen = ConfigService::make()->getConfigByCode('register_award_coupon', 0);
  311. $rewardCouponId = ConfigService::make()->getConfigByCode('register_reward_coupon_id', 0);
  312. if ($rewardCouponId <= 0 || $rewardOpen != 1) {
  313. $this->error = '未开启或配置注册优惠券奖励';
  314. return true;
  315. }
  316. $couponInfo = CouponModel::where(['id' => $rewardCouponId, 'mark' => 1])
  317. ->first();
  318. $status = isset($couponInfo['status']) ? $couponInfo['status'] : 0;
  319. $num = isset($couponInfo['num']) ? $couponInfo['num'] : 0;
  320. $receivedNum = isset($couponInfo['received_num']) ? $couponInfo['received_num'] : 0;
  321. if (empty($couponInfo) || $status != 1) {
  322. $this->error = '配置的注册奖励优惠券不存在或无效';
  323. return true;
  324. }
  325. if ($num > 0 && $receivedNum >= $num) {
  326. $this->error = '注册奖励优惠券已发放完~';
  327. return true;
  328. }
  329. $data = [
  330. 'coupon_id' => $rewardCouponId,
  331. 'user_id' => $userId,
  332. 'store_id' => isset($couponInfo['store_id']) ? $couponInfo['store_id'] : 0,
  333. 'name' => isset($couponInfo['name']) ? $couponInfo['name'] : '',
  334. 'coupon_type' => isset($couponInfo['coupon_type']) ? $couponInfo['coupon_type'] : 20,
  335. 'reduce_price' => isset($couponInfo['reduce_price']) ? $couponInfo['reduce_price'] : 0,
  336. 'discount' => isset($couponInfo['discount']) ? $couponInfo['discount'] : 0,
  337. 'min_price' => isset($couponInfo['min_price']) ? $couponInfo['min_price'] : 0,
  338. 'expire_day' => isset($couponInfo['expire_day']) ? $couponInfo['expire_day'] : 0,
  339. 'start_time' => isset($couponInfo['start_time']) ? $couponInfo['start_time'] : 0,
  340. 'end_time' => isset($couponInfo['end_time']) ? $couponInfo['end_time'] : 0,
  341. 'goods_ids' => isset($couponInfo['goods_ids']) && $couponInfo['goods_ids'] ? $couponInfo['goods_ids'] : '',
  342. 'create_time' => time(),
  343. 'status' => 1,
  344. ];
  345. if (!$id = MemberCouponModel::insertGetId($data)) {
  346. $this->error = '奖励注册优惠券失败';
  347. return true;
  348. }
  349. // 更新发放统计
  350. CouponModel::where(['id' => $id])->update(['received_num' => DB::raw("received_num+1"), 'update_time' => time()]);
  351. $data['id'] = $id;
  352. $data['discount'] = floatval($data['discount']);
  353. $data['reduce_price'] = floatval($data['reduce_price']);
  354. unset($data['create_time']);
  355. unset($data['status']);
  356. unset($data['goods_ids']);
  357. $this->error = '奖励注册优惠券成功';
  358. return $data;
  359. }
  360. }