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

259 lines
9.2 KiB
PHP

<?php
namespace App\Services;
use App\Enums\OfflineOrderStatus;
use App\Enums\PayWay;
use App\Enums\PointLogAction;
use App\Enums\SocialiteType;
use App\Enums\WxpayTradeType;
use App\Exceptions\BizException;
use App\Models\OfflineOrder;
use App\Models\OfflineOrderItem;
use App\Models\OfflineOrderPreview;
use App\Models\OfflineProductCategory;
use App\Models\SocialiteUser;
use App\Models\User;
use App\Services\Payment\WxpayService;
class OfflineOrderService
{
public function create(User $user, OfflineOrderPreview $orderPreview, int $points = 0): OfflineOrder
{
$items = $this->mapItems($orderPreview->payload['items']);
[
$productsTotalAmount,
$discountReductionAmount,
$paymentAmount,
] = $this->calculateFees($items);
// 积分抵扣金额
$pointsDeductionAmount = $points;
$paymentAmount -= $pointsDeductionAmount;
if ($paymentAmount < 0) {
$paymentAmount = 0;
}
do {
$sn = serial_number();
} while(OfflineOrder::where('sn', $sn)->exists());
/** @var \App\Models\OfflineOrder */
$order = OfflineOrder::create([
'user_id' => $user->id,
'store_id' => $orderPreview->store_id,
'staff_id' => $orderPreview->staff_id,
'sn' => $sn,
'products_total_amount' => $productsTotalAmount,
'discount_reduction_amount' => $discountReductionAmount,
'points_deduction_amount' => $pointsDeductionAmount,
'payment_amount' => $paymentAmount,
'status' => OfflineOrderStatus::Pending,
'orderable_type' => $orderPreview->getMorphClass(),
'orderable_id' => $orderPreview->id,
]);
$this->insertOrderItems($order, $items);
// 扣除积分
if ($points > 0) {
(new PointService)->change($user, -$points, PointLogAction::Consumption, "线下订单{$order->sn}使用积分", $order);
}
if ($order->payment_amount === 0) {
$this->pay($order, PayWay::None);
$order->refresh();
}
return $order;
}
public function check(User $user, array $items): array
{
$productCategoryIds = collect($items)->pluck('product_category_id');
$productCategories = OfflineProductCategory::query()
->whereIn('id', $productCategoryIds->all())
->get()
->keyBy('id');
if ($productCategories->count() != $productCategoryIds->count()) {
throw new BizException('商品分类异常');
}
$mapItems = $this->mapItems($items);
[
$productsTotalAmount,
$discountReductionAmount,
$paymentAmount,
] = $this->calculateFees($mapItems);
//---------------------------------------
// 积分当钱花
//---------------------------------------
$remainingPoints = $user->userInfo->points; // 用户剩余积分
$availablePoints = $paymentAmount > $remainingPoints ? $remainingPoints : $paymentAmount; // 可用积分
return [
'items' => collect($mapItems)->map(fn($item) => [
'product_category' => $productCategories->get($item['product_category_id']),
'discount_reduction_amount' => bcdiv($item['discount_reduction_amount'], 100, 2),
'products_total_amount' => bcdiv($item['products_total_amount'], 100, 2),
'payment_amount' => bcdiv($item['payment_amount'], 100, 2),
]),
'products_total_amount' => bcdiv($productsTotalAmount, 100, 2),
'discount_reduction_amount' => bcdiv($discountReductionAmount, 100, 2),
'payment_amount' => bcdiv($paymentAmount, 100, 2),
'remaining_points' => bcdiv($remainingPoints, 100, 2), // 剩余积分
'available_points' => bcdiv($availablePoints, 100, 2), // 可用积分
'points_discount_amount' => bcdiv($availablePoints, 100, 2), // 积分抵扣金额
];
}
public function pay(OfflineOrder $order, PayWay $payWay)
{
if (! $order->isPending()) {
throw new BizException('订单状态不是待付款');
}
$payLog = $order->payLogs()->create([
'pay_way' => $payWay,
]);
$data = null;
if ($order->payment_amount === 0) {
(new PayService())->handleSuccess($payLog, [
'pay_at' => now(),
]);
} 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->payment_amount,
'trade_type' => $tradeType->value,
];
if ($payLog->pay_way === PayWay::WxpayMiniProgram) {
$socialite = 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',
});
}
return [
'pay_way' => $payLog->pay_way,
'data' => $data,
];
}
public function revoke(OfflineOrder $order)
{
if ($order->isPaid()) {
throw new BizException('订单已付款');
}
if ($order->isRevoked()) {
throw new BizException('订单已取消');
}
if ($order->points_deduction_amount > 0) {
(new PointService())->change($order->user, $order->points_deduction_amount, PointLogAction::Refund, "线下订单{$order->sn}退还积分", $order);
}
$order->update([
'status' => OfflineOrderStatus::Revoked,
'revoked_at' => now(),
]);
}
protected function insertOrderItems(OfflineOrder $order, array $items)
{
$remainingPointDiscountAmount = $order->points_deduction_amount;
OfflineOrderItem::insert(
collect($items)->map(function ($item) use ($order, &$remainingPointDiscountAmount) {
$pointsDeductionAmount = $item['payment_amount'];
if ($item['payment_amount'] > $remainingPointDiscountAmount) {
$pointsDeductionAmount = $remainingPointDiscountAmount;
}
$remainingPointDiscountAmount -= $pointsDeductionAmount;
return [
'order_id' => $order->id,
'product_category_id' => $item['product_category_id'],
'products_total_amount' => $item['products_total_amount'],
'discount_reduction_amount' => $item['discount_reduction_amount'],
'points_deduction_amount' => $pointsDeductionAmount,
'payment_amount' => $item['payment_amount'] - $pointsDeductionAmount,
'created_at' => $order->created_at,
'updated_at' => $order->updated_at,
];
})->all()
);
}
protected function mapItems(array $items): array
{
return collect($items)->map(function ($item) {
$productsTotalAmount = bcmul($item['products_total_amount'], 100);
if ($productsTotalAmount < 0) {
throw new BizException('商品总额不能小于0');
}
$discountReductionAmount = 0;
if (is_numeric($item['discount'])) {
if ($item['discount'] <= 0) {
throw new BizException('折扣必须大于0');
} elseif ($item['discount'] >= 10) {
throw new BizException('折扣必须小于10');
}
$discount = bcdiv($item['discount'], 10, 3);
$discountReductionAmount = bcsub($productsTotalAmount, round(bcmul($productsTotalAmount, $discount, 2)));
}
return [
'product_category_id' => $item['product_category_id'],
'products_total_amount' => (int) $productsTotalAmount,
'discount_reduction_amount' => (int) $discountReductionAmount,
'payment_amount' => (int) ($productsTotalAmount - $discountReductionAmount),
];
})->all();
}
protected function calculateFees(array $items)
{
$totalProductsTotalAmount = 0;
$totalDiscountReductionAmount = 0;
$totalPaymentAmount = 0;
foreach ($items as $item) {
$totalProductsTotalAmount += $item['products_total_amount'];
$totalDiscountReductionAmount += $item['discount_reduction_amount'];
$totalPaymentAmount += $item['payment_amount'];
}
return [$totalProductsTotalAmount, $totalDiscountReductionAmount, $totalPaymentAmount];
}
}