ParentOrderService.php 15 KB

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