›
byrcsc/laravel-payrex · 1.x
Handle verified PayRex webhooks through typed, retry-safe Laravel listeners.
Every verified webhook dispatches PayrexWebhookReceived. When its type exists
in config('payrex.webhooks.events'), the package also dispatches the mapped
typed event.
namespace App\Listeners;
use App\Models\Order;
use ByRcsc\LaravelPayrex\Events\PaymentIntentSucceeded;
use Illuminate\Contracts\Queue\ShouldQueue;
final class MarkOrderAsPaid implements ShouldQueue
{
public function handle(PaymentIntentSucceeded $event): void
{
$intent = $event->event->paymentIntent();
if ($intent === null) {
return;
}
Order::query()
->where('payrex_payment_intent_id', $intent->id)
->whereNull('paid_at')
->update(['paid_at' => now()]);
}
}Laravel can discover the listener or you can register it explicitly in your application's event configuration.
| PayRex type | Laravel event |
|---|---|
payment_intent.awaiting_capture | PaymentIntentAwaitingCapture |
payment_intent.succeeded | PaymentIntentSucceeded |
setup_intent.succeeded | SetupIntentSucceeded |
checkout_session.expired | CheckoutSessionExpired |
refund.created | RefundCreated |
refund.updated | RefundUpdated |
payout.created | PayoutCreated |
payout.deposited | PayoutDeposited |
billing_statement.created | BillingStatementCreated |
billing_statement.updated | BillingStatementUpdated |
billing_statement.deleted | BillingStatementDeleted |
billing_statement.finalized | BillingStatementFinalized |
billing_statement.sent | BillingStatementSent |
billing_statement.marked_uncollectible | BillingStatementMarkedUncollectible |
billing_statement.voided | BillingStatementVoided |
billing_statement.paid | BillingStatementPaid |
billing_statement.will_be_due | BillingStatementWillBeDue |
billing_statement.overdue | BillingStatementOverdue |
billing_statement_line_item.created | BillingStatementLineItemCreated |
billing_statement_line_item.updated | BillingStatementLineItemUpdated |
billing_statement_line_item.deleted | BillingStatementLineItemDeleted |
All typed events extend PayrexWebhookEvent.
Use the generic event for audit logging or a PayRex type the installed package does not map yet:
use ByRcsc\LaravelPayrex\Events\PayrexWebhookReceived;
final class RecordPayrexEvent implements ShouldQueue
{
public function handle(PayrexWebhookReceived $event): void
{
$event->type();
$event->event->id;
$event->event->type;
$event->event->raw;
}
}The generic event fires even when a typed event fires.
$event->event is a readonly WebhookEvent:
$webhook = $event->event;
$webhook->id;
$webhook->type;
$webhook->livemode;
$webhook->pendingWebhooks;
$webhook->previousAttributes;
$webhook->resourceType();
$webhook->resourceData();
$webhook->resource();
$webhook->raw;resource() hydrates a supported subject into PaymentIntent, Payment,
PaymentMethod, Refund, CheckoutSession, SetupIntent, Customer,
BillingStatement, BillingStatementLineItem, or Payout. It returns null
for an unknown resource; the raw data remains available.
Convenience methods exist for common subjects:
$webhook->paymentIntent();
$webhook->payment();
$webhook->refund();
$webhook->checkoutSession();Use resource() plus instanceof for the other resource types.
is() accepts exact names and a trailing wildcard:
if ($webhook->is('refund.created', 'refund.updated')) {
// ...
}
if ($webhook->is('billing_statement.*')) {
// ...
}Add an event type by mapping it to a class that extends
PayrexWebhookEvent:
use App\Events\CustomerUpdatedByPayrex;
'events' => [
// package defaults...
'customer.updated' => CustomerUpdatedByPayrex::class,
],namespace App\Events;
use ByRcsc\LaravelPayrex\Events\PayrexWebhookEvent;
final class CustomerUpdatedByPayrex extends PayrexWebhookEvent
{
}The package validates this map during application boot. Keys must be non-empty
strings and classes must extend PayrexWebhookEvent.
PayRex may deliver an event more than once. A listener should claim the event ID before performing one-time work:
if (! PayrexDelivery::query()->firstOrCreate([
'event_id' => $event->event->id,
])->wasRecentlyCreated) {
return;
}
// Perform the state transition once.Back the event ID with a unique database index so two queue workers cannot claim it simultaneously. Also make the domain update itself conditional; this protects against retries that happen after the claim but before work finishes.