Перейти к содержимому

Что будет, если покупатель заплатил не ту сумму?

15 сент. 2026 г. 9 мин чтения Claude C. Claude C.
Три стопки тонких пластин у одной оранжевой пунктирной линии на уровне ожидаемой суммы: первая не достаёт до неё, вторая вровень, третья выше

Кратко

Переплату зачисляют целиком, и счёт закрывается оплаченным — но сообщает об этом событием invoice.paid_over, а не invoice.paid. Обработчик, сверяющий тип с одной строкой, теряет платёж, который вам уже заплатили. Недостача упирается в допуск проекта: до 2 %, шагом в десятую долю процента, 0,1 % у нового проекта. Дальше счёт либо остаётся открытым на остаток, либо закрывается недоплаченным. Деньги, дошедшие до адреса мимо счёта, — третий случай, и собственного события у него нет вовсе.

Счёт выставлен на 120 USDT. На адрес пришло 118,4 — ровно на комиссию биржи меньше. Никто ничего не перепутал, перевод подтверждён, и отката у него не будет.

Что произойдёт дальше, решили до того, как покупатель открыл страницу оплаты. Переплату зачисляют целиком, и счёт при этом закрывается. У недостачи два разных конца, и какой из них достанется счёту, решает настройка проекта.

Промахиваются в обе стороны, и не раз в месяц. Биржа удерживает свою комиссию из отправляемой суммы, у сумм в токенах длинный хвост после запятой, а человек, который доплачивает за заказ руками, округляет до ровного.

Как выставить счёт — отдельное руководство, а все переходы состояний собраны в описании платёжного потока. Здесь про сумму: что она делает с балансом, с заказом и с потоком событий.

Почему оплаченный счёт не закрывает заказ?

Потому что обработчик сверял тип события с одной строкой.

Событие до вас доходит. Подписка идёт категорией целиком, отдельные имена внутри неё по одному не выбираются, поэтому эндпоинт, подписанный на категорию счетов, получает все восемь её событий — invoice.paid_over в том числе. Доставка приезжает, обработчик сверяет тип с invoice.paid, совпадения не находит и кладёт её в стопку «нас не касается».

Ломается это тихо, по устройству. В дашборде счёт оплачен, на балансе деньги, заказ не отгружен, и нигде никакой ошибки не было. Всплывает через несколько дней вопросом покупателя, где его товар. Причём отбор идёт ровно по тем, кто заплатил вам больше, чем вы просили.

Ветвитесь по множеству, а не по равенству:

// Оба события закрывают счёт. Второе означает, что пришло больше.
if (in_array($type, ['invoice.paid', 'invoice.paid_over'], true)) {
    $this->fulfilOrder($invoiceId);
}

И держите две суммы врозь в собственной записи. Запрошено и получено — разные числа, а излишек это разница между ними. Вытаскивать его потом из имени события не придётся.

Что происходит с суммой сверх счёта?

Ничего не придерживают.

Всё, что пришло, зачисляется целиком, вместе с излишком, и счёт закрывается оплаченным. Комиссия считается по тому же правилу, что на любом другом платеже: с того, что пришло, а не с того, что просили, — значит, излишек несёт её наравне с остальной суммой. Сам процент лежит на странице цен.

Расходятся эти два случая только в событии. Платёж ровно в сумму сообщает invoice.paid. Платёж выше сообщает invoice.paid_over, и это имя — вся разница, которую получает ваша система.

Отправить излишек назад — ваш собственный перевод. С баланса само ничего не уходит, и решение здесь коммерческое, а не техническое: хвост округления перевода не стоит, а человек, промахнувшийся на один ноль, напишет вам раньше, чем вы ему.

Насколько меньше — это ещё оплачено?

Ползунок «Допуск недоплаты» живёт в настройках проекта. Верхняя граница у него — 2 %, двигается он десятыми долями процента, а проект, созданный сегодня, стоит на первой из них, на 0,1 %. Сдвиньте его в ноль, и сверка станет строгой: платёж должен дотянуть до суммы счёта или её перекрыть. Отдельно на один счёт допуск не выставить — в запросе на создание такого поля нет, и каждый счёт наследует то, что стоит в проекте.

В пределах допуска счёт закрывается и сообщает invoice.paid, как любой другой оплаченный. Расчёт идёт по пришедшей сумме, недостающее никто не добавляет.

Одна деталь решает, что ещё можно поправить потом. Счёт судят по тому допуску, который стоял в проекте в момент его создания, и эта копия замораживается на счёте до конца его жизни. Сдвинули ползунок — изменили те счета, которые выставите дальше, а не те, что уже открыты у покупателей на странице оплаты.

Откуда вообще берётся недостача?

Сетевая комиссия — первый подозреваемый и неверный.

Газ в Ethereum платят в эфире — так прямо и написано в документации сети, — а списывают его с монетного баланса отправляющего кошелька. Сумма в токене едет целиком. Кто отправил 120 USDT со своего кошелька, тот отправил 120 USDT.

Недостача почти всегда приходит из биржевого счёта, а в СНГ это способ оплаты по умолчанию. Binance.US формулирует правило про собственные выводы без обиняков: и комиссия площадки, и комиссия сети вычитаются из суммы вывода до того, как она дойдёт до кошелька назначения. Плательщик вбил сумму счёта, биржа отправила её за вычетом своей комиссии, и счёт недобрал ровно эту комиссию.

Вот почему допуск — процент, а причина — не процент. Фиксированное удержание откусывает заметную долю от маленького счёта и незаметную от большого, так что одним числом обе стороны не закрыть. На 0,1 % счёт в 120 единиц проглатывает 0,12 — хвост округления, и ничего похожего на биржевую комиссию. Ставьте допуск под хвост, а биржевой случай решайте тем, чем он и решается: предупреждением на странице оплаты или в подтверждении заказа.

Два конца у недоплаты — какой достанется счёту?

Развилку выбирает не поддержка и не покупатель, а поле allow_multiple_payments. Оно фиксируется при создании счёта, и API по умолчанию ставит true.

Пока несколько платежей разрешены, короткий перевод не закрывает ничего. Счёт уходит в underpaid_waiting и остаётся открытым на остаток, а арифметику за плательщика делает страница оплаты: QR-код и все ссылки в кошельки перестраиваются на недостающую сумму, и считает её сервер, а не браузер. В карточке Telegram кнопка копирования суммы переименовывается и копирует остаток вместо исходного итога.

Выключены — и один короткий перевод всё заканчивает. Счёт закрывается недоплаченным и сообщает invoice.underpaid.

Открытый счёт добирает таймер. Окно оплаты закрылось, а счёт так и не дотянул — значит, он становится недоплаченным. Замеченный перевод, который ещё подтверждается, этому мешает напрямую: счёт не истечёт из-под денег, уже идущих к нему.

Платит ли вам недоплаченный счёт?

Платит, и на выписке это выглядит странно.

Каждый перевод зачисляется, как только подтвердился, и комиссия берётся с суммы этого самого перевода. Значит, частичная оплата — это деньги на балансе при всё ещё открытом счёте, а счёт, закончившийся недоплаченным, оставляет вас с настоящими деньгами на балансе и незакрытым заказом в системе.

Поток событий тише, чем движение денег. invoice.underpaid_waiting уходит один раз, когда счёт входит в это состояние. Второй короткий перевод, который тоже не покрыл счёт, двигает баланс и нового события не рождает: состояние не изменилось, а вебхук встаёт в очередь именно на смену состояния. Экран поддержки, собранный на потоке событий, покажет один приход там, где их было два. Текущую сумму читайте по счёту.

Остаётся решение, которое за вас никто не примет: отгрузить, отгрузить часть, попросить разницу или вернуть деньги. Возврат — обычный перевод с вашего баланса, ровно как в случае с переплатой.

Когда деньги на балансе — вообще не оплата счёта?

Счёт может недобрать. А может прийти перевод, который не оплатил ни одного счёта, — и на странице баланса эти две строки стоят рядом и выглядят одинаково.

Природа у них разная. Недоплата — состояние счёта: у неё есть статус, который интеграция читает, и событие, на которое она подписывается. Зачисление мимо счёта — движение денег без счёта за ним: перевод дошёл до адреса после закрытия окна оплаты, или в другом активе, или на адрес, за которым счёта уже нет. Зачисляется он за вычетом обычной комиссии, а сам счёт не двигается вовсе: ни оплаченным, ни недоплаченным его не помечают.

Собственного события у него нет — не другое событие, а никакого. Поэтому система, живущая на потоке вебхуков, о таком приходе не узнает никогда: показывают его на странице баланса, в письме, в колокольчике дашборда и в Telegram, если контакт привязан.

Ошибка, которая из этого растёт, всегда одна и та же. Оператор видит приход, принимает его за недоплату и догружает заказ руками. Заказ уходит, счёт по-прежнему не оплачен, и в следующей сверке эта пара разъедется. Недобравший счёт надо догонять. Деньги без счёта просто разносят по учёту, и заказа за ними нет.

Как отрепетировать это до первого покупателя?

В тестовом режиме, симулятором оплаты: он поднимает те же события жизненного цикла, что настоящий платёж. Один подписанный вызов доводит подтверждённый тестовый счёт до нужного исхода:

POST /v1/sandbox/invoices/inv_7Qk2.../simulate-payment
Content-Type: application/json

{ "stage": "overpaid" }

overpaid платит на 10 % больше ожидаемой суммы, underpay — 40 % от неё, и каждое число сервер берёт из самого счёта, клиент сумм не присылает. Этапы складываются между вызовами, поэтому два underpay дают по одному счёту 80 %, и он всё ещё недоплачен.

До первого настоящего платежа стоит прогнать три сценария:

  1. overpaid по счёту, за которым следит ваша отгрузка. Заказ обязан уйти, а в вашей записи должно быть видно, что получено больше, чем запрошено.
  2. underpay по счёту, который допускает несколько платежей. Заказ уходить не должен, а то, во что смотрит поддержка, обязано показывать остаток, а не исходный итог.
  3. underpay по счёту, созданному с allow_multiple_payments в false. Заказ не уходит, счёт закрывается.

Одну ветку так не проверить, и это как раз допуск. Этапа, который отправит на долю процента меньше, здесь нет, поэтому настройку сверяют глазами в проекте, а не выстрелом из теста. Всё остальное — один подписанный запрос.

Что счёт делает с пришедшей суммой (сентябрь 2026 года)
Что пришлоЧто делает счётКакое событие придёт
Больше, чем просил счётЗакрывается оплаченным, излишек зачисленinvoice.paid_over
Меньше, но в пределах допуска проектаЗакрывается оплаченнымinvoice.paid
Меньше допуска, счёт принимает несколько платежейОстаётся открытым на остатокinvoice.underpaid_waiting
Меньше допуска, счёт рассчитан на один платёжЗакрывается недоплаченнымinvoice.underpaid
Остаток не пришёл до конца окна оплатыЗакрывается недоплаченнымinvoice.underpaid
Тот же адрес, живого счёта за ним нетНе двигаетсяНикакого

Частые вопросы

Что будет, если покупатель заплатил больше, чем в счёте?

Вся пришедшая сумма зачисляется на баланс, вместе с излишком, и счёт закрывается оплаченным. Ничего не удерживают и само собой ничего не возвращают. В вебхуке при этом приходит invoice.paid_over, а не invoice.paid.

invoice.paid_over — отдельное событие от invoice.paid?

Да, и на этом чаще всего спотыкаются интеграции. Эндпоинт, подписанный на категорию счетов, получает оба. Но обработчик, сравнивающий тип с одной строкой invoice.paid, переплату пропускает, и заказ остаётся неотгруженным.

Что такое допуск недоплаты и где он настраивается?

Это доля счёта, на которую платёж может не дотянуть и всё равно его закрыть. Настройка живёт в проекте, её потолок — 2 %, шаг — десятая доля процента, а новый проект стоит на 0,1 %. Ноль означает строгую сверку: платёж должен дотянуть до суммы счёта или её перекрыть.

Может ли покупатель доплатить по тому же счёту?

Если счёт создан с allow_multiple_payments true — это значение API по умолчанию, — короткий перевод оставляет его открытым в состоянии underpaid_waiting, и следующие переводы идут в тот же счёт, пока не закроется окно оплаты.

Зачисляется ли на баланс недоплаченный счёт?

Да. Каждый перевод зачисляется, как только подтвердился, и комиссия берётся с суммы этого перевода. Поэтому недобравший счёт оставляет вас с настоящими деньгами против заказа, который так и не закрылся.

Что происходит с переводом, пришедшим после закрытия окна оплаты?

Счёт он не оплачивает. Сумма зачисляется на баланс за вычетом обычной комиссии, счёт остаётся там, где был, а вебхука по нему нет — искать такой приход нужно на странице баланса и в письме.

Когда НЕ стоит использовать допуск недоплаты

  • Если все счета оплачивают из собственных кошельков и тем токеном, в котором вы выставили, сумма приходит ровно, а любой допуск выше нуля работает как постоянная скидка. Ставьте ноль и сверяйте строго.
  • Если маржа заказа тоньше допуска, который вы рассматриваете, это снижение цены для каждого, кто округлит вниз. Пусть уступка лежит там, где её видно, — в цене, а не в настройке сверки.
  • Если недостача, которую вы видите раз за разом, — фиксированное удержание биржи, процент не покроет её ровно. На больших счетах закроет, на маленьких промахнётся. Тут помогает разговор с плательщиком, а не ползунок.
  • Если вы продаёте одну неделимую вещь — лицензию, место, билет, — счёт, ждущий второго платежа, чаще всего порождает наполовину оплаченные заказы, которые потом догоняет поддержка. Такие счета создавайте с выключенными несколькими платежами.

Источники

  1. 1. Документация Paymos — платёжный поток (accessed 2026-09-15)
  2. 2. Документация Paymos — симуляция оплаты инвойса (accessed 2026-09-15)
  3. 3. ethereum.org — Gas and fees (accessed 2026-09-15)
  4. 4. Binance.US Help Center — Understanding network fees vs. exchange fees (accessed 2026-09-15)

Последняя проверка: 15 сент. 2026 г.

#переплата#недоплата#счета#вебхуки#сверка
Поделиться