Приём оплаты выглядит просто, пока заказ ровно один — тот, который покупатель оформил сам на сайте. На боевом проекте, о котором пойдёт речь, заказов два вида, и они приходят разными путями: один через витрину, второй заводит менеджер руками — по телефону, по переписке, по счёту. Витрину закрывает готовый модуль CloudPayments. Второй путь готового решения не имеет вовсе, и его мы собрали сами — по API, внутри своей CRM.
Дальше — что именно настраивали в готовом модуле, что пришлось к нему дописать под особенности проекта и как устроена интеграция по API: какие методы вызываются, что мы делаем с ответами и почему уведомлению об оплате нельзя верить на слово.
Два пути заказа
- Путь витрины. Покупатель кладёт товар в корзину, оформляет заказ и платит картой на стороне CloudPayments. К моменту, когда заказ попадает в CRM, он уже оплачен.
- Путь менеджера. Заказ заводится в CRM Ось Бизнеса — это наш корпоративный портал: заказы, статусы, чеки, коды маркировки, работа менеджера. Дальше по тексту — просто «портал». Оплаты у такого заказа ещё нет: менеджер нажимает кнопку, портал создаёт ссылку на оплату и отправляет её клиенту.
Пути разные, а результат должен быть один: оплаченный заказ в портале, из которого дальше едет отгрузка, чек и доставка. Разберу обе половины: сначала путь витрины на готовом модуле, потом путь портала по API.
Путь витрины: готовый модуль
На сайте стоит WooCommerce и официальный модуль CloudPayments. Это правильный выбор: писать свою оплату на витрине, когда есть поддерживаемый модуль, — значит взять на себя чужую работу, включая обновления и совместимость с самим WooCommerce.
Что здесь на самом деле является работой, вопреки ожиданию «поставил и работает»:
- Терминалы. Их два — тестовый и боевой, у каждого своя пара идентификатора и секретного ключа. Тестовый настраивается легко, потому что им занимаются с самого начала; боевой подключают в последний момент и в спешке.
- Адреса уведомлений. Это отдельная настройка в личном кабинете, и она не переезжает с тестового терминала на боевой. Чем заканчивается забытая настройка на боевом терминале, разбираю отдельным разделом: мы на этом обожглись.
- Данные чека. Система налогообложения, ставка НДС, признаки предмета и способа расчёта — это не «галочки для бухгалтерии», от них зависит, пройдёт чек или нет.
- Сценарий возврата на сайт. После оплаты покупателя надо вернуть на страницу «Спасибо» — и вот здесь началось самое интересное.
Где стандартный модуль не закрыл всё
Как устроен сам магазин, на котором всё это работает, — в разборе про интернет-магазин и его связь с CRM.
Готовые модули пишутся под типовую сборку: обычный WooCommerce, обычная корзина, обычная страница благодарности. Живой магазин типовым не бывает — у него конструктор посадочных страниц, свой личный кабинет, свои сценарии, а за оплатой тянется хвост из доставки, маркировки и второго чека. Стандартный модуль закрывает не всё, и разница дописывается руками. У нас набралось три таких места на самой витрине — каждое отдельным плагином-надстройкой, чтобы не править чужой код и не потерять правки при обновлении модуля, — и одно принципиальное, которое витрина закрыть не может в принципе.
Страница «Спасибо» после оплаты
Оформление заказа на проекте идёт через конструктор воронок: у страницы благодарности свой адрес и свой механизм проверки, чей это заказ. Модуль возвращает покупателя по адресу, который перед отправкой прогоняется через функцию экранирования — и амперсанд в этом адресе превращается в html-мнемонику. Браузер видит в адресе решётку, честно считает всё после неё якорем и до сервера вторую половину параметров не доносит.
Итог: конструктор не получает идентификатор заказа и вместо страницы «Спасибо» показывает «Мы не можем найти для вас заказ». Деньги при этом списаны, заказ создан — но человек, который только что заплатил, видит ошибку.
Чиним не там, где сломалось, а там, где можно починить безопасно: на раннем этапе загрузки страницы восстанавливаем идентификатор заказа по его ключу, который в адресе уцелел. Ключ заказа — секретный токен, и конструктор следом всё равно сверяет его с заказом, так что проверка доступа не ослабляется. Приоритет обработчика — самый ранний: конструктор читает параметр позже, и опоздать нельзя.
Страница, падающая с ошибкой сервера
Второе место того же сюжета: модуль регистрирует обработчик на событие вывода квитанции, указывая метод, которого в его классе нет. На обычной странице благодарности это событие не наступает — и проблема не видна. Конструктор воронок его вызывает, и на актуальной версии PHP обращение к несуществующему методу роняет всю страницу посреди вывода заказа.
Решение — снять «мёртвый» обработчик, причём аккуратно: мы проходим по списку обработчиков события и убираем только те, у которых метод действительно отсутствует. Как только модуль поправят, надстройка сама превратится в ничего не делающую — ничего снимать будет уже нечего, и удалять её в спешке не придётся.
Пункт «Способы оплаты» в личном кабинете
Третье — уже не про баг, а про то, как разные части системы понимают друг друга. WooCommerce показывает в кабинете пункт «Способы оплаты», только если среди доступных платёжных методов есть поддержка сохранённых карт. Доступность методов считается по корзине, а в кабинете корзина пуста — и CloudPayments честно отвечает, что сейчас недоступен. Пункт меню пропадает, хотя сама страница рабочая и ведёт к добавлению карты.
Возвращаем пункт на место сами — там же, где он был, после адресов. Ровно один фильтр, но без него у клиента нет способа привязать карту.
Чего у модуля нет и быть не может
Три предыдущих места — это разница между типовой сборкой и нашей. А есть граница, за которой готовое решение заканчивается не из-за качества, а по устройству: модуль знает про оплату и не знает ничего про то, что с заказом дальше. Доехал ли он, какие коды маркировки уехали клиенту, пора ли пробивать второй чек — всё это живёт вне витрины.
А чеков при предоплате два. Первый — на аванс, в момент оплаты; его закрывает и модуль, и наша ссылка. Второй — финальный, с зачётом аванса, когда товар фактически передан покупателю. В маркированном товаре именно он несёт коды единиц, и именно с него код уходит из оборота в Честный Знак через ОФД. Готовый модуль такой чек не пробьёт никогда: у него нет ни статуса доставки, ни кодов, которые пришли со склада вместе с отгрузкой.
Ручной вариант выглядит так: менеджер каждый день просматривает доставленные заказы, ищет по ним коды, пробивает чеки в кабинете кассы и следит, чтобы ни один не потерялся. На десятке заказов это терпимо, на потоке — нет: пропущенный финальный чек означает и незакрытый аванс, и код, который так и остался в обороте.
Поэтому вторую половину чековой цепочки делает портал и делает сам: заказ доставлен → выдержка, чтобы человек успел вмешаться → финальный чек с кодами → списание в Честном Знаке. Это и есть та часть, которую ни один готовый модуль за вас не закроет — не потому, что он плохой, а потому, что нужных данных у него нет и взять их неоткуда.
⚠️ Общий вывод по этой части: надстройка вместо правки чужого кода. Каждая доработка живёт отдельным плагином с описанием, что она делает и когда её можно удалить. Обновление модуля тогда не откатывает правки, а разработчик, который придёт после нас, видит причину, а не загадочный код внутри вендорского каталога.
Путь менеджера: интеграция по CloudPayments API
Теперь вторая половина — та, ради которой всё и затевалось. Заказ, созданный менеджером в портале, оплатить на сайте нельзя: его нет в корзине и не будет. Нужна ссылка на оплату — персональная, на конкретную сумму и с привязкой к номеру заказа.
Делает это портал сам, обращаясь к API CloudPayments. Разберём по шагам, как это работает.

Шесть шагов, и пятый — не формальность: именно на нём решается, действительно ли заказ оплачен.
Кнопка у менеджера
В карточке заказа есть кнопка «Ссылка на оплату». Нажатие уходит на нашу ручку в портале, и первое, что она делает, — проверяет права: создавать ссылки на оплату может сотрудник, а не кто угодно, кто узнал адрес. Дальше проверяется сумма: ноль или отрицательное — отказ до всякого обращения наружу.
Ключи доступа портал берёт только из окружения сервера. В коде их нет ни в каком виде — это правило появилось у нас после отдельного разбора и распространяется на все секреты: копия проекта, уехавшая другому клиенту, не должна нести с собой чужие ключи.
Что уходит в CloudPayments
Портал вызывает метод создания заказа (Orders API) и передаёт:
- сумму и валюту — из заказа, округлённые до копеек;
- номер заказа как идентификатор счёта. Это ключевое поле: по нему мы потом опознаем платёж;
- описание — «Оплата заказа №…», его человек видит на странице оплаты;
- почту и телефон клиента. Берутся из карточки клиента, а если заказ оформлен на лид без личного кабинета — из адреса доставки. Мелочь, но без неё ссылка не уйдёт и чек будет некуда отправить;
- данные чека отдельным блоком: позиции с ценой и количеством, доставка отдельной строкой как услуга, система налогообложения и ставка НДС.
Про доставку отдельно: она в заказе не является позицией, но в чеке обязана быть строкой. Считаем её как разницу между итогом заказа и суммой позиций — так строка появляется ровно тогда, когда доставка платная, и никогда не расходится с итогом.
⚠️ Обращение к CloudPayments мы делаем с повторами — до трёх попыток. Причина не в самом сервисе: сеть между сервером портала и внешним API периодически рвётся, и разовый сбой не должен показывать менеджеру «Ошибка сети» на глазах у клиента, ждущего ссылку.
Что происходит с полученной ссылкой
Ответ содержит адрес страницы оплаты. Дальше портал делает три вещи, и все три важны:
- Сохраняет ссылку в заказ. Идентификатор чужой системы сохраняется сразу после ответа — до любых следующих действий. Иначе повторная попытка создаст второй платёжный заказ на тот же товар.
- Отправляет ссылку на почту клиенту — письмом с кнопкой и той же ссылкой текстом, на случай, если кнопка не прорисуется. Менеджер при этом видит у себя кнопку «Копировать» и может продублировать ссылку в мессенджер: правило простое — на почту уходит всегда, руками дублируется по ситуации.
- Пишет строку в историю заказа: кто создал ссылку, когда и ушло ли письмо. Через неделю вопрос «мы вообще присылали ему оплату?» имеет ответ, а не версии.
Как портал узнаёт, что деньги пришли
Клиент оплатил — CloudPayments присылает уведомление на наш адрес. И вот здесь главное решение всей интеграции.
Телу уведомления мы не верим. Из него берётся ровно одно поле — номер транзакции. С ним портал сам обращается к CloudPayments методом получения платежа и уже из ответа сервиса читает: статус, номер счёта и сумму. Заказ переводится в «оплачен», только если:
- сервис подтвердил, что платёж списан (статус завершённого);
- номер счёта совпал с номером нашего заказа;
- сумма совпала с суммой заказа;
- заказ ещё не оплачен и не ушёл дальше по цепочке.
Зачем так. Адрес уведомления — открытая точка в интернете: его знает не только платёжный сервис. Если верить телу запроса, любой, кто узнал адрес, отправит на него «заказ №123 оплачен» — и заказ уедет на склад бесплатно. При перепроверке подделка не проходит: транзакции с таким номером либо нет, либо она не про наш заказ и не на нашу сумму.
Проверка суммы — второй слой. Она закрывает не подделку, а расхождение: частичную оплату, оплату не той ссылкой, изменение заказа после выставления счёта. Все три случая выглядят как «оплачено» и все три означают «разбираться человеку».
⚠️ И отдельное правило про ответ: если перепроверить не удалось — сеть, таймаут, — портал отвечает так, что CloudPayments повторит уведомление. Не «ошибка», после которой сервис перестанет пытаться, и не «принято», после которого оплата потеряется молча. Уведомление должно вернуться, когда связь восстановится.
Итог по опознанию платежа: заказ становится оплаченным по данным, полученным от сервиса нашим запросом, а не по данным, пришедшим к нам снаружи. Уведомление здесь — только сигнал «пойди проверь».
Что происходит, если не оплатили
Ссылка живёт, клиент думает, заказ висит в ожидании. Раз в четверть часа портал проходит по ручным заказам в ожидании оплаты и отменяет те, что старше установленного срока — по умолчанию четыре часа, срок меняется в настройках, лезть в код не нужно. Каждая отмена пишется в историю заказа с причиной.
Две тонкости, ради которых это отдельная механика:
- Отменяются только ручные заказы. Заказ с витрины приходит уже оплаченным, и автоматике там делать нечего.
- Срок — это не «когда надоест ждать», а компромисс: достаточно, чтобы человек успел дойти до карты, и достаточно мало, чтобы товар не висел зарезервированным сутками.
Грабля, которая стоила первого заказа
Первый боевой заказ завис в ожидании оплаты, хотя деньги были списаны. Код был ни при чём: на боевом терминале не были заполнены адреса уведомлений. Тестовый настраивали вдумчиво, боевой подключали в день запуска — ключи перенесли, настройку уведомлений не заметили.
Симптом при этом обманчивый: у клиента всё хорошо, деньги ушли, чек пришёл. Плохо только у нас — и узнаём мы об этом от клиента, а не от системы.
⚠️ При переезде на боевые ключи проверяйте не только ключи, но и настройки уведомлений, и проводите первый боевой платёж на минимальную сумму сами, глядя в журнал портала. Тестовый терминал этой ошибки не покажет никогда — он настроен.
О чеках — отдельно
Как именно устроена фискальная часть, в эту статью не поместилось, а материала там на самостоятельный разбор: состав чека предоплаты, фискальный документ, который возвращается вебхуком и целиком сохраняется в заказ, финальный чек с зачётом аванса и кодами маркировки, предпросмотр перед пробитием, чек возврата и сверка с ОФД — потому что платёжный сервис отвечает «успешно» до фискализации, и ошибка чека всплывает позже и только в личном кабинете. Это будет следующая статья — и, похоже, самая неприятная в серии: там ровно те места, где всё выглядит успешным, а на деле не пробилось. Ждите анонса в наших каналах в Telegram и MAX.
Что стоит выяснить до начала работ
Если вы подключаете эквайринг к чему-то сложнее одной корзины, я бы прошёл по этому списку заранее:
- Сколько у вас путей заказа. Витрина, менеджер, счёт, подписка, доплата — под каждый нужен свой способ получить оплату, и готовый модуль закрывает только первый.
- Насколько типовая у вас витрина. Конструктор посадочных страниц, свой кабинет, нестандартное оформление — это места, где стандартный модуль потребует доработок.
- Кто и как узнаёт об оплате. Уведомление, опрос, ручная сверка — и что произойдёт, если уведомление не дойдёт.
- Перепроверяете ли вы платёж. Если система верит телу уведомления — это дыра, и закрывается она одним дополнительным запросом.
- Кто пробивает второй чек. При предоплате их два, и финальный — с зачётом аванса, а в маркированном товаре ещё и с кодами — витрина не пробьёт: она не знает ни о доставке, ни о кодах.
- Что происходит с неоплаченным заказом. Висит вечно, отменяется руками или сам — и через какое время.
- Настройки боевого терминала. Ключи, адреса уведомлений, реквизиты чека — по списку, а не по памяти.
Как устроено то, что происходит с заказом после оплаты, — в разборах про отправку заказов Яндекс.Доставкой и про чеки по 54-ФЗ: доставка и вторая половина фискальной цепочки.
Общий принцип тот же, что и в других интеграциях: ломается не сложная логика, а стыки, где две системы по-разному понимают простое. «Оплачено» — самое опасное из таких слов, потому что за ним едет отгрузка.
Если вы работаете с CloudPayments
Всё описанное — не разовая история под один магазин. Настройка модуля на витрине, доработки под нетиповую сборку сайта, ссылки на оплату по API, перепроверка платежа, автоматическая отмена неоплаченного и фискальная часть уже пройдены на боевом проекте, и эти наработки применимы у любого клиента CloudPayments: сервис тот же, методы те же, грабли те же. Проходить их заново не нужно.
Что обычно входит в такую работу:
- разбор ваших путей заказа: витрина, менеджер, счёт, доплата, возврат;
- подключение витрины и доработки под вашу сборку сайта — отдельными надстройками, которые переживают обновления;
- ссылки на оплату из вашей учётной системы, с письмом клиенту и историей в заказе;
- приём уведомлений об оплате с перепроверкой на стороне сервиса;
- фискальная часть: чек предоплаты, финальный чек, возврат, сверка с ОФД;
- то, о чём вспоминают последним: что происходит с заказом, который не оплатили, и куда придёт сигнал, когда обмен сломается.
Особенности вашего бизнеса — не помеха для интеграции, а её содержание: типовую часть закрывает готовый модуль, а всё остальное дописывается, и именно в этом «остальном» обычно и живёт то, ради чего магазин работает именно так, а не как у всех.
Хотите такую же связку у себя — расскажите о задаче.
Коротко: частые вопросы
- Можно ли выставить ссылку на оплату из своей системы?
- Да. Заказ, созданный менеджером, оплачивается персональной ссылкой: система создаёт её через API платёжного сервиса, отправляет клиенту на почту и сохраняет в заказе.
- Почему нельзя верить уведомлению об оплате?
- Адрес уведомления открыт в интернет, и отправить на него «заказ оплачен» может кто угодно. Из уведомления берётся только номер транзакции, а статус, счёт и сумма запрашиваются у платёжного сервиса отдельным запросом.
- Что делать, если платёж прошёл, а система об этом не узнала?
- Первым делом проверить настройки уведомлений на боевом терминале: они не переезжают с тестового вместе с ключами. Мы потеряли на этом первый заказ.