Answer the question
In order to leave comments, you need to log in
How do transaction failures occur?
All with the upcoming!
They gave me a New Year's gift. The project has the ability to transfer funds from one user to another. The fact is that such a situation occurred:
There was one smart-ass user who magically threw funds to another account so that they were not withdrawn from his account.
The whole site is written in Yii2. There is a form model inherited from base / model, validation of each sneeze, including the time of the last transfer, that is, you cannot transfer funds more than once every 30 seconds.
The very logic of the transfer of funds:
/**
* Создать новую заявку на перевод средств
* @throws \Exception
* @throws \yii\db\Exception
* @return bool
*/
public function create()
{
$transaction = Yii::$app->db->beginTransaction();
try {
$transfer = new Transfer();
$transfer->user_sender = Yii::$app->user->id;
$transfer->user_recipient = $this->_user_recipient;
$transfer->time_create = time();
$transfer->funds = $this->amount;
$transfer->status = Transfer::STATUS_PENDING;
$transfer->comm = $this->comm;
$transfer->save(false);
Yii::$app->balance
->setModule('transfer')
->setUser(Yii::$app->user->identity)
->setEntityId($transfer->id)
->costs($transfer->funds, Transfer::TYPE_SUCCESS);
// Перевод без подтверждения
if (!Yii::$app->config->get('transferModeration')) {
$transfer->time_process = time();
$transfer->status = Transfer::STATUS_SUCCESS;
Yii::$app->balance
->setModule('transfer')
->setUser(User::findOne($this->_user_recipient))
->setEntityId($transfer->id)
->billing($transfer->funds, Transfer::TYPE_SUCCESS);
}
$transfer->save(false);
$transaction->commit();
return true;
} catch(\Exception $e) {
$transaction->rollBack();
return false;
}
}
...
// Было средств на счету
$was = $this->_user->$account;
$model = $this->_model;
if ($type == $model::DEPOSIT || $type == $model::BILLING) {
$this->_user->$account += $amount;
}
else {
$this->_user->$account -= $amount;
}
// Обновляем счет пользователя
$this->_user->save(false);
// Запись в историю денежного оборота
$result = call_user_func_array(
[$model, $this->_method], [
$this->_module,
$type,
$system,
$this->_user->id,
$was,
$amount,
$this->_entityId,
$this->_data,
]);
return $result;
Answer the question
In order to leave comments, you need to log in
If I don't confuse anything, then:
1) $transfer->save(false); - you have disabled validation when saving models.
2) When committing, you do not check that the result of saving all models is positive (true), and it’s stupid that you can’t validate anywhere.
3) When testing, have you tried sending a negative number? Perhaps it is worth checking that the value is a number, but that it is not worth more than 0, this can lead to problems.
All this, of course, is not a solution to your problem, but rather something that you can pay attention to.
if ($type == $model::DEPOSIT || $type == $model::BILLING) {
$this->_user->$account += $amount;
}
else {
$this->_user->$account -= $amount;
}
// Обновляем счет пользователя
$this->_user->save(false);
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question