Ко мне регулярно приходят с одной и той же задачей: сайт написан руками, без Tilda и коробочных CMS, и нужно подключить ЮKassa к самописному сайту так, чтобы клиент платил картой прямо на сайте, деньги приходили на расчётный счёт, а бухгалтерия не мучилась с чеками задним числом. Ниже - рабочая схема, которую я использую в проектах на Laravel, Node.js и обычном PHP: от получения ключей API до обработки уведомлений и фискализации чеков по 54-ФЗ.
По теме статьи
Готовое решение
Ограничение доставки по зонам на карте в корзине Tilda
Виджет доставки в шапке сайта + ограничение оформления в корзине по зонам на Яндекс.Карте.
от9 000 ₽
Интернет-магазин
Интернет-магазин под ключ
Интернет-магазин под ключ — на Tilda, WordPress + WooCommerce, Next.js Commerce или кастомный бэкенд. Подберу платформу под бюджет, ассортимент и
от80 000 ₽
Что подготовить до подключения Ю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 уведомлений в настройках магазина совпадает с реальным адресом на проде, а не с тестовым доменом.