Серверный платёжный процесс с защитой от повторов и дубликатов.
1. Подготовь ID и доступ
Начни с активного магазина и проверенных способов оплаты. В Настройки → Доступ к APIсоздай ключ чтения/записи только для нужного проекта. Храни токен на бэкенде, никогда в браузерном коде или публичном репозитории.
Скопируй API ID проекта и API ID магазина из блока магазина Основное → API IDs . Это UUID, а не читаемый идентификатор проекта или номер заказа. Используй своё имя хоста API.
В разделе Магазин → IPN, создай секрет подписи до указания ipn_url. Твой HTTPS-обработчик должен быть доступен с VPS продавца.
2. Создай счёт
Замени значения-заглушки и отправь запрос с бэкенда. Суммы - десятичные строки, не вычисления с плавающей точкой.
curl --fail-with-body --request POST \
'https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices' \
--header 'Authorization: Bearer YOUR_MERCHANT_API_TOKEN' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: order-1042-attempt-1' \
--data '{
"amount": "10.00",
"currency": "EUR",
"order_id": "order-1042",
"description": "Example order",
"ipn_url": "https://shop.example.com/payments/wholly-ipn",
"metadata": {
"cart_id": "cart-1042"
}
}'Сохрани data.invoice_id с заказом, затем перенаправь клиента на links.checkout. Для новой попытки оплаты используй новый ключ идемпотентности. При тайм-ауте повторяй с теми же данными доступа, ключом и точными байтами тела.
Если не указывать payment_methods , используются способы магазина. Можно сузить их для счёта через slug сетей и тикеры; это никогда не включает непринимаемый актив. Все поля запроса и примеры ответов →
3. Выбери IPN, вебхуки или оба
IPN следует жизненному циклу счёта. Задай URL IPN магазина или переопредели через ipn_url для отдельного счёта. Вебхуки подписывают эндпоинт на выбранные события, например invoice.settled.
Формат подписи одинаковый, но секреты разные: IPN использует секрет IPN магазина, каждый вебхук - свой секрет. API Bearer-токен не используется для подписи ни в одном случае.
Если оба доставляются в приложение, ожидай пересекающиеся уведомления. Не начисляй оплату заказа дважды.
4. Проверь и сохрани уведомление
- Прочитай точное сырое тело до разбора JSON. Проверь
Wholly-Signatureсо своим секретом, временем и защитой от повтора. Официальные SDK предоставляют проверку. - Проверь проект, магазин, счёт и событие в подписанном теле. Неподписанные заголовки доставки не являются источником аутентификации.
- Надёжно сохрани событие с уникальным
event_id, затем быстро верни HTTP 2xx. Обрабатывай заказы фоновой задачей. - Запроси текущий счёт у настроенного API-хоста, не у произвольного хоста из запроса. Сверь сохранённые проект, магазин, сумму, валюту и номер заказа.
PHP · Python · JavaScript / TypeScript · Спецификация подписи и примеры обработчиков
5. Выполни заказ один раз после оплаты
Для событийного обработчика принимай event_type = invoice.settled, затем проверяй текущий status = settled и свою политику исключений. Выполняй заказ один раз в транзакции БД/с уникальным ограничением заказа.
Быстро подтверждающая сеть может отправить оба payment.received и invoice.settled с status = settled. В другой сети payment.received ещё может содержать processing. Ни один вариант не является ошибкой.
Убирай дубликаты событий по event_id, не только sequence: разные типы событий могут иметь одну sequence. Обработчики SDK по состоянию вместо этого объединяют ревизии счёта и проверяют состояние независимо от типа события. Не совмещай такое объединение с фильтром типа события. Оба подхода требуют защиты от повторного выполнения заказа.
Проверь requires_review и ручные решения перед выполнением. amount_status = paid сам по себе не доказывает подтверждение. Верхнеуровневые поля оплаченного актива суммируют завершённую оплату; payment_info содержит подробные поступления и данные котировки. Все статусы, события и правила исключений →
6. Проверь повторы и восстановление
Проверь небольшой платёж, повтор доставки, истёкший счёт и временно недоступный обработчик. Повтор события не должен повторно начислить оплату. Обрабатывай события не по порядку, не затирая новое состояние старым.
Проверь Магазин → IPN / Вебхуки → История → Подробностиили разделы доставки в деталях счёта. Повторная отправка использует записанное событие, не создаёт новое завершение оплаты.
Нехватка кредитов ставит IPN/вебхуки на паузу, но платежи идут. Сверяй открытые заказы через API и обрабатывай сохранённую доставку после восстановления. Не выполняй заказ по перенаправлению браузера или скриншоту клиента.