ParentOrderService.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. <?php
  2. namespace app\common\Service;
  3. use app\common\model\ParentOrder;
  4. use app\common\model\Order;
  5. use app\common\model\OrderGoods;
  6. use app\common\model\Address;
  7. use app\common\model\UserCoupon;
  8. use app\common\Enum\OrderEnum;
  9. use app\common\exception\BusinessException;
  10. use app\common\Service\Order\OrderActionService;
  11. use app\common\Enum\OrderActionEnum;
  12. use think\Db;
  13. use think\Exception;
  14. /**
  15. * 父订单服务类
  16. * 管理父子订单的创建、支付和状态更新
  17. */
  18. class ParentOrderService
  19. {
  20. /**
  21. * 创建父子订单 - 一个商品一个子订单,统一支付父订单
  22. * @param int $addressId 地址ID
  23. * @param int $userId 用户ID
  24. * @param array $goodsList 商品列表
  25. * @param int $userCouponId 优惠券ID
  26. * @param string $remark 备注
  27. * @return ParentOrder 返回父订单对象
  28. * @throws BusinessException
  29. */
  30. public static function createParentChildOrders($addressId, $userId, $goodsList, $userCouponId = 0, $remark = '')
  31. {
  32. // 验证地址
  33. $address = Address::get($addressId);
  34. if (!$address || $address['user_id'] != $userId) {
  35. throw new BusinessException("地址未找到");
  36. }
  37. if (empty($goodsList)) {
  38. throw new BusinessException("商品列表不能为空");
  39. }
  40. // 验证商品列表格式
  41. OrderService::validateGoodsList($goodsList);
  42. $orderTimeout = ShopConfigService::getConfigs('shop.order.order_timeout', false) ?? 3600;
  43. $platform = request()->header('platform', 'H5');
  44. $ip = request()->ip();
  45. // 生成父订单号
  46. $parentOrderSn = 'P' . date("Ymdhis") . sprintf("%08d", $userId) . mt_rand(1000, 9999);
  47. $parentOrder = null;
  48. $childOrders = [];
  49. Db::startTrans();
  50. try {
  51. // 先创建所有子订单
  52. $firstOrder = true;
  53. foreach ($goodsList as $goodsItem) {
  54. // 创建子订单
  55. $childOrder = self::createSingleChildOrder($goodsItem, $userId, $address, $firstOrder ? $userCouponId : 0, $remark, $orderTimeout, $platform, $ip);
  56. $childOrders[] = $childOrder;
  57. $firstOrder = false;
  58. }
  59. // 计算父订单总金额
  60. $totalAmountData = self::calculateTotalAmount($childOrders);
  61. // 创建父订单
  62. $parentOrderInfo = [
  63. 'parent_order_sn' => $parentOrderSn,
  64. 'user_id' => $userId,
  65. 'total_amount' => $totalAmountData['total_amount'],
  66. 'total_goods_price' => $totalAmountData['total_goods_price'],
  67. 'total_goods_num' => $totalAmountData['total_goods_num'],
  68. 'total_express_fee' => $totalAmountData['total_express_fee'],
  69. 'total_discount_fee' => $totalAmountData['total_discount_fee'],
  70. 'coupon_discount_fee' => $totalAmountData['coupon_discount_fee'],
  71. 'promo_discount_fee' => 0,
  72. 'total_order_amount' => $totalAmountData['total_order_amount'],
  73. 'pay_amount' => $totalAmountData['pay_amount'],
  74. 'pay_original_amount' => $totalAmountData['pay_amount'],
  75. 'pay_remain_amount' => $totalAmountData['pay_amount'],
  76. 'user_coupon_id' => $userCouponId ?: null,
  77. 'remark' => $remark,
  78. 'expire_time' => time() + $orderTimeout,
  79. 'order_status' => OrderEnum::STATUS_CREATE,
  80. 'pay_status' => 0,
  81. 'platform' => $platform,
  82. 'ip' => $ip,
  83. 'status' => 'normal',
  84. ];
  85. $parentOrder = ParentOrder::create($parentOrderInfo, true);
  86. // 更新子订单的父订单ID
  87. foreach ($childOrders as $childOrder) {
  88. $childOrder->parent_order_id = $parentOrder->id;
  89. $childOrder->save();
  90. }
  91. // 提交事务
  92. Db::commit();
  93. // 记录操作
  94. OrderActionService::recordUserAction(
  95. $parentOrderSn,
  96. OrderActionEnum::ACTION_CREATE,
  97. $userId,
  98. '创建父订单',
  99. $userId
  100. );
  101. } catch (Exception $e) {
  102. Db::rollback();
  103. throw new BusinessException("创建父子订单失败: " . $e->getMessage());
  104. }
  105. return $parentOrder;
  106. }
  107. /**
  108. * 创建单个子订单
  109. * @param array $goodsItem 商品项
  110. * @param int $userId 用户ID
  111. * @param object $address 地址对象
  112. * @param int $userCouponId 优惠券ID
  113. * @param string $remark 备注
  114. * @param int $orderTimeout 订单超时时间
  115. * @param string $platform 平台
  116. * @param string $ip IP地址
  117. * @return Order
  118. */
  119. protected static function createSingleChildOrder($goodsItem, $userId, $address, $userCouponId, $remark, $orderTimeout, $platform, $ip)
  120. {
  121. // 构建单商品列表
  122. $singleGoodsList = [$goodsItem];
  123. // 生成子订单号
  124. $orderSn = date("Ymdhis") . sprintf("%08d", $userId) . mt_rand(1000, 9999);
  125. // 子订单信息
  126. $orderInfo = [
  127. 'type' => 1,
  128. 'order_sn' => $orderSn,
  129. 'user_id' => $userId,
  130. 'amount' => 0,
  131. 'goods_price' => 0,
  132. 'goods_num' => 0,
  133. 'discount_fee' => 0,
  134. 'coupon_discount_fee' => 0,
  135. 'promo_discount_fee' => 0,
  136. 'order_amount' => 0,
  137. 'express_fee' => 0,
  138. 'expire_time' => time() + $orderTimeout,
  139. 'order_status' => OrderEnum::STATUS_CREATE,
  140. 'invoice_status' => 0,
  141. 'remark' => $remark,
  142. 'user_coupon_id' => $userCouponId > 0 ? $userCouponId : null,
  143. 'ip' => $ip,
  144. 'status' => 'normal',
  145. 'platform' => $platform,
  146. 'parent_order_id' => null, // 稍后更新
  147. ];
  148. // 使用OrderService创建订单
  149. return OrderService::createOrder($address->id, $userId, $singleGoodsList, $userCouponId, $remark);
  150. }
  151. /**
  152. * 计算父订单总金额
  153. * @param array $childOrders 子订单数组
  154. * @return array
  155. */
  156. protected static function calculateTotalAmount($childOrders)
  157. {
  158. $totalAmount = 0;
  159. $totalGoodsPrice = 0;
  160. $totalGoodsNum = 0;
  161. $totalExpressFee = 0;
  162. $totalDiscountFee = 0;
  163. $couponDiscountFee = 0;
  164. foreach ($childOrders as $childOrder) {
  165. $totalAmount = bcadd($totalAmount, $childOrder->amount, 2);
  166. $totalGoodsPrice = bcadd($totalGoodsPrice, $childOrder->goods_price, 2);
  167. $totalGoodsNum += $childOrder->goods_num;
  168. $totalExpressFee = bcadd($totalExpressFee, $childOrder->express_fee, 2);
  169. $totalDiscountFee = bcadd($totalDiscountFee, $childOrder->discount_fee, 2);
  170. $couponDiscountFee = bcadd($couponDiscountFee, $childOrder->coupon_discount_fee, 2);
  171. }
  172. return [
  173. 'total_amount' => $totalAmount,
  174. 'total_goods_price' => $totalGoodsPrice,
  175. 'total_goods_num' => $totalGoodsNum,
  176. 'total_express_fee' => $totalExpressFee,
  177. 'total_discount_fee' => $totalDiscountFee,
  178. 'coupon_discount_fee' => $couponDiscountFee,
  179. 'total_order_amount' => bcadd($totalGoodsPrice, $totalExpressFee, 2),
  180. 'pay_amount' => $totalAmount
  181. ];
  182. }
  183. /**
  184. * 获取父订单详情
  185. * @param int $parentOrderId 父订单ID
  186. * @param int $userId 用户ID
  187. * @return ParentOrder|null
  188. */
  189. public static function getParentOrderDetail($parentOrderId, $userId = 0)
  190. {
  191. $parentOrderModel = new ParentOrder();
  192. $query = $parentOrderModel->with(['childOrders']);
  193. if ($userId > 0) {
  194. $query->where('user_id', $userId);
  195. }
  196. return $query->where('id', $parentOrderId)->find();
  197. }
  198. /**
  199. * 根据父订单号获取详情
  200. * @param string $parentOrderSn 父订单号
  201. * @return ParentOrder|null
  202. */
  203. public static function getParentOrderByOrderSn($parentOrderSn)
  204. {
  205. $parentOrderModel = new ParentOrder();
  206. return $parentOrderModel->with(['childOrders'])
  207. ->where('parent_order_sn', $parentOrderSn)
  208. ->find();
  209. }
  210. /**
  211. * 检查父订单是否可以支付
  212. * @param ParentOrder $parentOrder 父订单对象
  213. * @return bool
  214. */
  215. public static function canPay($parentOrder)
  216. {
  217. return $parentOrder->order_status == OrderEnum::STATUS_CREATE && $parentOrder->pay_status == 0;
  218. }
  219. /**
  220. * 检查父订单是否已支付
  221. * @param ParentOrder $parentOrder 父订单对象
  222. * @return bool
  223. */
  224. public static function isPaid($parentOrder)
  225. {
  226. return $parentOrder->pay_status == 1;
  227. }
  228. /**
  229. * 更新父订单支付状态
  230. * @param ParentOrder $parentOrder 父订单对象
  231. * @param int $payStatus 支付状态
  232. * @param string $payType 支付方式
  233. * @param string $transactionId 第三方交易号
  234. * @return bool
  235. */
  236. public static function updateParentOrderPayStatus($parentOrder, $payStatus, $payType = '', $transactionId = '')
  237. {
  238. $updateData = [
  239. 'pay_status' => $payStatus,
  240. 'pay_type' => $payType,
  241. 'transaction_id' => $transactionId
  242. ];
  243. if ($payStatus == 1) {
  244. $updateData['pay_time'] = time();
  245. $updateData['order_status'] = OrderEnum::STATUS_PAY;
  246. }
  247. return $parentOrder->save($updateData);
  248. }
  249. /**
  250. * 更新子订单状态
  251. * @param int $parentOrderId 父订单ID
  252. * @param int $status 订单状态
  253. * @return bool
  254. */
  255. public static function updateChildOrdersStatus($parentOrderId, $status)
  256. {
  257. $orderModel = new Order();
  258. return $orderModel->where('parent_order_id', $parentOrderId)->update(['order_status' => $status]);
  259. }
  260. /**
  261. * 获取父订单的所有商品
  262. * @param ParentOrder $parentOrder 父订单对象
  263. * @return array
  264. */
  265. public static function getAllOrderGoods($parentOrder)
  266. {
  267. $childOrderIds = [];
  268. foreach ($parentOrder->childOrders as $childOrder) {
  269. $childOrderIds[] = $childOrder->id;
  270. }
  271. if (empty($childOrderIds)) {
  272. return [];
  273. }
  274. $orderGoodsModel = new OrderGoods();
  275. return $orderGoodsModel->where('order_id', 'in', $childOrderIds)->select();
  276. }
  277. /**
  278. * 父订单支付成功后的处理
  279. * @param string $parentOrderSn 父订单号
  280. * @param string $payType 支付方式
  281. * @param string $transactionId 第三方交易号
  282. * @return bool
  283. */
  284. public static function handleParentOrderPaySuccess($parentOrderSn, $payType = '', $transactionId = '')
  285. {
  286. $parentOrder = self::getParentOrderByOrderSn($parentOrderSn);
  287. if (!$parentOrder) {
  288. throw new BusinessException('父订单不存在');
  289. }
  290. if (self::isPaid($parentOrder)) {
  291. throw new BusinessException('父订单已支付');
  292. }
  293. Db::startTrans();
  294. try {
  295. // 更新父订单支付状态
  296. self::updateParentOrderPayStatus($parentOrder, 1, $payType, $transactionId);
  297. // 更新所有子订单状态为已支付
  298. self::updateChildOrdersStatus($parentOrder->id, OrderEnum::STATUS_PAY);
  299. // 记录父订单支付操作
  300. OrderActionService::recordUserAction(
  301. $parentOrderSn,
  302. OrderActionEnum::ACTION_PAY,
  303. $parentOrder->user_id,
  304. '父订单支付成功',
  305. $parentOrder->user_id
  306. );
  307. // 记录子订单支付操作
  308. foreach ($parentOrder->childOrders as $childOrder) {
  309. OrderActionService::recordUserAction(
  310. $childOrder->order_sn,
  311. OrderActionEnum::ACTION_PAY,
  312. $parentOrder->user_id,
  313. '子订单支付成功(通过父订单)',
  314. $parentOrder->user_id
  315. );
  316. }
  317. Db::commit();
  318. return true;
  319. } catch (Exception $e) {
  320. Db::rollback();
  321. throw new BusinessException('处理父订单支付失败: ' . $e->getMessage());
  322. }
  323. }
  324. /**
  325. * 取消父订单
  326. * @param int $parentOrderId 父订单ID
  327. * @param int $userId 用户ID
  328. * @return bool
  329. */
  330. public static function cancelParentOrder($parentOrderId, $userId)
  331. {
  332. $parentOrder = self::getParentOrderDetail($parentOrderId, $userId);
  333. if (!$parentOrder) {
  334. throw new BusinessException('父订单不存在');
  335. }
  336. if (self::isPaid($parentOrder)) {
  337. throw new BusinessException('已支付的父订单无法取消');
  338. }
  339. Db::startTrans();
  340. try {
  341. // 更新父订单状态
  342. $parentOrder->save(['order_status' => OrderEnum::STATUS_CANCEL]);
  343. // 更新所有子订单状态
  344. self::updateChildOrdersStatus($parentOrder->id, OrderEnum::STATUS_CANCEL);
  345. // 恢复库存
  346. foreach ($parentOrder->childOrders as $childOrder) {
  347. OrderGoods::setGoodsStocksInc($childOrder->order_sn);
  348. }
  349. // 恢复优惠券
  350. if ($parentOrder->user_coupon_id) {
  351. $userCouponModel = new UserCoupon();
  352. $userCouponModel->where('id', $parentOrder->user_coupon_id)
  353. ->update(['is_used' => 1]);
  354. }
  355. Db::commit();
  356. return true;
  357. } catch (Exception $e) {
  358. Db::rollback();
  359. throw new BusinessException('取消父订单失败: ' . $e->getMessage());
  360. }
  361. }
  362. /**
  363. * 获取父订单列表
  364. * @param int $userId 用户ID
  365. * @param array $param 查询参数
  366. * @param array $status 状态筛选
  367. * @return \think\Paginator
  368. */
  369. public static function getParentOrderList($userId = 0, $param = [], $status = [])
  370. {
  371. $pageSize = $param['pageSize'] ?? 10;
  372. $parentOrderModel = new ParentOrder();
  373. $query = $parentOrderModel->with(['childOrders']);
  374. if (!empty($userId)) {
  375. $query->where('user_id', $userId);
  376. }
  377. if (!empty($status)) {
  378. $query->whereIn('order_status', $status);
  379. }
  380. if (isset($param['keywords']) && $param['keywords'] != '') {
  381. $query->where('parent_order_sn', 'like', '%' . $param['keywords'] . '%');
  382. }
  383. return $query->order('createtime desc')
  384. ->paginate($pageSize, false, ['query' => request()->get()]);
  385. }
  386. }