feat(platform-orders): 支持总台手工创建平台订单并进入闭环

This commit is contained in:
萝卜
2026-03-10 14:35:31 +00:00
parent 826c58085b
commit 3f809c8150
5 changed files with 304 additions and 1 deletions

View File

@@ -4,12 +4,15 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Concerns\ResolvesPlatformAdminContext;
use App\Http\Controllers\Controller;
use App\Models\Merchant;
use App\Models\Plan;
use App\Models\PlatformOrder;
use App\Support\SubscriptionActivationService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -17,6 +20,89 @@ class PlatformOrderController extends Controller
{
use ResolvesPlatformAdminContext;
public function create(Request $request): View
{
$this->ensurePlatformAdmin($request);
$merchants = Merchant::query()->orderBy('id')->get(['id', 'name']);
$plans = Plan::query()->orderBy('sort')->orderByDesc('id')->get();
return view('admin.platform_orders.form', [
'merchants' => $merchants,
'plans' => $plans,
'billingCycleLabels' => $this->billingCycleLabels(),
'orderTypeLabels' => $this->orderTypeLabels(),
]);
}
public function store(Request $request): RedirectResponse
{
$admin = $this->ensurePlatformAdmin($request);
$data = $request->validate([
'merchant_id' => ['required', 'integer', 'exists:merchants,id'],
'plan_id' => ['required', 'integer', 'exists:plans,id'],
'order_type' => ['required', Rule::in(array_keys($this->orderTypeLabels()))],
'quantity' => ['required', 'integer', 'min:1', 'max:120'],
'discount_amount' => ['nullable', 'numeric', 'min:0'],
'payment_channel' => ['nullable', 'string', 'max:30'],
'remark' => ['nullable', 'string', 'max:2000'],
]);
$plan = Plan::query()->findOrFail((int) $data['plan_id']);
$periodMonths = $this->periodMonthsFromBillingCycle((string) $plan->billing_cycle);
$quantity = (int) $data['quantity'];
$listAmount = (float) $plan->price * $quantity;
$discount = (float) ($data['discount_amount'] ?? 0);
$discount = max(0, min($listAmount, $discount));
$payable = max(0, $listAmount - $discount);
$now = now();
// 订单号PO + 时间 + 4位随机数足够用于当前阶段演示与手工补单
$orderNo = 'PO' . $now->format('YmdHis') . str_pad((string) random_int(1, 9999), 4, '0', STR_PAD_LEFT);
$order = PlatformOrder::query()->create([
'merchant_id' => (int) $data['merchant_id'],
'plan_id' => $plan->id,
'created_by_admin_id' => $admin->id,
'order_no' => $orderNo,
'order_type' => (string) $data['order_type'],
'status' => 'pending',
'payment_status' => 'unpaid',
'payment_channel' => $data['payment_channel'] ?? null,
'plan_name' => $plan->name,
'billing_cycle' => $plan->billing_cycle,
'period_months' => $periodMonths,
'quantity' => $quantity,
'list_amount' => $listAmount,
'discount_amount' => $discount,
'payable_amount' => $payable,
'paid_amount' => 0,
'placed_at' => $now,
'plan_snapshot' => [
'plan_id' => $plan->id,
'code' => $plan->code,
'name' => $plan->name,
'billing_cycle' => $plan->billing_cycle,
'price' => (float) $plan->price,
'list_price' => (float) $plan->list_price,
'status' => $plan->status,
'published_at' => optional($plan->published_at)->toDateTimeString(),
],
'meta' => [
'created_from' => 'manual_form',
],
'remark' => $data['remark'] ?? null,
]);
return redirect('/admin/platform-orders/' . $order->id)
->with('success', '平台订单已创建:' . $order->order_no . '(待支付/待生效)');
}
public function index(Request $request): View
{
$this->ensurePlatformAdmin($request);
@@ -610,4 +696,35 @@ class PlatformOrderController extends Controller
'failed' => '支付失败',
];
}
protected function orderTypeLabels(): array
{
return [
'new_purchase' => '新购',
'renewal' => '续费',
'upgrade' => '升级',
'downgrade' => '降级',
];
}
protected function billingCycleLabels(): array
{
return [
'monthly' => '月付',
'quarterly' => '季付',
'yearly' => '年付',
'one_time' => '一次性',
];
}
protected function periodMonthsFromBillingCycle(string $billingCycle): int
{
return match ($billingCycle) {
'monthly' => 1,
'quarterly' => 3,
'yearly' => 12,
'one_time' => 1,
default => 1,
};
}
}