Billing Engine for Laravel
Ingest a payment webhook
Ingest a payment webhook
Gateway webhooks are at-least-once and unordered. WebhookIngest makes applying
one exactly-once. Your controller's only job is to build the canonical
WebhookEvent from the (verified) payload and hand it over.
Ingest
use Cbox\Billing\Payment\Contracts\WebhookIngest;
use Cbox\Billing\Payment\Enums\WebhookIngestStatus;
public function handle(Request $request, WebhookIngest $ingest): Response
{
// The adapter's WebhookVerifier authenticates the payload and maps it to a WebhookEvent.
$event = $this->gatewayAdapter->parse($request);
$outcome = $ingest->ingest($event);
return response()->noContent(match ($outcome->status) {
WebhookIngestStatus::Applied,
WebhookIngestStatus::AlreadySettled,
WebhookIngestStatus::DuplicateEvent,
WebhookIngestStatus::Ignored => 200, // always ack so the gateway stops retrying
});
}
DefaultWebhookIngest:
- verifies the payload (
WebhookVerifier; the defaultDenyingWebhookVerifierrejects everything until an adapter binds a real one); - dedups on the event id via
ProcessedEventStore→DuplicateEventon a redelivery; - guards the settled state via
SettledPaymentStore→AlreadySettledif the invoice was settled by another path; - otherwise applies the payment to the invoice (
InvoicePaymentApplier) →Applied.
Acknowledge every terminal outcome with a 200 so the gateway stops retrying — the
dedup and settled guards mean a retry that does slip through is a no-op.