6
0
Fork 0
jiqu-library-server/app/Services/OrderService.php

1234 lines
39 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Services;
use App\Endpoint\Api\Http\Resources\ProductSkuSimpleResource;
use App\Endpoint\Api\Http\Resources\ShippingAddressResource;
use App\Endpoint\Api\Http\Resources\UserCouponResource;
use App\Enums\PayWay;
use App\Enums\SocialiteType;
use App\Enums\WxpayTradeType;
use App\Exceptions\BizException;
use App\Exceptions\ShippingNotSupportedException;
use App\Models\ActivityProductPart;
use App\Models\BargainOrder;
use App\Models\Order;
use App\Models\OrderActivity;
use App\Models\OrderProduct;
use App\Models\ProductGift;
use App\Models\ProductPartSku;
use App\Models\ProductSku;
use App\Models\ShippingAddress;
use App\Models\SocialiteUser;
use App\Models\{User, OrderPre, Tag};
use App\Models\Store\Store;
use App\Models\UserCoupon;
use App\Services\Payment\WxpayService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
class OrderService
{
/**
* 快速下单
*
* @param \App\Models\User $user
* @param int $skuId
* @param int $quantity
* @param int $shippingAddressId
* @param int|null $couponId
* @param string|null $note
* @param BargainOrder|null $bargainOrder
* @return \App\Models\Order
*/
public function createQuickOrder(
User $user,
int $skuId,
int $quantity,
?int $shippingAddressId,
?int $couponId = null,
?string $note = null,
?BargainOrder $bargainOrder = null,
): Order {
$sku = ProductSku::online()->findOrFail($skuId);
$product = [
'sku' => $sku,
'quantity' => $quantity,
];
return $this->createOrder($user, [$product], $shippingAddressId, $couponId, $note, $bargainOrder);
}
/**
* 购物车下单
*
* @param \App\Models\User $user
* @param int $skuId
* @param int $quantity
* @param int $shippingAddressId
* @param int|null $couponId
* @param string|null $note
* @return \App\Models\Order
*/
public function createShoppingCartOrder(
User $user,
array $shoppingCartItemIds,
int $shippingAddressId,
?int $couponId = null,
?string $note = null,
): Order {
$order = $this->createOrder(
$user,
$this->getProductsByShoppingCart($user, $shoppingCartItemIds),
$shippingAddressId,
$couponId,
$note
);
$user->shoppingCartItems()->whereIn('id', $shoppingCartItemIds)->delete();
return $order;
}
/**
* 创建订单
*
* @param \App\Models\User $user
* @param array $products
* @param int $shippingAddressId
* @param int|null $couponId
* @param string|null $note
* @param BargainOrder|null $bargainOrder
* @return \App\Models\Order
*/
public function createOrder(
User $user,
array $products,
?int $shippingAddressId,
?int $couponId = null,
?string $note = null,
?BargainOrder $bargainOrder = null,
): Order {
foreach ($products as $product) {
$sku = $product['sku'];
if ($product['quantity'] > $sku->saleable_stock) {
throw new BizException('商品库存不足');
}
}
$shippingAddress = $this->getShippingAddress($user, $shippingAddressId);
// 优惠券
$coupon = null;
if ($couponId) {
$coupon = $user->coupons()->onlyAvailable()->lockForUpdate()->findOrFail($couponId);
}
$mapProducts = $this->mapProducts($user, $products, $coupon, $bargainOrder);
// 计算运费
$shippingFee = $this->calculateShippingFee($mapProducts, $shippingAddress);
list(
$productsTotalAmount,
$vipDiscountAmount,
$couponDiscountAmount,
$salesValue
) = $this->calculateFees($mapProducts);
$order = $this->storeOrder(
$user,
$productsTotalAmount,
$couponDiscountAmount,
$vipDiscountAmount,
$shippingFee,
$salesValue,
$shippingAddress,
$note,
$coupon,
$bargainOrder,//添加砍价订单逻辑
);
$this->storeOrderProducts($order, $mapProducts);
// 将优惠券标记为已使用
$coupon?->markAsUse();
if ($order->total_amount === 0) {
$this->pay($order, PayWay::Balance);
$order->refresh();
}
return $order;
}
/**
* 添加店铺订单
*
*/
public function createOrderByPre(User $user, OrderPre $order_pre)
{
$products = [];
foreach($order_pre->products as $item) {
array_push($products, [
'sku' => ProductSku::findOrFail($item['sku_id']),
'quantity' => $item['quantity']
]);
}
$coupon_id = data_get($order_pre, 'others.coupon_id');
$note = data_get($order_pre, 'others.note');
// 优惠券
$coupon = null;
if ($coupon_id) {
$coupon = $user->coupons()->onlyAvailable()->lockForUpdate()->findOrFail($coupon_id);
}
$mapProducts = $this->mapProducts($user, $products, $coupon);
list(
$productsTotalAmount,
$vipDiscountAmount,
$couponDiscountAmount,
$salesValue
) = $this->calculateFees($mapProducts);
$order = $this->storeOrder(
$user,
$productsTotalAmount,
$couponDiscountAmount,
$vipDiscountAmount,
0,
$salesValue,
null,
$note,
$coupon
);
$order->update([
'store_id' => $order_pre->store_id,
'inviter_id' => $order_pre->user_id,
'source_type' => OrderPre::class,
'source_id' => $order_pre->id,
]);
$this->storeOrderProducts($order, $mapProducts);
// 将优惠券标记为已使用
$coupon?->markAsUse();
if ($order->total_amount === 0) {
$this->pay($order, PayWay::Balance);
$order->refresh();
}
return $order;
}
/**
* 保存订单
*
* @param \App\Models\User $user
* @param \App\Models\ShippingAddress $shippingAddress
* @param int $productsTotalAmount
* @param int $couponDiscountAmount
* @param int $vipDiscountAmount
* @param int $shippingFee
* @param float $salesValue
* @param string|null $note
* @param \App\Models\UserCoupon|null $coupon
* @return \App\Models\Order
*/
protected function storeOrder(
User $user,
int $productsTotalAmount,
int $couponDiscountAmount,
int $vipDiscountAmount,
int $shippingFee,
$salesValue,
?ShippingAddress $shippingAddress,
?string $note = null,
?UserCoupon $coupon = null,
?BargainOrder $bargainOrder = null,
): Order {
// 订单支付金额=商品总额-券折扣金额-会员折扣金额+邮费-砍价金额
$totalAmount = $productsTotalAmount - $couponDiscountAmount - $vipDiscountAmount;
//如果有砍价优惠
if ($bargainOrder) {
$totalAmount -= $bargainOrder->bargain_price;
}
if ($totalAmount < 0) {
$totalAmount = 0;
}
$totalAmount += $shippingFee;
do {
// 如果订单号重复,则直接重试
try {
$attrs = [
'sn' => serial_number(),
'user_coupon_id' => $coupon?->id,
'coupon_discount_amount' => $couponDiscountAmount,
'vip_discount_amount' => $vipDiscountAmount,
'shipping_fee' => $shippingFee,
'products_total_amount' => $productsTotalAmount,
'total_amount' => $totalAmount, // 商品总额-券折扣金额-会员折扣金额+邮费
'sales_value' => $salesValue, // 订单总销售值
'note' => $note,
// 收货地址
'consignee_name' => $shippingAddress->consignee??'',
'consignee_telephone' => $shippingAddress->telephone??'',
'consignee_zone' => $shippingAddress->zone??'',
'consignee_address' => $shippingAddress->address??'',
//砍价订单金额
'bargain_amount'=>$bargainOrder?->bargain_price ?? 0,
];
return $user->orders()->create($attrs);
} catch (QueryException $e) {
if (strpos($e->getMessage(), 'Duplicate entry') === false) {
throw $e;
}
}
} while (true);
}
/**
* 保存订单商品
*
* @param \App\Models\Order $order
* @param array $mapProducts
* @return void
*/
public function storeOrderProducts(Order $order, array $mapProducts)
{
$orderProducts = [];
foreach ($mapProducts as $product) {
$sku = $product['sku'];
$qty = $product['quantity'];
// 支付金额 = 商品总额 - 优惠券折扣金额- 会员折扣金额 - 砍价金额
$totalAmount = $product['total_amount'] - $product['coupon_discount_amount'] - $product['vip_discount_amount'] - $product['bargain_amount'];
$orderProducts[] = [
'gift_for_sku_id' => null,
'activity_id'=>null,
'is_gift'=> false,
'user_id' => $order->user_id,
'order_id' => $order->id,
'spu_id' => $sku->spu_id,
'sku_id' => $sku->id,
'category_id' => $sku->category_id,
'name' => $sku->name,
'specs' => json_encode($sku->specs),
'cover' => $sku->cover,
'weight' => $sku->weight,
'sell_price' => $sku->sell_price,
'vip_price' => $sku->vip_price,
'sales_value' => $sku->sales_value,
'quantity' => $qty,
'remain_quantity' => $qty, // 剩余发货数量
'coupon_discount_amount' => $product['coupon_discount_amount'],
'vip_discount_amount' => $product['vip_discount_amount'],
'total_amount' => $totalAmount,
'bargain_amount'=> $product['bargain_amount'],
'created_at' => $order->created_at,
'updated_at' => $order->updated_at,
];
// 扣除商品库存
if (!$order->store_id) {
$this->deductProduct($sku, $qty);
}
}
//根据订单参加的活动添加赠品;
$gifts = $this->activityGifts($order, $orderProducts);
$orderProducts = array_merge($orderProducts, $gifts);
OrderProduct::insert($orderProducts);
}
/**
* 扣商品的库存
* 03-04取消根据商品赠送赠品
*
* @param \App\Models\ProductSku $sku
* @param int $qty
* @return array
*/
protected function deductProduct(ProductSku $sku, int $qty)
{
// 扣商品库存
$sku->update([
'sales' => DB::Raw("sales + {$qty}"),
'stock' => DB::raw("stock - {$qty}"), // 库存
]);
$sku->spu?->increment('sales', $qty);
// // 如果是因为赠品库存不足引起的异常,则需重试
// do {
// try {
// return $this->deductGifts($sku, $qty);
// } catch (QueryException $e) {
// if (strpos($e->getMessage(), 'Numeric value out of range') === false) {
// throw $e;
// }
// }
// } while (true);
}
/**
* 从门店中扣除商品库存
*/
protected function deductProductFromStore(Store $store, ProductSku $sku, int $amount)
{
$sku = $store->productSkus()->findOrFail($sku->id);
// 添加出库记录
$tag = Tag::firstOrCreate([
'type' => Tag::TYPE_STORE_STOCK,
'name' => '下单出库'
]);
$store->stockLogs()->create([
'amount' => 0-$amount,
'product_sku_id' => $sku->id,
'remarks' => '购买',
'tag_id' => $tag->id
]);
$store->productSkus()->updateExistingPivot($sku->id, [
'amount' => $sku->pivot->amount - $amount
]);
}
/**
* 扣出商品的赠品
*
* @param \App\Models\ProductSku $sku
* @param int $qty
* @return array
*/
protected function deductGifts(ProductSku $sku, int $qty)
{
// 赠品
$gifts = [];
if ($qty < 1) {
return $gifts;
}
$sku->gifts->loadMissing('giftSku');
foreach ($sku->gifts as $gift) {
// 如果未找到赠品,则不赠送
if ($gift->giftSku === null) {
continue;
}
// 需赠送礼品的总数
$num = $gift->num * $qty;
if ($gift->isLimit()) {
if ($gift->remaining === 0) {
continue;
}
if ($num > $gift->remaining) {
// 计算剩余可赠送的份数
$remainingQty = (int) ($gift->remaining / $gift->num);
$num = $gift->num * $remainingQty;
}
}
if ($gift->isLimit()) {
$gift->update([
'remaining' => DB::raw("remaining-{$num}"),
'sent' => DB::raw("sent+{$num}"),
]);
} else {
$gift->increment('sent', $num);
}
$gifts[] = [
'sku' => $gift->giftSku,
'num' => $num, // 赠送商品总数
];
}
return $gifts;
}
/**
* Undocumented function
*
* @param Order $order
* @param [type] $products
* @return array $gifts
*/
protected function activityGifts(Order $order, $products)
{
$_products = array_column($products, 'total_amount', 'sku_id');
$inValidParts = [];
// 整理订单商品的分区
$partSkus = ProductPartSku::with('part')->whereIn('sku_id', array_keys($_products))->get();
foreach ($partSkus as $partSku) {
if ($partSku->part?->is_show) {
$inValidParts[$partSku->part_id][$partSku->sku_id] = $_products[$partSku->sku_id] ?? 0;
// if (isset($inValidParts[$partSku->part_id])) {
// $inValidParts[$partSku->part_id] += $_products[$partSku->sku_id] ?? 0;
// } else {
// $inValidParts[$partSku->part_id] = $_products[$partSku->sku_id] ?? 0;
// }
}
}
// dd($inValidParts);
//根据分区获取活动
$partActivities = ActivityProductPart::with(['activity', 'activity.gifts'])->whereHas('activity', function (Builder $query) {
return $query->where('is_use', true)->where('started_at', '<', now())->where('ended_at', '>=', now());
})->whereIn('part_id', array_keys($inValidParts))->get();
$activityArr = [];
$partActivities->each(function ($item) use (&$activityArr) {
$activityArr[$item->activity_id][] = $item->part_id;
});
$giveGifts = [];
$sendedActivities = [];
foreach ($partActivities as $partActivity) {
//判断该活动是否已处理
if (in_array($partActivity->activity_id, $sendedActivities)) {
continue;
}
$sendedActivities[] = $partActivity->activity_id;
//获取活动的赠品赠送规则
$_giftsRule = $partActivity->activity?->gifts_rule;
//判断是否首单times=0为仅首单赠送, 1为不限
if ($_giftsRule['times'] == 0 && OrderProduct::where([
'activity_id' => $partActivity->activity_id,
'user_id' => $order->user_id,
])->whereHas('order', function ($query) {//除取消外的订单
return $query->where('status', '<>', Order::STATUS_CANCELLED);
})->exists()) {
continue;//提前结束本次循环
}
//判断是否满足门槛
$inValidGoods = [];
foreach ($inValidParts as $key => $part) {
if (in_array($key, $activityArr[$partActivity->activity_id])) {
$inValidGoods = array_merge($inValidGoods, $part);
}
}
if (bcmul($_giftsRule['value'], 100) > array_sum($inValidGoods)) {
continue;//提前结束本次循环
}
//返回赠品
$_gifts = $partActivity->activity->gifts;
$_num = 0;
foreach ($_gifts as $_gift) {
if (($_gift->stock - $_gift->pivot->qty) >= 0) {
$_num += $_gift->pivot->qty;
$giveGifts[] = [
'gift_for_sku_id'=> null,
'user_id' => $order->user_id,
'order_id' => $order->id,
'spu_id' => $_gift->spu_id,
'sku_id' => $_gift->id,
'category_id' => $_gift->category_id,
'name' => $_gift->name,
'specs' => json_encode($_gift->specs),
'cover' => $_gift->cover,
'weight' => $_gift->weight,
'sell_price' => $_gift->sell_price,
'vip_price' => $_gift->vip_price,
'sales_value' => 0, // 赠品不算销售值
'quantity' => $_gift->pivot->qty,
'remain_quantity' => $_gift->pivot->qty, // 剩余发货数量
'coupon_discount_amount' => 0,
'vip_discount_amount' => 0,
'total_amount' => 0,
'bargain_amount'=> 0,
'created_at' => $order->created_at,
'updated_at' => $order->updated_at,
'is_gift'=> true,
'activity_id'=>$partActivity->activity_id,
];
// 扣除商品库存
$this->deductProduct($_gift, $_gift->pivot->qty);
}
}
if ($_num > 0) {
//记录订单参与活动信息
OrderActivity::firstOrCreate([
'order_id'=>$order->id,
'activity_id'=>$partActivity->activity_id,
]);
}
}
return $giveGifts;
}
/**
* 确认快速下单
*
* @param \App\Models\User $user
* @param int $skuId
* @param int $quantity
* @param int|null $shippingAddressId
* @param int|null $couponId
* @param int|null $bargainOrderId
* @return array
*/
public function verifyQuickOrder(User $user, int $skuId, int $quantity, ?int $shippingAddressId = null, ?int $couponId = null, ?int $bargainOrderId = null)
{
$sku = ProductSku::online()->findOrFail($skuId);
$product = [
'sku' => $sku,
'quantity' => $quantity,
];
return $this->verifyOrder($user, [$product], $shippingAddressId, $couponId, $bargainOrderId);
}
/**
* 确认购物车订单
*
* @param \App\Models\User $user
* @param array $shoppingCartItemIds
* @param int|null $shippingAddressId
* @param int|null $couponId
* @return array
*/
public function verifyShoppingCartOrder(User $user, array $shoppingCartItemIds, ?int $shippingAddressId = null, ?int $couponId = null)
{
// 获取购买商品
$products = $this->getProductsByShoppingCart($user, $shoppingCartItemIds);
return $this->verifyOrder($user, $products, $shippingAddressId, $couponId);
}
/**
* 确认订单
*
* @param \App\Models\User $user
* @param array $products
* @param int|null $shippingAddressId
* @param int|null $couponId
* @param int|null $bargainOrderId
* @return array
*/
protected function verifyOrder(User $user, array $products, ?int $shippingAddressId = null, ?int $couponId = null, ?int $bargainOrderId = null): array
{
// 获取收货地址
$shippingAddress = $this->getShippingAddress($user, $shippingAddressId);
// 优惠券
$coupon = null;
if ($couponId) {
$coupon = $user->coupons()->onlyAvailable()->findOrFail($couponId);
}
$bargainOrder = null;
if ($bargainOrderId) {
$bargainOrder = $user->bargainOrders()->findOrFail($bargainOrderId);
}
$mapProducts = $this->mapProducts($user, $products, $coupon, $bargainOrder);
// 是否支持配送
$shippingSupported = true;
// 运费
$shippingFee = 0;
if ($shippingAddress) {
try {
$shippingFee = $this->calculateShippingFee($mapProducts, $shippingAddress);
} catch (ShippingNotSupportedException $e) {
$shippingFee = 0;
$shippingSupported = false;
}
}
list(
$productsTotalAmount,
$vipDiscountAmount,
$couponDiscountAmount,
$salesValue,
$bargainAmount,
) = $this->calculateFees($mapProducts);
$totalAmount = $productsTotalAmount - $couponDiscountAmount - $vipDiscountAmount - $bargainAmount;
if ($totalAmount < 0) {
$totalAmount = 0;
}
$totalAmount += $shippingFee;
return [
'products' => collect($mapProducts)->map(function ($item) {
return [
'sku' => ProductSkuSimpleResource::make($item['sku']),
'quantity' => $item['quantity'],
];
}),
'coupons' => UserCouponResource::collection((new CouponService())->getAvailableCoupons($user, $products)),
'shipping_address' => $shippingAddress ? ShippingAddressResource::make($shippingAddress) : null, // 收货地址
'shipping_supported' => $shippingSupported,
'shipping_fee' => trim_trailing_zeros(bcdiv($shippingFee, 100, 2)), // 运费
'products_total_amount' => trim_trailing_zeros(bcdiv($productsTotalAmount, 100, 2)), // 商品总额
'vip_discount_amount' => trim_trailing_zeros(bcdiv($vipDiscountAmount, 100, 2)), // 会员折扣金额
'coupon_discount_amount' => trim_trailing_zeros(bcdiv($couponDiscountAmount, 100, 2)), // 优惠券折扣金额
'total_amount' => trim_trailing_zeros(bcdiv($totalAmount, 100, 2)), // 实付金额
'bargain_amount' => trim_trailing_zeros(bcdiv($bargainAmount, 100, 2)), //砍价金额
];
}
/**
* 计算费用
*
* @param array $products
* @return array
*/
protected function calculateFees(array $products)
{
$productsTotalAmount = 0;
$vipDiscountAmount = 0;
$couponDiscountAmount = 0;
$salesValue = 0;
$bargainAmount = 0;
foreach ($products as $product) {
$productsTotalAmount += $product['total_amount'];
$vipDiscountAmount += $product['vip_discount_amount'];
$couponDiscountAmount += $product['coupon_discount_amount'];
$salesValue = bcadd($salesValue, $product['total_sales_value']);
$bargainAmount += $product['bargain_amount'];
}
return [
$productsTotalAmount,
$vipDiscountAmount,
$couponDiscountAmount,
$salesValue,
$bargainAmount,
];
}
/**
* 计算运费
*
* @param array $products
* @param \App\Models\ShippingAddress $shippingAddress
* @return int
*/
protected function calculateShippingFee(array $products, ?ShippingAddress $shippingAddress): int
{
// 运费
$shippingFee = 0;
if (!$shippingAddress) {
return $shippingFee;
}
$shippings = [];
foreach ($products as $product) {
$sku = $product['sku'];
if (is_null($sku->shipping_template_id)) {
continue;
}
$shipping = $shippings[$sku->shipping_template_id] ?? [
'total_weight' => 0,
'total_amount' => 0,
];
$shipping['total_weight'] += $sku->weight * $product['quantity'];
$shipping['total_amount'] += $product['total_amount'] - $product['vip_discount_amount'] - $product['coupon_discount_amount'];
$shippings[$sku->shipping_template_id] = $shipping;
}
$shippingService = new ShippingService();
foreach ($shippings as $templateId => $shipping) {
$shippingFee += $shippingService->countShippingAmount(
$shipping['total_weight'],
$shipping['total_amount'],
$templateId,
$shippingAddress->zone_id
);
}
return $shippingFee;
}
/**
* 准备商品信息
*
* @param \App\Models\User $user
* @param array $products {sku: App\Models\ProductSku, quantity: 数量}
* @param \App\Models\UserCoupon|null $coupon
* @return array
*
* @throws \App\Exceptions\BizException
*/
protected function mapProducts(User $user, array $products, ?UserCoupon $coupon, ?BargainOrder $bargainOrder = null): array
{
$_products = collect($products)->map(function ($item) use ($user) {
$sku = $item['sku'];
return array_merge($item, [
// 优惠券折扣金额
'coupon_discount_amount' => 0,
// 会员折扣金额
'vip_discount_amount' => $this->calculateVipDiscountAmount(
$user, $sku, $item['quantity']
),
// 总金额
'total_amount' => $sku->sell_price * $item['quantity'],
// 总销售值
'total_sales_value' => bcmul($sku->sales_value, $item['quantity'], 2),
//砍价金额
'bargain_amount' => 0,
]);
});
//处理砍价金额
if ($bargainOrder) {
$bargainDiscountAmount = $this->getBargainDiscountAmount($bargainOrder, $_products->all());
$_products = $_products->map(function ($item) use ($bargainDiscountAmount) {
$item['bargain_amount'] = $bargainDiscountAmount[$item['sku']->id] ?? 0;
return $item;
});
}
if ($coupon === null) {
return $_products->all();
}
$couponDiscountAmounts = $this->getCouponDiscountAmounts($coupon, $_products->all());
return $_products->map(function ($item) use ($couponDiscountAmounts) {
$item['coupon_discount_amount'] = $couponDiscountAmounts[$item['sku']->id] ?? 0;
return $item;
})->all();
}
/**
* 计算会员折扣金额
*
* @param \App\Models\User $user
* @param \App\Models\ProductSku $sku
* @param int $quantity
* @return int
*/
protected function calculateVipDiscountAmount(User $user, ProductSku $sku, int $quantity)
{
$price = $sku->getRealPrice($user);
if ($price > $sku->sell_price) {
return 0;
}
return ($sku->sell_price - $price) * $quantity;
}
/**
* 获取商品的优惠券折扣金额
*
* @param \App\Models\UserCoupon $coupon
* @param array $products
* @return array
*
* @throws \App\Exceptions\BizException
*/
protected function getCouponDiscountAmounts(UserCoupon $coupon, array $products): array
{
// 启用的券的使用规则
$coupon->loadMissing(['ranges' => function ($query) {
$query->isEnable();
}]);
// 可使用优惠券的商品总额
$amounts = [];
foreach ($products as $item) {
$sku = $item['sku'];
if (! $coupon->isSupport($sku)) {
throw new BizException('优惠券不满足使用条件');
}
$amount = $item['total_amount'] - $item['vip_discount_amount'];
// 仅保留商品真实总额大于0的商品
if ($amount > 0) {
$amounts[$sku->id] = $amount;
}
}
// 全部商品总额
$totalAmount = array_sum($amounts);
if ($coupon->coupon_threshold > $totalAmount) {
throw new BizException('优惠券不满足使用条件');
}
// 优惠券折扣金额
$couponAmount = value(function ($coupon, $totalAmount) {
// 如果优惠券是折扣券,则需算出优惠总额
if ($coupon->isDiscountCoupon()) {
return (int) bcmul($totalAmount, bcdiv(100 - $coupon->coupon_amount, 100, 2));
}
// 如果优惠券的折扣金额超过商品总额,则优惠金额为商品总额
if ($coupon->coupon_amount > $totalAmount) {
return $totalAmount;
}
return $coupon->coupon_amount;
}, $coupon, $totalAmount);
// 待计算优惠的商品总数
$i = count($amounts);
foreach ($amounts as &$amount) {
$i--;
$originalAmount = $amount;
if ($i > 0) {
$amount = (int) bcdiv(bcmul($couponAmount, $amount, 0), $totalAmount, 2);
$couponAmount -= $amount;
$totalAmount -= $originalAmount;
} else {
$amount = $couponAmount;
}
unset($amount);
}
return $amounts;
}
/**
*
*/
protected function getBargainDiscountAmount(BargainOrder $bargainOrder, array $products): array
{
// 砍价的商品
$amounts = [];
foreach ($products as $item) {
$sku = $item['sku'];
$amount = $item['total_amount'] - $item['vip_discount_amount'];
// 仅保留商品真实总额大于0的商品
if ($amount > 0) {
$amounts[$sku->id] = $amount;
}
}
// 全部商品总额
$totalAmount = array_sum($amounts);
$bargainAmount = $bargainOrder->bargain_price ?? 0;
// 待计算优惠的商品总数
$i = count($amounts);
foreach ($amounts as &$amount) {
$i--;
$originalAmount = $amount;
if ($i > 0) {
$amount = (int) bcdiv(bcmul($bargainAmount, $amount, 0), $totalAmount, 2);
$bargainAmount -= $amount;
$totalAmount -= $originalAmount;
} else {
$amount = $bargainAmount;
}
unset($amount);
}
return $amounts;
}
/**
* 根据购物车获取商品
*
* @param \App\Models\User $user
* @param array $shoppingCartItemIds
* @return array
*
* @throws \App\Exceptions\BizException
*/
protected function getProductsByShoppingCart(User $user, array $shoppingCartItemIds): array
{
$shoppingCartItems = $user->shoppingCartItems()->findMany($shoppingCartItemIds);
if ($shoppingCartItems->count() !== count($shoppingCartItemIds)) {
throw new BizException('购物车商品已丢失');
}
$shoppingCartItems->load('sku.spu');
$lostShoppingCartItems = $shoppingCartItems->filter(function ($item) {
return $item->sku === null;
});
if ($lostShoppingCartItems->count() > 0) {
throw new BizException('购物车商品已失效');
}
return $shoppingCartItems->map(function ($item) {
return [
'sku' => $item->sku,
'quantity' => $item->quantity,
];
})->all();
}
/**
* 获取收货地址
*
* @param \App\Models\User $user
* @param int|null $shippingAddressId
* @return \App\Models\ShippingAddress|null
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
protected function getShippingAddress(User $user, ?int $shippingAddressId = null): ?ShippingAddress
{
if ($shippingAddressId) {
return $user->shippingAddresses()->findOrFail($shippingAddressId);
}
return $user->shippingAddresses()->where('is_default', true)->first();
}
/**
* 订单付款
*
* @param \App\Models\Order $order
* @param \App\Enums\PayWay $payWay
* @return mixed
*
* @throws \App\Exceptions\WeChatPayException
*/
public function pay(Order $order, PayWay $payWay)
{
if (! $order->isPending()) {
throw new BizException('订单状态不是待付款');
}
do {
$payLog = null;
try {
$payLog = $order->payLogs()->create([
'pay_sn' => serial_number(),
'pay_way' => $payWay,
]);
} catch (QueryException $e) {
if (strpos($e->getMessage(), 'Duplicate entry') === false) {
throw $e;
}
}
} while ($payLog === null);
$data = [
'pay_sn' => $payLog->pay_sn,
];
// 如果支付方式为线下支付,或支付金额为 0则按支付完成处理
if ($order->total_amount === 0 || $payLog->isOffline()) {
(new PayService())->handleSuccess($payLog, [
'pay_at' => now(),
]);
$data = null;
} elseif ($payLog->isWxpay()) {
if (is_null($tradeType = WxpayTradeType::tryFromPayWay($payLog->pay_way))) {
throw new BizException('支付方式 非法');
}
$params = [
'body' => app_settings('app.app_name').'-商城订单',
'out_trade_no' => $payLog->pay_sn,
'total_fee' => $order->total_amount,
'trade_type' => $tradeType->value,
];
if ($tradeType === WxpayTradeType::JSAPI) {
$socialite = match ($payLog->pay_way) {
PayWay::WxpayMiniProgram => SocialiteUser::where([
'user_id' => $order->user_id,
'socialite_type' => SocialiteType::WechatMiniProgram,
])->first(),
};
if ($socialite === null) {
throw new BizException('未绑定微信小程序');
}
$params['openid'] = $socialite->socialite_id;
}
$data = (new WxpayService())->pay($params, match ($payLog->pay_way) {
PayWay::WxpayMiniProgram => 'mini_program',
default => 'default',
});
} elseif ($payLog->isAlipay()) {
$params = [
'subject' => app_settings('app.app_name').'-商城订单',
'out_trade_no' => $payLog->pay_sn,
'total_amount' => bcdiv($order->total_amount, 100, 2),
];
$data = app(AlipayService::class)->pay($params);
}
return [
'pay_way' => $payLog->pay_way,
'data' => $data,
];
}
/**
* 确认订单
*
* @param \App\Models\Order $order
* @param bool $isSettlable
* @return void
*/
public function confirm(Order $order, $isSettlable = false)
{
if (! $order->isShipped()) {
throw new BizException('订单包裹未发完');
}
if ($isSettlable && $order->afterSales()->processing()->count() > 0) {
throw new BizException('订单商品售后中,不能完成此订单');
}
$orderPackageService = new OrderPackageService();
$order->loadMissing('packages');
$packages = $order->packages->filter(function ($item) {
return ! $item->is_failed && ! $item->isChecked();
});
foreach ($packages as $package) {
$orderPackageService->checkPackage($package, true);
}
$order->update([
'status' => Order::STATUS_COMPLETED,
'completed_at' => now(),
]);
}
/**
* 取消订单
*
* @param \App\Models\Order $order
* @return void
*/
public function cancel(Order $order)
{
if (! $order->isPending() && ! $order->isWaitShipping()) {
throw new BizException('订单状态不是待付款或待发货');
}
if ($order->isWaitShipping()) {
$order->refundLogs()->create([
'sn' => serial_number(),
'amount' => $order->total_amount,
'reason' => '取消订单',
]);
}
// 门店订单
if ($order->store_id) {
// 返还店铺库存
} else {
$products = $order->products()->get();
foreach ($products->load(['sku', 'spu']) as $product) {
if ($product->sku === null) {
continue;
}
// 如果商品不是赠品,则直接增加商品库存
if (! $product->isGift()) {
$product->spu?->increment('sales', -$product->quantity);
$product->sku?->update([
'stock' => DB::Raw("stock + {$product->quantity}"),
'sales' => DB::Raw("sales - {$product->quantity}"),
]);
continue;
}
//原赠品
if ($product->gift_for_sku_id) {
$gift = ProductGift::where('sku_id', $product->gift_for_sku_id)
->where('gift_sku_id', $product->sku_id)
->first();
if ($gift === null) {
continue;
}
if ($gift->isLimit()) {
$gift->update([
'remaining' => DB::raw("remaining+{$product->quantity}"),
'sent' => DB::raw("sent-{$product->quantity}"),
]);
} else {
$gift->decrement('sent', $product->quantity);
}
continue;
} else {//新赠品, 则直接增加商品库存
$product->spu?->increment('sales', -$product->quantity);
$product->sku?->update([
'stock' => DB::Raw("stock + {$product->quantity}"),
'sales' => DB::Raw("sales - {$product->quantity}"),
]);
continue;
}
}
}
// 取消订单,退券
if ($order->user_coupon_id) {
UserCoupon::where('user_id', $order->user_id)->where('id', $order->user_coupon_id)->update([
'is_use'=>false,
]);
}
$order->update([
'status' => Order::STATUS_CANCELLED,
]);
}
}