Fix: сбой платёжного API навсегда терял реальную оплату юзера
TonProvider::verify_payment и CryptoBotProvider::verify_payment на любой неуспешный HTTP-ответ (rate-limit/5xx/недоступность провайдера) возвращали Ok(false) — confirm_payment на это необратимо помечает инвойс `failed`. reconcile_pending_payments подбирает только `pending`, так что реальный платёж, наткнувшийся на временный сбой TonAPI/CryptoBot именно в момент подтверждения, терялся навсегда без пути восстановления. Теперь неуспешный HTTP-статус/ответ "не ok" от провайдера — это Err (не трогаем статус инвойса, можно повторить), а не Ok(false) (окончательное "не оплачено"). Найдено в ходе pre-launch аудита критичных путей оплаты.
This commit is contained in:
@@ -107,7 +107,16 @@ impl PaymentProvider for CryptoBotProvider {
|
||||
.map_err(|e| AppError::Internal(format!("Ошибка парсинга ответа CryptoBot: {e}")))?;
|
||||
|
||||
if res["ok"].as_bool() != Some(true) {
|
||||
return Ok(false);
|
||||
// Ответ API "не ok" (сбой/рейт-лимит/недоступность CryptoBot) — не то
|
||||
// же самое, что "инвойс не найден/не оплачен". Раньше это тоже
|
||||
// схлопывалось в `Ok(false)`, а confirm_payment необратимо помечает
|
||||
// инвойс `failed` на любое `false` — реальная оплата терялась
|
||||
// навсегда при простом временном сбое CryptoBot API. `Err` оставляет
|
||||
// инвойс `pending`, юзер может нажать "Я оплатил" ещё раз.
|
||||
return Err(AppError::Internal(format!(
|
||||
"CryptoBot getInvoices вернул ошибку: {}",
|
||||
res["error"]
|
||||
)));
|
||||
}
|
||||
|
||||
let Some(item) = res["result"]["items"].as_array().and_then(|a| a.first()) else {
|
||||
|
||||
@@ -62,7 +62,18 @@ impl PaymentProvider for TonProvider {
|
||||
.map_err(|e| AppError::Internal(format!("Ошибка запроса к TON API: {}", e)))?;
|
||||
|
||||
if !res.status().is_success() {
|
||||
return Ok(false); // Транзакция не найдена или еще не подтверждена
|
||||
// Неуспешный HTTP-статус (429/5xx/недоступность TonAPI) — это НЕ то
|
||||
// же самое, что "транзакция не найдена/невалидна". Раньше оба случая
|
||||
// схлопывались в `Ok(false)`, а confirm_payment на любое `false`
|
||||
// необратимо помечает инвойс `failed` — реальный платёж, наткнувшийся
|
||||
// на временный сбой TonAPI именно в момент подтверждения, терялся
|
||||
// навсегда: `reconcile_pending_payments` подбирает только `pending`,
|
||||
// не `failed`. Возвращаем `Err`, чтобы `confirm_payment` НЕ трогал
|
||||
// статус инвойса и юзер/сверка могли повторить попытку позже.
|
||||
return Err(AppError::Internal(format!(
|
||||
"TonAPI недоступен или транзакция ещё не проиндексирована: HTTP {}",
|
||||
res.status()
|
||||
)));
|
||||
}
|
||||
|
||||
let data: Value = res
|
||||
|
||||
Reference in New Issue
Block a user