You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

393 lines
16 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. <?php
  2. namespace App\Service\v3\Implementations;
  3. use App\Commons\Log;
  4. use App\Constants\v3\ErrorCode;
  5. use App\Constants\v3\LogLabel;
  6. use App\Constants\v3\OrderState;
  7. use App\Constants\v3\OrderType;
  8. use App\Exception\ErrorCodeException;
  9. use App\Model\v3\Coupon;
  10. use App\Model\v3\CouponRec;
  11. use App\Model\v3\CouponUse;
  12. use App\Model\v3\Goods;
  13. use App\Model\v3\GoodsActivity;
  14. use App\Model\v3\Order;
  15. use App\Model\v3\OrderGoods;
  16. use App\Model\v3\OrderMain;
  17. use App\Model\v3\ShoppingCart;
  18. use App\Model\v3\Store;
  19. use App\Model\v3\UserAddress;
  20. use App\Service\v3\Interfaces\CouponRecServiceInterface;
  21. use App\Service\v3\Interfaces\CouponServiceInterface;
  22. use App\Service\v3\Interfaces\DeliveryMoneyServiceInterface;
  23. use App\Service\v3\Interfaces\GoodsActivityServiceInterface;
  24. use App\Service\v3\Interfaces\GoodsServiceInterface;
  25. use App\Service\v3\Interfaces\PaymentServiceInterface;
  26. use Hyperf\DbConnection\Db;
  27. use Hyperf\Di\Annotation\Inject;
  28. use App\Service\v3\Interfaces\OrderOnlineServiceInterface;
  29. use Hyperf\Snowflake\IdGeneratorInterface;
  30. use Hyperf\Utils\ApplicationContext;
  31. class OrderOnlineService implements OrderOnlineServiceInterface
  32. {
  33. /**
  34. * @Inject
  35. * @var Log
  36. */
  37. protected $log;
  38. /**
  39. * @Inject
  40. * @var DeliveryMoneyServiceInterface
  41. */
  42. protected $deliveryService;
  43. /**
  44. * @Inject
  45. * @var CouponRecServiceInterface
  46. */
  47. protected $couponRecService;
  48. /**
  49. * @Inject
  50. * @var CouponServiceInterface
  51. */
  52. protected $couponService;
  53. /**
  54. * @Inject
  55. * @var GoodsActivityServiceInterface
  56. */
  57. protected $goodsActivityService;
  58. /**
  59. * @Inject
  60. * @var GoodsServiceInterface
  61. */
  62. protected $goodsService;
  63. /**
  64. * @Inject
  65. * @var PaymentServiceInterface
  66. */
  67. protected $paymentService;
  68. /**
  69. * 下单
  70. * @param $marketId
  71. * @param $userId
  72. * @param $userAddrId
  73. * @param $storeList
  74. * @param $totalMoney
  75. * @param string $deliveryTimeNote
  76. * @param int $serviceMoney
  77. * @param null $receiveCouponIds
  78. * @param string $plat
  79. * @return array[]
  80. */
  81. public function do($marketId, $userId, $userAddrId, $storeList, $totalMoney, $deliveryTimeNote='尽快送达', $serviceMoney=0, $receiveCouponIds=null, $plat=''){
  82. Db::beginTransaction();
  83. try {
  84. $currentTime = time();
  85. bcscale(6);
  86. // 用户收货地址
  87. $userAddr = UserAddress::query()->find($userAddrId);
  88. // 获取配送费用
  89. $deliveryAmount = $this->deliveryService->do($userAddr->lat, $userAddr->lng);
  90. // 优惠券数据
  91. $couponRec = CouponRec::query()->lockForUpdate()->whereIn('id', $receiveCouponIds)->get()->toArray();
  92. // 处理购物车数据,计算订单金额、子订单数据处理等
  93. $totalAmount = 0; # 实付金额
  94. $orderAmount = 0; # 订单金额
  95. $dataMain = []; # 主订单
  96. $dataChildren = []; # 子订单
  97. $dataOrderGoods = []; # 订单商品
  98. $storeTypeIds = []; # 订单中的商户类型,用于校验红包
  99. foreach ($storeList as $key => &$storeItem) {
  100. $storeId = $storeItem->store_id;
  101. // 子订单金额
  102. $subAmount = 0;
  103. // 店铺分类
  104. $storeType = Store::query()->where(['id' => $storeId])->value('category_id');
  105. $storeTypeIds[] = (string)$storeType;
  106. // 店铺今天的订单数
  107. $count = Order::query()
  108. ->where(['store_id' => $storeId])
  109. ->whereBetween('created_at', [strtotime(date('Y-m-d 00:00:00')), strtotime(date('Y-m-d 23:59:59'))])
  110. ->count();
  111. // 用户购物车数据
  112. $cartIds = explode(',', $storeItem->cart_ids);
  113. $carts = ShoppingCart::query()->whereIn('id', $cartIds)->where(['market_id' => $marketId, 'user_id' => $userId])->get();
  114. foreach ($carts as $k => &$cart) {
  115. // 查个商品
  116. $goods = [];
  117. if ($cart->activity_type == 1) {
  118. $goods = Goods::query()->lockForUpdate()->find($cart->goods_id);
  119. $check = $this->goodsService->check($goods->id, $cart->num);
  120. if (true !== $check) {
  121. // throw new ErrorCodeException($check, '[商品失效1]'.$cart->goods_id);
  122. }
  123. } elseif ($cart->activity_type == 2) {
  124. $goods = GoodsActivity::query()->lockForUpdate()->find($cart->goods_id);
  125. $check = $this->goodsActivityService->check($goods->id, $cart->num, $userId);
  126. if (true !== $check) {
  127. // throw new ErrorCodeException($check, '[商品失效2]'.$cart->goods_id);
  128. }
  129. // 活动商品不可用优惠券
  130. if ($goods->can_use_coupon!=1 && $receiveCouponIds) {
  131. throw new ErrorCodeException(ErrorCode::GOODS_ACTIVITY_CANNOT_USE_COUPON, '[商品失效]'.$cart->goods_id);
  132. }
  133. // 活动商品可用优惠券,再校验这些优惠券是不是可以在当前活动类型可用
  134. // receiveCouponIds中是不是有当前活动不可用的优惠券
  135. $couponIds = array_values(array_column($couponRec, 'coupon_id'));
  136. $couponsActivityAvailable = Coupon::query()
  137. ->select(['id'])
  138. ->whereJsonContains('activity_available', [(string)$goods->type])
  139. ->whereIn('id', $couponIds)
  140. ->get()->toArray();
  141. $couponsActivityAvailableIds = array_values(array_column($couponsActivityAvailable, 'id'));
  142. if (!empty(array_diff($couponIds, $couponsActivityAvailableIds))) {
  143. throw new ErrorCodeException(ErrorCode::GOODS_ACTIVITY_CANNOT_USE_COUPON, '[商品失效]'.json_encode($couponsActivityAvailableIds));
  144. }
  145. }
  146. if (empty($goods)) {
  147. throw new ErrorCodeException(ErrorCode::ORDER_GOODS_NOT_AVAILABLE);
  148. }
  149. // 库存判断
  150. if ($goods->is_infinite !=1 && $goods->inventory < $cart->num) {
  151. throw new ErrorCodeException(ErrorCode::ORDER_GOODS_INVENTORY_LIMIT);
  152. }
  153. // 算金额
  154. $goodsAmount = bcmul((string)$goods->price, (string)$cart->num); # 当前商品的金额
  155. $subAmount = bcadd((string)$subAmount, (string)$goodsAmount); # 当前店铺子订单的金额
  156. // 订单商品数据
  157. $dataOrderGoods[$storeId][] = [
  158. 'order_id' => 0,
  159. 'goods_id' => $cart->goods_id,
  160. 'activity_type' => $cart->activity_type,
  161. 'number' => $cart->num,
  162. 'price' => $goods->price,
  163. 'original_price' => $goods->original_price,
  164. 'vip_price' => $goods->vip_price,
  165. 'name' => $goods->name,
  166. 'goods_unit' => $goods->goods_unit,
  167. 'cover_img' => $goods->cover_img,
  168. 'spec' => $goods->spec,
  169. ];
  170. }
  171. // 子订单数据
  172. $dataChildren[] = [
  173. 'order_main_id' => 0,
  174. 'user_id' => $userId,
  175. 'store_id' => $storeId,
  176. 'money' => bcadd((string)$subAmount, '0', 2),
  177. 'oid' => $count + 1,
  178. 'order_num' => date('YmdHis').mt_rand(1000, 9999),
  179. 'note' => $storeItem->note
  180. ];
  181. // 订单金额
  182. $orderAmount = bcadd((string)$orderAmount, (string)$subAmount);
  183. }
  184. // 优惠券的使用
  185. $couponMoney = 0;
  186. if (!empty($couponRec)) {
  187. $couponsCanUse = $this->couponRecService->allForOrderOlAvailable($totalAmount, $userId, $marketId, 2, $storeTypeIds);
  188. $couponCanUseIds = array_column($couponsCanUse, 'id');
  189. $couponCanUseIds = array_intersect($couponCanUseIds, $receiveCouponIds);
  190. $couponCannotUseIds = array_diff($receiveCouponIds, $couponCanUseIds);
  191. if (empty($couponCanUseIds)||!empty($couponCannotUseIds)) {
  192. throw new ErrorCodeException(ErrorCode::COUPON_NOT_AVAILABLE);
  193. }
  194. // 计算红包折扣金额
  195. foreach ($couponsCanUse as $key => $coupon) {
  196. if (!in_array($coupon->id, $receiveCouponIds)) {
  197. continue;
  198. }
  199. if ($coupon->discount_type == Coupon::DISCOUNT_TYPE_CASH) {
  200. $couponMoney = bcadd($couponMoney, $coupon->discounts, 2);
  201. } elseif ($coupon->discount_type == Coupon::DISCOUNT_TYPE_RATE) {
  202. $discountRate = bcdiv($coupon->discounts,10);
  203. $discountRate = bcsub(1,$discountRate);
  204. $discountMoney = bcmul($orderAmount, $discountRate);
  205. $couponMoney = bcadd($couponMoney, $discountMoney, 2);
  206. }
  207. }
  208. }
  209. // 获取分布式全局ID
  210. $generator = ApplicationContext::getContainer()->get(IdGeneratorInterface::class);
  211. $globalOrderId = $generator->generate();
  212. $orderAmount = bcadd((string)$orderAmount, '0', 2);
  213. $totalAmount = bcadd((string)$totalAmount, (string)$orderAmount);
  214. $totalAmount = bcadd((string)$totalAmount, (string)$deliveryAmount);
  215. $totalAmount = bcadd((string)$totalAmount, (string)$serviceMoney);
  216. $totalAmount = bcsub((string)$totalAmount, (string)$couponMoney, 2);
  217. // 校验订单总金额
  218. if ($totalAmount != $totalMoney) {
  219. throw new ErrorCodeException(ErrorCode::ORDER_TOTAL_AMOUNT_ERROR);
  220. }
  221. $dataMain = [
  222. 'market_id' => $marketId,
  223. 'order_num' => $globalOrderId,
  224. 'global_order_id' => $globalOrderId,
  225. 'user_id' => $userId,
  226. 'type' => OrderType::ONLINE,
  227. 'money' => $totalAmount,
  228. 'total_money' => $orderAmount,
  229. 'services_money' => $serviceMoney,
  230. 'coupon_money' => $couponMoney,
  231. 'delivery_money' => $deliveryAmount,
  232. 'state' => OrderState::UNPAID,
  233. 'tel' => $userAddr->tel,
  234. 'address' => $userAddr->address.$userAddr->doorplate,
  235. 'lat' => $userAddr->lat,
  236. 'lng' => $userAddr->lng,
  237. 'name' => $userAddr->user_name,
  238. 'plat' => $plat,
  239. 'delivery_time_note' => $deliveryTimeNote
  240. ];
  241. // 生成主订单
  242. $orderMain = OrderMain::query()->create($dataMain);
  243. $orderMainId = $orderMain->id;
  244. // 处理子订单
  245. foreach ($dataChildren as $key => &$child) {
  246. $child['order_main_id'] = $orderMainId;
  247. $orderChild = Order::query()->create($child);
  248. $orderChildId = $orderChild->id;
  249. foreach ($dataOrderGoods[$child['store_id']] as $k => &$orderGoods) {
  250. $orderGoods['order_id'] = $orderChildId;
  251. $orderGoods['created_at'] = $currentTime;
  252. $orderGoods['updated_at'] = $currentTime;
  253. }
  254. OrderGoods::query()->insert($dataOrderGoods[$child['store_id']]);
  255. }
  256. // 判断是否有购买多个特价商品
  257. $check = $this->goodsActivityService->checkOrderActivityCount($dataOrderGoods);
  258. if(!$check){
  259. throw new ErrorCodeException(ErrorCode::GOODS_ACTIVITY_RESTRICT_LIMIT, '[同一订单同种类型活动商品]'.json_encode($dataOrderGoods));
  260. }
  261. // 订单成功,做一些处理
  262. // 活动商品购买记录
  263. foreach ($dataOrderGoods as $key => &$goods) {
  264. foreach ($goods as $k => &$goodsItem)
  265. if ($goodsItem['activity_type'] == 2) {
  266. $this->goodsActivityService->cacheRecord($goodsItem['goods_id'], $goodsItem['number'], $userId);
  267. }
  268. }
  269. // 优惠券红包使用记录
  270. if (is_array($couponRec)&&!empty($couponRec)) {
  271. # 使用记录、更新当前优惠券
  272. foreach ($couponRec as $key => &$coupon) {
  273. $couponUse = [
  274. 'user_id' => $coupon['user_id'],
  275. 'user_receive_id' => $coupon['id'],
  276. 'coupon_id' => $coupon['coupon_id'],
  277. 'order_main_id' => $orderMainId,
  278. 'use_time' => $currentTime,
  279. 'return_time' => 0,
  280. 'number' => 1,
  281. 'status' => 1,
  282. 'update_time' => 0,
  283. ];
  284. $insertRes = CouponUse::query()->insert($couponUse);
  285. if ($insertRes) {
  286. $numberRemain = $coupon['number_remain'] - 1;
  287. if ($numberRemain == 0) {
  288. $status = 2;
  289. } elseif ($numberRemain > 0 && $numberRemain < $coupon['number']) {
  290. $status = 1;
  291. } elseif ($numberRemain == $coupon['number']) {
  292. $status = 0;
  293. }
  294. $upRes = CouponRec::query()->where(['id' => $coupon['id']])->update(['number_remain' => $numberRemain, 'status' => $status]);
  295. if (!$upRes) {
  296. Db::rollBack();
  297. throw new ErrorCodeException(ErrorCode::COUPON_USE_FAILURE);
  298. }
  299. // 缓存使用记录
  300. $usedRes = $this->couponService->cacheTodayCouponUsed($coupon['user_id'], $coupon['coupon_id'], $coupon['id']);
  301. if (!$usedRes) {
  302. Db::rollBack();
  303. throw new ErrorCodeException(ErrorCode::COUPON_USE_FAILURE);
  304. }
  305. } else {
  306. Db::rollBack();
  307. throw new ErrorCodeException(ErrorCode::COUPON_USE_FAILURE);
  308. }
  309. }
  310. }
  311. Db::commit();
  312. // 支付
  313. return $this->paymentService->do($globalOrderId, $totalAmount, $userId, config('site_host') . '/wechat/notify/wxminionline');
  314. } catch (\Exception $e) {
  315. Db::rollBack();
  316. $this->log->event(LogLabel::ORDER_ONLINE_LOG, ['exception_msg' => $e->getMessage()]);
  317. throw new ErrorCodeException(ErrorCode::ORDER_ONLINE_FAIL);
  318. }
  319. }
  320. public function check()
  321. {
  322. // TODO: Implement check() method.
  323. }
  324. public function undo()
  325. {
  326. // TODO: Implement undo() method.
  327. }
  328. public function detailByUser($orderMainId, $userId)
  329. {
  330. $orderMain = OrderMain::with(['market'])->find($orderMainId);
  331. $orders = Order::query()
  332. ->where(['order_main_id' => $orderMainId, 'user_id' => $userId])
  333. ->with([
  334. 'orderGoods',
  335. 'store'
  336. ])
  337. ->get()->toArray();
  338. return ['order_main' => $orderMain, 'orders' => $orders];
  339. }
  340. }