Как подключить оплату Click на сайт
Практика3 минРедакция Innovialabs
Коротко
Click присылает на ваш сервер два запроса — Prepare (проверить заказ и зарезервировать его) и Complete (зафиксировать оплату); ваша задача — проверить подпись, сумму и статус заказа и отвечать идемпотентно.
Коротко: как устроена оплата Click
Для интернет-магазинов Click использует схему Shop API: покупатель уходит на страницу оплаты Click, а Click сам обращается к вашему серверу двумя запросами.
- Prepare (
action=0) — Click спрашивает: «Такой заказ существует, сумма верная, его можно оплатить?» Вы проверяете и отвечаете кодом ошибки0(всё в порядке) или кодом отказа. - Complete (
action=1) — Click сообщает результат списания. Если оплата прошла, вы переводите заказ в «оплачен». Если Click передал ошибку — отменяете резерв.
Чтобы начать, нужен договор с Click и данные мерчанта: service_id, merchant_id, merchant_user_id и секретный ключ. Адреса Prepare и Complete вы указываете в кабинете мерчанта.
Что приходит в запросах
Click отправляет POST-запрос с параметрами формы. Основные поля:
click_trans_id— ID транзакции на стороне Click;service_id— ваш сервис;merchant_trans_id— ID заказа в вашей системе (то, что вы передали при создании ссылки);amount— сумма;action— 0 для Prepare, 1 для Complete;error,error_note— статус на стороне Click (важно в Complete);sign_time,sign_string— время и подпись;merchant_prepare_id— только в Complete: ID, который вы вернули на шаге Prepare.
Ответ — JSON с click_trans_id, merchant_trans_id, merchant_prepare_id (или merchant_confirm_id), error и error_note.
Проверка подписи
Подпись — это MD5 от склеенных полей и секретного ключа. Порядок полей описан в официальной документации Click; для Complete в строку добавляется merchant_prepare_id. Сверяйте формулу с актуальной документацией перед запуском.
import { createHash } from "crypto";
function checkSign(p: Record<string, string>, secret: string) {
const base =
p.click_trans_id + p.service_id + secret + p.merchant_trans_id +
(p.action === "1" ? p.merchant_prepare_id : "") +
p.amount + p.action + p.sign_time;
const md5 = createHash("md5").update(base).digest("hex");
return md5 === p.sign_string;
}
Если подпись не совпала — сразу отвечайте кодом ошибки подписи и ничего не меняйте в базе.
Логика Prepare и Complete
В Prepare проверьте:
- подпись;
- что заказ с
merchant_trans_idсуществует; - что
amountсовпадает с суммой заказа (сравнивайте как числа, а не строки); - что заказ ещё не оплачен и не отменён.
Затем создайте запись транзакции со статусом «подготовлена» и верните её ID как merchant_prepare_id.
В Complete проверьте:
- подпись и что
merchant_prepare_idсуществует; - поле
error: если оно отрицательное, Click сообщает об отмене — пометьте транзакцию отменённой; - если
error = 0— переведите заказ в «оплачен» в одной транзакции базы данных с обновлением записи платежа.
Как не сломать статусы заказов
- Идемпотентность. Click может повторить запрос. Если Complete для этой транзакции уже обработан, верните тот же ответ, а не ошибку и не повторное начисление.
- Блокировки. Обновляйте заказ через
SELECT ... FOR UPDATEили аналог, чтобы два одновременных запроса не оплатили заказ дважды. - Одна оплата на заказ. Если заказ уже оплачен через другой способ, отвечайте соответствующим кодом ошибки.
- Сумма фиксируется. Не меняйте сумму заказа после Prepare — иначе Complete придёт с расхождением.
- Логи. Сохраняйте каждый входящий запрос и ваш ответ: это главный инструмент при спорных платежах.
Тестирование
- Поднимите эндпоинты на доступном из интернета адресе с HTTPS (для локальной разработки подойдёт туннель).
- Прогоните сценарии: успешная оплата, неверная сумма, несуществующий заказ, повторный Complete, отмена со стороны Click.
- Проверьте, что при неверной подписи база не меняется.
- Сделайте реальный платёж на минимальную сумму перед запуском.
Частые ошибки
- Проверка подписи с неправильным порядком полей.
- Сравнение
amountкак строк:"1000"и"1000.00"не равны. - Перевод заказа в «оплачен» уже на шаге Prepare.
- Ответ HTTP-ошибкой вместо JSON с кодом — Click ожидает структурированный ответ.