SettleService.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  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. // 分销奖励计算
  95. if ($parents) {
  96. $userList = MemberModel::with(['levelData'])->whereIn('id', $parents)
  97. ->select(['id', 'nickname', 'mobile', 'member_level', 'status', 'mark'])
  98. ->orderBy(DB::raw("FIELD(id, " . implode(',', $parents) . ")"))
  99. ->get();
  100. $bonusRows = ConfigService::make()->getConfigByGroup(11);
  101. foreach ($userList as $k => $user) {
  102. // 默认只分2层
  103. $layer = $k + 1;
  104. if ($layer > $rewardLevel) {
  105. continue;
  106. }
  107. // 处理奖励数据
  108. $pid = isset($point['id']) ? $point['id'] : 0;
  109. $pMark = isset($point['mark']) ? $point['mark'] : 0;
  110. $data["bonus_level{$layer}_uid"] = $pid;
  111. $bonus = isset($bonusRows['bonus_rate_level' . $layer]) ? $bonusRows['bonus_rate_level' . $layer] : 0;
  112. $bonusData = [
  113. 'id' => $pid,
  114. 'nickname' => isset($point['nickname']) ? $point['nickname'] : '',
  115. 'mobile' => isset($point['mobile']) ? $point['mobile'] : '',
  116. 'layer' => $layer,
  117. 'bonus' => 0,
  118. ];
  119. if ($pMark == 1) {
  120. $bonusData['bonus'] = $bonus;
  121. } else {
  122. $bonusData["error"] = "{$layer}级分销用户不存在";
  123. }
  124. // 数据
  125. $data["bonus_level{$layer}_data"] = json_encode($bonusData, 256);
  126. }
  127. }
  128. // 佣金数据入库
  129. if (!$cid = OrderCommissionModel::where(['order_no' => $orderNo])->value('id')) {
  130. $cid = OrderCommissionModel::insertGetId($data);
  131. } else {
  132. OrderCommissionModel::where(['id' => $cid])->update($data);
  133. }
  134. // 是否直接结算
  135. if($settle){
  136. return $this->commissionSettle($orderId);
  137. }
  138. $this->error = '佣金计算成功';
  139. $data['id'] = $cid;
  140. return $data;
  141. }
  142. /**
  143. * 订单佣金结算
  144. * @param $orderId 订单ID
  145. * @return array|false
  146. */
  147. public function commissionSettle($orderId)
  148. {
  149. try {
  150. $info = OrderModel::with(['commission', 'user'])
  151. ->where(['id' => $orderId, 'mark' => 1])
  152. ->select(['id', 'order_no', 'user_id', 'total', 'pay_total', 'pay_status', 'status', 'refund_status'])
  153. ->first();
  154. $info = $info ? $info->toArray() : [];
  155. if (empty($info)) {
  156. $this->error = '结算订单不存在';
  157. return false;
  158. }
  159. if ($info['status'] <= 1 || $info['pay_status'] == 10) {
  160. $this->error = '订单未支付';
  161. return false;
  162. }
  163. if (in_array($info['refund_status'], [1, 2, 3])) {
  164. $this->error = '订单已退款';
  165. return false;
  166. }
  167. $orderNo = isset($info['order_no']) ? $info['order_no'] : '';
  168. $orderUserId = isset($info['user_id']) ? $info['user_id'] : 0;
  169. $user = isset($info['user']) ? $info['user'] : [];
  170. $mobile = isset($user['mobile']) ? $user['mobile'] : $orderUserId;
  171. $commission = isset($info['commission']) ? $info['commission'] : [];
  172. $commissionId = isset($commission['id']) ? $commission['id'] : 0;
  173. if (empty($commission) || $commissionId <= 0) {
  174. $this->error = '结算订单收益参数错误';
  175. return false;
  176. }
  177. if ($commission['status'] == 1) {
  178. $this->error = '订单收益已结算';
  179. return false;
  180. }
  181. $cacheKey = "caches:ordersCommission:{$orderNo}_{$orderUserId}:";
  182. if (RedisService::get("{$cacheKey}lock")) {
  183. $this->error = '订单结算中';
  184. return false;
  185. }
  186. // 创业订单
  187. $logs = [];
  188. RedisService::set("{$cacheKey}lock", $info, 300);
  189. // TODO 商家结算
  190. $storeInfo = isset($commission['store']) ? $commission['store'] : [];
  191. $storeId = isset($commission['store_id']) ? $commission['store_id'] : 0;
  192. $storeBonus = isset($commission['bonus']) ? $commission['bonus'] : 0;
  193. $storeUserId = isset($storeInfo['user_id']) ? $storeInfo['user_id'] : 0;
  194. $storeBalance = isset($storeInfo['balance']) ? $storeInfo['balance'] : 0;
  195. if ($storeId > 0 && $storeInfo && $storeBonus > 0) {
  196. $updateData = [
  197. 'balance' => DB::raw("balance + {$storeBonus}"),
  198. 'income' => DB::raw("income + {$storeBonus}"),
  199. 'update_time' => time()
  200. ];
  201. if (!StoreModel::where(['id' => $storeId])->update($updateData)) {
  202. $this->error = '商家提成结算失败';
  203. RedisService::clear("{$cacheKey}lock");
  204. return false;
  205. }
  206. $storeBalance = moneyFormat($storeBalance + $storeBonus, 2);
  207. $data = [
  208. 'user_id' => $storeUserId,
  209. 'store_id' => $storeId,
  210. 'source_order_no' => $orderNo,
  211. 'user_type' => 2,
  212. 'type' => 7,
  213. 'bonus_type' => 0,
  214. 'money' => $storeBonus,
  215. 'after_money' => $storeBalance,
  216. 'date' => date('Y-m-d'),
  217. 'create_time' => time(),
  218. 'remark' => '商家结算',
  219. 'remark1' => "用户[{$mobile}]购买商品订单结算",
  220. 'status' => 1,
  221. 'mark' => 1
  222. ];
  223. $logs[] = $data;
  224. }
  225. // TODO 分销结算
  226. $layer = 3;
  227. $bonusUids = [];
  228. for ($i = 1; $i <= $layer; $i++) {
  229. $uid = isset($commission["bonus_level{$i}_uid"]) ? $commission["bonus_level{$i}_uid"] : 0;
  230. if ($uid) {
  231. $bonusUids[] = $uid;
  232. }
  233. }
  234. $bonusUsers = MemberModel::whereIn('id', $bonusUids)
  235. ->select(['id', 'openid', 'balance', 'mobile', 'nickname', 'property', 'ls_score', 'status'])
  236. ->get()
  237. ->keyBy('id');
  238. $bonusUsers = $bonusUsers ? $bonusUsers->toArray() : [];
  239. for ($i = 1; $i <= $layer; $i++) {
  240. $uid = isset($commission["bonus_level{$i}_uid"]) ? $commission["bonus_level{$i}_uid"] : 0;
  241. $bonusData = isset($commission["bonus_level{$i}_data"]) ? $commission["bonus_level{$i}_data"] : '';
  242. $bonusData = $bonusData ? json_decode($bonusData, true) : [];
  243. $bonus = isset($bonusData['bonus']) ? $bonusData['bonus'] : 0;
  244. $bonusUser = isset($bonusUsers[$uid]) ? $bonusUsers[$uid] : [];
  245. $bonusUserBalance = isset($bonusUser['balance']) ? $bonusUser['balance'] : 0;
  246. if ($uid && $bonusUser && $bonus > 0) {
  247. $updateData = [
  248. 'balance' => DB::raw("balance + {$bonus}"),
  249. 'bonus_total' => DB::raw("bonus_total + {$bonus}"),
  250. 'update_time' => time()
  251. ];
  252. if (!MemberModel::where(['id' => $uid])->update($updateData)) {
  253. $this->error = '分销收益结算错误';
  254. RedisService::clear("{$cacheKey}lock");
  255. return false;
  256. }
  257. if ($bonus > 0) {
  258. $bonusUserBalance = moneyFormat($bonusUserBalance + $bonus, 2);
  259. $data = [
  260. 'user_id' => $uid,
  261. 'source_order_no' => $orderNo,
  262. 'user_type' => 1,
  263. 'type' => 8,
  264. 'bonus_type' => 1,
  265. 'money' => $bonus,
  266. 'after_money' => $bonusUserBalance,
  267. 'date' => date('Y-m-d'),
  268. 'create_time' => time() + $i,
  269. 'remark' => "分销奖励",
  270. 'remark1' => "用户ID:{$orderUserId}购买商品{$i}级分销奖励",
  271. 'status' => 1,
  272. 'mark' => 1
  273. ];
  274. $logs[] = $data;
  275. }
  276. }
  277. }
  278. // TODO 明细入表
  279. RedisService::set("{$cacheKey}logs", $logs, 3600);
  280. if ($logs && !AccountLogModel::insert($logs)) {
  281. $this->error = '奖励明细结算处理错误';
  282. RedisService::clear("{$cacheKey}lock");
  283. return false;
  284. }
  285. // 订单结算状态更新
  286. if (!OrderCommissionModel::where(['id' => $commissionId])->update(['status' => 1, 'arrival_at' => date('Y-m-d H:i:s'), 'update_time' => time()])) {
  287. $this->error = '订单结算状态更新失败';
  288. return false;
  289. }
  290. $this->error = '订单结算完成';
  291. RedisService::clear("{$cacheKey}lock");
  292. RedisService::clear("caches:orders:settleList");
  293. RedisService::set("{$cacheKey}result", $info, 3600);
  294. $result = ['id' => $orderId, 'user_id' => $orderUserId, 'logs' => $logs, 'commission' => $commission];
  295. RedisService::set("caches:settles:{$orderId}_{$orderNo}_success", $result, 7200);
  296. return $result;
  297. } catch (\Exception $exception) {
  298. RedisService::set("caches:settles:{$orderId}_error", ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()], 7200);
  299. return false;
  300. }
  301. }
  302. /**
  303. * 新人注册奖励
  304. * @param $userId
  305. * @return array|bool
  306. */
  307. public function registerReward($userId)
  308. {
  309. $rewardOpen = ConfigService::make()->getConfigByCode('register_award_coupon', 0);
  310. $rewardCouponId = ConfigService::make()->getConfigByCode('register_reward_coupon_id', 0);
  311. if ($rewardCouponId <= 0 || $rewardOpen != 1) {
  312. $this->error = '未开启或配置注册优惠券奖励';
  313. return true;
  314. }
  315. $couponInfo = CouponModel::where(['id' => $rewardCouponId, 'mark' => 1])
  316. ->first();
  317. $status = isset($couponInfo['status']) ? $couponInfo['status'] : 0;
  318. $num = isset($couponInfo['num']) ? $couponInfo['num'] : 0;
  319. $receivedNum = isset($couponInfo['received_num']) ? $couponInfo['received_num'] : 0;
  320. if (empty($couponInfo) || $status != 1) {
  321. $this->error = '配置的注册奖励优惠券不存在或无效';
  322. return true;
  323. }
  324. if ($num > 0 && $receivedNum >= $num) {
  325. $this->error = '注册奖励优惠券已发放完~';
  326. return true;
  327. }
  328. $data = [
  329. 'coupon_id' => $rewardCouponId,
  330. 'user_id' => $userId,
  331. 'store_id' => isset($couponInfo['store_id']) ? $couponInfo['store_id'] : 0,
  332. 'name' => isset($couponInfo['name']) ? $couponInfo['name'] : '',
  333. 'coupon_type' => isset($couponInfo['coupon_type']) ? $couponInfo['coupon_type'] : 20,
  334. 'reduce_price' => isset($couponInfo['reduce_price']) ? $couponInfo['reduce_price'] : 0,
  335. 'discount' => isset($couponInfo['discount']) ? $couponInfo['discount'] : 0,
  336. 'min_price' => isset($couponInfo['min_price']) ? $couponInfo['min_price'] : 0,
  337. 'expire_day' => isset($couponInfo['expire_day']) ? $couponInfo['expire_day'] : 0,
  338. 'start_time' => isset($couponInfo['start_time']) ? $couponInfo['start_time'] : 0,
  339. 'end_time' => isset($couponInfo['end_time']) ? $couponInfo['end_time'] : 0,
  340. 'goods_ids' => isset($couponInfo['goods_ids']) && $couponInfo['goods_ids'] ? $couponInfo['goods_ids'] : '',
  341. 'create_time' => time(),
  342. 'status' => 1,
  343. ];
  344. if (!$id = MemberCouponModel::insertGetId($data)) {
  345. $this->error = '奖励注册优惠券失败';
  346. return true;
  347. }
  348. // 更新发放统计
  349. CouponModel::where(['id' => $id])->update(['received_num' => DB::raw("received_num+1"), 'update_time' => time()]);
  350. $data['id'] = $id;
  351. $data['discount'] = floatval($data['discount']);
  352. $data['reduce_price'] = floatval($data['reduce_price']);
  353. unset($data['create_time']);
  354. unset($data['status']);
  355. unset($data['goods_ids']);
  356. $this->error = '奖励注册优惠券成功';
  357. return $data;
  358. }
  359. }