Files
famous-ly4-ev/app-modules/payment/src/Filament/Resources/Refunds/Actions/ProcessRefundAction.php
T
Nyan Lin Paing d19a14a45e Complete Payment module: initiate/webhook/confirm/refund actions, Filament resources (T5.8-T5.13)
- InitiatePaymentAction + POST /api/v1/payments/{booking}/initiate
- Generic KBZ webhook (POST /api/v1/webhooks/{method}/{encryptBookingId?}),
  signature verification per KBZ's real callback spec, PaymentGatewayInterface::handleWebhook()
- ConfirmPaymentAction: idempotent confirmation, PaymentCompleted/PaymentFailed events,
  MarkBookingPaid listener
- RefundBookingAction + POST /api/v1/bookings/{booking}/refund: partial refunds validated
  against remaining balance, RefundProcessed event, MarkBookingRefunded listener
- CancelBookingAction now refunds confirmed bookings instead of rejecting; BookingPolicy::cancel
  requires process_refunds for confirmed bookings
- PaymentPlugin + PaymentResource/RefundResource Filament admin UI (read-only payments,
  refund list + Process action)
- Booking detail page now shows related payments
- Fix CACHE_STORE mismatch (database -> redis) so tagged route caching works
- CLAUDE.md: never run migrate:fresh/migrate:refresh/db:wipe on dev without being asked
2026-08-09 16:20:21 +07:00

75 lines
2.8 KiB
PHP

<?php
namespace Modules\Payment\Filament\Resources\Refunds\Actions;
use Filament\Actions\Action;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Support\Icons\Heroicon;
use Modules\Payment\Actions\RefundBookingAction;
use Modules\Payment\Enums\PaymentStatus;
use Modules\Payment\Exceptions\RefundFailedException;
use Modules\Payment\Exceptions\RefundNotAllowedException;
use Modules\Payment\Models\Payment;
/**
* Header action on ListRefunds — staff pick a successful Payment (only
* Completed ones are offered, domain.md §6) and an amount/reason, which
* calls RefundBookingAction the same way the API endpoint does (T5.11).
*/
class ProcessRefundAction
{
public static function make(): Action
{
return Action::make('process')
->label('Process Refund')
->icon(Heroicon::OutlinedReceiptRefund)
->color('danger')
->visible(fn (): bool => auth()->user()?->can('process_refunds') ?? false)
->schema([
Select::make('payment_id')
->label('Payment')
->options(fn () => Payment::query()
->where('status', PaymentStatus::Completed->value)
->with('booking')
->get()
->mapWithKeys(fn (Payment $payment) => [
$payment->id => "{$payment->booking?->booking_ref}{$payment->amount} {$payment->currency} (#{$payment->id})",
]))
->searchable()
->required(),
TextInput::make('amount')
->numeric()
->minValue(0.01)
->required(),
Textarea::make('reason')
->required(),
])
->action(function (array $data): void {
$payment = Payment::with('booking')->findOrFail($data['payment_id']);
try {
app(RefundBookingAction::class)->handle(
$payment->booking,
(string) $data['amount'],
$data['reason'],
auth()->id(),
);
Notification::make()
->title('Refund processed')
->success()
->send();
} catch (RefundNotAllowedException|RefundFailedException $exception) {
Notification::make()
->title('Refund failed')
->body($exception->getMessage())
->danger()
->send();
}
});
}
}