$user_id, 'order_sn' => $order_sn, 'address_id' => $address->id, 'province_id' => $address->province_id, 'city_id' => $address->city_id, 'area_id' => $address->area_id, 'receiver' => $address->receiver, 'mobile' => $address->mobile, 'address' => $address->address, 'zipcode' => $address->zipcode, 'goodsprice' => 0, // 商品金额 (不含运费) 'amount' => 0, // 总金额 (含运费) 'shippingfee' => 0, // 运费 'discount' => 0, // 优惠金额 'saleamount' => 0, 'memo' => $memo, 'expiretime' => time() + $config['order_timeout'], // 订单失效 'status' => 'normal' ]; // 通过商品列表计算订单明细 list($orderItem, $goodsList, $userCoupon) = self::computeGoods($orderInfo, $goods_list, $user_id, $address->area_id, $user_coupon_id); // 创建订单 $order = self::createOrderWithTransaction($orderInfo, $orderItem, $goodsList, $userCoupon); // 如果是购物车模式,需要清空购物车 if (!empty($cart_ids)) { Carts::clear($cart_ids); } return $order; } /** * 通过购物车创建订单 * @param int $address_id 地址ID * @param int $user_id 用户ID * @param array $cart_ids 购物车ID数组 * @param int $user_coupon_id 优惠券ID * @param string $memo 备注 * @return Order * @throws Exception */ public static function createOrderByCart($address_id, $user_id, $cart_ids, $user_coupon_id = 0, $memo = '') { if (empty($cart_ids)) { throw new Exception("购物车列表不能为空"); } // 将购物车数据转换为标准的商品列表格式 $goods_list = self::convertCartToGoodsList($cart_ids, $user_id); return self::createOrder($address_id, $user_id, $goods_list, $user_coupon_id, $memo, $cart_ids); } /** * 直接通过商品规格数量创建订单 * @param int $address_id 地址ID * @param int $user_id 用户ID * @param array $goods_list 商品列表 [['goods_id'=>1, 'goods_sku_id'=>0, 'nums'=>1], ...] * @param int $user_coupon_id 优惠券ID * @param string $memo 备注 * @return Order * @throws Exception */ public static function createOrderByGoods($address_id, $user_id, $goods_list, $user_coupon_id = 0, $memo = '') { return self::createOrder($address_id, $user_id, $goods_list, $user_coupon_id, $memo); } /** * 根据商品列表计算订单明细 * @param array $orderInfo 订单基础信息 * @param array $goods_list 商品列表 * @param int $user_id 用户ID * @param int $area_id 地区ID * @param int $user_coupon_id 优惠券ID * @return array * @throws Exception */ protected static function computeGoods(&$orderInfo, $goods_list, $user_id, $area_id, $user_coupon_id = 0) { $config = get_addon_config('shop'); $orderInfo['amount'] = 0; $orderInfo['goodsprice'] = 0; $orderInfo['shippingfee'] = 0; $orderInfo['discount'] = 0; $orderItem = []; $shippingTemp = []; $userCoupon = null; $goodsList = []; // 校验优惠券 if ($user_coupon_id) { $userCouponModel = new UserCoupon(); $userCoupon = $userCouponModel->checkUserOrUse($user_coupon_id, $user_id); $orderInfo['user_coupon_id'] = $user_coupon_id; } // 提取所有商品ID和SKU ID,进行批量查询 $goods_ids = array_column($goods_list, 'goods_id'); $sku_ids = []; $goods_without_sku = []; // 记录没有指定SKU ID的商品,需要查询默认SKU foreach ($goods_list as $index => $item) { if (isset($item['goods_sku_id']) && $item['goods_sku_id'] > 0) { $sku_ids[] = $item['goods_sku_id']; } else { // 没有指定SKU ID的商品,记录下来后续查询默认SKU $goods_without_sku[$index] = $item['goods_id']; } } // 批量查询商品信息 $goodsData = []; if (!empty($goods_ids)) { $goodsCollection = Goods::with(['category', 'brand']) ->where('id', 'in', $goods_ids) ->where('status', GoodsEnum::STATUS_ON_SALE) ->select(); foreach ($goodsCollection as $goods) { $goodsData[$goods->id] = $goods; } } // 批量查询SKU信息 $skuData = []; $multiSpecSkuIds = []; // 用于存储多规格商品的SKU ID if (!empty($sku_ids)) { $skuCollection = Sku::where('id', 'in', $sku_ids)->select(); foreach ($skuCollection as $sku) { $skuData[$sku->id] = $sku; // 过滤出有规格值的SKU ID(spec_value_ids不为空) if (!empty($sku->spec_value_ids)) { $multiSpecSkuIds[] = $sku->id; } } } // 查询没有指定SKU ID的商品的默认SKU(单规格商品或多规格商品的默认SKU) if (!empty($goods_without_sku)) { $defaultSkuCollection = Sku::where('goods_id', 'in', array_values($goods_without_sku)) ->where('is_default', 1) ->select(); foreach ($defaultSkuCollection as $sku) { $skuData[$sku->id] = $sku; // 更新对应的goods_list项,补充SKU ID foreach ($goods_without_sku as $list_index => $goods_id) { if ($sku->goods_id == $goods_id) { $goods_list[$list_index]['goods_sku_id'] = $sku->id; $sku_ids[] = $sku->id; // 添加到sku_ids中用于后续查询规格属性 // 如果默认SKU有规格值,也加入到多规格SKU列表 if (!empty($sku->spec_value_ids)) { $multiSpecSkuIds[] = $sku->id; } unset($goods_without_sku[$list_index]); break; } } } } // 批量查询规格属性字符串(只查询多规格商品的SKU) $skuAttrData = []; if (!empty($multiSpecSkuIds)) { $skuAttrData = \app\common\Service\SkuSpec::getSkuAttrs($multiSpecSkuIds); } // 验证并构建商品数据 foreach ($goods_list as $item) { $goods_id = $item['goods_id']; $goods_sku_id = $item['goods_sku_id']; // 现在所有商品都应该有SKU ID $nums = $item['nums']; if ($nums <= 0) { throw new Exception("商品数量必须大于0"); } // 检查商品是否存在 if (!isset($goodsData[$goods_id])) { throw new Exception("商品已下架"); } $goods = $goodsData[$goods_id]; // 所有商品都必须有SKU(包括单规格商品的默认SKU) if (empty($skuData) || !isset($skuData[$goods_sku_id])) { throw new Exception("商品规格不存在"); } $sku = $skuData[$goods_sku_id]; // 验证SKU是否属于该商品 if ($sku->goods_id != $goods_id) { throw new Exception("商品规格不匹配"); } // 获取规格属性字符串(单规格商品的sku_attr为空) $sku_attr = $skuAttrData[$goods_sku_id] ?? ''; // 构建商品对象,模拟购物车数据结构 $goodsItem = (object)[ 'goods_id' => $goods_id, 'goods_sku_id' => $goods_sku_id, 'nums' => $nums, 'goods' => $goods, 'sku' => $sku, 'sku_attr' => $sku_attr ]; $goodsList[] = $goodsItem; } // 计算商品价格和运费(统一使用SKU进行计算) foreach ($goodsList as $item) { $goodsItemData = []; if (empty($item->goods) || empty($item->sku)) { throw new Exception("商品已下架"); } // 库存验证(统一使用SKU库存) if ($item->sku->stocks < $item->nums) { throw new Exception("商品库存不足,请重新修改数量"); } // 统一使用SKU数据进行计算 $goodsItemData['image'] = !empty($item->sku->image) ? $item->sku->image : $item->goods->image; $goodsItemData['price'] = $item->sku->price; $goodsItemData['lineation_price'] = $item->sku->lineation_price; $goodsItemData['sku_sn'] = $item->sku->sku_sn; $amount = bcmul($item->sku->price, $item->nums, 2); $goodsItemData['amount'] = $amount; // 订单总价 $orderInfo['amount'] = bcadd($orderInfo['amount'], $amount, 2); // 商品总价 $orderInfo['goodsprice'] = bcadd($orderInfo['goodsprice'], $amount, 2); $freight_id = $item->goods->express_template_id; // 计算邮费【合并运费模板】 if (!isset($shippingTemp[$freight_id])) { $shippingTemp[$freight_id] = [ 'nums' => $item->nums, 'weight' => $item->sku->weight, 'amount' => $amount ]; } else { $shippingTemp[$freight_id] = [ 'nums' => bcadd($shippingTemp[$freight_id]['nums'], $item->nums, 2), 'weight' => bcadd($shippingTemp[$freight_id]['weight'], $item->sku->weight, 2), 'amount' => bcadd($shippingTemp[$freight_id]['amount'], $amount, 2) ]; } // 创建订单商品数据 $orderItem[] = array_merge($goodsItemData, [ 'order_sn' => $orderInfo['order_sn'], 'goods_id' => $item->goods_id, 'title' => $item->goods->title, 'url' => $item->goods->url, 'nums' => $item->nums, 'goods_sku_id' => $item->goods_sku_id, 'attrdata' => $item->sku_attr, 'weight' => $item->sku->weight, 'category_id' => $item->goods->category_id, 'brand_id' => $item->goods->brand_id, ]); } // 按运费模板计算 foreach ($shippingTemp as $key => $item) { $shippingfee = Freight::calculate($key, $area_id, $item['nums'], $item['weight'], $item['amount']); $orderInfo['shippingfee'] = bcadd($orderInfo['shippingfee'], $shippingfee, 2); } // 订单总价(含邮费) $orderInfo['amount'] = bcadd($orderInfo['goodsprice'], $orderInfo['shippingfee'], 2); if (!empty($userCoupon)) { // 校验优惠券 $goods_ids = array_column($orderItem, 'goods_id'); $category_ids = array_column($orderItem, 'category_id'); $brand_ids = array_column($orderItem, 'brand_id'); $couponModel = new Coupon(); $coupon = $couponModel->getCoupon($userCoupon['coupon_id']) ->checkCoupon() ->checkOpen() ->checkUseTime($userCoupon['createtime']) ->checkConditionGoods($goods_ids, $user_id, $category_ids, $brand_ids); // 计算折扣金额,判断是使用不含运费,还是含运费的金额 $amount = !isset($config['shippingfeecoupon']) || $config['shippingfeecoupon'] == 0 ? $orderInfo['goodsprice'] : $orderInfo['amount']; list($new_money, $coupon_money) = $coupon->doBuy($amount); // 判断优惠金额是否超出总价,超出则直接设定优惠金额为总价 $orderInfo['discount'] = $coupon_money > $amount ? $amount : $coupon_money; } // 计算订单的应付金额【减去折扣】 $orderInfo['saleamount'] = max(0, bcsub($orderInfo['amount'], $orderInfo['discount'], 2)); $orderInfo['discount'] = bcadd($orderInfo['discount'], 0, 2); return [ $orderItem, $goodsList, $userCoupon ]; } /** * 在事务中创建订单 * @param array $orderInfo 订单信息 * @param array $orderItem 订单商品列表 * @param array $goodsList 商品列表 * @param object $userCoupon 优惠券 * @return Order * @throws Exception */ protected static function createOrderWithTransaction($orderInfo, $orderItem, $goodsList, $userCoupon) { $order = null; Db::startTrans(); try { // 创建订单 $order = Order::create($orderInfo, true); // 减库存 foreach ($goodsList as $index => $item) { if ($item->sku) { $item->sku->setDec('stocks', $item->nums); } $item->goods->setDec("stocks", $item->nums); } // 计算单个商品折扣后的价格 $saleamount = bcsub($order['saleamount'], $order['shippingfee'], 2); $saleratio = $order['goodsprice'] > 0 ? bcdiv($saleamount, $order['goodsprice'], 10) : 1; $saleremains = $saleamount; foreach ($orderItem as $index => &$item) { if (!isset($orderItem[$index + 1])) { $saleprice = $saleremains; } else { $saleprice = $order['discount'] == 0 ? bcmul($item['price'], $item['nums'], 2) : bcmul(bcmul($item['price'], $item['nums'], 2), $saleratio, 2); } $saleremains = bcsub($saleremains, $saleprice, 2); $item['realprice'] = $saleprice; } unset($item); // 创建订单商品数据 foreach ($orderItem as $index => $item) { OrderGoods::create($item, true); } // 修改地址使用次数 $address = Address::get($orderInfo['address_id']); if ($address) { $address->setInc('usednums'); } // 优惠券已使用 if (!empty($userCoupon)) { $userCoupon->save(['is_used' => 2]); } // 提交事务 Db::commit(); } catch (Exception $e) { Db::rollback(); throw new Exception($e->getMessage()); } // 记录操作 OrderAction::push($orderInfo['order_sn'], '系统', '订单创建成功'); // 订单应付金额为0时直接结算 if ($order['saleamount'] == 0) { Order::settle($order->order_sn, 0); $order = Order::get($order->id); } return $order; } /** * 验证商品规格参数 * @param array $goods_list 商品列表 * @throws Exception */ public static function validateGoodsList($goods_list) { if (empty($goods_list) || !is_array($goods_list)) { throw new Exception("商品列表不能为空"); } foreach ($goods_list as $item) { if (!isset($item['goods_id']) || !is_numeric($item['goods_id']) || $item['goods_id'] <= 0) { throw new Exception("商品ID无效"); } if (!isset($item['nums']) || !is_numeric($item['nums']) || $item['nums'] <= 0) { throw new Exception("商品数量必须大于0"); } if (isset($item['goods_sku_id']) && !is_numeric($item['goods_sku_id'])) { throw new Exception("商品规格ID无效"); } } } /** * 统一的订单计算方法(用于预览订单) * @param array $goods_list 标准化的商品列表 * @param int $user_id 用户ID * @param int $area_id 地区ID * @param int $user_coupon_id 优惠券ID * @return array * @throws Exception */ public static function calculateOrder($goods_list, $user_id, $area_id = 0, $user_coupon_id = 0) { if (empty($goods_list)) { throw new Exception("商品列表不能为空"); } // 验证商品列表格式 self::validateGoodsList($goods_list); $order_sn = date("Ymdhis") . sprintf("%08d", $user_id) . mt_rand(1000, 9999); // 订单基础信息 $orderInfo = [ 'order_sn' => $order_sn, 'goodsprice' => 0, // 商品金额 (不含运费) 'amount' => 0, // 总金额 (含运费) 'shippingfee' => 0, // 运费 'discount' => 0, // 优惠金额 'saleamount' => 0 // 应付金额 ]; // 计算商品明细 list($orderItem, $goodsList, $userCoupon) = self::computeGoods($orderInfo, $goods_list, $user_id, $area_id, $user_coupon_id); return [ 'orderItem' => $orderItem, 'goodsList' => $goodsList, 'orderInfo' => $orderInfo, 'userCoupon' => $userCoupon ]; } /** * 通过商品规格计算订单明细(用于预览订单) * @param array $goods_list 商品列表 * @param int $user_id 用户ID * @param int $area_id 地区ID * @param int $user_coupon_id 优惠券ID * @return array * @throws Exception */ public static function calculateOrderByGoods($goods_list, $user_id, $area_id = 0, $user_coupon_id = 0) { return self::calculateOrder($goods_list, $user_id, $area_id, $user_coupon_id); } /** * 通过购物车计算订单明细(用于预览订单) * @param array $cart_ids 购物车ID列表 * @param int $user_id 用户ID * @param int $area_id 地区ID * @param int $user_coupon_id 优惠券ID * @return array * @throws Exception */ public static function calculateOrderByCart($cart_ids, $user_id, $area_id = 0, $user_coupon_id = 0) { if (empty($cart_ids)) { throw new Exception("购物车列表不能为空"); } // 将购物车数据转换为标准的商品列表格式 $goods_list = self::convertCartToGoodsList($cart_ids, $user_id); return self::calculateOrder($goods_list, $user_id, $area_id, $user_coupon_id); } /** * 将购物车数据转换为标准的商品列表格式 * @param array $cart_ids 购物车ID列表 * @param int $user_id 用户ID * @return array * @throws Exception */ public static function convertCartToGoodsList($cart_ids, $user_id) { // 查询购物车数据 $cartItems = Carts::where('id', 'in', $cart_ids) ->where('user_id', $user_id) ->select(); if (empty($cartItems)) { throw new Exception("购物车数据不存在"); } $goods_list = []; foreach ($cartItems as $cart) { $goods_list[] = [ 'goods_id' => $cart->goods_id, 'goods_sku_id' => $cart->goods_sku_id, 'nums' => $cart->nums ]; } return $goods_list; } }