From 825320a1173876fc8d2f4c5fb32a4093d4d6b29b Mon Sep 17 00:00:00 2001 From: Nyan Lin Paing <117423022+LinPaing21@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:51:43 +0700 Subject: [PATCH] Make MarkBookingRefunded synchronous so refunds cancel instantly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was ShouldQueue, so on QUEUE_CONNECTION=database with no worker running (or just queue lag), a booking stayed Confirmed in the UI until a worker processed the job — refund actions in Filament looked like they hadn't taken effect without a page reload. A refund is always initiated from a request already waiting on it (the Filament action or the API endpoint), so there's no reason to defer the status flip. --- .../payment/src/Listeners/MarkBookingRefunded.php | 13 +++++++------ .../tests/Feature/MarkBookingRefundedTest.php | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app-modules/payment/src/Listeners/MarkBookingRefunded.php b/app-modules/payment/src/Listeners/MarkBookingRefunded.php index e58ecf1..27a73b8 100644 --- a/app-modules/payment/src/Listeners/MarkBookingRefunded.php +++ b/app-modules/payment/src/Listeners/MarkBookingRefunded.php @@ -2,7 +2,6 @@ namespace Modules\Payment\Listeners; -use Illuminate\Contracts\Queue\ShouldQueue; use Modules\Booking\Enums\BookingStatus; use Modules\Payment\Events\RefundProcessed; @@ -11,8 +10,14 @@ use Modules\Payment\Events\RefundProcessed; * completes (domain.md §5). Guarded the same way as MarkBookingPaid — only * ever moves a still-confirmed booking, never clobbers one that moved on * for another reason. + * + * Deliberately synchronous, unlike MarkBookingPaid — a refund is always + * initiated from a request that's already waiting on it (the Filament + * action or the API endpoint), and staff expect the booking's status to + * read Cancelled the instant that request completes, not once a queue + * worker gets to it. */ -class MarkBookingRefunded implements ShouldQueue +class MarkBookingRefunded { public function handle(RefundProcessed $event): void { @@ -22,10 +27,6 @@ class MarkBookingRefunded implements ShouldQueue // pre-redesign rows where booking_id wasn't yet recorded. $booking = $event->refund->booking ?? $event->refund->payment->booking; - // Booking uses SoftDeletes — normally unreachable here (a confirmed - // booking is never deletable, BookingPolicy::delete), but this - // listener is queued, so it's worth guarding against a booking that - // vanished between dispatch and execution regardless. if ($booking === null) { return; } diff --git a/app-modules/payment/tests/Feature/MarkBookingRefundedTest.php b/app-modules/payment/tests/Feature/MarkBookingRefundedTest.php index bebab92..2942a37 100644 --- a/app-modules/payment/tests/Feature/MarkBookingRefundedTest.php +++ b/app-modules/payment/tests/Feature/MarkBookingRefundedTest.php @@ -29,7 +29,7 @@ test('does not touch a booking that already moved on for another reason', functi expect($booking->refresh()->status)->toBe(BookingStatus::Cancelled); }); -test('does not crash if the booking was soft-deleted before this queued listener ran', function () { +test('does not crash if the booking was soft-deleted before this listener ran', function () { $booking = Booking::factory()->create(['status' => BookingStatus::Confirmed]); $payment = Payment::factory()->completed()->create(['booking_id' => $booking->id, 'gateway' => PaymentMethod::KbzMiniApp]); $refund = Refund::factory()->create(['payment_id' => $payment->id, 'status' => RefundStatus::Completed]);