平台订单:提供手动标记已退款治理动作
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
<div class="muted" style="margin-top:10px;">
|
||||
提示:退款总额已达到/超过已付金额,但支付状态尚未是「已退款」,如确认无误,可直接修正。
|
||||
<form method="post" action="/admin/platform-orders/{{ $order->id }}/mark-refunded" style="display:inline; margin-left:8px;" onsubmit="return confirm('确认将该订单支付状态标记为已退款?该操作不会自动写入退款回执,仅修正状态');">
|
||||
@csrf
|
||||
<button type="submit">标记为已退款</button>
|
||||
</form>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($order->payment_status === 'refunded' && ($refundTotal + 0.01) < $paidAmountFloat)
|
||||
<div class="muted text-danger" style="margin-top:10px;">提示:当前订单状态为「已退款」,但退款总额小于已付金额,可能存在数据不一致,请核对退款轨迹与订单金额。</div>
|
||||
@elseif($order->payment_status !== 'refunded' && $paidAmountFloat > 0 && $refundTotal >= $paidAmountFloat)
|
||||
@@ -260,6 +276,27 @@
|
||||
<h3>退款记录(退款轨迹留痕)</h3>
|
||||
<p class="muted muted-tight">用于记录退款动作与对账轨迹(先落 meta,不引入独立表)。追加退款后,系统会自动把支付状态推进为“部分退款/已退款”(仅在订单当前为已支付时)。</p>
|
||||
|
||||
@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)
|
||||
<div class="muted text-danger" style="margin-top:10px;">
|
||||
提示:该订单疑似存在「退款状态 vs 退款总额」不一致,建议核对退款轨迹、已付金额与支付状态。
|
||||
<span class="muted">|</span>
|
||||
<a class="link" href="/admin/platform-orders?refund_inconsistent=1">查看全部退款不一致订单</a>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(count($refundReceipts) > 0)
|
||||
@php $items = array_slice(array_reverse($refundReceipts), 0, 20); @endphp
|
||||
<table>
|
||||
@@ -355,6 +392,7 @@
|
||||
'mark_activated' => '仅标记为已生效',
|
||||
'batch_mark_activated' => '批量仅标记为已生效',
|
||||
'activate_subscription' => '同步订阅',
|
||||
'mark_refunded' => '手动标记为已退款',
|
||||
];
|
||||
@endphp
|
||||
<table>
|
||||
|
||||
@@ -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']);
|
||||
|
||||
74
tests/Feature/AdminPlatformOrderMarkRefundedTest.php
Normal file
74
tests/Feature/AdminPlatformOrderMarkRefundedTest.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Merchant;
|
||||
use App\Models\Plan;
|
||||
use App\Models\PlatformOrder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AdminPlatformOrderMarkRefundedTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function loginAsPlatformAdmin(): void
|
||||
{
|
||||
$this->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'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user