Make MarkBookingRefunded synchronous so refunds cancel instantly

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.
This commit is contained in:
Nyan Lin Paing
2026-09-03 10:51:43 +07:00
parent e555413295
commit 825320a117
2 changed files with 8 additions and 7 deletions
@@ -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;
}
@@ -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]);