diff --git a/app/Http/Controllers/Admin/PlatformOrderController.php b/app/Http/Controllers/Admin/PlatformOrderController.php
index ff34284..79646ef 100644
--- a/app/Http/Controllers/Admin/PlatformOrderController.php
+++ b/app/Http/Controllers/Admin/PlatformOrderController.php
@@ -613,6 +613,72 @@ class PlatformOrderController extends Controller
return redirect()->back()->with('success', '已将订单支付状态标记为已退款(未自动写入退款回执)。');
}
+ public function markPartiallyRefunded(Request $request, PlatformOrder $order): RedirectResponse
+ {
+ $admin = $this->ensurePlatformAdmin($request);
+
+ if ((float) ($order->paid_amount ?? 0) <= 0) {
+ return redirect()->back()->with('warning', '当前订单已付金额为 0,无法标记为部分退款。');
+ }
+
+ if ((string) $order->payment_status === 'partially_refunded') {
+ return redirect()->back()->with('warning', '当前订单已是部分退款状态,无需重复操作。');
+ }
+
+ $now = now();
+ $order->payment_status = 'partially_refunded';
+ $order->refunded_at = $order->refunded_at ?: $now;
+
+ $meta = (array) ($order->meta ?? []);
+ $audit = (array) (data_get($meta, 'audit', []) ?? []);
+ $audit[] = [
+ 'action' => 'mark_partially_refunded',
+ 'scope' => 'single',
+ 'at' => $now->toDateTimeString(),
+ 'admin_id' => $admin->id,
+ 'note' => '手动标记为部分退款(仅修正支付状态,不自动写退款回执)',
+ ];
+ data_set($meta, 'audit', $audit);
+
+ $order->meta = $meta;
+ $order->save();
+
+ return redirect()->back()->with('success', '已将订单支付状态标记为部分退款(未自动写入退款回执)。');
+ }
+
+ public function markPaidStatus(Request $request, PlatformOrder $order): RedirectResponse
+ {
+ $admin = $this->ensurePlatformAdmin($request);
+
+ if ((float) ($order->paid_amount ?? 0) <= 0) {
+ return redirect()->back()->with('warning', '当前订单已付金额为 0,无法标记为已支付。');
+ }
+
+ if ((string) $order->payment_status === 'paid') {
+ return redirect()->back()->with('warning', '当前订单已是已支付状态,无需重复操作。');
+ }
+
+ $now = now();
+ $order->payment_status = 'paid';
+ // paid 状态不强依赖 refunded_at,这里不做清空,避免丢历史痕迹
+
+ $meta = (array) ($order->meta ?? []);
+ $audit = (array) (data_get($meta, 'audit', []) ?? []);
+ $audit[] = [
+ 'action' => 'mark_paid_status',
+ 'scope' => 'single',
+ 'at' => $now->toDateTimeString(),
+ 'admin_id' => $admin->id,
+ 'note' => '手动标记为已支付(仅修正支付状态,不自动写回执/退款回执)',
+ ];
+ data_set($meta, 'audit', $audit);
+
+ $order->meta = $meta;
+ $order->save();
+
+ return redirect()->back()->with('success', '已将订单支付状态标记为已支付(未自动写入回执/退款回执)。');
+ }
+
public function markActivated(Request $request, PlatformOrder $order): RedirectResponse
{
$admin = $this->ensurePlatformAdmin($request);
diff --git a/resources/views/admin/platform_orders/show.blade.php b/resources/views/admin/platform_orders/show.blade.php
index 78e048e..f946aab 100644
--- a/resources/views/admin/platform_orders/show.blade.php
+++ b/resources/views/admin/platform_orders/show.blade.php
@@ -85,6 +85,11 @@
$canMarkRefunded = $paidAmountFloat > 0
&& $order->payment_status !== 'refunded'
&& round($refundTotal * 100) >= round($paidAmountFloat * 100);
+
+ // - refunded 但退款总额不足 => 提供降级动作:标记为部分退款/已支付(仍不自动写回执)
+ $canFixRefundedButNotEnough = $paidAmountFloat > 0
+ && $order->payment_status === 'refunded'
+ && (round($refundTotal * 100) + 1) < round($paidAmountFloat * 100);
@endphp
@if($canMarkRefunded)
@@ -94,6 +99,18 @@
+ @elseif($canFixRefundedButNotEnough)
+
+ 提示:当前支付状态为「已退款」,但退款总额不足。如确认无误,可将状态修正为「部分退款」或「已支付」。
+
+
+
@endif
@if($order->payment_status === 'refunded' && ($refundTotal + 0.01) < $paidAmountFloat)
@@ -393,6 +410,8 @@
'batch_mark_activated' => '批量仅标记为已生效',
'activate_subscription' => '同步订阅',
'mark_refunded' => '手动标记为已退款',
+ 'mark_partially_refunded' => '手动标记为部分退款',
+ 'mark_paid_status' => '手动标记为已支付',
];
@endphp
diff --git a/routes/web.php b/routes/web.php
index eed155a..1d85fe5 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -112,6 +112,8 @@ Route::prefix('admin')->group(function () {
Route::post('/platform-orders/{order}/add-payment-receipt', [PlatformOrderController::class, 'addPaymentReceipt']);
Route::post('/platform-orders/{order}/add-refund-receipt', [PlatformOrderController::class, 'addRefundReceipt']);
Route::post('/platform-orders/{order}/mark-refunded', [PlatformOrderController::class, 'markRefunded']);
+ Route::post('/platform-orders/{order}/mark-partially-refunded', [PlatformOrderController::class, 'markPartiallyRefunded']);
+ Route::post('/platform-orders/{order}/mark-paid-status', [PlatformOrderController::class, 'markPaidStatus']);
Route::post('/platform-orders/{order}/mark-activated', [PlatformOrderController::class, 'markActivated']);
Route::get('/site-subscriptions', [SiteSubscriptionController::class, 'index']);
diff --git a/tests/Feature/AdminPlatformOrderMarkRefundStatusBackTest.php b/tests/Feature/AdminPlatformOrderMarkRefundStatusBackTest.php
new file mode 100644
index 0000000..ed46ce6
--- /dev/null
+++ b/tests/Feature/AdminPlatformOrderMarkRefundStatusBackTest.php
@@ -0,0 +1,80 @@
+seed();
+
+ $this->post('/admin/login', [
+ 'email' => 'platform.admin@demo.local',
+ 'password' => 'Platform@123456',
+ ])->assertRedirect('/admin');
+ }
+
+ public function test_platform_admin_can_mark_order_refund_status_back_to_partially_refunded_and_paid(): void
+ {
+ $this->loginAsPlatformAdmin();
+
+ $merchant = Merchant::query()->firstOrFail();
+ $plan = Plan::query()->create([
+ 'code' => 'mark_refund_status_back_test_plan',
+ 'name' => '退款状态回退治理测试套餐',
+ 'billing_cycle' => 'monthly',
+ 'price' => 10,
+ 'list_price' => 10,
+ 'status' => 'active',
+ 'sort' => 10,
+ 'published_at' => now(),
+ ]);
+
+ $order = PlatformOrder::query()->create([
+ 'merchant_id' => $merchant->id,
+ 'plan_id' => $plan->id,
+ 'order_no' => 'PO_MARK_REFUND_BACK_0001',
+ 'order_type' => 'new_purchase',
+ 'status' => 'activated',
+ 'payment_status' => 'refunded',
+ 'plan_name' => $plan->name,
+ 'billing_cycle' => $plan->billing_cycle,
+ 'period_months' => 1,
+ 'quantity' => 1,
+ 'payable_amount' => 10,
+ 'paid_amount' => 10,
+ 'placed_at' => now(),
+ 'paid_at' => now(),
+ 'activated_at' => now(),
+ 'refunded_at' => now(),
+ 'meta' => [
+ 'refund_summary' => [
+ 'count' => 1,
+ 'total_amount' => 1.00,
+ ],
+ ],
+ ]);
+
+ // 回退到部分退款
+ $this->post('/admin/platform-orders/' . $order->id . '/mark-partially-refunded')
+ ->assertRedirect();
+ $order->refresh();
+ $this->assertSame('partially_refunded', $order->payment_status);
+ $this->assertSame('mark_partially_refunded', (string) data_get($order->meta, 'audit.0.action'));
+
+ // 再回退到已支付
+ $this->post('/admin/platform-orders/' . $order->id . '/mark-paid-status')
+ ->assertRedirect();
+ $order->refresh();
+ $this->assertSame('paid', $order->payment_status);
+ $this->assertSame('mark_paid_status', (string) data_get($order->meta, 'audit.1.action'));
+ }
+}