Отправка цели "Заказ оплачен" в Яндекс Метрику, если пользователь не вернулся на сайт из платёжной системы

Приветствую, друзья.

Расскажу, как я решил задачу с отправкой цели «Заказ оплачен» в Яндекс.Метрику, точнее статуса Заказа на «Оплачен». Что здесь такого, можете подумать вы? Вот и я так же подумал, но всё оказалось несколько сложнее, и готовых решений на этот счёт тоже нет.

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

Т.к. мы не можем на это повлиять, то решать этот вопрос мы будем с помощью API Метрики, офлайн-конверсий, плагина для MODX, а также нам придется расширять поля заказа.

В моём случае для онлайн-платежей использовался компонент mspYooKassa, главное, чтобы после оплаты заказа было автоматическое изменение статуса заказа, это всё, что нужно для корректной работы.

Начнём с простого и создадим цель JavaScript событие в Метрике. Для работы нам понадобится только идентификатор цели, который вы придумаете.



Теперь нам необходимо получить OAuth-токен для работы с API Метрики. Я не буду на этом останавливаться. Подробно о том, как это сделать можно найти в документации к API Метрики.

Далее необходимо расширить поля заказа. Зачем это нужно я расскажу чуть позже. Для этого конкретно в своём случае я воспользовался компонентом msOrderFields, но вы можете сделать это и другими удобными для вас способами. В интернете можно найти и бесплатные решения.

Создаем новое поле с ключом _ym_uid



Я добавил его на вкладке Адрес, не суть важно где оно будет, можете сделать для него отдельную вкладку, нам главное записать в него ClientID, который пользователю присваивает Метрика (значение хранится в cookie) и в дальнейшем по этому ID будет осуществляться привязка офлайн-конверсии. Подробно об этом можно почитать также в документации.

Добавляем плагин на 2 события: msOnBeforeCreateOrder и msOnChangeOrderStatus.

<?php
switch ($modx->event->name) {
    case 'msOnChangeOrderStatus':
        if ($status == 2) {
            $counterId = 'ваш  код счётчика'; // ID счетчика
            $apiToken = 'полученный OAuth-токен'; // OAuth-токен
            $goal = 'название цели какое вы придумали'; // Название цели
            $timestamp = time(); // Время в формате Unix Timestamp
            
            // Проверяем наличие объекта заказа
            if (!isset($order) || !is_object($order)) {
                $modx->log(modX::LOG_LEVEL_ERROR, "[YandexMetrika] Объект заказа не доступен");
                return;
            }

            $clientId = $order->Address->get('_ym_uid');
            //$modx->log(modX::LOG_LEVEL_ERROR, "[YandexMetrika] ID клиента {$clientId}");
            
            if (empty($clientId)) {
                $modx->log(modX::LOG_LEVEL_ERROR, "[YandexMetrika] ID клиента не задан");
                return;
            }

            $url = "https://api-metrika.yandex.net/management/v1/counter/{$counterId}/offline_conversions/upload";

            // Генерируем уникальное имя CSV-файла
            $randomNumber = mt_rand(100000, 999999);
            $csvFilePath = MODX_BASE_PATH . "assets/tmp/conversions_{$timestamp}_{$randomNumber}.csv";

            // Генерируем CSV-данные
            $csvData = "ClientID,Target,DateTime\n"; // Заголовки CSV
            $csvData .= "{$clientId},{$goal},{$timestamp}\n"; // Данные конверсии

            if (file_put_contents($csvFilePath, $csvData) === false) {
                $modx->log(modX::LOG_LEVEL_ERROR, "[YandexMetrika] Не удалось создать CSV-файл: {$csvFilePath}");
                return;
            }

            // Формируем POST-данные
            $postFields = [
                'file' => new CURLFile($csvFilePath, 'text/csv', basename($csvFilePath)),
            ];

            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                "Authorization: OAuth {$apiToken}",
                "Content-Type: multipart/form-data"
            ]);

            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $error = curl_error($ch);
            curl_close($ch);

            // Удаляем временный CSV-файл после отправки
            if (file_exists($csvFilePath)) {
                unlink($csvFilePath);
            }

            // Проверяем результат
            if ($response === false) {
                $modx->log(modX::LOG_LEVEL_ERROR, "[YandexMetrika] Ошибка выполнения cURL: {$error}");
                return;
            }

            $responseData = json_decode($response, true);

            if ($httpCode == 200) {
                $modx->log(modX::LOG_LEVEL_ERROR, "[YandexMetrika] Цель '{$goal}' успешно отправлена для посетителя с ID {$clientId}");
            } else {
                $modx->log(modX::LOG_LEVEL_ERROR,
                    "[YandexMetrika] Ошибка при отправке цели '{$goal}' для посетителя с ID {$clientId}. " .
                    "Код ответа: {$httpCode}. " .
                    "Ошибка cURL: " . ($error ? $error : 'нет') . ". " .
                    "Ответ API: " . print_r($responseData, true)
                );
            }
        }
        break;

    case 'msOnBeforeCreateOrder':
        $_ym_uid = $_COOKIE['_ym_uid'];
        if(!empty($_ym_uid)){
            $msOrder->Address->set('_ym_uid', $_ym_uid);
        }
        break;
}

Что делает плагин?

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



Теперь, когда мы имеем ClientID — нам уже всё равно вернётся посетитель из платежной системы или нет, т.к. основная часть плагина будет срабатывать на изменение статуса заказа по id, а именно, статус «Оплачен».

Согласно документации того же Яндекса рекомендуемым способом загрузки офлайн конверсий является csv файл, хотя можно и напрямую отправить данные, я всё же решил сделать это рекомендуемым способом через CSV. При каждой отправке заказа мы будем создавать временный файл по пути assets/tmp

Лучше создать директорию assets/tmp заранее, чтобы не было проблем.

Здесь же стоит проверка на наличие в заказе ClientID, чтобы не отправлять в Метрику, например, менеджерские заказы, когда заказ создаётся менеджером в админке и ClientID там пустой. Помимо этого, без ClientID мы просто не сможем привязать оплату заказа к посетителю и Метрика вернёт нам ошибку. Для таких случаев потребуется применять другие идентификаторы, но это уже отдельный вопрос.

Если посетитель оформил заказ на сайте без оплаты, и пришел забирать заказ в офис или на пункт выдачи с оплатой на месте, например, наличными, то при изменении статуса заказа в админке менеджером конверсия будет засчитана, т.к. у заказа есть ClientID.

У офлайн-конверсий есть ограничение:

Для всех офлайн-данных (офлайн-конверсий, звонков, заказов из CRM) период дополнения визитов составляет 21 день. Данные будут добавлены к визиту, если между последним визитом посетителя на сайт и моментом обработки файла с данными о конверсиях прошло не больше 21 дня.

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

По итогу в Метрике мы будем наблюдать такую картину:



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

В разделе «Конверсии» всё будет стандартно:





Вот такое нетривиальное решение оказалось. Традиционно, если вы найдёте ошибки — прошу меня поправить.

Спасибо за внимание!

Дмитрий
01 апреля 2025, 13:48
modx.pro
2
404
+8

Комментарии: 1

Дима Касаткин
01 апреля 2025, 14:22
+1
Большое спасибо за качественное и подробное описание!

Вообще считаю, что в нынешние времена, веб-аналитика в минишопе должна быть если не из коробки, то хотя бы модулем каким-то готовым. На самом деле конверсии полезно отправлять не только в Метрику, но и в другие счетчики (GA, VK, TMR и даже в тикток пиксель иногда, и другие)

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