Содержание
Синхронизация товаров
Создание товаров в Тильде
На странице сайта добавим блог из раздела Магазин, например, ST305N.

Нажимаем на Контент.

Прокручиваем настройки контента в самый низ и находим фразу: “Если у вас много товаров, для управления ими используйте каталог товаров”. Нажимаем на каталог товаров.

Вы попадете на страницу создания каталога. Нажмите кнопку Начать работать с каталогом.

В каталоге есть предустановленные товары для примера.

Удаляем эти товары и создаем свои, нажав на кнопку Добавить товар.
Вводим название товара и нажимаем Добавить.

Откроется карточка товара, в которой нужно заполнить все параметры и нажать Сохранить и закрыть. Аналогично добавляем другие товары.
Если у вас уже были добавлены товары в виде карточки, а не в каталоге, то их можно перенести в каталог. В настройках контента блока ST305N, или аналогичного, в самом низу нажмите Скопировать товары в каталог из данного блока.

Для смены раздела нажимаем Разделы каталога.

Раздел по умолчанию удаляем с помощью значка корзины. Новый раздел добавляем с помощью кнопки Добавить раздел.

Теперь экспортируем товары для загрузки в Битрикс24.




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

Обратите внимание, что на странице сайта появилась вкладка Товары.

Вернемся на страницу магазина и к карточкам товаров в блоке ST305N. Прокручиваем в самый низ настроек и в параметре Выводить товары из каталога выбираем раздел или все товары. Нажимаем Сохранить и закрыть.

Вместо предустановленных товаров в блоке ST305N появились товары каталога. Можно публиковать страницу.

Импорт товаров в Битрикс24
Переходим в Битрикс24, CRM —> Товары и Склады —> Каталог товаров.

Далее нажмите на шестеренку и выберите Импорт товаров.

Появятся параметры импорта. Выберите файл CSV с товарами, который мы выгрузили в Тильде, и нажмите Далее.

На втором шаге необходимо сопоставить поля каталогов в Битриксе и Тильде.
- Tilda UID — Внешний код;
- Title — Название;
- Description — Описание;
- Photo — Детальная картинка;
- Price — Цена.
Нажмите Далее.


На последнем этапе вы увидите сколько товаров импортировано. Нажмите Готово.

Перед вами откроется каталог товаров.

Если вы действуете в обратном порядке, и товары в Битриксе у вас уже есть, то их можно выгрузить в Excel, сохранить в CSV и загрузить в Тильду. К сожалению, картинки при таком варианте автоматом не выгрузятся.

С товарами разобрались, сопоставлять будем по внешнему коду. Теперь переходим непосредственно к интеграции.
Интеграция Тильды с Битрикс24
Сначала создадим вебхук в битриксе.
Настройка вебхука в Б24
В левом меню Битрикс24 Разработчикам —> Другое —> Входящий вебхук.



Редактируем название, метод можно не трогать, в настройке прав выбираем CRM(crm), сохраняем.


Вебхук для вызова rest api нам понадобится далее. Так что либо скопируйте сразу, либо оставьте вкладку с вебхуком открытой.

Созданные вебхуки всегда можно посмотреть в интеграциях. Разработчикам —> Интеграции.

Настройка вебхука на стороннем сервере

Заходим в папку сайта, в котором хотим разместить вебхук, далее public_html и создаем три файла.

- Tilda.php
[
'TITLE' => $_POST['Name'].', '.$_POST['Email'].', '.$_POST['Phone'],
"TYPE_ID" => "GOODS",
"STAGE_ID" => "NEW",
"OPENED" => "Y",
"ASSIGNED_BY_ID" => 1,
"PROBABILITY" => 90,
"CURRENCY_ID" => "RUB",
"OPPORTUNITY" => (float)$_POST['payment']['amount'],
]
]
);
$ID = (int)$resultDeal['result'];
//CRest::log($_POST, '', '/pak.log');
//CRest::log($resultDeal, 'ID='.$ID, '/pak.log');
if ($ID > 0)
{
$rows = array();
$sort = 1;
foreach($_POST['payment']['products'] as $key => $item)
{
$item['sku'] = trim($item['sku']);
$pid = 0;
$name = $item['name'];
$measure = '9';
if (!empty($item['sku']))
{
$resultProduct = CRest::call(
'crm.product.list',
[
'order' => array( "ID" => "ASC" ),
'filter' => array( "PROPERTY_107" => $item['sku']),
'select' => [ "ID", "NAME", "MEASURE" ]
]
);
if ($resultProduct['total'] == 1)
{
$pid = $resultProduct['result'][0]['ID'];
$measure = $resultProduct['result'][0]['MEASURE'];
}
else
{
$name = $item['sku'].': '.$item['name'];
}
unset($resultProduct);
}
$rows[] = array(
'PRODUCT_ID' => $pid,
'PRODUCT_NAME' => $name,
'PRICE' => (float)$item['price'],
'QUANTITY' => (float)$item['quantity'],
'SORT' => $sort++,
'TAX_RATE' => 15,
'TAX_INCLUDED' => 'Y',
'MEASURE_CODE' => $measure
);
}
//CRest::log($rows, 'ROWS', '/pak.log');
$result = CRest::call(
'crm.deal.productrows.set',
[
'id' => $ID,
'rows' => $rows
]
);
//CRest::log($result, 'RESULT', '/pak.log');
}
?>
- crest.php
'.$str.' '.$val.PHP_EOL.'
';
}
public static function log($val, $str = '', $log_file = '/upload/tmp/clan_dev.log')
{
if (!empty($log_file))
{
if (substr($log_file, 0, 1) == '/')
{
$f = fopen($_SERVER["DOCUMENT_ROOT"] . $log_file, 'a');
if (!$f)
die('Alarm! Log-file not accepted!' . $_SERVER["DOCUMENT_ROOT"] . $log_file);
$rc = fwrite($f, date('j.m.Y H:i:s ') . (!empty($str) ? $str . ' = ' : ' ') . print_r($val, 1) . PHP_EOL);
if (!$rc)
die('Alarm! Log-file not writable! ' . $_SERVER["DOCUMENT_ROOT"] . $log_file);
fclose($f);
}
}
}public static function installApp()
{
$result = [
'rest_only' => true,
'install' => false
];
if($_REQUEST[ 'event' ] == 'ONAPPINSTALL' && !empty($_REQUEST[ 'auth' ]))
{
$result['install'] = static::setAppSettings($_REQUEST[ 'auth' ], true);
}
elseif($_REQUEST['PLACEMENT'] == 'DEFAULT')
{
$result['rest_only'] = false;
$result['install'] = static::setAppSettings(
[
'access_token' => htmlspecialchars($_REQUEST['AUTH_ID']),
'expires_in' => htmlspecialchars($_REQUEST['AUTH_EXPIRES']),
'application_token' => htmlspecialchars($_REQUEST['APP_SID']),
'refresh_token' => htmlspecialchars($_REQUEST['REFRESH_ID']),
'domain' => htmlspecialchars($_REQUEST['DOMAIN']),
'client_endpoint' => 'https://' . htmlspecialchars($_REQUEST['DOMAIN']) . '/rest/',
],
true
);
}static::setLog(
[
'request' => $_REQUEST,
'result' => $result
],
'installApp'
);
return $result;
}/**
* @var $arParams array
* $arParams = [
* 'method' => 'some rest method',
* 'params' => []//array params of method
* ];
* @return mixed array|string|boolean curl-return or error
*
*/
protected static function callCurl($arParams)
{
if(!function_exists('curl_init'))
{
return [
'error' => 'error_php_lib_curl',
'error_information' => 'need install curl lib'
];
}
$arSettings = static::getAppSettings();
if($arSettings !== false)
{
if(isset($arParams[ 'this_auth' ]) && $arParams[ 'this_auth' ] == 'Y')
{
$url = 'https://oauth.bitrix.info/oauth/token/';
}
else
{
$url = $arSettings[ "client_endpoint" ] . $arParams[ 'method' ] . '.' . static::TYPE_TRANSPORT;
if(empty($arSettings[ 'is_web_hook' ]) || $arSettings[ 'is_web_hook' ] != 'Y')
{
$arParams[ 'params' ][ 'auth' ] = $arSettings[ 'access_token' ];
}
}$sPostFields = http_build_query($arParams[ 'params' ]);try
{
$obCurl = curl_init();
curl_setopt($obCurl, CURLOPT_URL, $url);
curl_setopt($obCurl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($obCurl, CURLOPT_POSTREDIR, 10);
curl_setopt($obCurl, CURLOPT_USERAGENT, 'Bitrix24 CRest PHP ' . static::VERSION);
if($sPostFields)
{
curl_setopt($obCurl, CURLOPT_POST, true);
curl_setopt($obCurl, CURLOPT_POSTFIELDS, $sPostFields);
}
curl_setopt(
$obCurl, CURLOPT_FOLLOWLOCATION, (isset($arParams[ 'followlocation' ]))
? $arParams[ 'followlocation' ] : 1
);
if(defined("C_REST_IGNORE_SSL") && C_REST_IGNORE_SSL === true)
{
curl_setopt($obCurl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($obCurl, CURLOPT_SSL_VERIFYHOST, false);
}
$out = curl_exec($obCurl);
$info = curl_getinfo($obCurl);
if(curl_errno($obCurl))
{
$info[ 'curl_error' ] = curl_error($obCurl);
}
if(static::TYPE_TRANSPORT == 'xml' && (!isset($arParams[ 'this_auth' ]) || $arParams[ 'this_auth' ] != 'Y'))//auth only json support
{
$result = $out;
}
else
{
$result = static::expandData($out);
}
curl_close($obCurl);if(!empty($result[ 'error' ]))
{
if($result[ 'error' ] == 'expired_token' && empty($arParams[ 'this_auth' ]))
{
$result = static::GetNewAuth($arParams);
}
else
{
$arErrorInform = [
'expired_token' => 'expired token, cant get new auth? Check access oauth server.',
'invalid_token' => 'invalid token, need reinstall application',
'invalid_grant' => 'invalid grant, check out define C_REST_CLIENT_SECRET or C_REST_CLIENT_ID',
'invalid_client' => 'invalid client, check out define C_REST_CLIENT_SECRET or C_REST_CLIENT_ID',
'QUERY_LIMIT_EXCEEDED' => 'Too many requests, maximum 2 query by second',
'ERROR_METHOD_NOT_FOUND' => 'Method not found! You can see the permissions of the application: CRest::call(\'scope\')',
'NO_AUTH_FOUND' => 'Some setup error b24, check in table "b_module_to_module" event "OnRestCheckAuth"',
'INTERNAL_SERVER_ERROR' => 'Server down, try later'
];
if(!empty($arErrorInform[ $result[ 'error' ] ]))
{
$result[ 'error_information' ] = $arErrorInform[ $result[ 'error' ] ];
}
}
}
if(!empty($info[ 'curl_error' ]))
{
$result[ 'error' ] = 'curl_error';
$result[ 'error_information' ] = $info[ 'curl_error' ];
}static::setLog(
[
'url' => $url,
'info' => $info,
'params' => $arParams,
'result' => $result
],
'callCurl'
);return $result;
}
catch(Exception $e)
{
static::setLog(
[
'message' => $e->getMessage(),
'code' => $e->getCode(),
'trace' => $e->getTrace(),
'params' => $arParams
],
'exceptionCurl'
);return [
'error' => 'exception',
'error_exception_code' => $e->getCode(),
'error_information' => $e->getMessage(),
];
}
}
else
{
static::setLog(
[
'params' => $arParams
],
'emptySetting'
);
}return [
'error' => 'no_install_app',
'error_information' => 'error install app, pls install local application '
];
}/**
* Generate a request for callCurl()
*
* @var $method string
* @var $params array method params
* @return mixed array|string|boolean curl-return or error
*/public static function call($method, $params = [])
{
$arPost = [
'method' => $method,
'params' => $params
];
if(defined('C_REST_CURRENT_ENCODING'))
{
$arPost[ 'params' ] = static::changeEncoding($arPost[ 'params' ]);
}$result = static::callCurl($arPost);
return $result;
}/**
* @example $arData:
* $arData = [
* 'find_contact' => [
* 'method' => 'crm.duplicate.findbycomm',
* 'params' => [ "entity_type" => "CONTACT", "type" => "EMAIL", "values" => array("info@bitrix24.com") ]
* ],
* 'get_contact' => [
* 'method' => 'crm.contact.get',
* 'params' => [ "id" => '$result[find_contact][CONTACT][0]' ]
* ],
* 'get_company' => [
* 'method' => 'crm.company.get',
* 'params' => [ "id" => '$result[get_contact][COMPANY_ID]', "select" => ["*"],]
* ]
* ];
*
* @var $arData array
* @var $halt integer 0 or 1 stop batch on error
* @return array
*
*/public static function callBatch($arData, $halt = 0)
{
$arResult = [];
if(is_array($arData))
{
if(defined('C_REST_CURRENT_ENCODING'))
{
$arData = static::changeEncoding($arData);
}
$arDataRest = [];
$i = 0;
foreach($arData as $key => $data)
{
if(!empty($data[ 'method' ]))
{
$i++;
if(static::BATCH_COUNT >= $i)
{
$arDataRest[ 'cmd' ][ $key ] = $data[ 'method' ];
if(!empty($data[ 'params' ]))
{
$arDataRest[ 'cmd' ][ $key ] .= '?' . http_build_query($data[ 'params' ]);
}
}
}
}
if(!empty($arDataRest))
{
$arDataRest[ 'halt' ] = $halt;
$arPost = [
'method' => 'batch',
'params' => $arDataRest
];
$arResult = static::callCurl($arPost);
}
}
return $arResult;
}/**
* Getting a new authorization and sending a request for the 2nd time
*
* @var $arParams array request when authorization error returned
* @return array query result from $arParams
*
*/private static function GetNewAuth($arParams)
{
$result = [];
$arSettings = static::getAppSettings();
if($arSettings !== false)
{
$arParamsAuth = [
'this_auth' => 'Y',
'params' =>
[
'client_id' => $arSettings[ 'C_REST_CLIENT_ID' ],
'grant_type' => 'refresh_token',
'client_secret' => $arSettings[ 'C_REST_CLIENT_SECRET' ],
'refresh_token' => $arSettings[ "refresh_token" ],
]
];
$newData = static::callCurl($arParamsAuth);
if(isset($newData[ 'C_REST_CLIENT_ID' ]))
{
unset($newData[ 'C_REST_CLIENT_ID' ]);
}
if(isset($newData[ 'C_REST_CLIENT_SECRET' ]))
{
unset($newData[ 'C_REST_CLIENT_SECRET' ]);
}
if(isset($newData[ 'error' ]))
{
unset($newData[ 'error' ]);
}
if(static::setAppSettings($newData))
{
$arParams[ 'this_auth' ] = 'N';
$result = static::callCurl($arParams);
}
}
return $result;
}/**
* @var $arSettings array settings application
* @var $isInstall boolean true if install app by installApp()
* @return boolean
*/private static function setAppSettings($arSettings, $isInstall = false)
{
$return = false;
if(is_array($arSettings))
{
$oldData = static::getAppSettings();
if($isInstall != true && !empty($oldData) && is_array($oldData))
{
$arSettings = array_merge($oldData, $arSettings);
}
$return = static::setSettingData($arSettings);
}
return $return;
}/**
* @return mixed setting application for query
*/private static function getAppSettings()
{
if(defined("C_REST_WEB_HOOK_URL") && !empty(C_REST_WEB_HOOK_URL))
{
$arData = [
'client_endpoint' => C_REST_WEB_HOOK_URL,
'is_web_hook' => 'Y'
];
$isCurrData = true;
}
else
{
$arData = static::getSettingData();
$isCurrData = false;
if(
!empty($arData[ 'access_token' ]) &&
!empty($arData[ 'domain' ]) &&
!empty($arData[ 'refresh_token' ]) &&
!empty($arData[ 'application_token' ]) &&
!empty($arData[ 'client_endpoint' ])
)
{
$isCurrData = true;
}
}return ($isCurrData) ? $arData : false;
}/**
* Can overridden this method to change the data storage location.
*
* @return array setting for getAppSettings()
*/protected static function getSettingData()
{
$return = [];
if(file_exists(__DIR__ . '/settings.json'))
{
$return = static::expandData(file_get_contents(__DIR__ . '/settings.json'));
if(defined("C_REST_CLIENT_ID") && !empty(C_REST_CLIENT_ID))
{
$return['C_REST_CLIENT_ID'] = C_REST_CLIENT_ID;
}
if(defined("C_REST_CLIENT_SECRET") && !empty(C_REST_CLIENT_SECRET))
{
$return['C_REST_CLIENT_SECRET'] = C_REST_CLIENT_SECRET;
}
}
return $return;
}/**
* @var $data mixed
* @var $encoding boolean true - encoding to utf8, false - decoding
*
* @return string json_encode with encoding
*/
protected static function changeEncoding($data, $encoding = true)
{
if(is_array($data))
{
$result = [];
foreach ($data as $k => $item)
{
$k = static::changeEncoding($k, $encoding);
$result[$k] = static::changeEncoding($item, $encoding);
}
}
else
{
if($encoding)
{
$result = iconv(C_REST_CURRENT_ENCODING, "UTF-8//TRANSLIT", $data);
}
else
{
$result = iconv( "UTF-8",C_REST_CURRENT_ENCODING, $data);
}
}return $result;
}/**
* @var $data mixed
* @var $debag boolean
*
* @return string json_encode with encoding
*/
protected static function wrapData($data, $debag = false)
{
if(defined('C_REST_CURRENT_ENCODING'))
{
$data = static::changeEncoding($data, true);
}
$return = json_encode($data, JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT);if($debag)
{
$e = json_last_error();
if ($e != JSON_ERROR_NONE)
{
if ($e == JSON_ERROR_UTF8)
{
return 'Failed encoding! Recommended \'UTF - 8\' or set define C_REST_CURRENT_ENCODING = current site encoding for function iconv()';
}
}
}return $return;
}/**
* @var $data mixed
* @var $debag boolean
*
* @return string json_decode with encoding
*/
protected static function expandData($data)
{
$return = json_decode($data, true);
if(defined('C_REST_CURRENT_ENCODING'))
{
$return = static::changeEncoding($return, false);
}
return $return;
}/**
* Can overridden this method to change the data storage location.
*
* @var $arSettings array settings application
* @return boolean is successes save data for setSettingData()
*/protected static function setSettingData($arSettings)
{
return (boolean)file_put_contents(__DIR__ . '/settings.json', static::wrapData($arSettings));
}/**
* Can overridden this method to change the log data storage location.
*
* @var $arData array of logs data
* @var $type string to more identification log data
* @return boolean is successes save log data
*/public static function setLog($arData, $type = '')
{
$return = false;
if(!defined("C_REST_BLOCK_LOG") || C_REST_BLOCK_LOG !== true)
{
if(defined("C_REST_LOGS_DIR"))
{
$path = C_REST_LOGS_DIR;
}
else
{
$path = __DIR__ . '/logs/';
}
$path .= date("Y-m-d/H") . '/';if (!file_exists($path))
{
@mkdir($path, 0775, true);
}$path .= time() . '_' . $type . '_' . rand(1, 9999999) . 'log';
if(!defined("C_REST_LOG_TYPE_DUMP") || C_REST_LOG_TYPE_DUMP !== true)
{
$jsonLog = static::wrapData($arData);
if ($jsonLog === false)
{
$return = file_put_contents($path . '_backup.txt', var_export($arData, true));
}
else
{
$return = file_put_contents($path . '.json', $jsonLog);
}
}
else
{
$return = file_put_contents($path . '.txt', var_export($arData, true));
}
}
return $return;
}/**
* check minimal settings server to work CRest
* @var $print boolean
* @return array of errors
*/
public static function checkServer($print = true)
{
$return = [];//check curl lib install
if(!function_exists('curl_init'))
{
$return['curl_error'] = 'Need install curl lib.';
}//creat setting file
file_put_contents(__DIR__ . '/settings_check.json', static::wrapData(['test'=>'data']));
if(!file_exists(__DIR__ . '/settings_check.json'))
{
$return['setting_creat_error'] = 'Check permission! Recommended: folders: 775, files: 664';
}
unlink(__DIR__ . '/settings_check.json');
//creat logs folder and files
$path = __DIR__ . '/logs/'.date("Y-m-d/H") . '/';
if(!mkdir($path, 0775, true) && !file_exists($path))
{
$return['logs_folder_creat_error'] = 'Check permission! Recommended: folders: 775, files: 664';
}
else
{
file_put_contents($path . 'test.txt', var_export(['test'=>'data'], true));
if(!file_exists($path . 'test.txt'))
{
$return['logs_file_creat_error'] = 'check permission! recommended: folders: 775, files: 664';
}
unlink($path . 'test.txt');
}if($print === true)
{
if(empty($return))
{
$return['success'] = 'Success!';
}
echo ''; print_r($return); echo '';}return $return; } }
- settings.php (здесь нам понадобится адрес вебхука, который мы создали в битриксе)
На этом все, переходим к Тильде.
Настройка вебхука на Тильде
В Тильде переходим в Настройки сайта —> Формы —> Другое —> Webhook.



Вводим адрес, по которому лежит файлик Tilda.php, который мы создали ранее, и нажимаем Добавить.

После сохранения система предложит добавить приемщик данных ко всем формам на сайте. Нажмите Добавить, либо сами отметьте в формах только что созданный вебхук.

После нажатия кнопки система перекинет вас в подключенные сервисы. Заходим в настройки созданного вебхука.

Отметьте опцию Передавать данные по товарам в заказе – массивом, а также укажите название в списке (так будет называться вебхук в формах Тильды). Нажмите Сохранить.

Если вы не добавили вебхук во все формы автоматически, когда предлагала система, то самое время сделать это вручную. Перейдите на страницу сайта, найдите блок корзины, нажмите Контент.

Установите галочку на вебхуке и нажмите Сохранить и закрыть.

Теперь необходимо опубликовать изменения.

Все готово, можно тестировать.
Результат
Создаем заказ на сайте:


В битриксе появляется сделка с контактными данными и товарами.

