Reacting to failures
Run your own code when a step fails: alert someone, release a hold, record a metric.
Two hooks
Hubs and managers both take them. Each is called with the envelope and the exception:
onError(fn ($order, $exception) => ...)runs for everyerror()and unexpected exception, and then the next step runs.onFatal(fn ($order, $fatal) => ...)runs when a fatal stops things.$fatal->getMessage()is the reason.
A hub's hook hears about its own rules. A manager's hears about every hub's, after the hub's own hook.
Alert someone, and say so on the record
Put the hook on the flow so it fires once, when the whole flow stops. Inside it, write what you did to the log, so the alert is part of the saved run:
<?php
$order = new Order(
id: 'ord_48215',
items: [['sku' => 'sold-out', 'name' => 'Limited-run poster', 'price' => 40.00]],
shippingAddress: '500 Congress Ave, Austin',
shippingCountry: 'US',
billingCountry: 'US',
);
$envelope = CheckoutFlow::build()
->subject($order)
->onFatal(function ($order, $fatal) {
// send the alert here, then say so on the receipt
$order->logger()->info('Alert', "Told #merchandising: {$fatal->getMessage()}");
})
->sendIt();
echo $envelope->logger()->infos()->firstWhere('key', 'Alert')['data'];
Told #merchandising: Out of stock: Limited-run posterHooks run before sendIt() returns, so anything they log is saved with the rest of the run.
Keep hooks quiet
What a hook throws changes what happens next. A redirect or view leaves sendIt(); anything else is recorded as a fatal. Catch your own exceptions inside the hook. The details are on Outcome tables.
Next: Redirects and views.