Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Интеграция платёжного шлюза: практика для разработчика

Интеграция платёжного шлюза: шаги и типовые ошибки

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

Прямой ответ

Интеграция платёжного шлюза сводится к четырём элементам: создание платежа на стороне сервиса, передача пользователя на stronę подтверждения, получение результата и фиксация состояния в собственной базе.

Базовая схема взаимодействия:

Сервис ──1. создание платежа──▶ Провайдер
Сервис ◀──2. идентификатор платежа──
Пользователь ──3. подтверждение──▶ Провайдер
Провайдер ──4. уведомление──▶ Сервис
Сервис ──5. проверка статуса──▶ Провайдер

Шаг пятый обязателен: уведомление приходит по сети и может быть подделано либо не доставлено, поэтому состояние платежа проверяется запросом к провайдеру.

import httpx

r = httpx.post(
    "",
    auth=(SHOP_ID, SECRET_KEY),
    json={"amount": {"value": "1500.00", "currency": "RUB"},
          "reference": order_id, "return_url": RETURN_URL},
    timeout=10.0,
)
r.raise_for_status()
payment_id = r.json()["id"]

Собственная таблица платежей хранит состояние независимо от провайдера:

CREATE TABLE payments (
    id            bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    order_id      bigint NOT NULL,
    provider_id   text,
    status        text NOT NULL,
    amount_minor  bigint NOT NULL,
    currency      char(3) NOT NULL,
    created_at    timestamptz NOT NULL DEFAULT now(),
    updated_at    timestamptz NOT NULL DEFAULT now()
);

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

Что определяет надёжность интеграции

Различия между провайдерами касаются формата запросов, но надёжность определяется четырьмя решениями, не зависящими от конкретного API.

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

Идемпотентность создания платежа. Повторный запрос с тем же ключом возвращает существующий платёж, а не создаёт новый; о механизме — в статье про ключ идемпотентности.

Разделение синхронного и асинхронного результата. Ответ на создание платежа означает принятие, а не завершение оплаты. Завершение приходит уведомлением либо проверяется опросом.

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

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

Ключи доступа хранятся вне кода и вне репозитория; о хранении секретов — в статье про управление секретами.

Что проверить перед выводом интеграции в продуктив

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

Замена ключей и адресов. Тестовые ключи не работают в продуктовом контуре, а адрес API отличается. Ключи хранятся в системе секретов, поэтому замена выполняется без изменения кода.

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

Настройка уведомлений у провайдера. Адрес обработчика уведомлений указывается в настройках провайдера отдельно для каждого контура. Отсутствие настройки означает, что уведомления не поступают и состояние не обновляется.

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

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

Практика: внедрение платёжной интеграции

1. Начинайте с тестового контура провайдера. Тестовые ключи и тестовые карты позволяют проверить все ветки обработки без реальных списаний.

2. Фиксируйте состояние в собственной базе. Таблица платежей с историей изменений позволяет разбирать расхождения без обращения к панели провайдера.

3. Храните суммы в минимальных единицах. Копейки как целое число исключают ошибку округления при сложении и сравнении.

4. Проверяйте подпись уведомлений. Уведомление без проверки подписи принимается от любого отправителя, что позволяет подделать успешную оплату.

5. Устанавливайте таймаут на запросы к провайдеру. Запрос без таймаута удерживает поток приложения при замедлении провайдера.

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

7. Логируйте идентификаторы операций. Идентификатор платежа у провайдера и внутренний идентификатор связывают запись в журнале с записью в базе.

8. Проверяйте состояние при возврате пользователя. Возврат на страницу результата не означает успех: пользователь мог закрыть страницу подтверждения.

Рекомендации:

  1. Не отправляйте данные карты через свой сервер без необходимости. Хостинг платёжной формы у провайдера снижает объём требований безопасности; о требованиях — в статье про PCI DSS.
  2. Разделяйте суммы и валюту. Хранение суммы без валюты приводит к ошибкам при работе с несколькими валютами.
  3. Учитывайте задержку уведомлений. Уведомление может прийти через минуты после оплаты, поэтому интерфейс показывает промежуточное состояние.
  4. Проверяйте обработку частичного отказа. Отказ после списания требует отдельного сценария, а не общего сообщения об ошибке.
  5. Документируйте сопоставление статусов. Статусы провайдера отображаются во внутренние статусы по явной таблице, а не по совпадению названий.

Практический сценарий: двойное списание при повторной отправке формы

Контекст: пользователь столкнулся с двумя списаниями за один заказ.

  1. Диагностика. В журнале провайдера оказались два платежа с одинаковой суммой и разными идентификаторами, созданные с интервалом в несколько секунд. В собственной базе присутствовала одна запись платежа.
  2. Причина. Форма оплаты отправлялась повторно при отсутствии ответа, а запрос создания платежа не содержал ключа идемпотентности. Первый запрос выполнился на стороне провайдера, но ответ не дошёл до сервиса из-за таймаута, поэтому повтор создал второй платёж.
  3. Действия. (а) Добавили ключ идемпотентности, формируемый из идентификатора заказа; (б) сохранили идентификатор провайдера в базе до ожидания ответа, чтобы повторный запрос находил существующий платёж; (в) при таймауте перевели платёж в неопределённое состояние с последующим уточнением запросом, а не повторным созданием; (г) выполнили возврат по второму платежу; (д) добавили проверку на дубли платежей по заказу в периодическую сверку.
  4. Результат. Повторная отправка формы возвращает существующий платёж, а расхождение обнаруживается сверкой.

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

FAQ

Нужно ли хранить данные карты в своей базе? Нет, если используется хостинг формы у провайдера либо токенизация. Хранение данных карты расширяет объём требований безопасности.

Что делать при таймауте запроса к провайдеру? Перевести платёж в неопределённое состояние и уточнить статус запросом. Повторное создание платежа без ключа идемпотентности приводит к двойному списанию.

Можно ли доверять уведомлению об успешной оплате? Нет без проверки подписи и последующего запроса состояния. Уведомление — сигнал к проверке, а не источник истины.

Как проверять интеграцию без реальных списаний? Тестовым контуром провайдера с тестовыми ключами и картами, покрывая успешный сценарий, отказ и таймаут.

Источники: Stripe API, PayPal REST API, PCI Security Standards Council — ссылки в шапке.