From fa085980b4b8b2b495e4b8615c5bb64f9a7901cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=9D=E5=8D=9C?= Date: Wed, 11 Mar 2026 05:12:53 +0000 Subject: [PATCH] =?UTF-8?q?=E9=80=80=E6=AC=BE=E6=B2=BB=E7=90=86=EF=BC=9A?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=A0=87=E8=AE=B0=E9=83=A8=E5=88=86=E9=80=80?= =?UTF-8?q?=E6=AC=BE/=E5=B7=B2=E6=94=AF=E4=BB=98=E4=BB=A5=E5=9B=9E?= =?UTF-8?q?=E9=80=80=E9=80=80=E6=AC=BE=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Admin/PlatformOrderController.php | 66 +++++++++++++++ .../admin/platform_orders/show.blade.php | 19 +++++ routes/web.php | 2 + ...nPlatformOrderMarkRefundStatusBackTest.php | 80 +++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 tests/Feature/AdminPlatformOrderMarkRefundStatusBackTest.php 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) +
+ 提示:当前支付状态为「已退款」,但退款总额不足。如确认无误,可将状态修正为「部分退款」或「已支付」。 +
+ @csrf + +
+
+ @csrf + +
+
@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')); + } +}