Files
qztool/backend/app/Services/MembershipService.php
DevandCursor 60daff2365 搭建轻舟一期门户、后台和统一登录。
让分类、会员和示例工具共用同一账号,本地即可跑通登录与授权。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-09 16:46:35 +08:00

130 lines
4.0 KiB
PHP

<?php
namespace App\Services;
use App\Models\Order;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use App\Support\Settings;
use Illuminate\Support\Carbon;
class MembershipService
{
public function snapshot(User $user, ?string $toolSlug = null): array
{
$user->loadMissing('subscription.plan.entitlements');
$subscription = $user->subscription;
$status = $this->effectiveStatus($subscription);
$plan = in_array($status, ['active', 'grace'], true)
? $subscription?->plan
: Plan::query()->where('code', 'free')->with('entitlements')->first();
$entitlements = [];
foreach ($plan?->entitlements ?? [] as $item) {
$entitlements[$item->entitlement_key] = $this->castValue($item->entitlement_value);
}
if ($toolSlug) {
$entitlements = array_filter(
$entitlements,
fn ($value, $key) => str_contains($key, ':'.$toolSlug.':')
|| (! str_starts_with($key, 'tool:') && ! str_starts_with($key, 'quota:')),
ARRAY_FILTER_USE_BOTH
);
}
return [
'plan_code' => $plan->code ?? 'free',
'plan_name' => $plan->name ?? '免费',
'status' => $status,
'starts_at' => $subscription?->starts_at?->toIso8601String(),
'ends_at' => $subscription?->ends_at?->toIso8601String(),
'entitlement_version' => $user->entitlement_version,
'entitlements' => $entitlements,
];
}
public function effectiveStatus(?Subscription $subscription): string
{
if (! $subscription || $subscription->status === 'stopped') {
return $subscription?->status === 'stopped' ? 'stopped' : 'none';
}
if (now()->lt($subscription->ends_at)) {
return 'active';
}
$graceDays = (int) Settings::get('grace_days', '3');
if (now()->lt($subscription->ends_at->copy()->addDays($graceDays))) {
return 'grace';
}
return 'expired';
}
public function grant(User $user, Plan $plan, Carbon $endsAt): Subscription
{
$subscription = Subscription::query()->updateOrCreate(
['user_id' => $user->id],
[
'plan_id' => $plan->id,
'status' => 'active',
'starts_at' => now(),
'ends_at' => $endsAt,
]
);
$user->increment('entitlement_version');
$user->refresh();
return $subscription;
}
public function extendWithOrder(User $user, Plan $plan): Subscription
{
$current = $user->subscription;
$base = ($current && $current->ends_at && $current->ends_at->isFuture() && $current->status !== 'stopped')
? $current->ends_at->copy()
: now();
$ends = match ($plan->period) {
'year' => $base->addYear(),
'lifetime' => $base->addYears(100),
default => $base->addMonth(),
};
return $this->grant($user, $plan, $ends);
}
public function stop(User $user): void
{
$user->subscription?->update(['status' => 'stopped']);
$user->increment('entitlement_version');
}
public function confirmOrder(Order $order): void
{
if ($order->status !== 'pending') {
abort(422, '只有待支付订单可以确认收款');
}
$order->update(['status' => 'paid', 'paid_at' => now()]);
if ($order->type === 'subscription') {
$item = $order->items()->with('plan')->first();
if ($item?->plan) {
$this->extendWithOrder($order->user, $item->plan);
}
}
}
private function castValue(string $value): bool|int|string
{
if ($value === 'true') {
return true;
}
if ($value === 'false') {
return false;
}
if (preg_match('/^-?\d+$/', $value)) {
return (int) $value;
}
return $value;
}
}