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.

378 lines
15 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. $totalAmount = 0; # 实付金额
  92. $orderAmount = 0; # 订单金额
  93. $dataMain = []; # 主订单
  94. $dataChildren = []; # 子订单
  95. $dataOrderGoods = []; # 订单商品
  96. $storeTypeIds = []; # 订单中的商户类型,用于校验红包
  97. foreach ($storeList as $key => &$storeItem) {
  98. $storeId = $storeItem->store_id;
  99. // 子订单金额
  100. $subAmount = 0;
  101. // 店铺分类
  102. $storeType = Store::query()->where(['id' => $storeId])->value('category_id');
  103. $storeTypeIds[] = (string)$storeType;
  104. // 店铺今天的订单数
  105. $count = Order::query()
  106. ->where(['store_id' => $storeId])
  107. ->whereBetween('created_at', [strtotime(date('Y-m-d 00:00:00')), strtotime(date('Y-m-d 23:59:59'))])
  108. ->count();
  109. // 用户购物车数据
  110. $cartIds = explode(',', $storeItem->cart_ids);
  111. $carts = ShoppingCart::query()->whereIn('id', $cartIds)->where(['market_id' => $marketId])->get();
  112. foreach ($carts as $k => &$cart) {
  113. // 查个商品
  114. $goods = [];
  115. if ($cart->activity_type == 1) {
  116. $goods = Goods::query()->lockForUpdate()->find($cart->goods_id);
  117. $check = $this->goodsService->check($goods->id, $cart->num);
  118. if (true !== $check) {
  119. throw new ErrorCodeException($check, '[商品失效]'.$cart->goods_id);
  120. }
  121. } elseif ($cart->activity_type == 2) {
  122. $goods = GoodsActivity::query()->lockForUpdate()->find($cart->goods_id);
  123. $check = $this->goodsActivityService->check($goods->id, $cart->num, $userId);
  124. if (true !== $check) {
  125. throw new ErrorCodeException($check, '[商品失效]'.$cart->goods_id);
  126. }
  127. if ($goods->can_use_coupon!=1 && $receiveCouponIds) {
  128. throw new ErrorCodeException(ErrorCode::GOODS_ACTIVITY_CANNOT_USE_COUPON, '[商品失效]'.$cart->goods_id);
  129. }
  130. }
  131. if (empty($goods)) {
  132. throw new ErrorCodeException(ErrorCode::ORDER_GOODS_NOT_AVAILABLE);
  133. }
  134. // 库存判断
  135. if ($goods->is_infinite !=1 && $goods->inventory < $cart->num) {
  136. throw new ErrorCodeException(ErrorCode::ORDER_GOODS_INVENTORY_LIMIT);
  137. }
  138. // 算金额
  139. $goodsAmount = bcmul((string)$goods->price, (string)$cart->num); # 当前商品的金额
  140. $subAmount = bcadd((string)$subAmount, (string)$goodsAmount); # 当前店铺子订单的金额
  141. // 订单商品数据
  142. $dataOrderGoods[$storeId][] = [
  143. 'order_id' => 0,
  144. 'goods_id' => $cart->goods_id,
  145. 'activity_type' => $cart->activity_type,
  146. 'number' => $cart->num,
  147. 'price' => $goods->price,
  148. 'original_price' => $goods->original_price,
  149. 'vip_price' => $goods->vip_price,
  150. 'name' => $goods->name,
  151. 'goods_unit' => $goods->goods_unit,
  152. 'cover_img' => $goods->cover_img,
  153. 'spec' => $goods->spec,
  154. ];
  155. }
  156. // 子订单数据
  157. $dataChildren[] = [
  158. 'order_main_id' => 0,
  159. 'user_id' => $userId,
  160. 'store_id' => $storeId,
  161. 'money' => bcadd((string)$subAmount, '0', 2),
  162. 'oid' => $count + 1,
  163. 'order_num' => date('YmdHis').mt_rand(1000, 9999),
  164. 'note' => $storeItem->note
  165. ];
  166. // 订单金额
  167. $orderAmount = bcadd((string)$orderAmount, (string)$subAmount);
  168. }
  169. // 优惠券的使用
  170. $couponMoney = 0;
  171. // 优惠券数据
  172. $couponRec = CouponRec::query()->lockForUpdate()->whereIn('id', $receiveCouponIds)->get()->toArray();
  173. if (!empty($couponRec)) {
  174. $couponsCanUse = $this->couponRecService->allForOrderOlAvailable($totalAmount, $userId, $marketId, 2, $storeTypeIds);
  175. $couponCanUseIds = array_column($couponsCanUse, 'id');
  176. $couponCanUseIds = array_intersect($couponCanUseIds, $receiveCouponIds);
  177. $couponCannotUseIds = array_diff($receiveCouponIds, $couponCanUseIds);
  178. if (empty($couponCanUseIds)||!empty($couponCannotUseIds)) {
  179. throw new ErrorCodeException(ErrorCode::COUPON_NOT_AVAILABLE);
  180. }
  181. // 计算红包折扣金额
  182. foreach ($couponsCanUse as $key => $coupon) {
  183. if (!in_array($coupon->id, $receiveCouponIds)) {
  184. continue;
  185. }
  186. if ($coupon->discount_type == Coupon::DISCOUNT_TYPE_CASH) {
  187. $couponMoney = bcadd($couponMoney, $coupon->discounts, 2);
  188. } elseif ($coupon->discount_type == Coupon::DISCOUNT_TYPE_RATE) {
  189. $discountRate = bcdiv($coupon->discounts,10);
  190. $discountRate = bcsub(1,$discountRate);
  191. $discountMoney = bcmul($orderAmount, $discountRate);
  192. $couponMoney = bcadd($couponMoney, $discountMoney, 2);
  193. }
  194. }
  195. }
  196. // 获取分布式全局ID
  197. $generator = ApplicationContext::getContainer()->get(IdGeneratorInterface::class);
  198. $globalOrderId = $generator->generate();
  199. $orderAmount = bcadd((string)$orderAmount, '0', 2);
  200. $totalAmount = bcadd((string)$totalAmount, (string)$orderAmount);
  201. $totalAmount = bcadd((string)$totalAmount, (string)$deliveryAmount);
  202. $totalAmount = bcadd((string)$totalAmount, (string)$serviceMoney);
  203. $totalAmount = bcsub((string)$totalAmount, (string)$couponMoney, 2);
  204. // 校验订单总金额
  205. if ($totalAmount != $totalMoney) {
  206. throw new ErrorCodeException(ErrorCode::ORDER_TOTAL_AMOUNT_ERROR);
  207. }
  208. $dataMain = [
  209. 'market_id' => $marketId,
  210. 'order_num' => $globalOrderId,
  211. 'global_order_id' => $globalOrderId,
  212. 'user_id' => $userId,
  213. 'type' => OrderType::ONLINE,
  214. 'money' => $totalAmount,
  215. 'total_money' => $orderAmount,
  216. 'services_money' => $serviceMoney,
  217. 'coupon_money' => $couponMoney,
  218. 'delivery_money' => $deliveryAmount,
  219. 'state' => OrderState::UNPAID,
  220. 'tel' => $userAddr->tel,
  221. 'address' => $userAddr->address.$userAddr->doorplate,
  222. 'lat' => $userAddr->lat,
  223. 'lng' => $userAddr->lng,
  224. 'name' => $userAddr->user_name,
  225. 'plat' => $plat,
  226. 'delivery_time_note' => $deliveryTimeNote
  227. ];
  228. // 生成主订单
  229. $orderMain = OrderMain::query()->create($dataMain);
  230. $orderMainId = $orderMain->id;
  231. // 处理子订单
  232. foreach ($dataChildren as $key => &$child) {
  233. $child['order_main_id'] = $orderMainId;
  234. $orderChild = Order::query()->create($child);
  235. $orderChildId = $orderChild->id;
  236. foreach ($dataOrderGoods[$child['store_id']] as $k => &$orderGoods) {
  237. $orderGoods['order_id'] = $orderChildId;
  238. $orderGoods['created_at'] = $currentTime;
  239. $orderGoods['updated_at'] = $currentTime;
  240. }
  241. OrderGoods::query()->insert($dataOrderGoods[$child['store_id']]);
  242. }
  243. // 判断是否有购买多个特价商品
  244. $check = $this->goodsActivityService->checkOrderActivityCount($dataOrderGoods);
  245. if(!$check){
  246. throw new ErrorCodeException(ErrorCode::GOODS_ACTIVITY_RESTRICT_LIMIT, '[同一订单同种类型活动商品]'.json_encode($dataOrderGoods));
  247. }
  248. // 订单成功,做一些处理
  249. // 活动商品购买记录
  250. foreach ($dataOrderGoods as $key => &$goods) {
  251. foreach ($goods as $k => &$goodsItem)
  252. if ($goodsItem['activity_type'] == 2) {
  253. $this->goodsActivityService->cacheRecord($goodsItem['goods_id'], $goodsItem['number'], $userId);
  254. }
  255. }
  256. // 优惠券红包使用记录
  257. if (is_array($couponRec)&&!empty($couponRec)) {
  258. # 使用记录、更新当前优惠券
  259. foreach ($couponRec as $key => &$coupon) {
  260. $couponUse = [
  261. 'user_id' => $coupon['user_id'],
  262. 'user_receive_id' => $coupon['id'],
  263. 'coupon_id' => $coupon['coupon_id'],
  264. 'order_main_id' => $orderMainId,
  265. 'use_time' => $currentTime,
  266. 'return_time' => 0,
  267. 'number' => 1,
  268. 'status' => 1,
  269. 'update_time' => 0,
  270. ];
  271. $insertRes = CouponUse::query()->insert($couponUse);
  272. if ($insertRes) {
  273. $numberRemain = $coupon['number_remain'] - 1;
  274. if ($numberRemain == 0) {
  275. $status = 2;
  276. } elseif ($numberRemain > 0 && $numberRemain < $coupon['number']) {
  277. $status = 1;
  278. } elseif ($numberRemain == $coupon['number']) {
  279. $status = 0;
  280. }
  281. $upRes = CouponRec::query()->where(['id' => $coupon['id']])->update(['number_remain' => $numberRemain, 'status' => $status]);
  282. if (!$upRes) {
  283. Db::rollBack();
  284. throw new ErrorCodeException(ErrorCode::COUPON_USE_FAILURE);
  285. }
  286. // 缓存使用记录
  287. $usedRes = $this->couponService->cacheTodayCouponUsed($coupon['user_id'], $coupon['coupon_id'], $coupon['id']);
  288. if (!$usedRes) {
  289. Db::rollBack();
  290. throw new ErrorCodeException(ErrorCode::COUPON_USE_FAILURE);
  291. }
  292. } else {
  293. Db::rollBack();
  294. throw new ErrorCodeException(ErrorCode::COUPON_USE_FAILURE);
  295. }
  296. }
  297. }
  298. Db::commit();
  299. // 支付
  300. return $this->paymentService->do($globalOrderId, $totalAmount, $userId, config('site_host') . '/wechat/notify/wxminionline');
  301. } catch (\Exception $e) {
  302. Db::rollBack();
  303. $this->log->event(LogLabel::ORDER_ONLINE_LOG, ['exception_msg' => $e->getMessage()]);
  304. throw new ErrorCodeException(ErrorCode::ORDER_ONLINE_FAIL);
  305. }
  306. }
  307. public function check()
  308. {
  309. // TODO: Implement check() method.
  310. }
  311. public function undo()
  312. {
  313. // TODO: Implement undo() method.
  314. }
  315. public function detailByUser($orderMainId, $userId)
  316. {
  317. $orderMain = OrderMain::with(['market'])->find($orderMainId);
  318. $orders = Order::query()
  319. ->where(['order_main_id' => $orderMainId, 'user_id' => $userId])
  320. ->with([
  321. 'orderGoods',
  322. 'store'
  323. ])
  324. ->get()->toArray();
  325. return ['order_main' => $orderMain, 'orders' => $orders];
  326. }
  327. }