Небольшая интеграция miniShop2 с сервисом iiko
Добрый день.
В этой статье будет затронута тема минимальной интеграции магазина miniShop2 с специализированным сервисом для кафе/ресторанов iiko. Интеграция это пожалуй громкое слово, так как расскажу только о передаче заказов в систему, а не полная синхронизация номенклатуры и заказчиков.
Achtung! Николай в комментариях дал отличное решение нижеописанного плагина. Код решения оставлю для примера, можно будет надергать кусочками для своих нужд.
В моем случае была задача создавать доставку в системе, чтобы передавался тип доставки, способ оплаты, адрес доставки, сами товары заказа.
Нам понадобится
С помощью apiLogin нам необходимо получить токен, который действителен 1 час. Для просто-то всего вот этого хранение и проверка токена не описана.
Можно использовать 2 варианта работы с api, через программу Postman, либо через console/modalConsole самого MODx.
Приведу простенький код для получения токена через php (я использовал Postman)
После получения токена нам необходимо получить еще 2 параметра: organizationId и terminalGroupId. Они необходимо в дальнейшем для получения id оплат и создания заказа. Данные параметры являются постоянными и проще их один раз получить и сохранить, чем запрашивать постоянно.
organizationId можно получить по адресу
api-ru.iiko.services/api/1/organizations
Ссылка на домументацию:
api-ru.iiko.services/#tag/Organizations
По сути описывать все эти манипуляции с получением промежуточных данных нет смысла, так как в родной документации все максимально подробно. Расскажу только про основные шаги.
Далее необходимо получить terminalGroupId. Принцип тот же, запрос на адрес api-ru.iiko.services/api/1/terminal_groups
Но в теле запроса уже должен быть json с organizationIds (важно, в некоторых случаях названия параметров отличаются: organizationId и organizationIds так как в системе может быть несколько точек продаж, в моем случае 1 точка и данные я получаю только для нее)
Также важно сопоставить ID способов оплаты
Не забываем что в Postman в header должны быть указаны параметры Content-Type = application/json, а в авторизации выбран тип Bearer Token и ваш свежий токен (помните, он годен всего час)
Получим из iiko все варианты:

Отсюда мы берем 2 параметра id и paymentTypeKind
Все эти параметры, кроме токена, являются постоянными и их можно получить один раз на все время. Только если со временем появляются новые методы оплаты или точки продажи, тогда уже надо вносить изменения.
Также важно чтобы ваши товары и товары в системе были сопоставлены по id из системы, то есть я создал поле у товара и внес туда id из iiko: iiko_product_id. Так как в саму систему нельзя передать просто название товара, то приходится делать такие привязки. Если какое-то значение не совпадет, то система будет выдавать ошибку.
После получения всех необходимых данных можно приступить к написанию плагина для минишопа.
Создаем плагин на событие msOnCreateOrder
Вопросы по системе iiko мне задавать бесполезно, так как тут и так описаны все мои действия и больше чем описано в документации я вам врятли расскажу.
В этой статье будет затронута тема минимальной интеграции магазина miniShop2 с специализированным сервисом для кафе/ресторанов iiko. Интеграция это пожалуй громкое слово, так как расскажу только о передаче заказов в систему, а не полная синхронизация номенклатуры и заказчиков.
Achtung! Николай в комментариях дал отличное решение нижеописанного плагина. Код решения оставлю для примера, можно будет надергать кусочками для своих нужд.
В моем случае была задача создавать доставку в системе, чтобы передавался тип доставки, способ оплаты, адрес доставки, сами товары заказа.
Нам понадобится
- магазин на miniShop2
- API ключ (или apiLogin) из системы (мне его передал заказчик)
- Документация
С помощью apiLogin нам необходимо получить токен, который действителен 1 час. Для просто-то всего вот этого хранение и проверка токена не описана.
Можно использовать 2 варианта работы с api, через программу Postman, либо через console/modalConsole самого MODx.
Приведу простенький код для получения токена через php (я использовал Postman)
$apiLogin = 'xxx-xxxx-xx'; // исходные данные, предоставляются системой
$params = ['apiLogin' => $apiLogin];
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api-ru.iiko.services/api/1/access_token',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_HTTPHEADER => array(
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
$response_arr = json_decode($response, true);
if($response_arr['token']) {
$token = $response_arr['token'];
} else {
echo 'Error get token';
}После получения токена нам необходимо получить еще 2 параметра: organizationId и terminalGroupId. Они необходимо в дальнейшем для получения id оплат и создания заказа. Данные параметры являются постоянными и проще их один раз получить и сохранить, чем запрашивать постоянно.
organizationId можно получить по адресу
api-ru.iiko.services/api/1/organizations
Ссылка на домументацию:
api-ru.iiko.services/#tag/Organizations
По сути описывать все эти манипуляции с получением промежуточных данных нет смысла, так как в родной документации все максимально подробно. Расскажу только про основные шаги.
Далее необходимо получить terminalGroupId. Принцип тот же, запрос на адрес api-ru.iiko.services/api/1/terminal_groups
Но в теле запроса уже должен быть json с organizationIds (важно, в некоторых случаях названия параметров отличаются: organizationId и organizationIds так как в системе может быть несколько точек продаж, в моем случае 1 точка и данные я получаю только для нее)
Также важно сопоставить ID способов оплаты
Не забываем что в Postman в header должны быть указаны параметры Content-Type = application/json, а в авторизации выбран тип Bearer Token и ваш свежий токен (помните, он годен всего час)
Получим из iiko все варианты:

Отсюда мы берем 2 параметра id и paymentTypeKind
Все эти параметры, кроме токена, являются постоянными и их можно получить один раз на все время. Только если со временем появляются новые методы оплаты или точки продажи, тогда уже надо вносить изменения.
Также важно чтобы ваши товары и товары в системе были сопоставлены по id из системы, то есть я создал поле у товара и внес туда id из iiko: iiko_product_id. Так как в саму систему нельзя передать просто название товара, то приходится делать такие привязки. Если какое-то значение не совпадет, то система будет выдавать ошибку.
После получения всех необходимых данных можно приступить к написанию плагина для минишопа.
Создаем плагин на событие msOnCreateOrder
<?php
if ($modx->event->name=='msOnCreateOrder') {
////////////////
$apiLogin = 'xxx-xxxxx-xx'; // выдется заказчиком
$organizationId = "xxxxxx-xxxxxxx-xx-xxxxx"; // получаем один раз через api
$terminalGroupId = "xxxxxx-xxxxxxx-xx-xxxxx"; // получаем один раз через api
$city_id = "xxxxxx-xxxxxxx-xx-xxxxx"; // получаем один раз через api
$token = false;
///////////////
if (!function_exists('getResponse')) {
function getResponse($action, $params, $auth = false, $token = '') {
$url = 'https://api-ru.iiko.services/'.$action;
if($auth) {
$auth = 'Authorization: Bearer '.$token;
} else {
$auth = '';
}
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
$auth
],
));
$response = curl_exec($curl);
curl_close($curl);
return $response;
}
}
// get token
$params = ['apiLogin' => $apiLogin];
$tokenJson = getResponse('api/1/access_token', $params);
if($tokenJson) {
$response_arr = json_decode($tokenJson, true);
if($response_arr['token']) {
$token = $response_arr['token'];
} else {
$modx->log(1,'[iiko]: Error to get token '.print_r($response_arr, 1));
return;
}
}
// если нет токена, то ничего и не отправляем
if($token) {
$data = [];
$customer = [];
$products = [];
$address = [];
$orderData = [
'order' => $msOrder->toArray(),
'delivery' => $msOrder->Delivery->toArray(),
'payment' => $msOrder->Payment->toArray(),
'address' => $msOrder->Address->toArray(),
'user' => $msOrder->User->toArray(),
'user_profile' => $msOrder->UserProfile->toArray(),
];
//$modx->log(1,'Order data '.print_r($orderData, 1));
// получаем данные покупателя
$customer['name'] = $orderData['address']['receiver'];
$customer['email'] = $orderData['user_profile']['email'];
$data['customer'] = $customer;
// получаем телефон заказчика
// Телефон должен передаваться с кодом страны
$data['phone'] = '+7'.$orderData['address']['phone'];
$data['phone'] = str_replace('+7+7', '+7', $data['phone']);
// уникальный идентификатор, чтобы отличать в системе заказы
$data['sourceKey'] = 'Site online';
// информация о способе оплаты
$payment_id = $orderData['order']['payment'];
switch ($payment_id) {
case 1:
$paymentType = 'Cash'; // наличка
$paymentTypeId = 'xxxxxx-xxxxxxx-xxx-xxxxxx-xx';// получаем один раз через api
$payTitle = 'Наличные';
break;
case 3:
$paymentType = 'Card'; // карта курьеру
$paymentTypeId = 'exxxxxx-xxxxxxx-xxx-xxxxxx-xx';// получаем один раз через api
$payTitle = 'Картой курьеру';
break;
case 4:
$paymentType = 'Card'; // онлайн оплата
$paymentTypeId = 'xxxxxx-xxxxxxx-xxx-xxxxxx-xx';// получаем один раз через api
$payTitle = 'Онлайн оплата на сайте';
break;
}
//
$data['payments'][] = [
'paymentTypeKind' => $paymentType,
'sum' => $orderData['order']['cost'],
'paymentTypeId' => $paymentTypeId
];
//////////////////////
// товары заказа
if($orderProducts = $msOrder->getMany('Products') ) {
$i = 0;
foreach ($orderProducts as $orderProduct) {
if($fields = $modx->getObject('msProductData', array('id' => $orderProduct->get('product_id') ))){
$fields_array = $fields->toArray();
if($fields_array['iiko_product_id']) {
$products[$i]['productId']= $fields_array['iiko_product_id'];
$products[$i]['price'] = $orderProduct->get('price');
$products[$i]['amount'] = $orderProduct->get('count');
$products[$i]['type'] = 'Product';
$i++;
}
}
}
}
//////////////////////////////////
// получение адреса заказчика
/*
// тут описан вариант как получить ID улиц адреса из системы iiko.
// надо лишь через api получить 1 раз json всех улиц и потом искать по нему
if($orderData['address']['street']) {
$file_path = '/assets/streets.json';
$streetsJson = file_get_contents($modx->getOption('base_path')."/assets/streets.json");
if($streetsJson) {
$streetsArr = json_decode($streetsJson, true);
$streets = $streetsArr['streets'];
foreach($streets as $street) {
if($street['name'] == $orderData['address']['street']) {
$street_id = $street['id'];
break;
}
}
}
} else {
$street_id = false;
}
// Важно! нельзя передавать и id и название улицы одновременно
if($street_id) {
$street = [
'id' => $street_id,
'city' => $orderData['address']['city'] ?: 'Не указан'
];
} else {
$street = [
'name' => 'Не указана',
'city' => $orderData['address']['city'] ?: 'Не указан'
];
}
*/
if($orderData['delivery']['id'] > 1) {
//Координаты доставки
// если передается улица и номер дома, то нет необходимости передавать координаты
/*
$coordinates = [
'latitude' => 0,
'longitude' => 0
];
*/
///////////////////////
// если один город для доставки, то город может быть не обязателен
$street = [
'name' => $orderData['address']['street'] ?: 'Не указана',
//'city' => $orderData['address']['city'] ?: 'Не указан'
];
///////////////////////
$address = [
'street' => $street, // улица - массив
'house' => $orderData['address']['building'] ?: '-', // номер дома
'flat' => $orderData['address']['room'] ?: '-', // номер квартиры
'entrance' => $orderData['address']['entrance'] ?: '-' // подъезд
];
///////////////////////
$data['deliveryPoint'] = [
//'coordinates' => $coordinates,
'address' => $address
];
}
///////////////////////////////////
$deliveryInfo = false;
$orderServiceType = false;
$has_delivery = false;
switch ($orderData['delivery']['id']) {
case 1:
$deliveryInfo = 'Доставка: Самовывоз';
$orderServiceType = 'DeliveryByClient'; // Важно - отличается от курьерской доставки
$delivery_id = 'xxxxxx-xxxxxxx-xxx-xxxxxx-xx'; // получаем один раз через api
$delivery_cost = 100;
break;
case 2:
$deliveryInfo = 'Доставка: Предзаказ на время '.$orderData['address']['metro'];
$orderServiceType = 'DeliveryByCourier';
$delivery_id = 'xxxxxx-xxxxxxx-xxx-xxxxxx-xx'; // получаем один раз через api
$delivery_cost = 100;
break;
case 3:
$deliveryInfo = 'Доставка: Доставка по адресу';
$orderServiceType = 'DeliveryByCourier';
$delivery_id = 'xxxxxx-xxxxxxx-xxx-xxxxxx-xx'; // получаем один раз через api
$delivery_cost = 100;
break;
}
////////////////////////////////
// оказалось что доставка тоже является товаром
// добавить доставку - товар
$products[$i] = [
'productId' => $delivery_id,
'price' => $delivery_cost,
'amount' => 1,
'type' => 'Product'
];
////////////////////////////////
if(count($products)>0){
$data['items'] = $products;
}
////////////////////////////////
$data['comment'] = $orderData['address']['comment']. " Способ оплаты: ".$payTitle.". ".$deliveryInfo ;
$data['sum'] = $orderData['order']['cost'];
$data['orderServiceType'] = $orderServiceType;
// передаем заказ в iiko
$params = [
'organizationId' => $organizationId,
'terminalGroupId' => $terminalGroupId,
'order' => $data
];
//$modx->log(1,'iiko params --- '.print_r(json_encode($params), 1));
$createOrderJson = getResponse('api/1/deliveries/create', $params, true, $token);
// логируем в отдельный файл все запросы для дальнейшей отладки
$modx->log(1, 'Отправленные параметры: '.json_encode($params) , [
"target" => "FILE",
"options" => [
"filename" => "iiko.log", // Имя файла
]
]);
// логируем в отдельный файл все ответы для дальнейшей отладки
$modx->log(1, 'Полученный ответ: '.$createOrderJson , [
"target" => "FILE",
"options" => [
"filename" => "iiko.log", // Имя файла
]
]);
//$modx->log(1,'Create response --- '.print_r($createOrderJson, 1));
}
}Статья дает небольшой толчек для написания полной интеграции. Мою задачу это решило. Вопросы по системе iiko мне задавать бесполезно, так как тут и так описаны все мои действия и больше чем описано в документации я вам врятли расскажу.
Комментарии: 5
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
Нужно переносить в классы. Бить на методы. Это прям антипаттерн. Нужен серьезный рефакторинг.
Упростил код плагина, перенес содержимое в компонент
github.com/biz87/iiko
Код по-прежнему хромает, но первый шаг к оптимизации сделан.
Что-то у меня ничего не получается с этой интеграцией… в ответе «creationStatus»: «InProgress» и заказа нет в программе — похоже каких-то параметров не хватает в запросе… ((
Никто не разобрался?