Laravel как правильно обновить баланс при покупке?
1. Лочить запись в таблице балансов до момента списания. Залочили, проверили, списали, разлочили. Другие процессы либо будут ждать - либо вылетят по таймауту.

2. Использовать update с условием. update user_balance where user_id = ? and balance > нужного. У вас запрос не выполнится если кто то уже списал деньги. А вы по affected rows можете судить списалось или нет
Нужно просто для всех команд списания с баланса использовать мьютекс, внутри которого:
1. Получать текущий баланс.
2. Проверять, что он больше, либо равен сумме списания.
3. Вносить изменения в БД, если всё ок.
Третий шаг можно реализовать по-разному - через таблицу транзакций, через единственное поле баланса, как у вас сейчас или ещё как угодно. Эта часть совершенно не важна.
Важно только то, что функционал списания с баланса в целом становится однопоточным за счёт блокировки.
Нужно только учитывать, что блокировка должна быть распределённой. Стандартом является использование алгоритма Redlock, реализованном на базе Redis.
Ещё важно использовать один блокировщик именно для всех типов списаний. Если вы в buyProduct будете использовать один мьютекс, а в каком-нибудь buyService другой, то работать это правильно не будет.