Хочу поделиться опытом автоматизации экспорта заказов из
Aliexpress в несколько. Приведенные примеры написаны на PHP, но
библиотеки для работы с Aliexpress есть и для других языков.
Структура запросов и ответов аналогична. Если перед вами стоит
задача интеграции Aliexpress, надеюсь, эта статья будет вам
полезна.
Итак, предположим, что у вас уже есть магазин на Aliexpress. И
вы выбрали пункт меню Разработчикам. На момент публикации
наблюдались сбои в локализации консоли разработчика, возможно, они
будут исправлены в будущем, но если в консоли не отображаются уже
созданные приложения или вход в консоль из кабинета продавца не
срабатывает, есть пара советов:
-
Заходите по ссылке https://seller.aliexpress.ru/login?return_url=https%3A%2F%2Fseller.aliexpress.ru
Это исправит глюк с редирекстом на китайскую версию консоли, в
которой создать приложение не возможно
-
Перед входом, разлогиньтесь из под покупателя. Хотя вход будет
произведен отдельно, консоль каким-то образом, может оказаться
пустой. Т.е. вы войдете не под тем логином, под которым ожидали.
Тот же глюк может быть позже, когда доступ для приложения может
быть запрещён с ошибкой main account not auth subAccount
В консоли можно скачать SDK для разных языков и создать
приложение. В результате у вас в распоряжении окажутся два из 3-х
нужных для работы значения - App Key и App Secret. С третьим
значением access_token все немного сложнее. Тут необходимо
небольшое пояснение. В терминах консоли значения называются App
Key, App Secret и access_token, а в терминах SDK, по крайней мере
PHP-версии, эти же pачения называются appkey, secretKey и
sessionKey. Ключ сессии, он же access_token можно получить двумя
способами:
-
Применив редирект обратно на свой сервер. Т.е. вы пишите
приложение для менеджера, который по мере необходимости импортирует
заказы из Aliexpress в CRM. Каждый раз он будет переходить из
приложения на сайт Aliexpress, нажимать кнопку предоставления
полномочий и возвращаться в приложение. При возврате буте получен
параметр code, который с помощью curl вызова на https://oauth.aliexpress.com/token
можно будет обменять на access_token
-
Парсинг страницы после перехода по ссылке https://oauth.aliexpress.com/authorize.
Ну или однократный вызов с ручным копированием access_token
Оба метода подробно описаны в документации. Там же
упомянуто, что срок жизни access_token 1 год. Но не тут то было.
Приложение созданное в консоли остается в стадии testing. А для нее
срок жизни - 1 сутки. Чтобы опубликовать приложение, нужно
отсканировать паспорт через приложение AliPay (российский
загранпаспорт подошел). Но и это вам не поможет, т.к. следующий шаг
введение номера кредитной карты и телефона, которые, видимо, должны
быть выданы в Китае, т.к. ни то ни другое ввести не удается.
Обращение в техподдержу Aliexpress в марте 2020 год осталось без
ответа.
Надеюсь в будущем локализация будет доведена до конца и
публикация для россиян станет доступна. Таким образом для
дальнейшей работы подходит первый способ с нажатием кнопки каждый
раз при импорте заказов, но передо мной была поставлена задача -
сделать автоматический импорт без участия менеджера.
Я упомяну парсинг с помощью Selenium в конце стать, т.к.
проблема с жизнью access_token может перед вами не стоять или
решиться исправлением международного механизма публикации
приложения.
Думаю, стоит начать с импорта справочника товаров Ailexpress в
промежуточную таблица, чтоб присвоить им идентификаторы из CRM.
include "TopSdk.php";date_default_timezone_set('Asia/Shanghai'); $c = new TopClient;$c->appkey = 'xxxxxxxxxxx';$c->secretKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';$sessionKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';$i = 1;$req = new AliexpressSolutionProductListGetRequest;$aeop_a_e_product_list_query = new ItemListQuery;$aeop_a_e_product_list_query->current_page=$i;$aeop_a_e_product_list_query->product_status_type="onSelling";$req->setAeopAEProductListQuery(json_encode($aeop_a_e_product_list_query));$rez = $c->execute($req, $sessionKey);var_dump($rez);
Получаем в $rez список товаров. Обратите
внимание на счетчик $i. При первом запросе он
будет равен 1, затем его нужно увеличивать, пока он не достигнет
$rez->result->total_page и получать
следующие страницы с товарами. Предлагаю вам ознакомиться с выводом
скрипта. Первое что бросается в глаза машинный перевод названий
товаров на английский язык. Это можно исправить на следующем шаге.
Или сориентироваться по ссылкам на фотографию. А если товаров не
много и перевод однозначный, то английские названия вас тоже могут
устроить. Выведем эти названия и id (если ответ на запрос
разбивается на несколько страниц, роцедуру нужно повторить для
каждой из них)
foreach($rez->result->aeop_a_e_product_display_d_t_o_list->item_display_dto as $item){ echo $item->product_id."\t".$category->subject."\n";}
Если по каждой позиции предусмотрен выбор вариантов (размер,
комплектация и т.д.), то нужно для каждого id вызвать метод,
возвращающий эти варианты. Этим же методом можно получить
оригинальное название товара на русском языке.
$req = new AliexpressSolutionProductInfoGetRequest;$req->setProductId($id);$rez = $c->execute($req, $sessionKey);var_dump ($rez);
В выводе содержится, много полезной информации, мы возьмём
только идентификатоы и строки характеристик.
foreach($rez->result->aeop_ae_product_s_k_us->global_aeop_ae_product_sku as $sku){ echo $sku ->id."\t".$sku ->sku_code."\n";}
На этом этапе я столкнулся небольшой проблемой. Надписи в
категориях товаров, которые видит покупатель, не возвращаются через
API и доступны только через личный кабинет. Я имею ввиду, надписи,
которые появляются при наведении на картинку с подвидом товара или
надписи, которые сами определяют подвид.
На скриншоте пример с обувью. В карточке товара покупатель видит
размер, а в заказ падает часть строки характеристик, по этой части
можно понять какой именно Product code выбран. Обычно это штрих код
товара. Я предполагаю, что те, кто заводят товар в личном кабинете
продавца, ведут учет подкатегорий товара и их соответствие Product
code. Я сделал первую сверку товаров и написал несколько простых
страничек для пополнения и редактирования таблицы. Осталось
получить список заказов и загрузить их в CRM.
$req = new AliexpressSolutionOrderGetRequest;$param0 = new OrderQuery;$param0->create_date_end="2221-12-31 12:12:12";$param0->create_date_start="2021-03-30 12:12:12";$param0->order_status_list = array("PLACE_ORDER_SUCCESS","IN_CANCEL","WAIT_SELLER_SEND_GOODS","SELLER_PART_SEND_GOODS","WAIT_BUYER_ACCEPT_GOODS","FUND_PROCESSING","IN_ISSUE","IN_FROZEN","WAIT_SELLER_EXAMINE_MONEY","RISK_CONTROL", "FINISH");$param0->current_page=$i;$req->setParam0(json_encode($param0));$rez = $c->execute($req, $sessionKey);
Ответ, так же как и в случаях со справочником товаров,
разбивается на страницы. Обратите внимание на order_status_list. Он
описан документации, которую я упоминал. Насколько я понял, список
в моем коде является полным, что может пригодиться, если вы
захотите экспортировать заказы во всех статусах.
Информацию по каждому новому заказу нужно получать отдельно.
Строки идентифицирующие покупателей как RU shopper и т.п., довольно
бесполезны, поэтому полное имя лучше брать из блока доставки.
Примерно половина покупателей пишет свои контакты транслитом и не
полностью, однако в нашем распоряжении есть номер телефона. По нему
можно найти покупателя в накопленной, до начала работы с
Aliexpress, базе и привязать соответствующий заказ.
При следующем получении списка заказов, можно проверить оплату
неоплаченных заказов. В таблице загруженных ранее заказов таких
записей может быть много, но Aliexpress заказы которые долго были
не оплачены, через какое-то время перестает отображать в консоли в
статусе ожидает оплаты. Думаю это связано с фиксацией цены.
Менеджер, работающий около года с Aliexpress, сказал что стоит
проверять заказы, которым не более 20 дней.
$req = new AliexpressSolutionOrderInfoGetRequest;$param1 = new OrderDetailQuery;$param1->ext_info_bit_flag="11111";$param1->order_id=$order;$req->setParam1(json_encode($param1));$rez = $c->execute($req, $sessionKey);$n = str_replace("'","''",$rez->result->data->receipt_address->contact_person); // мягкий знак в ФИО$p = $rez->result->data->receipt_address->phone_country . $rez->result->data->receipt_address->mobile_no;//$p = normalize_phone($p) // здесь могут быть варианты с лишней восьмёркой и т.д.$zi = $rez->result->data->receipt_address->zip;$gor = $rez->result->data->receipt_address->city;$pro = $rez->result->data->receipt_address->province;$adr = $rez->result->data->receipt_address->detail_address;if(isset ($rez->result->data->receipt_address->address2)) $adr .=$rez->result->data->receipt_address->address2;foreach($rez->result->data->child_order_ext_info_list->global_aeop_tp_order_product_info_dto as $item){ $pid = $item->product_id; $j=json_decode($item->sku); $s = ""; if(isset ($j->sku[0])) { $s = $j->sku[0]->pValueId; } $cn =$category->quantity; $pr =$item->unit_price->amount ; echo $pid."\t". $s." \t " .$cn ." \t ".$pr."\n";}
Этот запрос возвращает данные и о скидке, если вы проводите в
вашем магазине на Aliexpress промоакцию.
В заключении, хочу привести еще несколько фрагментов кода. По
одному для каждой CRM с которыми мне доводилось работать. Я делаю
это, чтобы вся информация по интеграции была в одном месте.
Детальные разборы добавления заказов ищутся проще, чем информация
по Aliexpress, но если кому-то нужно будет быстро сделать
интеграцию, можно взять эти наработки.
Я взял из их своих проектов и отвязал от интеграций (в том числе
Алиэкспресса). Каждый пример создает контакт, затем лид/сделку,
привязывает к ней контакт и добавляет один товар. Я с удовольствием
помогу вам c интеграцией за небольшую плату, если вы решите начать
продажи на Алиэкспресс. Тем более что приближается пора отпусков и
дополнительные деньги не помешают ни мне, не вам. Почта для связи
tlx {cобака} list.ru. Есть большой опыт по интеграции фулфилментов,
телефонии и прочего API c различными CRM и общий навык
автоматизации.
amoCRM
$subdomain = "xxxxxxxxx";function amo_call($access_token, $link, $data) {$headers = ['Authorization: Bearer ' . $access_token];$curl = curl_init(); //Сохраняем дескриптор сеанса cURLcurl_setopt($curl,CURLOPT_RETURNTRANSFER, true);curl_setopt($curl,CURLOPT_USERAGENT,'amoCRM-oAuth-client/1.0');curl_setopt($curl,CURLOPT_URL, $link);curl_setopt($curl,CURLOPT_CUSTOMREQUEST,'POST');curl_setopt($curl,CURLOPT_POSTFIELDS,json_encode($data));curl_setopt($curl,CURLOPT_HTTPHEADER, $headers);curl_setopt($curl,CURLOPT_HEADER, false);curl_setopt($curl,CURLOPT_SSL_VERIFYPEER, 1);curl_setopt($curl,CURLOPT_SSL_VERIFYHOST, 2);$out = curl_exec($curl); //Инициируем запрос к API и сохраняем ответ в переменную$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);curl_close($curl);$code = (int)$code;$errors = [400 => 'Bad request',401 => 'Unauthorized',403 => 'Forbidden',404 => 'Not found',500 => 'Internal server error',502 => 'Bad gateway',503 => 'Service unavailable',];try{if ($code < 200 || $code > 204) {return -1;}}catch(\Exception $e){return -1;}$response = json_decode($out, true);return $response;}/* //Это упрощенный способ получения $access_token. Подробнее о получении, вермени жизни и обновлении, см. документацию $link = 'https://' . $subdomain . '.amocrm.ru/oauth2/access_token'; //Формируем URL для запроса$data = ['client_id' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx','client_secret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx','grant_type' => 'authorization_code','code' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx','redirect_uri' => 'http://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,];$curl = curl_init(); //Сохраняем дескриптор сеанса cURLcurl_setopt($curl,CURLOPT_RETURNTRANSFER, true);curl_setopt($curl,CURLOPT_USERAGENT,'amoCRM-oAuth-client/1.0');curl_setopt($curl,CURLOPT_URL, $link);curl_setopt($curl,CURLOPT_HTTPHEADER,['Content-Type:application/json']);curl_setopt($curl,CURLOPT_HEADER, false);curl_setopt($curl,CURLOPT_CUSTOMREQUEST, 'POST');curl_setopt($curl,CURLOPT_POSTFIELDS, json_encode($data));curl_setopt($curl,CURLOPT_SSL_VERIFYPEER, 1);curl_setopt($curl,CURLOPT_SSL_VERIFYHOST, 2);$out = curl_exec($curl); //Инициируем запрос к API и сохраняем ответ в переменную$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);curl_close($curl);$code = (int)$code;$errors = [400 => 'Bad request',401 => 'Unauthorized',403 => 'Forbidden',404 => 'Not found',500 => 'Internal server error',502 => 'Bad gateway',503 => 'Service unavailable',];try{if ($code < 200 || $code > 204) {throw new Exception(isset($errors[$code]) ? $errors[$code] : 'Undefined error', $code);}}catch(\Exception $e){die("Ошибка: ' . $e->getMessage() . PHP_EOL . 'Код ошибки: ' . $e->getCode());}$response = json_decode($out, true);var_dump($response);$access_token = $response['access_token']; //Access токен$refresh_token = $response['refresh_token']; //Refresh токен$token_type = $response['token_type']; //Тип токена$expires_in = $response['expires_in']; //Через сколько действие токена истекает*/$access_token ="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";// создаем контакт. В рабочей версии лучше перед этим поискать контакт по номеру телефона. Методом /api/v4/contacts с параметром filter[custom_fields_values]//тестовые данные$p ="+79000000000";$n= "Петр Петров";$qnt = 1;$ord = "xxxxxxxxxxxxxxxxx";$link='https://'.$subdomain.'.amocrm.ru/api/v2/contacts';$data = array ('add' =>array (0 =>array ('name' => $n,'custom_fields'=>array(array('id' => '953127','values' => array(array("value"=>$p,"enum"=> "MOB"),),),),) ,) , ) ;$response = amo_call($access_token, $link, $data);if ($response==-1) {die("что-то пошло не так при добавлении контакта /n");} else {var_dump($response);$cid = $response["_embedded"]["items"][0]["id"];// создаем сделку с привязкой к найденному или созданному контакту$pipeline_id = '4188580';$lead_status_id = '39384853';$lead_name ="Заказ из Aliexpress " . $ord;$link='https://'.$subdomain.'.amocrm.ru/api/v2/leads';$data = array ('add' => array (0 =>array ('name' => $lead_name,'status_id' => $lead_status_id, //id статуса'pipeline_id' => $pipeline_id,'contacts_id'=> array ($cid ),), ),);$response = amo_call($access_token, $link, $data);if ($response==-1) {die("что-то пошло не так при добавлении сделки /n");} else {$did = $response["_embedded"]["items"][0]["id"];var_dump($response);// добавляем товары в сделку$link='https://'.$subdomain.'.amocrm.ru/api/v4/leads/'.$did.'/link';$data = array (0 =>array ('to_entity_id' => 327219, //id товара можно посмореть методами api или в html коде страницы товара (не путать с артикулом)'to_entity_type' =>'catalog_elements','metadata' => array("quantity" => $qnt,"catalog_id" => 5321 // id каталога, виден в адресной строке при входе в каталог),),);$response = amo_call($access_token, $link, $data);if ($response==-1) {die("что-то пошло не так при добавлении товаров в сделку /n");} else {var_dump($response);//сообщить об успешно выполнной операции}}
Bitrix24
function b24_call($queryUrl, $queryData) {$curl = curl_init();curl_setopt_array($curl, array(CURLOPT_SSL_VERIFYPEER => 0,CURLOPT_POST => 1,CURLOPT_HEADER => 0,CURLOPT_RETURNTRANSFER => 1,CURLOPT_URL => $queryUrl,CURLOPT_POSTFIELDS => $queryData,));$out = curl_exec($curl);$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);if ($code < 200 || $code > 204) {return -1;}curl_close($curl);$response = json_decode($out, true);return $response;}$p ="+79000000000";$n= "Петр Петров";$zi = "111111";$pro = "Ньюйорская область";$gor = "Ньюджерси";$adr = "ул. Линкольна дом 1";$qnt = 1;$ord = "xxxxxxxxxxxxxxxxx";//Перед созданием контакта лучше поискать его методом crm.contact.list с параметром array('filter' => array('PHONE' => $ph))$queryUrl = 'https://xxxxxxxxxx.ru/rest/48/xxxxxxxxxxxxxxxxx/crm.contact.add.json';$queryData = http_build_query(array('fields' => array("NAME"=> $n, "PHONE" => array(array('VALUE' =>$p, 'VALUE_TYPE' => 'MOBILE')),),'params' => array("REGISTER_SONET_EVENT" => "N")));$response= b24_call($queryUrl, $queryData); if ($response==-1) {die("что-то пошло не так при добавлении контакта /n");} else {var_dump($response);$cid = $response["result"];echo "!".$cid."!\n";$queryUrl = 'https://xxxxxxxxxx.ru/rest/48/xxxxxxxxxxxxxxxxx/crm.requisite.add.json';$queryData = http_build_query(array('fields'=> array("PRESET_ID"=> 3,"ENTITY_TYPE_ID"=> 3,"ENTITY_ID"=> $cid,"NAME"=>"Реквизит","ACTIVE"=>"Y","SORT"=>100)));$response= b24_call($queryUrl, $queryData);if ($response==-1) {die("что-то пошло не так при добавлении реквизита /n");} else {var_dump($response);$rid = $response["result"];$queryUrl = 'https://xxxxxxxxxx.ru/rest/48/xxxxxxxxxxxxxxxxx/crm.address.add.json';$queryData = http_build_query(array('fields'=> array("TYPE_ID"=> 1,"ENTITY_TYPE_ID"=> 8,"ENTITY_ID"=> $rid,"POSTAL_CODE"=> "$zi", "PROVINCE"=> "$pro","CITY"=> "$gor","ADDRESS_1"=> "$adr","COUNTRY"=> "Россия")));$response= b24_call($queryUrl, $queryData);if ($response==-1) {die("что-то пошло не так при добавлении адреса в реквизит /n");} else {var_dump($response);$lead_name = "Заказ из Aliexpress " . $ord;$queryUrl = 'https://xxxxxxxxxx.ru/rest/48/xxxxxxxxxxxxxxxxx/crm.lead.add.json';$queryData = http_build_query(array('fields' => array("TITLE" => $lead_name,"TYPE_ID" => "GOODS","STAGE_ID" => "NEW","CONTACT_ID"=> $cid,"CURRENCY_ID"=> "RUB",),'params' => array("REGISTER_SONET_EVENT" => "Y")));$response= b24_call($queryUrl, $queryData);if ($response==-1) {die("что-то пошло не так при добавлении сделки /n");} else {var_dump($response);$did = $response["result"];$items = array();$items[] = array('PRODUCT_ID' => "2968", 'PRICE' => "1.00", 'QUANTITY' => $qnt);$queryUrl = 'https://xxxxxxxxxx.ru/rest/48/xxxxxxxxxxxxxxxxx/crm.lead.productrows.set.json';$queryData = http_build_query(array("id" => $did,"rows"=>$items));$response= b24_call($queryUrl, $queryData);if ($response==-1) {die("что-то пошло не так при добавлении товаров в сделку /n");} else {var_dump($response);//сообщить об успешно выполнной операции}}}}}
RetailCRM
$crmKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';function retail_call($url, $postData) {$curl = curl_init();curl_setopt($curl, CURLOPT_URL, $url);curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);curl_setopt($curl, CURLOPT_FAILONERROR, false);curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);curl_setopt($curl, CURLOPT_TIMEOUT, 30);curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30);curl_setopt($curl, CURLOPT_POST, true);curl_setopt($curl, CURLOPT_POSTFIELDS, $postData); $out = curl_exec($curl);$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);if ($code < 200 || $code > 204) {return -1;}curl_close($curl);$response = json_decode($out, true);return $response;}$p ="+79000000000";$n= "Петр Петров";$zi = "111111";$pro = "Ньюйорская область";$gor = "Ньюджерси";$adr = "ул. Линкольна дом 1";$qnt = 1;$ord = "xxxxxxxxxxxxxxxxx";$url = 'https://xxxxxxxxxx..ru/api/v5/customers/create';$postData = array('site' =>'xxxxxxxxxx.ru','customer' => json_encode(array('firstName' =>$n,)),'apiKey' => $crmKey,);$rez = retail_call($url, $postData);var_dump($rez);$cid = $rez["id"];$url = 'https://xxxxxxxxxx.ru/api/v5/orders/create';$postData = array('site' =>'xxxxxxxxxx.ru','order' => json_encode(array('customer'=> array('id' => $cid,), 'items' => array(array('quantity' => 6,'offer'=> array('externalId' => 'xxxxxxxxxxx',)),))),'apiKey' => $crmKey,);$rez = retail_call($url, $postData);var_dump($rez);echo "\n".$rez["id"]."\n";
Selenium (для получения access_token)
from selenium import webdriverfrom time import sleepbrowser = webdriver.Firefox()browser.get("https://seller.aliexpress.ru/")sleep(5)browser.switch_to.frame(browser.find_element_by_css_selector("iframe.iframe-with-loader_iframe__QQc_0"))e = browser.find_element_by_id("fm-login-id")e.send_keys("xxxxxx@xxxxxxxx.ru")e = browser.find_element_by_id("fm-login-password")e.send_keys("xxxxxxxxxxxxx")e = browser.find_element_by_id("fm-login-submit")e.click()sleep(15)browser.get("https://oauth.aliexpress.com/authorize?&response_type=token&client_id=XXXXXXXX&state=1212&view=web&sp=ae")sleep(5)try: e = driver.find_element_by_id("sub") e.click()except Exception: print('Session login')sleep(5)txt = "-1"try: e = driver.find_element_by_id("wrap") print(e.get_attribute('innerHTML')) txt = e.get_attribute('innerHTML')except Exception: print('Session login')x = txt.split("access_token: ")y = x[1].split("<br>")print y[0]