mirror of
https://github.com/lkddi/Xboard.git
synced 2026-04-03 18:40:52 +08:00
refactor: optimize surplus value calculation for plan deduction
This commit is contained in:
@@ -14,11 +14,8 @@ use App\Services\CouponService;
|
|||||||
use App\Services\OrderService;
|
use App\Services\OrderService;
|
||||||
use App\Services\PaymentService;
|
use App\Services\PaymentService;
|
||||||
use App\Services\PlanService;
|
use App\Services\PlanService;
|
||||||
use App\Services\Plugin\HookManager;
|
|
||||||
use App\Services\UserService;
|
use App\Services\UserService;
|
||||||
use App\Utils\Helper;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class OrderController extends Controller
|
class OrderController extends Controller
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ use App\Services\Plugin\HookManager;
|
|||||||
use App\Utils\Helper;
|
use App\Utils\Helper;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use App\Services\PlanService;
|
||||||
|
|
||||||
class OrderService
|
class OrderService
|
||||||
{
|
{
|
||||||
@@ -71,7 +73,7 @@ class OrderService
|
|||||||
|
|
||||||
$orderService->setVipDiscount($user);
|
$orderService->setVipDiscount($user);
|
||||||
$orderService->setOrderType($user);
|
$orderService->setOrderType($user);
|
||||||
$orderService->setInvite($user);
|
$orderService->setInvite(user: $user);
|
||||||
|
|
||||||
if ($user->balance && $order->total_amount > 0) {
|
if ($user->balance && $order->total_amount > 0) {
|
||||||
$orderService->handleUserBalance($user, $userService);
|
$orderService->handleUserBalance($user, $userService);
|
||||||
@@ -89,6 +91,7 @@ class OrderService
|
|||||||
public function open()
|
public function open()
|
||||||
{
|
{
|
||||||
$order = $this->order;
|
$order = $this->order;
|
||||||
|
HookManager::call('order.before_open', $order);
|
||||||
$this->user = User::find($order->user_id);
|
$this->user = User::find($order->user_id);
|
||||||
$plan = Plan::find($order->plan_id);
|
$plan = Plan::find($order->plan_id);
|
||||||
|
|
||||||
@@ -136,6 +139,7 @@ class OrderService
|
|||||||
throw new \Exception('订单信息保存失败');
|
throw new \Exception('订单信息保存失败');
|
||||||
}
|
}
|
||||||
DB::commit();
|
DB::commit();
|
||||||
|
HookManager::call('order.after_open', $order);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
Log::error($e);
|
Log::error($e);
|
||||||
@@ -212,78 +216,71 @@ class OrderService
|
|||||||
private function haveValidOrder(User $user): Order|null
|
private function haveValidOrder(User $user): Order|null
|
||||||
{
|
{
|
||||||
return Order::where('user_id', $user->id)
|
return Order::where('user_id', $user->id)
|
||||||
->whereNotIn('status', [0, 2])
|
->whereNotIn('status', [Order::STATUS_PENDING, Order::STATUS_CANCELLED])
|
||||||
->first();
|
->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getSurplusValue(User $user, Order $order)
|
private function getSurplusValue(User $user, Order $order)
|
||||||
{
|
{
|
||||||
if ($user->expired_at === NULL) {
|
if ($user->expired_at === NULL) {
|
||||||
$this->getSurplusValueByOneTime($user, $order);
|
$lastOneTimeOrder = Order::where('user_id', $user->id)
|
||||||
|
->where('period', Plan::PERIOD_ONETIME)
|
||||||
|
->where('status', Order::STATUS_COMPLETED)
|
||||||
|
->orderBy('id', 'DESC')
|
||||||
|
->first();
|
||||||
|
if (!$lastOneTimeOrder)
|
||||||
|
return;
|
||||||
|
$nowUserTraffic = Helper::transferToGB($user->transfer_enable);
|
||||||
|
if (!$nowUserTraffic)
|
||||||
|
return;
|
||||||
|
$paidTotalAmount = ($lastOneTimeOrder->total_amount + $lastOneTimeOrder->balance_amount);
|
||||||
|
if (!$paidTotalAmount)
|
||||||
|
return;
|
||||||
|
$trafficUnitPrice = $paidTotalAmount / $nowUserTraffic;
|
||||||
|
$notUsedTraffic = $nowUserTraffic - Helper::transferToGB($user->u + $user->d);
|
||||||
|
$result = $trafficUnitPrice * $notUsedTraffic;
|
||||||
|
$order->surplus_amount = (int) ($result > 0 ? $result : 0);
|
||||||
|
$order->surplus_order_ids = Order::where('user_id', $user->id)
|
||||||
|
->where('period', '!=', Plan::PERIOD_RESET_TRAFFIC)
|
||||||
|
->where('status', Order::STATUS_COMPLETED)
|
||||||
|
->pluck('id')
|
||||||
|
->all();
|
||||||
} else {
|
} else {
|
||||||
$this->getSurplusValueByPeriod($user, $order);
|
$orders = Order::query()
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->whereNotIn('period', [Plan::PERIOD_RESET_TRAFFIC, Plan::PERIOD_ONETIME])
|
||||||
|
->where('status', Order::STATUS_COMPLETED)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
if ($orders->isEmpty()) {
|
||||||
|
$order->surplus_amount = 0;
|
||||||
|
$order->surplus_order_ids = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderAmountSum = $orders->sum(fn($item) => $item->total_amount + $item->balance_amount + $item->surplus_amount - $item->refund_amount);
|
||||||
|
$orderMonthSum = $orders->sum(fn($item) => self::STR_TO_TIME[PlanService::getPeriodKey($item->period)] ?? 0);
|
||||||
|
$firstOrderAt = $orders->min('created_at');
|
||||||
|
$expiredAt = Carbon::createFromTimestamp($firstOrderAt)->addMonths($orderMonthSum);
|
||||||
|
|
||||||
|
$now = now();
|
||||||
|
$totalSeconds = $expiredAt->timestamp - $firstOrderAt;
|
||||||
|
$remainSeconds = max(0, $expiredAt->timestamp - $now->timestamp);
|
||||||
|
$cycleRatio = $totalSeconds > 0 ? $remainSeconds / $totalSeconds : 0;
|
||||||
|
|
||||||
|
$plan = Plan::find($user->plan_id);
|
||||||
|
$totalTraffic = $plan?->transfer_enable * $orderMonthSum;
|
||||||
|
$usedTraffic = Helper::transferToGB($user->u + $user->d);
|
||||||
|
$remainTraffic = max(0, $totalTraffic - $usedTraffic);
|
||||||
|
$trafficRatio = $totalTraffic > 0 ? $remainTraffic / $totalTraffic : 0;
|
||||||
|
|
||||||
|
$minRatio = min($cycleRatio, $trafficRatio);
|
||||||
|
|
||||||
|
$order->surplus_amount = (int) max(0, $orderAmountSum * $minRatio);
|
||||||
|
$order->surplus_order_ids = $orders->pluck('id')->all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function getSurplusValueByOneTime(User $user, Order $order)
|
|
||||||
{
|
|
||||||
$lastOneTimeOrder = Order::where('user_id', $user->id)
|
|
||||||
->where('period', Plan::PERIOD_ONETIME)
|
|
||||||
->where('status', Order::STATUS_COMPLETED)
|
|
||||||
->orderBy('id', 'DESC')
|
|
||||||
->first();
|
|
||||||
if (!$lastOneTimeOrder)
|
|
||||||
return;
|
|
||||||
$nowUserTraffic = $user->transfer_enable / 1073741824;
|
|
||||||
if (!$nowUserTraffic)
|
|
||||||
return;
|
|
||||||
$paidTotalAmount = ($lastOneTimeOrder->total_amount + $lastOneTimeOrder->balance_amount);
|
|
||||||
if (!$paidTotalAmount)
|
|
||||||
return;
|
|
||||||
$trafficUnitPrice = $paidTotalAmount / $nowUserTraffic;
|
|
||||||
$notUsedTraffic = $nowUserTraffic - (($user->u + $user->d) / 1073741824);
|
|
||||||
$result = $trafficUnitPrice * $notUsedTraffic;
|
|
||||||
$orderModel = Order::where('user_id', $user->id)->where('period', '!=', Plan::PERIOD_RESET_TRAFFIC)->where('status', Order::STATUS_COMPLETED);
|
|
||||||
$order->surplus_amount = (int) ($result > 0 ? $result : 0);
|
|
||||||
$order->surplus_order_ids = array_column($orderModel->get()->toArray(), 'id');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getSurplusValueByPeriod(User $user, Order $order)
|
|
||||||
{
|
|
||||||
$orders = Order::where('user_id', $user->id)
|
|
||||||
->whereNotIn('period', [Plan::PERIOD_RESET_TRAFFIC, Plan::PERIOD_ONETIME])
|
|
||||||
->where('status', Order::STATUS_COMPLETED)
|
|
||||||
->get()
|
|
||||||
->toArray();
|
|
||||||
if (!$orders)
|
|
||||||
return;
|
|
||||||
$orderAmountSum = 0;
|
|
||||||
$orderMonthSum = 0;
|
|
||||||
$lastValidateAt = 0;
|
|
||||||
foreach ($orders as $item) {
|
|
||||||
$period = self::STR_TO_TIME[PlanService::getPeriodKey($item['period'])];
|
|
||||||
if (strtotime("+{$period} month", $item['created_at']) < time())
|
|
||||||
continue;
|
|
||||||
$lastValidateAt = $item['created_at'];
|
|
||||||
$orderMonthSum = $period + $orderMonthSum;
|
|
||||||
$orderAmountSum = $orderAmountSum + ($item['total_amount'] + $item['balance_amount'] + $item['surplus_amount'] - $item['refund_amount']);
|
|
||||||
}
|
|
||||||
if (!$lastValidateAt)
|
|
||||||
return;
|
|
||||||
$expiredAtByOrder = strtotime("+{$orderMonthSum} month", $lastValidateAt);
|
|
||||||
if ($expiredAtByOrder < time())
|
|
||||||
return;
|
|
||||||
$orderSurplusSecond = $expiredAtByOrder - time();
|
|
||||||
$orderRangeSecond = $expiredAtByOrder - $lastValidateAt;
|
|
||||||
$avgPrice = $orderAmountSum / $orderRangeSecond;
|
|
||||||
$orderSurplusAmount = $avgPrice * $orderSurplusSecond;
|
|
||||||
if (!$orderSurplusSecond || !$orderSurplusAmount)
|
|
||||||
return;
|
|
||||||
$order->surplus_amount = (int) ($orderSurplusAmount > 0 ? $orderSurplusAmount : 0);
|
|
||||||
$order->surplus_order_ids = array_column($orders, 'id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function paid(string $callbackNo)
|
public function paid(string $callbackNo)
|
||||||
{
|
{
|
||||||
$order = $this->order;
|
$order = $this->order;
|
||||||
@@ -344,11 +341,8 @@ class OrderService
|
|||||||
$this->user->expired_at = time();
|
$this->user->expired_at = time();
|
||||||
}
|
}
|
||||||
$this->user->transfer_enable = $plan->transfer_enable * 1073741824;
|
$this->user->transfer_enable = $plan->transfer_enable * 1073741824;
|
||||||
// 从一次性转换到循环
|
// 从一次性转换到循环或者新购的时候,重置流量
|
||||||
if ($this->user->expired_at === NULL)
|
if ($this->user->expired_at === NULL || $order->type === Order::TYPE_NEW_PURCHASE)
|
||||||
app(TrafficResetService::class)->performReset($this->user, TrafficResetLog::SOURCE_ORDER);
|
|
||||||
// 新购
|
|
||||||
if ($order->type === Order::TYPE_NEW_PURCHASE)
|
|
||||||
app(TrafficResetService::class)->performReset($this->user, TrafficResetLog::SOURCE_ORDER);
|
app(TrafficResetService::class)->performReset($this->user, TrafficResetLog::SOURCE_ORDER);
|
||||||
$this->user->plan_id = $plan->id;
|
$this->user->plan_id = $plan->id;
|
||||||
$this->user->group_id = $plan->group_id;
|
$this->user->group_id = $plan->group_id;
|
||||||
@@ -364,26 +358,24 @@ class OrderService
|
|||||||
$this->user->expired_at = NULL;
|
$this->user->expired_at = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getTime($str, $timestamp)
|
/**
|
||||||
|
* 计算套餐到期时间
|
||||||
|
* @param string $periodKey
|
||||||
|
* @param int $timestamp
|
||||||
|
* @return int
|
||||||
|
* @throws ApiException
|
||||||
|
*/
|
||||||
|
private function getTime(string $periodKey, ?int $timestamp = null): int
|
||||||
{
|
{
|
||||||
if ($timestamp < time()) {
|
$timestamp = $timestamp < time() ? time() : $timestamp;
|
||||||
$timestamp = time();
|
$periodKey = PlanService::getPeriodKey($periodKey);
|
||||||
}
|
|
||||||
$str = PlanService::getPeriodKey($str);
|
if (isset(self::STR_TO_TIME[$periodKey])) {
|
||||||
switch ($str) {
|
$months = self::STR_TO_TIME[$periodKey];
|
||||||
case Plan::PERIOD_MONTHLY:
|
return Carbon::createFromTimestamp($timestamp)->addMonths($months)->timestamp;
|
||||||
return strtotime('+1 month', $timestamp);
|
|
||||||
case Plan::PERIOD_QUARTERLY:
|
|
||||||
return strtotime('+3 month', $timestamp);
|
|
||||||
case Plan::PERIOD_HALF_YEARLY:
|
|
||||||
return strtotime('+6 month', $timestamp);
|
|
||||||
case Plan::PERIOD_YEARLY:
|
|
||||||
return strtotime('+12 month', $timestamp);
|
|
||||||
case Plan::PERIOD_TWO_YEARLY:
|
|
||||||
return strtotime('+24 month', $timestamp);
|
|
||||||
case Plan::PERIOD_THREE_YEARLY:
|
|
||||||
return strtotime('+36 month', $timestamp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new ApiException('无效的套餐周期');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function openEvent($eventId)
|
private function openEvent($eventId)
|
||||||
@@ -406,6 +398,12 @@ class OrderService
|
|||||||
$this->order->coupon_id = $couponService->getId();
|
$this->order->coupon_id = $couponService->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summary of handleUserBalance
|
||||||
|
* @param User $user
|
||||||
|
* @param UserService $userService
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
protected function handleUserBalance(User $user, UserService $userService): void
|
protected function handleUserBalance(User $user, UserService $userService): void
|
||||||
{
|
{
|
||||||
$remainingBalance = $user->balance - $this->order->total_amount;
|
$remainingBalance = $user->balance - $this->order->total_amount;
|
||||||
|
|||||||
@@ -211,4 +211,14 @@ class Helper
|
|||||||
}
|
}
|
||||||
return $suffix;
|
return $suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert the transfer_enable to GB
|
||||||
|
* @param int $transfer_enable
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function transferToGB(int $transfer_enable): int
|
||||||
|
{
|
||||||
|
return $transfer_enable / 1073741824;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user