diff --git a/app/Http/Controllers/Admin/PlatformOrderController.php b/app/Http/Controllers/Admin/PlatformOrderController.php
index 0563ff6..ff34284 100644
--- a/app/Http/Controllers/Admin/PlatformOrderController.php
+++ b/app/Http/Controllers/Admin/PlatformOrderController.php
@@ -580,6 +580,39 @@ class PlatformOrderController extends Controller
return redirect()->back()->with('success', '已追加退款记录(用于退款轨迹留痕)。');
}
+ public function markRefunded(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 === 'refunded') {
+ return redirect()->back()->with('warning', '当前订单已是已退款状态,无需重复操作。');
+ }
+
+ $now = now();
+ $order->payment_status = 'refunded';
+ $order->refunded_at = $order->refunded_at ?: $now;
+
+ $meta = (array) ($order->meta ?? []);
+ $audit = (array) (data_get($meta, 'audit', []) ?? []);
+ $audit[] = [
+ 'action' => 'mark_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 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 3cbe806..78e048e 100644
--- a/resources/views/admin/platform_orders/show.blade.php
+++ b/resources/views/admin/platform_orders/show.blade.php
@@ -79,7 +79,23 @@
@php
$paidAmountFloat = (float) ($order->paid_amount ?? 0);
+
+ // 若订单疑似退款不一致:
+ // - 非 refunded 但退款总额已达/超已付 => 给出“可一键标记为已退款”的治理动作(不自动写回执)
+ $canMarkRefunded = $paidAmountFloat > 0
+ && $order->payment_status !== 'refunded'
+ && round($refundTotal * 100) >= round($paidAmountFloat * 100);
@endphp
+ @if($canMarkRefunded)
+
+ 提示:退款总额已达到/超过已付金额,但支付状态尚未是「已退款」,如确认无误,可直接修正。
+
+
+ @endif
+
@if($order->payment_status === 'refunded' && ($refundTotal + 0.01) < $paidAmountFloat)
提示:当前订单状态为「已退款」,但退款总额小于已付金额,可能存在数据不一致,请核对退款轨迹与订单金额。
@elseif($order->payment_status !== 'refunded' && $paidAmountFloat > 0 && $refundTotal >= $paidAmountFloat)
@@ -260,6 +276,27 @@
退款记录(退款轨迹留痕)
用于记录退款动作与对账轨迹(先落 meta,不引入独立表)。追加退款后,系统会自动把支付状态推进为“部分退款/已退款”(仅在订单当前为已支付时)。
+ @php
+ // 退款不一致(与列表/筛选口径保持一致,按分取整 + 0.01 容差)
+ $paidAmountFloat3 = (float) ($order->paid_amount ?? 0);
+ $isRefundInconsistent3 = false;
+ if ($paidAmountFloat3 > 0) {
+ if ((string) $order->payment_status === 'refunded') {
+ $isRefundInconsistent3 = (round($refundTotal * 100) + 1) < round($paidAmountFloat3 * 100);
+ } else {
+ $isRefundInconsistent3 = round($refundTotal * 100) >= round($paidAmountFloat3 * 100);
+ }
+ }
+ @endphp
+
+ @if($isRefundInconsistent3)
+
+ 提示:该订单疑似存在「退款状态 vs 退款总额」不一致,建议核对退款轨迹、已付金额与支付状态。
+
|
+
查看全部退款不一致订单
+
+ @endif
+
@if(count($refundReceipts) > 0)
@php $items = array_slice(array_reverse($refundReceipts), 0, 20); @endphp
@@ -355,6 +392,7 @@
'mark_activated' => '仅标记为已生效',
'batch_mark_activated' => '批量仅标记为已生效',
'activate_subscription' => '同步订阅',
+ 'mark_refunded' => '手动标记为已退款',
];
@endphp
diff --git a/routes/web.php b/routes/web.php
index 3c9e725..eed155a 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -111,6 +111,7 @@ Route::prefix('admin')->group(function () {
Route::post('/platform-orders/{order}/mark-paid-and-activate', [PlatformOrderController::class, 'markPaidAndActivate']);
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-activated', [PlatformOrderController::class, 'markActivated']);
Route::get('/site-subscriptions', [SiteSubscriptionController::class, 'index']);
diff --git a/tests/Feature/AdminPlatformOrderMarkRefundedTest.php b/tests/Feature/AdminPlatformOrderMarkRefundedTest.php
new file mode 100644
index 0000000..c0dbbd7
--- /dev/null
+++ b/tests/Feature/AdminPlatformOrderMarkRefundedTest.php
@@ -0,0 +1,74 @@
+seed();
+
+ $this->post('/admin/login', [
+ 'email' => 'platform.admin@demo.local',
+ 'password' => 'Platform@123456',
+ ])->assertRedirect('/admin');
+ }
+
+ public function test_platform_admin_can_mark_order_as_refunded(): void
+ {
+ $this->loginAsPlatformAdmin();
+
+ $merchant = Merchant::query()->firstOrFail();
+ $plan = Plan::query()->create([
+ 'code' => 'mark_refunded_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_REFUNDED_0001',
+ 'order_type' => 'new_purchase',
+ 'status' => 'activated',
+ 'payment_status' => 'paid',
+ '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(),
+ 'meta' => [
+ 'refund_summary' => [
+ 'count' => 1,
+ 'total_amount' => 10.00,
+ ],
+ ],
+ ]);
+
+ $this->post('/admin/platform-orders/' . $order->id . '/mark-refunded')
+ ->assertRedirect();
+
+ $order->refresh();
+
+ $this->assertSame('refunded', $order->payment_status);
+ $this->assertNotNull($order->refunded_at);
+ $this->assertSame('mark_refunded', (string) data_get($order->meta, 'audit.0.action'));
+ }
+}