Чтобы связать криптоплатёж с правильным заказом, сначала создайте заказ в своей системе и назначьте ему стабильный внутренний ID. Затем создайте отдельный payment invoice и передайте в него этот ID как контекст продавца. Сохраните invoice ID рядом с заказом. Когда доверенный серверный статус или подписанный webhook сообщит об оплате, найдите заказ по возвращённому reference, проверьте статус invoice, сохраните токен, сеть, сумму и tx hash и выполните заказ идемпотентно. Надёжная цепочка выглядит так: merchant order ID → payment invoice ID → confirmed blockchain transaction.

ЗаписьЧто она подтверждаетЧего она не подтверждает сама по себе
Заказ продавцаКто, за что и сколько должен, что означает исполнениеЧто средства поступили в блокчейне
Payment invoiceКакой токен, маршрут, сумму и срок ожидала системаЧто продавец выполнил собственное бизнес-действие
Blockchain-транзакцияПубличный факт движения токенаКакой коммерческий заказ имел в виду продавец
Подписанное событиеЧто платёжный сервис связал invoice со статусом и транзакциейЧто downstream worker применил изменение только один раз

Blockchain-перевод не содержит ваш заказ

Публичный блокчейн записывает движение активов. Для ERC-20 стандартное событие Transfer содержит отправителя, получателя и значение, а receipt позволяет найти транзакцию. В стандарте ERC-20 нет номера ecommerce-заказа, аккаунта клиента, SKU или правила выдачи товара. Эти данные принадлежат системе продавца.

В этом различии находится основная проблема сопоставления. Поступление 100 USDC отвечает на вопрос «был ли перевод токена», но не сообщает, относится ли он к ORDER-4821, к поздней оплате ORDER-4710 или вообще к другой операции. Explorer показывает blockchain evidence, но не заменяет базу заказов.

Связь нужно создать до отправки checkout покупателю. Если после поступления средств пытаться угадать заказ по адресу, сумме или времени, каждый неоднозначный случай превращается в ручную работу поддержки.

Не используйте адрес или сумму как уникальный ключ

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

Сумма тоже не является безопасным join key. У нескольких заказов может быть одинаковая цена, клиент может недоплатить, переплатить или округлить значение. Необычные дробные суммы снижают число совпадений при небольшом объёме, но не заменяют явную связь заказа и invoice и не решают исключения.

Если проверенная сумма отличается от инвойса, используйте сценарий для недоплаты и переплаты и не запускайте штатное автоматическое исполнение заказа.

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

Скриншот и присланный покупателем hash должны запускать проверку, но не закрывать заказ. Настоящий hash может относиться к другому токену, адресу, amount или network, а один hash можно предъявить повторно. Чек-лист ручной проверки показывает, как проверить сам перевод.

Храните три записи и явные внешние ключи

Разделите заказ продавца, payment invoice и blockchain transaction — у них разные зоны ответственности.

ПолеГде хранитьЗачем оно нужно
Local order IDБаза продавца и payment contextСтабильный ключ поиска и fulfilment
Payment invoice IDЗаказ продавца и провайдерПрямой lookup и ссылка для поддержки
Сумма обязательстваЗаказ продавцаЧто клиент должен коммерчески
Payable token amountPayment invoiceТочная сумма, показанная в checkout
Token и networkInvoice и финальная payment recordНе даёт смешать разные маршруты
Payment statusПлатёжная запись и историяОпределяет разрешённое действие
Transaction hashФинальная payment recordНаходит публичное доказательство
Paid и expiry timestampsПлатёжная записьОбъясняют timing и решения по исключениям
Event или delivery keyWebhook inboxПредотвращает повторные side effects
Ручное решение и reviewerException logДелает override восстанавливаемым

Используйте неизменяемый database ID или UUID. Номер, который сотрудник может отредактировать или выдать повторно, не подходит. Не помещайте в metadata секреты, private key и лишние персональные данные: контекст платежа может появляться в логах, кабинете и webhook body.

Invoice ID нужно записать в заказ сразу после создания — до redirect клиента. Тогда поддержка сможет пройти в обе стороны: от заказа к payment request и от invoice провайдера к бизнес-записи.

Создавайте invoice повторобезопасно

Нормальный flow сайта:

  1. Проверить корзину или заявку.
  2. Создать локальный заказ в состоянии payment_pending.
  3. Сформировать стабильный idempotency key для этого заказа и попытки оплаты.
  4. Создать payment invoice и передать local order ID как merchant context.
  5. Сохранить invoice ID и checkout URL в одной транзакции базы или в восстанавливаемом workflow.
  6. Отправить покупателя в hosted checkout.

Идемпотентность при создании нужна из-за неоднозначных сетевых ошибок. Провайдер мог создать invoice, даже если сайт не получил ответ из-за timeout. Повтор без того же ключа создаст второй активный запрос. Повтор со стабильным ключом должен вернуть исходный результат или явно показать конфликт.

В GramPayBot reference продавца передаётся в payload, а Idempotency-Key предотвращает дублирование invoice. Идентичный повтор возвращает исходный invoice; тот же ключ с другими параметрами приводит к конфликту. Сохраните public_id, а покупателю передайте web_app_invoice_url. Точный контракт описан в API quickstart и API reference.

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

order → payment attempt 1 (expired) → payment attempt 2 (paid)

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

Финальный переход заказа должен принадлежать серверу

Браузер не является доверенным платёжным каналом. Клиент может закрыть страницу до подтверждения или повторно открыть старый success state. Нельзя выдавать продукт только потому, что в URL появился параметр успеха.

Используйте авторизованный API lookup или подписанный server-to-server webhook. В событии GramPayBot payload.payload содержит merchant reference, а invoice object — public_id, status, paid token, network и tx_hash. Формат определён в документации событий.

Безопасный webhook handler работает в таком порядке:

  1. Прочитать точный raw request body.
  2. Проверить HMAC-подпись сравнением constant time.
  3. Сохранить delivery и body до долгой бизнес-обработки.
  4. Дедуплицировать по стабильному delivery или event key.
  5. Быстро подтвердить валидное событие.
  6. В асинхронном worker найти payment attempt и order по сохранённым ID.
  7. Применить только разрешённый переход состояния.
  8. По возможности атомарно записать payment и fulfilment marker.

GramPayBot повторяет реальные события до 17 раз примерно в течение четырёх дней, поэтому дубликат доставки — ожидаемый механизм надёжности. Это общая практика: официальная документация Stripe также рекомендует сохранять обработанные event IDs и игнорировать дубли.

Сделайте идемпотентным само исполнение

Дедупликации delivery ID недостаточно. Два разных события либо webhook и polling worker могут одновременно увидеть оплаченный invoice. Само бизнес-действие нужно защитить unique constraint или условным state transition.

Worker может менять заказ только из payment_pending и создавать уникальный fulfilment record по order ID. Если не обновлена ни одна строка, другой процесс уже обработал оплату и повтор завершается без второй отправки или активации.

BEGIN
  lock order ORDER-4821
  if order.payment_state != payment_pending: stop safely
  record paid invoice and tx_hash
  set order.payment_state = paid
  create unique fulfilment job for ORDER-4821
COMMIT

Цель — не exactly-once доставка webhook, которую нельзя гарантировать по всей цепочке. Практическая цель — at-least-once доставка с exactly-once бизнес-эффектом.

Свяжите статусы с разрешёнными действиями

Payment stateЗначение для продавцаАвтоматическое действие
activeЗапрос действует, принятого подтверждённого платежа нетОставить заказ pending
paidInvoice выполнил правило подтвержденияРазрешить определённый идемпотентный fulfilment
expiredОкно оплаты завершилось без принятого matchЗаблокировать обычную выдачу, поздние переводы проверять отдельно
cancelledЗапрос намеренно закрытНе принимать как текущую payment attempt

Payment state и order state не обязаны называться одинаково. Оплаченный invoice может перевести заказ в ready_for_review, если нужны compliance или inventory checks. Истёкший invoice не обязательно отменяет коммерческий заказ — сайт может предложить новую попытку оплаты.

Храните историю статусов, а не только последнее значение. Поддержке может потребоваться понять, истёк ли invoice до поступления средств, кто разрешил manual override и какое событие запустило исполнение.

Отправляйте несовпадения в явные exception states

  • Другой token contract: совпадение ticker не означает правильный платёж.
  • Другая сеть: средства могут существовать на том же адресе в другом блокчейне, не совпадая с маршрутом invoice.
  • Недоплата или переплата: храните expected и received amounts и применяйте письменную политику.
  • Поздняя оплата: свяжите её с исторической попыткой, но не открывайте изменённый заказ автоматически.
  • Повторный tx hash: не принимайте его как доказательство для второго invoice.
  • Paid invoice без local order: сохраните событие, поднимите alert и не теряйте данные.
  • Заказ уже исполнен: подтвердите retry без повторного side effect.
  • Manual override: запишите сотрудника, причину и использованное доказательство.

Не стирайте фактическую транзакцию, чтобы записи выглядели удобнее. Оставьте исключение связанным с payment attempt, а решение о принятии, доплате или возврате передайте уполномоченному человеку. При direct-to-wallet зачислении исходящий refund контролирует продавец — monitoring service не может отменить on-chain перевод.

Отделяйте operational matching от бухгалтерской сверки

Operational matching отвечает на вопрос, можно ли сайту исполнить заказ. Accounting reconciliation шире: могут потребоваться fiat value на нужный момент, комиссии, конвертация, движение между кошельками, документы клиента и история возврата. Blockchain transaction не является автоматически налоговым invoice, договором или полной бухгалтерской записью.

Для ежедневного контроля сравнивайте оплаченные payment attempts с paid orders. Отмечайте записи только на одной стороне, повторные tx hashes и заказы, у которых fulfilment state расходится с payment state. Периодическая сверка находит проблемы после отключённого webhook, неудачного deployment или ручного изменения.

Hash нужен для публичного доказательства, но business join должен опираться на local order ID и payment invoice ID. Документация Ethereum показывает, что token transfers доступны через логи событий smart contract; эти логи содержат on-chain факты, но не коммерческий смысл продавца.

Пример с GramPayBot

Клиент выбирает тариф Pro на 30 дней:

  1. SaaS создаёт ORDER-4821 со статусом payment_pending.
  2. Backend вызывает createInvoice с amount 99.00, payload: "ORDER-4821" и Idempotency-Key: ORDER-4821:crypto:1.
  3. GramPayBot возвращает public_id и hosted checkout URL; SaaS сохраняет их до redirect.
  4. Покупатель переводит показанную сумму USDC в Base.
  5. GramPayBot подтверждает подходящую транзакцию и отправляет подписанное событие invoice_paid с order reference, статусом, токеном, сетью и hash.
  6. Webhook endpoint проверяет подпись raw body, сохраняет delivery и возвращает 2xx.
  7. Worker блокирует ORDER-4821, убеждается, что заказ ещё pending, записывает payment и создаёт одну уникальную задачу активации.
  8. Повторная доставка подтверждается, но не может включить тариф второй раз.

GramPayBot предоставляет invoice checkout, мониторинг поддерживаемых сетей и связанный с заказом платёжный результат. SaaS остаётся источником истины для продукта, клиента, периода доступа и fulfilment. Более широкий процесс раскрывает руководство по автоматической проверке платежей, а website use case показывает его место в продажах.

Проверьте ошибки до запуска

Проведите небольшой production pilot и протестируйте не только успешную оплату. Повторите создание invoice с тем же idempotency key. Доставьте валидный webhook дважды. Дайте одному invoice истечь. Отправьте неправильную сумму без автоматического исполнения. Сымитируйте падение worker после сохранения события, но до изменения заказа. Убедитесь, что reconciliation находит и безопасно повторяет незавершённую работу.

Интеграция готова, когда каждый оплаченный заказ можно проследить от коммерческого обязательства через один payment invoice к принятой транзакции, а каждый retry и mismatch либо не создаёт повторного действия, либо появляется как видимый review item.

Следующий шаг

Автоматизируйте проверку оплаты на сайте

Создавайте invoice для каждого заказа, отправляйте покупателя на hosted checkout и получайте результат, связанный с заказом.

Приём платежей на сайте →