增加 订阅

This commit is contained in:
iVampireSP.com 2023-02-28 19:31:05 +08:00
parent 4fecd91165
commit d7a294aaf9
No known key found for this signature in database
GPG Key ID: 2F7B001CA27A8132
15 changed files with 829 additions and 9 deletions

View File

@ -7,6 +7,8 @@
use App\Jobs\Host\ScanAllHostsJob; use App\Jobs\Host\ScanAllHostsJob;
use App\Jobs\Module\DispatchFetchModuleJob; use App\Jobs\Module\DispatchFetchModuleJob;
use App\Jobs\Module\SendModuleEarningsJob; use App\Jobs\Module\SendModuleEarningsJob;
use App\Jobs\Subscription\DeleteDraftJob;
use App\Jobs\Subscription\UpdateSubscriptionStatusJob;
use App\Jobs\User\CheckAndChargeBalanceJob; use App\Jobs\User\CheckAndChargeBalanceJob;
use App\Jobs\User\ClearTasksJob; use App\Jobs\User\ClearTasksJob;
use App\Jobs\User\DeleteUnverifiedUserJob; use App\Jobs\User\DeleteUnverifiedUserJob;
@ -61,6 +63,10 @@ protected function schedule(Schedule $schedule): void
// 删除注册超过 3 天未验证邮箱的用户 // 删除注册超过 3 天未验证邮箱的用户
$schedule->job(new DeleteUnverifiedUserJob())->daily()->onOneServer()->name('删除注册超过 3 天未验证邮箱的用户'); $schedule->job(new DeleteUnverifiedUserJob())->daily()->onOneServer()->name('删除注册超过 3 天未验证邮箱的用户');
// 订阅
$schedule->job(new DeleteDraftJob())->daily()->onOneServer()->name('删除超过 1 天的草稿订阅');
$schedule->job(new UpdateSubscriptionStatusJob())->everyMinute()->onOneServer()->name('更新订阅状态');
} }
/** /**

View File

@ -0,0 +1,66 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Subscription;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class SubscriptionController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(): JsonResponse
{
$subscriptions = Subscription::thisUser()->with('module')->orderBy('status')->paginate();
return $this->success($subscriptions);
}
/**
* Display the specified resource.
*/
public function show(Subscription $subscription): JsonResponse
{
$subscription->load('module');
return $this->success($subscription);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Subscription $subscription): JsonResponse
{
$request->validate([
'cancel_at_period_end' => 'nullable|boolean',
'status' => 'nullable|in:active',
]);
if ($request->filled('cancel_at_period_end')) {
$subscription->update([
'cancel_at_period_end' => $request->cancel_at_period_end,
]);
}
if ($request->filled('status') && $request->input('status') === 'active') {
if (! $subscription->active()) {
return $this->badRequest('无法激活此订阅。');
}
}
return $this->success($subscription);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Subscription $subscription): JsonResponse
{
$subscription->safeDelete();
return $this->deleted();
}
}

View File

@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers\Module;
use App\Http\Controllers\Controller;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class SubscriptionController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request, User $user): JsonResponse
{
$subscriptions = $user->subscriptions()->where('module_id', $request->user('module')->id);
if ($request->filled('status')) {
$subscriptions->where('status', $request->input('status'));
}
if ($request->filled('plan_id')) {
$subscriptions->where('plan_id', $request->input('plan_id'));
}
$subscriptions = $subscriptions->paginate();
return $this->success($subscriptions);
}
/**
* 向用户发送订阅请求。
*/
public function store(Request $request, User $user): JsonResponse
{
$request->validate([
'name' => 'required|string|max:255',
'plan_id' => 'required|string|max:255',
'configuration' => 'nullable|json',
'price' => 'required|numeric|min:0',
'trial_ends_at' => 'nullable|date|after:now',
]);
$subscription = $user->subscriptions()->create([
'name' => $request->input('name'),
'plan_id' => $request->input('plan_id'),
'configuration' => $request->input('configuration'),
'price' => $request->input('price'),
'trial_ends_at' => $request->input('trial_ends_at'),
'module_id' => $request->user('module')->id,
]);
$subscription->url = route('subscriptions.show', $subscription);
return $this->success($subscription);
}
/**
* 展示订阅详情。
*/
public function show(Subscription $subscription): JsonResponse
{
return $this->success($subscription);
}
/**
* 更新订阅。
*/
public function update(Request $request, User $user, Subscription $subscription): JsonResponse
{
unset($user);
if ($subscription->status === 'active') {
return $this->badRequest('此订阅已经成立,无法修改。');
}
$subscription->update($request->only([
'name',
'plan_id',
'configuration',
'price',
'trial_ends_at',
]));
return $this->success($subscription);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(User $user, Subscription $subscription): JsonResponse
{
unset($user);
$subscription->safeDelete();
return $this->deleted();
}
public function by_plan_id(User $user, Subscription $subscription): JsonResponse
{
unset($user);
return $this->success($subscription);
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use App\Models\Subscription;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class SubscriptionController extends Controller
{
public function index(Subscription $subscription)
{
$subscriptions = $subscription->thisUser()->with('module')->orderBy('status')->get();
return view('subscription.index', compact('subscriptions'));
}
public function update(Request $request, Subscription $subscription): JsonResponse|RedirectResponse
{
$request->validate([
'cancel_at_period_end' => 'nullable|boolean',
'status' => 'nullable|in:active',
]);
if ($request->filled('cancel_at_period_end')) {
$subscription->update([
'cancel_at_period_end' => $request->cancel_at_period_end,
]);
}
if ($request->filled('status') && $request->input('status') === 'active') {
if (! $subscription->active()) {
if ($request->ajax()) {
return $this->badRequest('无法激活此订阅。');
}
return back()->withErrors('无法激活此订阅。');
}
}
if ($request->ajax()) {
return $this->success($subscription);
}
return back();
}
public function show(Subscription $subscription)
{
$subscription->load('module');
return view('subscription.show', compact('subscription'));
}
public function destroy(Subscription $subscription)
{
$subscription->safeDelete();
return back();
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Jobs\Subscription;
use App\Models\Subscription;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class DeleteDraftJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 删除超过 1 天的草稿订阅。
*/
public function handle(): void
{
Subscription::where('status', 'draft')
->where('created_at', '<', now()->subDay())
->delete();
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace App\Jobs\Subscription;
use App\Models\Subscription;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class UpdateSubscriptionStatusJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private ?Subscription $subscription;
public function __construct(?Subscription $subscription = null)
{
$this->subscription = $subscription;
}
/**
* Execute the job.
*/
public function handle(): void
{
if (! $this->subscription) {
// 遍历所有订阅
(new Subscription)->where('status', 'active')->chunk(100, function ($subscriptions) {
$subscriptions->each(function ($subscription) {
self::dispatch($subscription);
});
});
return;
}
$subscription = $this->subscription;
// 如果是试用期,在到期当天,自动续费
if ($subscription->trial_ends_at && $subscription->trial_ends_at->isToday()) {
if ($subscription->cancel_at_period_end) {
// 到期不续费了,直接过期
$subscription->update([
'status' => 'expired',
]);
} else {
// 去除试用标识
$subscription->update([
'trial_ends_at' => null,
]);
$subscription->renew();
}
return;
}
// 如果已经过期,则设置为 expired
if ($subscription->expired_at && $subscription->expired_at->lt(now())) {
$subscription->update([
'status' => 'expired',
]);
return;
}
// 如果还有 7 天过期,则提醒用户续费
// if ($subscription->cancel_at_period_end && $subscription->expired_at && $subscription->expired_at->gt(now()->addDays(7))) {
//
// }
// 剩余 3 天就要过期时,自动续费
if ($subscription->cancel_at_period_end && $subscription->expired_at && $subscription->expired_at->gt(now()->addDays(3))) {
// 发送邮件提醒用户续费
$subscription->renew();
}
}
}

169
app/Models/Subscription.php Normal file
View File

@ -0,0 +1,169 @@
<?php
namespace App\Models;
use App\Exceptions\User\BalanceNotEnoughException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Subscription extends Model
{
public $fillable = [
'name',
'status',
'plan_id',
'configuration',
'price',
'expired_at',
'trial_ends_at',
'module_id',
'user_id',
'cancel_at_period_end',
'renew_price',
];
protected $casts = [
'configuration' => 'array',
'price' => 'decimal:2',
'renew_price' => 'decimal:2',
];
protected $dates = [
'expired_at',
'trial_ends_at',
];
public function scopeThisUser($query, $module_id = null)
{
$query = $query->where('user_id', auth()->id());
if ($module_id) {
$query = $query->where('module_id', $module_id);
}
return $query;
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function module(): BelongsTo
{
return $this->belongsTo(Module::class);
}
public function active(): bool
{
return $this->canActivate() && $this->renew();
}
public function canActivate(bool $ignore_activated = false): bool
{
if ($ignore_activated && $this->isActive()) {
return true;
}
// 检测 trial_ends_at 是否过期
if ($this->trial_ends_at && $this->trial_ends_at->isPast()) {
return false;
}
return true;
}
public function isActive(): bool
{
return $this->status === 'active';
}
public function renew(): bool
{
if ($this->isTrial()) {
$this->status = 'active';
}
// 如果过期时间距离今天超过了 7 天,那么就不能续费了
if ($this->expired_at && $this->expired_at->diffInDays(now()) > 7) {
return false;
}
$price = $this->price;
try {
$this->user->reduce($price, '订阅: '.$this->name, true, [
'module_id' => $this->module_id,
'user_id' => $this->user_id,
'subscription_id' => $this->id,
]);
$this->module->charge($price, 'module_balance', '订阅: '.$this->name.' 续费', [
'module_id' => $this->module_id,
'user_id' => $this->user_id,
'subscription_id' => $this->id,
]);
} catch (BalanceNotEnoughException) {
return false;
}
$this->renew_price = $price;
if (! $this->isTrial()) {
$this->expired_at = now()->addMonth();
}
$this->status = 'active';
$this->save();
return true;
}
public function isTrial(): bool
{
return $this->trial_ends_at !== null;
}
public function safeDelete()
{
// 如果是试用,那么就直接删除
if ($this->isTrial() || $this->isExpired() || $this->isDraft() || ! $this->renew_price) {
$this->delete();
return;
}
// 如果是正式订阅,那么就按使用天数计算退款
$days = $this->expired_at ? $this->expired_at->diffInDays(now()) : 27;
// 获取 create_at 当时的月份的天数
$daysInMonth = $this->created_at->daysInMonth;
// 按照使用天数计算退款(bcdiv 保留两位小数)
$refund = bcdiv($this->renew_price, $daysInMonth, 2) * $days;
// 如果退款金额大于 0那么就退款
if ($refund > 0) {
$this->user->charge($refund, 'balance', '订阅: '.$this->name.' 退款', [
'module_id' => $this->module_id,
'user_id' => $this->user_id,
'subscription_id' => $this->id,
]);
$this->module->reduce($refund, '订阅: '.$this->name.' 退款', false, [
'module_id' => $this->module_id,
'user_id' => $this->user_id,
'subscription_id' => $this->id,
]);
}
$this->delete();
}
public function isExpired(): bool
{
return $this->expired_at && $this->expired_at->isPast();
}
public function isDraft(): bool
{
return $this->status === 'draft';
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Observers;
use App\Events\Users;
use App\Models\Subscription;
class SubscriptionObserve
{
public function creating(Subscription $subscription): void
{
// 如果没有设置 status就设置为 draft
if (! $subscription->status) {
$subscription->status = 'draft';
}
}
/**
* Handle the Subscription "created" event.
*/
public function created(Subscription $subscription): void
{
broadcast(new Users($subscription->user, 'subscription.created', $subscription));
}
public function updating(Subscription $subscription): void
{
// 如果 status 是 expired, expired_at 为空,就设置为当前时间
if ($subscription->status === 'expired' && ! $subscription->expired_at) {
$subscription->expired_at = now();
}
// 如果 expired_at 和 trial_ends_at 为空,就当作过期处理
if ($subscription->status !== 'draft' && ! $subscription->expired_at && ! $subscription->trial_ends_at) {
$subscription->status = 'expired';
}
}
/**
* Handle the Subscription "updated" event.
*/
public function updated(Subscription $subscription): void
{
broadcast(new Users($subscription->user, 'subscription.updated', $subscription));
}
/**
* Handle the Subscription "deleted" event.
*/
public function deleted(Subscription $subscription): void
{
broadcast(new Users($subscription->user, 'subscription.deleted', $subscription));
}
}

View File

@ -6,12 +6,14 @@
use App\Models\Host; use App\Models\Host;
use App\Models\Module; use App\Models\Module;
use App\Models\PersonalAccessToken; use App\Models\PersonalAccessToken;
use App\Models\Subscription;
use App\Models\Task; use App\Models\Task;
use App\Models\User; use App\Models\User;
use App\Models\WorkOrder\WorkOrder; use App\Models\WorkOrder\WorkOrder;
use App\Observers\BalanceObserver; use App\Observers\BalanceObserver;
use App\Observers\HostObserver; use App\Observers\HostObserver;
use App\Observers\ModuleObserver; use App\Observers\ModuleObserver;
use App\Observers\SubscriptionObserve;
use App\Observers\TaskObserver; use App\Observers\TaskObserver;
use App\Observers\UserObserver; use App\Observers\UserObserver;
use App\Observers\WorkOrderObserver; use App\Observers\WorkOrderObserver;
@ -20,16 +22,11 @@
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Laravel\Sanctum\Sanctum; use Laravel\Sanctum\Sanctum;
// use App\Models\Invoice;
// use App\Observers\InvoiceObserver;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
/**
* Register any application services.
*/
public function register(): void
{
//
}
/** /**
* Bootstrap any application services. * Bootstrap any application services.
*/ */
@ -63,5 +60,7 @@ private function registerObservers(): void
Module::observe(ModuleObserver::class); Module::observe(ModuleObserver::class);
Balance::observe(BalanceObserver::class); Balance::observe(BalanceObserver::class);
WorkOrder::observe(WorkOrderObserver::class); WorkOrder::observe(WorkOrderObserver::class);
// Invoice::observe(InvoiceObserver::class);
Subscription::observe(SubscriptionObserve::class);
} }
} }

View File

@ -0,0 +1,68 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('subscriptions', function (Blueprint $table) {
$table->id();
// 类型
$table->enum('status', [
'draft',
'active',
'expired',
'canceled',
])->index();
// 名称
$table->string('name')->nullable();
// 计划 ID
$table->string('plan_id')->index();
// 配置项目
$table->json('configuration')->nullable();
// 价格
$table->decimal('price', 10)->default(0);
// 结束时间
$table->timestamp('expired_at')->nullable();
// 试用结束时间
$table->timestamp('trial_ends_at')->nullable();
// 下个月取消
$table->boolean('cancel_at_period_end')->default(false)->index();
// 续费时价格
$table->decimal('renew_price', 10)->default(0);
// 模块 ID
$table->string('module_id')->index()->nullable();
$table->foreign('module_id')->references('id')->on('modules')->nullOnDelete();
// 用户 ID
$table->unsignedBigInteger('user_id')->index()->nullable();
$table->foreign('user_id')->references('id')->on('users')->nullOnDelete();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('subscriptions');
}
};

View File

@ -0,0 +1,109 @@
@extends('layouts.app')
@section('title', '订阅')
@section('content')
<h3>订阅</h3>
<div>
<table class="table table-hover">
<thead>
<th>ID</th>
<th>名称</th>
<th>模块</th>
<th>计划 ID</th>
<th>续期价格</th>
<th>状态</th>
<th>到期时间</th>
<th>操作</th>
</thead>
<tbody>
@foreach ($subscriptions as $subscription)
<tr>
<td>
{{ $subscription->id }}
</td>
<td>
{{ $subscription->name }}
</td>
<td>
{{ $subscription->module->name }}
</td>
<td>
{{ $subscription->plan_id }}
</td>
<td class="small">
{{ $subscription->price }}
</td>
<td>
<x-host-status :status="$subscription->status"/>
@if ($subscription->cancel_at_period_end)
<br/>
<small>
<span class="text-danger">自动续订已取消</span>
</small>
@endif
</td>
<td>
<span class="small">
@if ($subscription->isTrial())
{{ $subscription->trial_ends_at }}(试用)
@else
{{ $subscription->expired_at }}
@endif
</span>
</td>
<td>
<div class="dropdown">
<button class="btn btn-sm btn-secondary dropdown-toggle" type="button"
data-bs-toggle="dropdown"
aria-expanded="false">
操作
</button>
<ul class="dropdown-menu">
@if ($subscription->isDraft())
<a class="dropdown-item active"
href="{{ route('subscriptions.show', $subscription) }}">
开始订阅
</a>
@endif
@if ($subscription->isActive())
<a class="dropdown-item" href="#"
onclick="document.getElementById('update-{{$subscription->id}}').submit()">
{{ $subscription->cancel_at_period_end ? '启用自动续订' : '取消自动续订'}}
</a>
<form action="{{ route('subscriptions.update', $subscription) }}"
id="update-{{$subscription->id}}"
method="post" class="d-none">
@csrf
@method('PATCH')
<input type="hidden" name="cancel_at_period_end"
value="{{ !$subscription->cancel_at_period_end ? '1' : '0'}}">
</form>
@endif
<a class="dropdown-item" href="#"
onclick="return confirm('删除操作将不可恢复,确定吗?') ? document.getElementById('delete-{{$subscription->id}}').submit() : false;">
删除订阅
</a>
<form action="{{ route('subscriptions.destroy', $subscription) }}"
id="delete-{{$subscription->id}}"
method="post" class="d-none">
@csrf
@method('DELETE')
</form>
</ul>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endsection

View File

@ -0,0 +1,64 @@
@extends('layouts.app')
@section('title', $subscription->name)
@section('content')
@if (!$subscription->canActivate(true))
<div class="d-flex justify-content-center align-items-center" style="height: 85vh">
<div class="text-center">
<h2>不能激活此订阅。</h2>
<br />
<p>
模块向您发送了一个不正确的订阅草稿。
</p>
</div>
</div>
@elseif ($subscription->isDraft())
<div class="d-flex justify-content-center align-items-center" style="height: 85vh">
<div>
<h2 class="text-center">激活此订阅</h2>
<table class="table table-bordered table-striped">
<tr>
<td>模块</td>
<td>{{ $subscription->module->name }}</td>
</tr>
<tr>
<td>月付价格</td>
<td>{{ $subscription->price }}</td>
</tr>
<tr>
<td>计划 ID</td>
<td>{{ $subscription->plan_id }}</td>
</tr>
<tr>
<td>截止</td>
<td>
{{ ($subscription->expired_at ?? $subscription->trial_ends_at) ?? '订阅后开始计算' }}
@if ($subscription->isTrial())
<span class="badge badge-success">试用</span>
@endif
</td>
</tr>
</table>
<div class="text-center">
<form action="{{ route('subscriptions.update', $subscription) }}" method="post">
@csrf
@method('PATCH')
<input type="hidden" name="status" value="active"/>
<button type="submit" class="btn btn-primary btn-sm btn-block">激活</button>
</form>
</div>
</div>
</div>
@endif
@if($subscription->isActive())
<div class="d-flex justify-content-center align-items-center" style="height: 85vh">
<div>
<h2 class="text-center">谢谢。</h2>
</div>
</div>
@endif
@endsection

View File

@ -7,6 +7,7 @@
use App\Http\Controllers\Api\MaintenanceController; use App\Http\Controllers\Api\MaintenanceController;
use App\Http\Controllers\Api\ModuleController; use App\Http\Controllers\Api\ModuleController;
use App\Http\Controllers\Api\ReplyController; use App\Http\Controllers\Api\ReplyController;
use App\Http\Controllers\Api\SubscriptionController;
use App\Http\Controllers\Api\TaskController; use App\Http\Controllers\Api\TaskController;
use App\Http\Controllers\Api\UserController; use App\Http\Controllers\Api\UserController;
use App\Http\Controllers\Api\WorkOrderController; use App\Http\Controllers\Api\WorkOrderController;
@ -39,6 +40,7 @@
Route::apiResource('hosts', HostController::class); Route::apiResource('hosts', HostController::class);
Route::apiResource('work-orders', WorkOrderController::class)->only(['index', 'store']); Route::apiResource('work-orders', WorkOrderController::class)->only(['index', 'store']);
Route::apiResource('subscriptions', SubscriptionController::class)->middleware('resource_owner:subscription');
Route::withoutMiddleware('auth:sanctum')->prefix('work-orders')->group(function () { Route::withoutMiddleware('auth:sanctum')->prefix('work-orders')->group(function () {
Route::get('{workOrder:uuid}', [WorkOrderController::class, 'show']); Route::get('{workOrder:uuid}', [WorkOrderController::class, 'show']);

View File

@ -5,6 +5,7 @@
use App\Http\Controllers\Module\HostController; use App\Http\Controllers\Module\HostController;
use App\Http\Controllers\Module\ModuleController; use App\Http\Controllers\Module\ModuleController;
use App\Http\Controllers\Module\ReplyController; use App\Http\Controllers\Module\ReplyController;
use App\Http\Controllers\Module\SubscriptionController;
use App\Http\Controllers\Module\TaskController; use App\Http\Controllers\Module\TaskController;
use App\Http\Controllers\Module\UserController; use App\Http\Controllers\Module\UserController;
use App\Http\Controllers\Module\WorkOrderController; use App\Http\Controllers\Module\WorkOrderController;
@ -24,7 +25,8 @@
// 用户信息 // 用户信息
Route::post('users/attempt', [UserController::class, 'attempt']); Route::post('users/attempt', [UserController::class, 'attempt']);
Route::resource('users', UserController::class)->only(['index', 'show', 'update', 'store']); Route::apiResource('users', UserController::class)->only(['index', 'show', 'update', 'store']);
Route::apiResource('users.subscriptions', SubscriptionController::class);
Route::get('token/{token}', [UserController::class, 'auth']); Route::get('token/{token}', [UserController::class, 'auth']);
Route::get('users/{user}/hosts', [UserController::class, 'hosts']); Route::get('users/{user}/hosts', [UserController::class, 'hosts']);

View File

@ -12,6 +12,7 @@
use App\Http\Controllers\Web\HostController; use App\Http\Controllers\Web\HostController;
use App\Http\Controllers\Web\MaintenanceController; use App\Http\Controllers\Web\MaintenanceController;
use App\Http\Controllers\Web\RealNameController; use App\Http\Controllers\Web\RealNameController;
use App\Http\Controllers\Web\SubscriptionController;
use App\Http\Controllers\Web\TransferController; use App\Http\Controllers\Web\TransferController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -86,6 +87,10 @@ function () {
Route::middleware('guest')->withoutMiddleware(['verified', 'auth:web'])->get('affiliates/{affiliate:uuid}', [AffiliateController::class, 'show'])->name('affiliates.show'); Route::middleware('guest')->withoutMiddleware(['verified', 'auth:web'])->get('affiliates/{affiliate:uuid}', [AffiliateController::class, 'show'])->name('affiliates.show');
/* End 推介 */ /* End 推介 */
/* Start 订阅 */
Route::middleware('resource_owner:subscription')->resource('subscriptions', SubscriptionController::class)->except(['edit']);
/* End 订阅 */
/* Start 匿名登录 */ /* Start 匿名登录 */
Route::get('auth_request/{auth_request}', [AuthController::class, 'showAuthRequest'])->withoutMiddleware(['auth:web', 'verified'])->name('auth_request.show'); Route::get('auth_request/{auth_request}', [AuthController::class, 'showAuthRequest'])->withoutMiddleware(['auth:web', 'verified'])->name('auth_request.show');
Route::post('auth_request', [AuthController::class, 'storeAuthRequest'])->name('auth_request.store'); Route::post('auth_request', [AuthController::class, 'storeAuthRequest'])->name('auth_request.store');