6
0
Fork 0

提交订单

release
李静 2021-12-15 19:08:36 +08:00
parent 9733f929dc
commit 8b354a5593
7 changed files with 299 additions and 6 deletions

View File

@ -4,8 +4,13 @@ namespace App\Endpoint\Api\Http\Controllers\Order;
use App\Endpoint\Api\Http\Controllers\Controller;
use App\Endpoint\Api\Http\Resources\OrderSimpleResource;
use App\Exceptions\BizException;
use App\Helpers\Paginator as PaginatorHelper;
use App\Services\OrderService;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Throwable;
class OrderController extends Controller
{
@ -27,4 +32,76 @@ class OrderController extends Controller
return OrderSimpleResource::collection($orders);
}
/**
* 创建订单
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
$rules = [
'shipping_address_id' => ['bail', 'required', 'int'],
'coupon_id' => ['bail', 'nullable', 'int'],
'note' => ['bail', 'nullable', 'string', 'max:255'],
];
if ($isQuick = $request->filled('product')) {
$rules = array_merge($rules, [
'product.sku_id' => ['bail', 'required', 'int'],
'product.quantity' => ['bail', 'required', 'int', 'min:1'],
]);
} else {
$rules = array_merge($rules, [
'shopping_cart' => ['bail', 'required', 'array'],
]);
}
$request->validate($rules, [], [
'product.sku_id' => '商品',
'product.quantity' => '数量',
'shopping_cart' => '购物车商品',
'shipping_address_id' => '收货地址',
'coupon_id' => '优惠券',
'note' => '订单备注',
]);
$user = $request->user();
try {
$order = retry(3, function () use ($isQuick, $user, $request) {
return DB::transaction(function () use ($isQuick, $user, $request) {
$orderService = new OrderService();
if ($isQuick) {
return $orderService->createQuickOrder(
$user,
$request->input('product.sku_id'),
$request->input('product.quantity'),
$request->input('shipping_address_id'),
$request->input('coupon_id'),
$request->input('note'),
);
}
return $orderService->createShoppingCartOrder(
$user,
$request->input('shopping_cart'),
$request->input('shipping_address_id'),
$request->input('coupon_id'),
$request->input('note'),
);
});
});
} catch (BizException | ModelNotFoundException $e) {
throw $e;
} catch (Throwable $e) {
report($e);
throw new BizException('系统繁忙,请稍后再试');
}
return OrderSimpleResource::make($order);
}
}

View File

@ -20,8 +20,9 @@ class OrderProductResource extends JsonResource
'name' => $this->name,
'cover' => $this->cover,
'specs' => array_values((array) $this->specs),
'sell_price' => $this->sell_price_format,
'vip_price' => $this->vip_price_format,
'quantity' => $this->quantity,
'total_amount' => $this->total_amount_format,
];
}
}

View File

@ -19,8 +19,8 @@ class OrderSimpleResource extends JsonResource
'sn' => $this->sn,
'total_amount' => $this->total_amount_format,
'status' => $this->status,
'products' => OrderProductResource::collection($this->whenLoaded('products')),
'created_date' => $this->created_at->toDateString(),
'products' => OrderProductResource::collection($this->whenLoaded('products')),
];
}
}

View File

@ -11,6 +11,7 @@ class Order extends Model
use Filterable;
public const STATUS_PENDING = 0; // 待付款
public const STATUS_PAID = 1; // 已付款
public const STATUS_COMPLETED = 9; // 已完成
public const STATUS_CANCELLED = 10; // 已取消

View File

@ -37,13 +37,27 @@ class OrderProduct extends Model
];
/**
* 获取订单支付金额
* 获取订单商品真实价格
*
* @return string
*/
public function getTotalAmountFormatAttribute()
public function getSellPriceFormatAttribute()
{
return Numeric::trimTrailingZero(bcdiv($this->attributes['total_amount'], 100, 2));
return Numeric::trimTrailingZero(bcdiv($this->attributes['sell_price'], 100, 2));
}
/**
* 获取订单商品真实价格
*
* @return string
*/
public function getVipPriceFormatAttribute()
{
if (is_null($price = $this->attributes['vip_price'])) {
return '';
}
return Numeric::trimTrailingZero(bcdiv($price, 100, 2));
}
public function packageProducts()

View File

@ -121,6 +121,16 @@ class UserCoupon extends Model
return false;
}
/**
* 将此优惠券标记为已使用
*/
public function markAsUse()
{
$this->forceFill([
'is_use' => true,
])->save();
}
/**
* 获取此优惠券的面值
*

View File

@ -8,13 +8,77 @@ use App\Endpoint\Api\Http\Resources\UserCouponResource;
use App\Exceptions\BizException;
use App\Exceptions\ShippingNotSupportedException;
use App\Helpers\Numeric;
use App\Helpers\Order as OrderHelper;
use App\Models\Order;
use App\Models\OrderProduct;
use App\Models\ProductSku;
use App\Models\ShippingAddress;
use App\Models\User;
use App\Models\UserCoupon;
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
* @return \App\Models\Order
*/
public function createQuickOrder(
User $user,
int $skuId,
int $quantity,
int $shippingAddressId,
?int $couponId = null,
?string $note = null
): Order {
$sku = ProductSku::online()->findOrFail($skuId);
$product = [
'sku' => $sku,
'quantity' => $quantity,
];
return $this->createOrder($user, [$product], $shippingAddressId, $couponId, $note);
}
/**
* 购物车下单
*
* @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;
}
/**
* 确认快速下单
*
@ -54,6 +118,129 @@ class OrderService
return $this->verifyOrder($user, $products, $shippingAddressId, $couponId);
}
/**
* 创建订单
*
* @param \App\Models\User $user
* @param array $products
* @param int $shippingAddressId
* @param int|null $couponId
* @param string|null $note
* @return \App\Models\Order
*/
protected function createOrder(
User $user,
array $products,
int $shippingAddressId,
?int $couponId = null,
?string $note = 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);
// 计算运费
$shippingFee = $this->calculateShippingFee($mapProducts, $shippingAddress);
list(
$productsTotalAmount,
$vipDiscountAmount,
$couponDiscountAmount,
$totalAmount,
) = $this->calculateFees($mapProducts);
// 订单总费用
$totalAmount += $shippingFee;
$order = $user->orders()->create([
'sn' => OrderHelper::serialNumber(),
'coupon_discount_amount' => $couponDiscountAmount,
'vip_discount_amount' => $vipDiscountAmount,
'shipping_fee' => $shippingFee,
'products_total_amount' => $productsTotalAmount,
'total_amount' => $totalAmount,
'status' => $totalAmount > 0 ? Order::STATUS_PENDING : Order::STATUS_PAID,
'note' => $note,
// 收货地址
'consignee_name' => $shippingAddress->consignee,
'consignee_telephone' => $shippingAddress->telephone,
'consignee_zone' => $shippingAddress->zone,
'consignee_address' => $shippingAddress->address,
]);
$data = [];
foreach ($mapProducts as $product) {
$sku = $product['sku'];
// 支付金额 = 商品总额 - 优惠券折扣金额- 会员折扣金额
$totalAmount = $product['total_amount'] - $product['coupon_discount_amount'] - $product['vip_discount_amount'];
$data[] = [
'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,
'quantity' => $product['quantity'],
'coupon_discount_amount' => $product['coupon_discount_amount'],
'vip_discount_amount' => $product['vip_discount_amount'],
'total_amount' => $totalAmount,
'created_at' => $order->created_at,
'updated_at' => $order->updated_at,
];
$this->deductSkuStock($sku, $product['quantity']);
}
OrderProduct::insert($data);
// 将优惠券标记为已使用
$coupon?->markAsUse();
return $order;
}
/**
* 扣除商品库存
*
* @param \App\Models\ProductSku $sku
* @param int $quantity
* @return void
*/
protected function deductSkuStock(ProductSku $sku, int $quantity): void
{
$sku->update([
'stock' => DB::raw("stock + {$quantity}"), // 库存
'sales' => DB::raw("sales + {$quantity}"), // 销量
]);
$sku->spu->update([
'sales' => DB::raw("sales + {$quantity}"), // 销量
]);
}
/**
* 确认订单
*
@ -98,6 +285,9 @@ class OrderService
$totalAmount,
) = $this->calculateFees($mapProducts);
// 订单总费用
$totalAmount += $shippingFee;
return [
'products' => collect($mapProducts)->map(function ($item) {
return [
@ -112,7 +302,7 @@ class OrderService
'products_total_amount' => Numeric::trimTrailingZero(bcdiv($productsTotalAmount, 100, 2)), // 商品总额
'vip_discount_amount' => Numeric::trimTrailingZero(bcdiv($vipDiscountAmount, 100, 2)), // 会员折扣金额
'coupon_discount_amount' => Numeric::trimTrailingZero(bcdiv($couponDiscountAmount, 100, 2)), // 优惠券折扣金额
'total_amount' => Numeric::trimTrailingZero(bcdiv($totalAmount + $shippingFee, 100, 2)), // 实付金额
'total_amount' => Numeric::trimTrailingZero(bcdiv($totalAmount, 100, 2)), // 实付金额
];
}