Опыт интеграции с Tilda

2021.03.25
Обзор разработки интеграции с Tilda с использованием WebHook и самописного API на php для запросов к Tilda, разработан внешний рабочий сервис
php

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

В статье речь идет о разработке внешнего сервиса на php, который внутри ведет работу с Tilda посредством WebHook и самописного API для запросов к Tilda.

Предыстория

Tilda - конструктор сайтов. Есть возможность прикрутить каталог с товарами, корзину, сбор заявок (которые нельзя редактировать) и эквайринг.

Кроме того есть возможность интеграции с внешними сервисами, они делятся на виды, но суть интеграций одна - отправка данных из форм в интегрируемый сервис:

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

Один из наших клиентов интегрировался с amoCRM, поэтому было решено разработать интеграция с этим продуктом, но как оказалось, в amoCRM не передается информация о статусе оплаты заказа, что было критично в нашем случае.

И тогда мы нашли способ интеграции по WebHook ...

Интеграция по WebHook

В документации подробно описано про WebHook.

Провели эксперименты ... получилось - мы смогли получать данные из Tilda в наш сервис. Но приключения только начинались ...

Когда подъехал второй клиент на Tilda, то оказалось что отправляемый по WebHook json может иметь ключи в разных регистрах: у одного клиента lower-case, у другого camel-sase. Это решается следующим образом на php:

$_POST = array_change_key_case($_POST);

Позже (через ~2 недели) клиенты организовали скидки по промокодам, которые распространяются на весь заказ, и как выяснилось:

Tilda не считает скидки для позиций товара, а лишь имеет данные об общей скидке (процентной или фиксированной).

Этот прикол мы разруливали (юридические консультации, возврат заказов, пересчет цен позиций заказов, создание чеков прихода) ~3 дня вперемешку с текущими задачами.

И под конец один из клиентов написал js скрипт индивидуальной скидки, это привело к тому, что цены позиций заказа и общая сумма заказа не совпадали. Клиент был предупрежден о невозможности поддержки не стандартизированных (сервисом Tilda или нами) фич и убрал свой скрипт.

Но статус оплаты в данных WebHook не приходил, и тогда пришлось искать другое решение ...

Разработка API для Tilda

Теперь (31.10.2022) это неактуально, при установке WebHook есть возможность установить флаг "Отправлять после оплаты". Но ниже описан интересный опыт :)

Дела обстояли так: оформленный заказ мы получали, а статус оплаты нет. Так как статус оплаты для нас является критичным, значит мы должны сами забрать информацию о платеже ... идем снифать запросы.

Первым делом необходимо пройти авторизацию.

Не забываем подставлять нужные заголовки исходящих запросов из консоли браузера.

Авторизация

Идем по адресу /login/ и видим что исходящий запрос в заголовках отправляет куки вида:

Cookie:	tildasid=1616609523493.626598; tildauid=1616609838237.285363; previousUrl=tilda.cc%2Fru%2F

Отправимся на предыдущю страницу сайта (она же главная). В ответ на запрос самой страницы заголовка Set-cookie нет. Тогда идем в отладчик и пробуем найти хотя бы одну куку в одном из подгружаемых скриптов ... и находим установку куки tildasid в файле /js/tildastat-0.2.min.js.

Куки при входе на главную страницу Tilda.cc: Куки при входе на главную страницу Tilda.cc

При детальном поиске обнаруживаем что tildasid и tildauid используют одну и ту же функцию генерации generateUniqID:

function generateUniqID() {
  var d = new Date();
  var uniq = '';
  var random = Math.floor(Math.random() * (999999 - 100000)) + 100000;
  uniq = '' + d.getTime() + '.' + random;
  return uniq
}

Теперь повторяем примерно такое же на php:

$iTimeMls = time();
$iTimeMcsUID = random_int(500000, 1000000);
$iTimeMcsSID = $iTimeMcsUID - random_int(100000, 200000);

А кука previousUrl у нас всегда имеет одно и тоже значение при авторизации.

Теперь пробуем авторизироваться через web-интерфейс и смотрим запросы.

Как оказалось для авторизации отправляется запрос /login/submit/, который при успешном результате отправляет в заголовках ответа куки вида (данные подкорректированы):

Set-Cookie: PHPSESSID=dc2kseiqlfpcv85dfe3578hk94; path=/; secure; HttpOnly
Set-Cookie: userid=1092881; expires=Wed, 07-Apr-2021 18:36:44 GMT; path=/; secure; httponly
Set-Cookie: hash=01a14f9a4d8cc987e08f0ad9353aec5d; expires=Wed, 07-Apr-2021 18:36:44 GMT; path=/; secure; httponly
Set-Cookie: registered=yes; expires=Thu, 24-Mar-2022 18:36:44 GMT; path=/; secure
Set-Cookie: tildacommonsessid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=.tilda.cc; secure; httponly
Set-Cookie: lang=RU; expires=Wed, 07-Apr-2021 18:36:44 GMT; path=/; secure

Если установить куки полученные при успешной авторизации через запрос /login/submit/ и установить куки полученные ранее (tildasid, tildauid), то при запросе /login/ будет получен 302 код ответа - редирект на страницу проектов /projects/. В ином случае код ответа 200.

Настало время авторизироваться через скрипт. Однако, на странице авторизации иногда появляется ReCaptcha, которую надо решать, иначе запрос авторизации закончится провалом. Но начнем с простейшего случая - когда капчи нет.

Опытным путем было выявлено что появление ReCaptcha имеет некие промежутки времени, и не зависит от количества неудачных авторизаций или IP адреса - она просто появляется в какие-то моменты времени, а когда-то её нет.

При отсутсвии необходимости ввода капчи запрос авторизации отправляет следующие post данные:

email=email&password=password&csrf=239vBL6ZC9

Как видно, необходимо передавать csrf, который можно найти в коде страницы ввода email/пароля (/login/) так:

$sCSRF = "";
$aMatches = [];
if(preg_match("/window\.csrf\=(?:\'|\")(.*?)(?:\'|\")\;/", $sResponse, $aMatches))
	$sCSRF = $aMatches[1];

Чтобы определить необходимость решения капчи нужно в коде страницы /login/ найти window.needcaptcha так:

$needCaptcha = false;
$aMatches = [];
if(preg_match("/window\.needcaptcha\=(?:\'|\")(.*?)(?:\'|\")\;/", $sResponse, $aMatches))
	$needCaptcha = ($aMatches[1] == 'y');

А ключ window.keycaptcha для ReCaptcha находим так:

$sKeyCaptcha = "";
$aMatches = [];
if(preg_match("/window\.keycaptcha\=(?:\'|\")(.*?)(?:\'|\")\;/", $sResponse, $aMatches))
	$sKeyCaptcha = $aMatches[1];

Теперь имея ключ капчи идем на сервис решения капчи rucaptcha.com, создаем акк, оплачиваем пару сотен рублей, и вызывая несколько API запросов этого сервиса получаем ключ решения капчи.

ReCaptcha может отсутсвовать при авторизации (на странице /login/ будет отсутствовать window.needcaptcha), поэтому не на каждом запросе авторизации понадобится решение капчи.

Имея ключ решения капчи пробуем авторизироваться добавив к post данным ключ g-recaptcha-response:

curl_setopt($this->m_hCurl, CURLOPT_POSTFIELDS, "email=".$this->m_sTildaLogin."&password=".$this->m_sTildaPass."&csrf=".$sCSRF.($needCaptcha ? "&g-recaptcha-response=$sReCaptchaResponse" : ""));

Получение заявок

Здесь уже намного проще. Заходим на страницу заявок проекта и смотрим запросы:

Запросы получения заявок с сайта на Tilda

Всего 3 запроса:

  1. /projects/leads/?projectid=id - html страница проекта,
  2. /projects/get/getleads/ - получение информации о количестве заявок и названии проекта,
  3. /projects/submit/leads/ - сами заявки.

У последних двух запросов ответ в виде json, хотя в заголовках ответа Content-Type: text/html; charset=utf-8.

У последних двух запросов адреса и ответы немного соответствуют друг другу.

Запрос получения заявок требует post параметров:

В ответе получаем json:

На данный момент Tilda имеет лимит по сроку хранения заявок, максимум 30 дней.

Итог

Смотря на опыт нескольких подключенных клиентов, на Tilda можно сделать прибыльный интернет-магазин, но назвать это полноценным интернет-магазином, сложно, это больше похоже на прототип.

В целом, интеграция с Tilda заняла около 2-х дней и оставила положиельные впечатления, как от программной работы, так и от работы с самим сервисом.

В телеграм канале DevOps от первого лица можно оставить комментарий или почитать интересные истории из практики DevOps