Разработка · 6 мин чтения

Как подключить ЮKassa к самописному сайту: API, уведомления и чеки

Ко мне регулярно приходят с одной и той же задачей: сайт написан руками, без Tilda и коробочных CMS, и нужно подключить ЮKassa к самописному сайту так, чтобы клиент платил картой прямо на сайте, деньги приходили на расчётный счёт, а бухгалтерия не мучилась с чеками задним числом. Ниже - рабочая схема, которую я использую в проектах на Laravel, Node.js и обычном PHP: от получения ключей API до обработки уведомлений и фискализации чеков по 54-ФЗ.

Что подготовить до подключения ЮKassa к сайту

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

  • Открыть магазин в личном кабинете ЮKassa на юрлицо или ИП, привязать расчётный счёт.
  • Разместить на сайте публичную оферту и политику обработки персональных данных - без этого магазин не пройдёт модерацию.
  • Получить shopId и secretKey: сначала тестовые, после подтверждения магазина - боевые.
  • Определиться со схемой чеков: фискализирует ЮKassa сама (аренда онлайн-кассы) или у вас уже есть своя касса.
  • Поднять на сайте HTTPS-эндпоинт для уведомлений - обычный URL с валидным SSL-сертификатом, без самоподписанных сертификатов.

Создание платежа через Payments API

Дальше два пути: встроить готовый виджет Checkout (JS-скрипт с формой оплаты) или собрать процесс через Payments API самостоятельно. Виджет быстрее во внедрении, но управляете вы только цветом кнопки, а API даёт полный контроль над тем, что происходит на странице оформления заказа, и я в большинстве проектов иду именно этим путём.

Запрос на создание платежа - это POST на /v3/payments с базовой авторизацией по shopId и secretKey и обязательным заголовком Idempotence-Key: без него при повторной отправке запроса (например, если оборвалось соединение) ЮKassa может создать два платежа вместо одного.

curl https://api.yookassa.ru/v3/payments \
  -u $SHOP_ID:$SECRET_KEY \
  -H "Idempotence-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": { "value": "1000.00", "currency": "RUB" },
    "capture": true,
    "confirmation": {
      "type": "redirect",
      "return_url": "https://example.ru/order/1234/success"
    },
    "description": "Заказ №1234",
    "metadata": { "order_id": "1234" },
    "receipt": {
      "customer": { "email": "client@example.ru" },
      "items": [
        {
          "description": "Тариф Про",
          "quantity": "1.00",
          "amount": { "value": "1000.00", "currency": "RUB" },
          "vat_code": 1,
          "payment_mode": "full_payment",
          "payment_subject": "service"
        }
      ]
    }
  }'

В ответе приходит объект платежа со ссылкой confirmation.confirmation_url - на неё и редиректите пользователя. В metadata кладу свой order_id, чтобы потом связать пришедшее уведомление с конкретным заказом в базе, не завязываясь на id платежа ЮKassa как на единственный ключ.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Обработка уведомлений (webhooks) от ЮKassa

Самая частая ошибка, которую я вижу в чужих интеграциях - когда статус заказа меняют по факту редиректа пользователя на return_url. Это неправильно: человек может закрыть вкладку сразу после оплаты, оплата пройдёт банком с задержкой, а на return_url он вообще может не вернуться. Единственный надёжный источник истины - уведомление, которое ЮKassa шлёт POST-запросом на ваш эндпоинт при смене статуса платежа: payment.succeeded, payment.canceled, refund.succeeded и другие.

app.post('/yookassa/webhook', express.json(), async (req, res) => {
  const event = req.body;

  if (event.event === 'payment.succeeded') {
    const payment = event.object;
    const orderId = payment.metadata.order_id;

    const order = await Order.findById(orderId);
    if (order.status !== 'paid' && order.amount === Number(payment.amount.value)) {
      await order.markAsPaid(payment.id);
    }
  }

  res.sendStatus(200);
});

На что обращаю внимание в такой обработке: сравниваю сумму из уведомления с суммой заказа в базе (сумма приходит строкой, приводите к числу сами), обрабатываю событие идемпотентно - если ЮKassa пришлёт то же уведомление повторно (а она это делает, если не получила от вас ответ 200 вовремя), заказ не должен оплатиться дважды и товар не должен уйти клиенту второй раз.

Из практики: если нужно, чтобы о новой оплате сразу узнавал менеджер, из этого же обработчика удобно дёрнуть Telegram-бота на aiogram и отправить сообщение в рабочий чат, либо прокинуть событие через n8n дальше в amoCRM или Битрикс24 - это быстрее, чем городить отдельный сервис уведомлений, и данные клиентов при этом остаются на серверах в России, а не улетают в сторонние облачные таблицы.

Чеки по 54-ФЗ: как фискализировать оплату

Здесь два рабочих варианта, и путаница вокруг них - основная причина, почему подключение затягивается.

Чеки от ЮKassa

ЮKassa сдаёт вам в аренду свою онлайн-кассу и сама пробивает чек в момент оплаты. От вас требуется только передать объект receipt в запросе на создание платежа - с составом заказа, ставкой НДС (vat_code) и признаком предмета расчёта (payment_subject). Комиссия за эквайринг в этом случае немного выше, зато не нужно покупать и регистрировать физическую кассу или отдельный сервис фискализации.

Своя касса

Если у вас уже есть онлайн-касса для другого канала продаж (розница, другой эквайринг), логичнее фискализировать через неё - например, через АТОЛ Онлайн или Такском. В этом случае вебхук payment.succeeded становится триггером: как только пришло подтверждение оплаты, отправляете данные заказа в API своей кассы, и уже она формирует и рассылает чек.

По закону чек обязан дойти до покупателя не позднее суток с момента расчёта - для email это письмо со ссылкой на чек, для телефона - смс со ссылкой. Если у вас интернет-магазин на самописном движке, эта задача решается один раз на этапе интеграции и дальше работает без ручного вмешательства.

Виджет, API или готовый плагин: как выбрать способ подключения

Если сайт собран не на самописном движке, а на WooCommerce, у ЮKassa есть официальный плагин, который ставится за 15-20 минут и решает большую часть задач без единой строчки кода. Для самописного сайта такого плагина никто не даст, поэтому выбор стоит между виджетом и Payments API.

Способ Контроль над UX оплаты Где оправдан
Виджет Checkout Минимальный - готовая форма ЮKassa MVP, лендинг, разовый прототип
Payments API Полный - своя форма, свои шаги оформления Интернет-магазин, сервис с подписками, кастомная воронка
Готовый плагин (WooCommerce и подобные) Ограничен настройками плагина Сайт на готовой CMS, а не на своём коде

Если параллельно рассматриваете эквайринг Т‑Банка как альтернативу или запасной канал оплаты, логика та же самая: свой API, свои webhook-события, отдельный набор ключей, и на самописном сайте обе интеграции обычно живут рядом, просто с разным приоритетом на этапе оформления заказа. Если разбираться с вебхуками и фискализацией самостоятельно некогда, эту работу можно отдать на аутсорс через разработку сайтов под ключ.

Тестовый режим и типичные ошибки при подключении

Пока магазин не прошёл модерацию, работаете с тестовыми shopId и secretKey из личного кабинета и тестовыми картами из документации ЮKassa - деньги при этом нигде не списываются.

Ошибки, которые встречаю чаще всего при разборе чужих интеграций:

  • Обрабатывают только payment.succeeded и забывают про payment.canceled - заказ повисает в статусе «в обработке» навсегда.
  • Секретный ключ закоммичен в открытый репозиторий вместе с остальным кодом.
  • Не проставлен Idempotence-Key, из-за чего при разрывах соединения создаются дублирующиеся платежи на одну и ту же сумму.
  • Сумму из уведомления сравнивают со строкой в базе без приведения типов и получают ложные срабатывания.
  • Забывают, что боевые ключи и тестовые ключи - это разные магазины, и после релиза продолжают стучаться в тестовый API.

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

Чем ЮKassa отличается от эквайринга Т‑Банка для самописного сайта?

Логика интеграции похожа: тот же принцип создания платежа, редирект на форму оплаты и приём уведомлений о статусе. Разница в комиссиях по вашему тарифу, наборе способов оплаты (СБП, рассрочки, кошельки) и в том, какие банки уже видели ваш ИНН при подключении расчётного счёта - иногда это ускоряет модерацию магазина.

Нужна ли отдельная онлайн-касса, если чеки формирует ЮKassa?

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

Сколько времени занимает подключение ЮKassa к самописному сайту?

Сама интеграция API и обработка уведомлений на стороне бэкенда - обычно 1-2 дня работы разработчика, если сайт уже построен и есть база заказов. Основное время съедает не код, а модерация магазина в личном кабинете ЮKassa и подготовка юридических документов сайта.

Что делать, если уведомление payment.succeeded не приходит на сайт?

Сначала проверяю в личном кабинете ЮKassa журнал отправленных уведомлений и коды ответа вашего сервера - часто эндпоинт возвращает не 200 (например, из-за ошибки в коде или неверного SSL-сертификата), и ЮKassa считает уведомление недоставленным. Дальше смотрю логи самого сервера и проверяю, что URL уведомлений в настройках магазина совпадает с реальным адресом на проде, а не с тестовым доменом.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно