A deadlock is normal; retrying is your job

InnoDB detects a deadlock, kills one transaction and returns an error, and an application that treats that as a bug will treat a normal event as an outage.

foreach (range(1, 3) as $attempt) {
    try {
        DB::transaction(fn () => $this->applyPayment($order));
        return;
    } catch (QueryException $e) {
        if ($e->getCode() !== '40001' || $attempt === 3) {
            throw $e;
        }

        usleep(random_int(10_000, 50_000) * $attempt);
    }
}

SQLSTATE 40001 is the serialisation failure and covers both deadlocks and lock wait timeouts, which want the same response. The retry must re-run the whole transaction rather than the failed statement, because the transaction was rolled back entirely — retrying one statement inside a dead transaction produces a different and more confusing error. Jitter on the backoff matters here for the same reason it matters anywhere: two transactions retrying in lockstep deadlock again.