A distributed transaction is not available, so the pattern is: do each step, and if a later one fails, run compensating actions to undo the earlier ones. Compensation is not rollback — the effect happened and is being reversed by another effect.
try {
$payment = $this->payments->take($order);
$this->warehouse->reserve($order);
} catch (ReservationFailed $e) {
$this->payments->refund($payment); // compensation, not rollback
throw $e;
}
The customer sees a charge and then a refund, which is visible and occasionally a support call — that is inherent, not a flaw in the implementation. Compensations must themselves be idempotent and must be able to fail, which is where a naive saga falls over: a refund that errors leaves the system in the state the saga existed to prevent, and there is no third level.