[СДЕЛАЙ САМ] Промокоды для minishop2 с помощью MIGX.
Приветствую, начнём с того, что есть много прекрасных дополнений реализующих данных функционал, и я советую всем использовать именно их, поскольку функциональность их значительно выше, а цена вполне демократичная. Но если бюджет сильно ограничен, а промокоды очень хочется и самому придумывать реализацию не с руки, то читайте дальше.
Нам понадобится:
1. minshop2;
2. MIGX;
3. msOrderDiscount (если хотите после оформления заказа менять размер скидки);
Начнём с того, что создадим новую конфигурацию в компоненте MIGX с названием promocode со следующими полями:
1. code — сюда будем записывать сам промокод;
2. count — доступное количество активаций;
3. date — дата окончания срока действия;
4. percent — процент скидки;
Для ленивых прилагаю json с конфигурацией.
Теперь у ресурса с id=1 создадим поле типа migx с названием promocodes и установим для него только что созданную конфигурацию.
Затем напишем сниппет validatePromocode, который будет проверять валидность введенного промокода
В чанк оформления заказа добавляем поле для ввода промокода
И блок для вывода информации о скидке
На этом всё!
Нам понадобится:
1. minshop2;
2. MIGX;
3. msOrderDiscount (если хотите после оформления заказа менять размер скидки);
Начнём с того, что создадим новую конфигурацию в компоненте MIGX с названием promocode со следующими полями:
1. code — сюда будем записывать сам промокод;
2. count — доступное количество активаций;
3. date — дата окончания срока действия;
4. percent — процент скидки;
Для ленивых прилагаю json с конфигурацией.
{
"formtabs":[
{
"MIGX_id":28,
"caption":"",
"print_before_tabs":"0",
"fields":[
{
"MIGX_id":106,
"field":"code",
"caption":"\u041f\u0440\u043e\u043c\u043e\u043a\u043e\u0434",
"description":"",
"description_is_code":"0",
"inputTV":"",
"inputTVtype":"",
"validation":"",
"configs":"",
"restrictive_condition":"",
"display":"",
"sourceFrom":"config",
"sources":"",
"inputOptionValues":"",
"default":"",
"useDefaultIfEmpty":"0",
"pos":1
},
{
"MIGX_id":107,
"field":"count",
"caption":"\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u0439",
"description":"",
"description_is_code":"0",
"inputTV":"",
"inputTVtype":"",
"validation":"",
"configs":"",
"restrictive_condition":"",
"display":"",
"sourceFrom":"config",
"sources":"",
"inputOptionValues":"",
"default":"",
"useDefaultIfEmpty":"0",
"pos":2
},
{
"MIGX_id":108,
"field":"date",
"caption":"\u0414\u0430\u0442\u0430 \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f",
"description":"",
"description_is_code":"0",
"inputTV":"",
"inputTVtype":"date",
"validation":"",
"configs":"",
"restrictive_condition":"",
"display":"",
"sourceFrom":"config",
"sources":"",
"inputOptionValues":"",
"default":"",
"useDefaultIfEmpty":"0",
"pos":3
},
{
"MIGX_id":109,
"field":"percent",
"caption":"\u041f\u0440\u043e\u0446\u0435\u043d\u0442 \u0441\u043a\u0438\u0434\u043a\u0438",
"description":"\u043f\u0440\u043e\u0441\u0442\u043e \u0447\u0438\u0441\u043b\u043e",
"description_is_code":"0",
"inputTV":"",
"inputTVtype":"",
"validation":"",
"configs":"",
"restrictive_condition":"",
"display":"",
"sourceFrom":"config",
"sources":"",
"inputOptionValues":"",
"default":"",
"useDefaultIfEmpty":"0",
"pos":4
}
],
"pos":1
}
],
"contextmenus":"",
"actionbuttons":"",
"columnbuttons":"",
"filters":"",
"extended":{
"migx_add":"",
"disable_add_item":"",
"add_items_directly":"",
"formcaption":"",
"update_win_title":"",
"win_id":"",
"maxRecords":"",
"addNewItemAt":"bottom",
"media_source_id":"",
"multiple_formtabs":"",
"multiple_formtabs_label":"",
"multiple_formtabs_field":"",
"multiple_formtabs_optionstext":"",
"multiple_formtabs_optionsvalue":"",
"actionbuttonsperrow":4,
"winbuttonslist":"",
"extrahandlers":"",
"filtersperrow":4,
"packageName":"",
"classname":"",
"task":"",
"getlistsort":"",
"getlistsortdir":"",
"sortconfig":"",
"gridpagesize":"",
"use_custom_prefix":"0",
"prefix":"",
"grid":"",
"gridload_mode":1,
"check_resid":1,
"check_resid_TV":"",
"join_alias":"",
"has_jointable":"yes",
"getlistwhere":"",
"joins":"",
"hooksnippets":"",
"cmpmaincaption":"",
"cmptabcaption":"",
"cmptabdescription":"",
"cmptabcontroller":"",
"winbuttons":"",
"onsubmitsuccess":"",
"submitparams":""
},
"permissions":"",
"fieldpermissions":"",
"columns":[
{
"MIGX_id":1,
"header":"\u041f\u0440\u043e\u043c\u043e\u043a\u043e\u0434",
"dataIndex":"code",
"width":"",
"sortable":"false",
"show_in_grid":1,
"customrenderer":"",
"renderer":"",
"clickaction":"",
"selectorconfig":"",
"renderchunktpl":"",
"renderoptions":"",
"editor":"this.textEditor"
},
{
"MIGX_id":2,
"header":"\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u0439",
"dataIndex":"count",
"width":"",
"sortable":"false",
"show_in_grid":1,
"customrenderer":"",
"renderer":"",
"clickaction":"",
"selectorconfig":"",
"renderchunktpl":"",
"renderoptions":"",
"editor":"this.textEditor"
},
{
"MIGX_id":3,
"header":"\u0414\u0430\u0442\u0430 \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f",
"dataIndex":"date",
"width":"",
"sortable":"false",
"show_in_grid":1,
"customrenderer":"",
"renderer":"this.renderDate",
"clickaction":"",
"selectorconfig":"",
"renderchunktpl":"",
"renderoptions":"",
"editor":"this.textEditor"
},
{
"MIGX_id":4,
"header":"\u041f\u0440\u043e\u0446\u0435\u043d\u0442 \u0441\u043a\u0438\u0434\u043a\u0438",
"dataIndex":"percent",
"width":"",
"sortable":"false",
"show_in_grid":1,
"customrenderer":"",
"renderer":"",
"clickaction":"",
"selectorconfig":"",
"renderchunktpl":"",
"renderoptions":"",
"editor":"this.textEditor"
}
],
"category":""
}
Теперь у ресурса с id=1 создадим поле типа migx с названием promocodes и установим для него только что созданную конфигурацию.
Затем напишем сниппет validatePromocode, который будет проверять валидность введенного промокода
<?php
if(!$value){return false;}
$res = $modx->getObject('modResource', 1); // получаем ресурс, у которого есть поле с промокодами
$allCodes = json_decode($res->getTVValue('promocodes'),1); // получаем поле с промокодами
$isset = false; // флаг существования промокода
$output = '';
foreach($allCodes as $key => $item){
if($item['code'] == trim($value)){ // если текущий промокод равен введённому
$isset = true;
$now = time(); // текущее время в UNIX формате
$date = strtotime($item['date']) ?: $now; // дата окончания срока действия промокода, если не указана промокод действует вечно
if(($date - $now) < 0){ // если срок действия введённого промокода закончился
return 'Истёк срок действия промокода.';
}
if($item['count'] <= 0){ // если количество акциваций равно 0
return 'Превышен лимит активаций.';
}
if($changeCount === true){
$allCodes[$key]['count'] = (($item['count'] - 1) > 0) ? ($item['count'] - 1) : 0; // уменьшаем количество на единицу
$res->setTVValue('promocodes', json_encode($allCodes, JSON_UNESCAPED_UNICODE)); // сохраняем новые значения промокодов в TV
$res->save(); // сохраняем ресурс
}
return array('percent'=> $item['percent'], 'value' => $value); // массив возвращаемых из плагина данных
}
}
if(!$isset){ // если введённого промокода нет среди созданных админом
return 'Несуществующий промокод.';
}
Далее нам понадобится плагин на событие msOnAddToOrder, в котором мы будем запускать созданный ранее сниппет и отправлять на фронт полученные данные. Я прокомментировал почти каждую строчку кода, надеюсь всё понятно.<?php
if($key == 'promocode'){ // если ввели промокод
$result = $modx->runSnippet('validatePromocode', array('value' => $value));
if(!is_array($result)){ // если есть ошибки
$modx->event->output($result);
}
$values = & $modx->event->returnedValues; // х.з. как это назвается, но в этот массив мы добавим наши значения
$values['value'] = json_encode($result, JSON_UNESCAPED_UNICODE); // записываем наши значения в массив возвращаемых значений
}
Кроме этого нам нужен плагин на событие msOnBeforeCreateOrder, в котором мы будем пересчитывать стоимость заказа перед его сохранением.<?php
if($_POST['promocode']){ // если есть промокод
$delivery_cost = $msOrder->get('delivery_cost'); // получаем стомость доставки
$old_cost = $msOrder->get('cart_cost'); // получаем старую стомость заказа
$result = $modx->runSnippet('validatePromocode', array('value' => $_POST['promocode'], 'changeCount' => true)); // проверяем валидность промокода
if(!is_array($result)){ // если есть ошибки
$modx->log(1, 'PLUGIN recalcOrderCost' . $result); // выводим их в лог и больше ничего не делаем
}
else{ // если ошибок нет
$percent = (int)$result['percent']; // получаем процент скидки
$new_cost = $old_cost - $old_cost * $percent / 100; // рассчитываем новую стоимость заказа
$msOrder->set('old_cost', $old_cost); // сохраняем старую стоимость заказа для корректной работы msOrderDiscount
$msOrder->set('cost', $new_cost + $delivery_cost); // устанавливаем новую стоимость заказа
$msOrder->set('cart_cost', $new_cost); // устанавливаем новую стоимость покупок
$msOrder->set('discount_size', $percent.'%'); // записываем размер скидки (требуется установить компонент msOrderDiscount)
$msOrder->set('comment', 'Применён промокод ' . strip_tags($_POST['promocode'])); // устанавливаем comment с названием промокода в заказ
}
}
Осталось немного дополнить стандартный JavaScript. Для этого копируем assets/components/minishop2/js/web/default.js, новый файл называем assets/components/minishop2/js/web/custom.js. Меняем системную настройку ms2_frontend_js на [[+jsUrl]]web/custom.js. Затем открываем custom.js на редактирование и добавляем следующий код//ПРОМОКОДЫ
// функция получения cookie
function getCookie(name) {
let matches = document.cookie.match(new RegExp(
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
// функция очистки cookie от данных о промокоде
function clearPromocodeCookie() {
recalcOrderCost(getCookie('old_cost'), 0); // пересчитываем стоимость заказа
document.querySelector('.jsDiscount').classList.add('d-none'); // скрываем блок с сообщением о скидке
// очищаем куки
document.cookie = 'discount=';
document.cookie = 'old_cost=';
document.cookie = 'promocode=';
document.cookie = 'new_cost=';
}
// функция пересчёта стоимости заказа
function recalcOrderCost(cost, percent) {
let newCost = cost - cost * percent / 100, // рассчитываем стоимость со скидкой
delivery_cost = 0; // стоимость доставки
if(document.querySelector('.jsDeliveryCost')){
delivery_cost = Number(document.querySelector('.jsDeliveryCost').innerText.replace('/\s+/', '')); // получаем стоимость доставки если есть
}
document.getElementById('ms2_order_cost').innerText = miniShop2.Utils.number_format(newCost + delivery_cost, 0, '.', ' '); // форматируем цену
document.querySelector('.jsCost').innerText = miniShop2.Utils.number_format(newCost, 0, '.', ' ');
// устанавливаем куки
document.cookie = 'old_cost=' + cost;
document.cookie = 'new_cost=' + newCost;
}
miniShop2.Callbacks.add('Order.add.response.success', 'orders_add_ok', function (response) {
if (response.data.promocode) { // если промокод введён
let promocode = JSON.parse(response.data.promocode), // получаем данные из ответа сервера
cost = Number(document.querySelector('.jsCost').dataset.cost), // получаем текущую стоимость заказа
percent = Number(promocode.percent); // получаем процент скидки
document.querySelector('input[name="promocode"]').value = promocode.value; // прописываем промокод после проверки
document.getElementById('discount').innerText = percent; // прописываем размер скидки по промокоду
recalcOrderCost(cost, percent); // пересчитываем стоимость заказа
document.cookie = 'discount=' + percent; // записываем % в куки
document.cookie = 'promocode=' + promocode.value; // записываем промокод в куки
document.querySelector('.jsDiscount').classList.remove('d-none'); // показываем блок с сообщением о скидке
}
});
miniShop2.Callbacks.add('Order.getcost.response.success', 'order_getcost', function (response) {
if (getCookie('promocode')) { // если промокод есть в куках
let cost = Number(response.data.cart_cost), // получаем текущую стоимость заказа
delivery_cost = Number(response.data.delivery_cost), // получаем стоимость доставки
percent = Number(getCookie('discount')); // получаем процент скидки из куков
recalcOrderCost(cost, percent); // пересчитываем стоимость заказа
}
});
miniShop2.Callbacks.add('Order.submit.response.success', 'clear_cookie', function (response) {
clearPromocodeCookie(); // очищаем куки
});
if (document.querySelector('input[name="promocode"]')) {
document.querySelector('input[name="promocode"]').addEventListener('change', function (e) {
if (!e.target.value) { // если промокод удалили
clearPromocodeCookie(); // пересчитываем стоимость заказа
}
});
}
});
Код подробно прокомментирован, поэтому надеюсь вопрос не возникнет.В чанк оформления заказа добавляем поле для ввода промокода
<input type="text" placeholder="Промокод" name="promocode" value="{$.cookie.promocode}" class="input">
И блок для вывода информации о скидке
<h5 class="mb-md-0 text-red jsDiscount {!$.cookie.discount ? 'd-none': ''}">
Скидка:
<span id="discount">{$.cookie.discount ?: 0}</span>
<small>%</small>
</h5>
Блок вывода стоимости заказа тоже немного изменим{set $orderCost = $.cookie.new_cost ?: $order.cart_cost}
<h3>
<span class="jsCost" id="ms2_order_cart_cost" data-cost="{$order.cart_cost | replace: ' ' : ''}">{$orderCost ?: 0}</span> {'ms2_frontend_currency' | lexicon} +
<span class="jsDeliveryCost" id="ms2_order_delivery_cost">{$order.delivery_cost ?: 0}</span> {'ms2_frontend_currency' | lexicon} =
<span id="ms2_order_cost">{$order.cost ?: 0}</span> {'ms2_frontend_currency' | lexicon}
</h3>
Функционал конечно простой, скидка может быть указана только в процентах, но зато бесплатно.На этом всё!
Поблагодарить автора
Отправить деньги
Комментарии: 23
А почему кто-то минусует? Можно услышать аргументацию?
Уверяю тебя, у Павлика богатая фантазия, он всегда там находит убедительные аргументы в пользу своего мнения:-) С этим бессмысленно бороться.
Минус ставить не буду, так как видно, что за этим стоит большая работа и желание поделиться.
По сути проблемы что здесь, что в других топиках от недостатка опыта.
Чтобы пользователь оформил бесплатный заказ ему достаточно поставить себе одну куку с отрицательной стоимостью доставки)
Куки для таких данных не желательно использовать. Уж лучше писать в сессию и всем управлять на сервере.
По сути проблемы что здесь, что в других топиках от недостатка опыта.
Чтобы пользователь оформил бесплатный заказ ему достаточно поставить себе одну куку с отрицательной стоимостью доставки)
Куки для таких данных не желательно использовать. Уж лучше писать в сессию и всем управлять на сервере.
Спасибо за комментарий. Я действительно не подумал про возможность установить куки вручную и таким образом оформить бесплатный заказ, буду иметь в виду.
И ещё я тут вспомнил, почему куки решил использовать, из плагина minishop2 достаточно проблематично вернуть данные, т.е. конечно можно, но выглядит некрасиво. А куки… даже если кто-то установит их вручную и оформит заказ со 100% скидкой и что? Он его всё равно не получит))) Так что, за что вы мне тут минусов понаставили даже ума не приложу.
Как вариант за большую «простынь кода», лучше залить его на гит и давать ссылки на код.
Ну как вариант. Правда я сам новичок и пишу код здесь для таких же новичков, которым с гитом может быть пока сложно работать. А ещё простыню я прячу под cut.
$msOrder->set('cost', $_COOKIE['new_cost'] + $delivery_cost);Не, ну это не то что выстрел в ногу, это самоподрыв уже какой-то получается.
Исправляй эту херню, пока тебя тут не сожрали.
Упс! Реальный косяк. Исправлю, если конечно правильно уловил мысль, надо перед $_COOKIE['new_cost'] хотя бы приведение к float поставить, да?
Тебя не смущает, что пользователь сам устанавливает цену своего заказа?
Или тебя волнует только то, что он может прислать не число?)
Или тебя волнует только то, что он может прислать не число?)
Ты про то, что новая цена в куках с фронта передаётся? Ну в общем да, пожалуй стоит переписать, я один хрен там весь список получаю и в цикле его перебираю.
Думаю стоит вынести проверку промокода в отдельный сниппет, и вызывать в обоих плагинах. Ну и с фронта брать только сам код, остальное буду рассчитывать в том же сниппите.
Новый вариант лучше? Теперь вроде как без разницы что там в куках придёт)))
Новый вариант лучше? Теперь вроде как без разницы что там в куках придёт)))Артур без разницы. У тебя проблема изначально в неверной архитектуре.
Ресурсы предназначены для страниц, то есть для того, у чего есть адрес, для того что выводится посетителям на фронте.
Не нужно пихать в них любые служебные настройки, просто потому что это просто и быстро.
Разберись с тем как создать отдельную табличку (или набор табличек), как написать модель для таблички, чтобы MODX работал с ней как с родной и храни там данные. Для этого есть готовый инструмент modExtra.
Вот честно слово — ты больше пользы принесешь и себе и другим малоопытным посетителям этого форума, если разберешься в этом и напишешь простым языком пару статей об этом.
из плагина minishop2 достаточно проблематично вернуть данныеЗдесь тоже самое. Ты не прав. Писать велосипеды, костылить и говорить, что есть какая то проблема просто потому что ты не разобрался как такое делать — это так себе занятие. Тоже можешь разобраться и написать исчерпывающую статью для коллег. На форуме материала достаточно. Если что то не получается и не знаешь как сделать — спроси.
Я знаю как сделать табличку и работать с ней, правда не через modExtra, а через CMPGenerator и уже писал об этом. И я знаю, что ресурсы они для фронта. Проблема в том, что если делать табличку в БД, то для её заполнения нужен интерфейс в админке, а для этого надо разобраться как этот интерфейс сделать на ExtJs, и это уже ни фига не для новичков. И в ресурс я засунул не служебные настройки, а как раз интерфейс — табличку с промокодами. Я могу предположить, что тот же MIGX может работать с таблицами, но как он это делает я не знаю и информацию об этом нигде не встречал.
По поводу возврата данных из плагина
И нет, я не говорю, что это супер-пупер решение и вы все неправы, наоборот призываю всех использовать готовые компоненты, если есть такая возможность, а нищим выбирать не приходится)))
По поводу возврата данных из плагина
$values = & $modx->event->returnedValues;
$values['value'] = json_encode($result, JSON_UNESCAPED_UNICODE);
Мне кажется это не самый правильный способ вернуть массив из плагина, а по-другому никак, потому что в minishop2 вот так$validated = $this->validate($key, $value);
if ($validated !== false) {
$this->order[$key] = $validated;
$response = $this->ms2->invokeEvent('msOnAddToOrder', array(
'key' => $key,
'value' => $validated,
'order' => $this,
));
if (!$response['success']) {
return $this->error($response['message']);
}
$validated = $response['data']['value'];
Хотя так сходу не скажу как я бы это реализовал.И нет, я не говорю, что это супер-пупер решение и вы все неправы, наоборот призываю всех использовать готовые компоненты, если есть такая возможность, а нищим выбирать не приходится)))
для её заполнения нужен интерфейс в админке, а для этого надо разобраться как этот интерфейс сделать на ExtJsВ том же modExtra есть пример такого интерфейса, который при минимальных знаниях JS можно скорректировать, изменив поля на свои.
На следующем шаге можно вспомнить какие из знакомых тебе компонентов выглядят похоже и посмотреть исходники. Ну например как сделаны таблички минишопа. Подсмотреть исходный код, добавить его себе в modExtra компонент и вуаля — у тебя есть готовый интерфейс.
И в ресурс я засунул не служебные настройки, а как раз интерфейс — табличку с промокодами.Все равно мусоришь ненужными в этом месте данными. Ты во первых занимаешь ресурсы базы данных лишней записью. Во вторых когда выбираешь свою информацию с промокодами получаешь кучу не нужных полей, что так же сказывается на памяти.
Теперь подумай — а если у тебя, или того кто повторит твое решение будет несколько тысяч промо-кодов — это тебе судя по коду их все нужно вытащить в память, в цикле перебрать и каждый проверить на соответствие. Тогда как при хранении их в табличке ты одним запросом вытащишь нужный тебе промо-код.
По поводу возврата данных из плагинаСлушай, ну а кто сказал что данные нужно вообще именно из плагина возвращать?
Может быть проще расширить нужный класс минишопа и дописать дополнительный метод.
У меня сейчас к великому моему сожалению нет времени разбираться в исходниках других компонентов, но как я сказал раньше, в планах на ближайшее будущее это есть.
И если кто-то будет использовать это решение для тысяч промокодов, я ему сочувствую, даже моих небольших знаний хватит, чтобы понять, что данное решение для небольшого количества промокодов.
И если кто-то будет использовать это решение для тысяч промокодов, я ему сочувствую, даже моих небольших знаний хватит, чтобы понять, что данное решение для небольшого количества промокодов.
А писать статьи каждую неделю, которые все без исключения хейтят у тебя значит время есть. Ок
Это занимает совсем немного времени, его недостаточно для того чтобы разобраться в написании компонентов. Чтобы понять как там всё устроено, надо потратить не один день.
Слушай, ну а кто сказал что данные нужно вообще именно из плагина возвращать?Никто не говорил, я сам так решил, как раз чтобы класс не расширять. Я понимаю, что можно расширить, но это сложнее, чем мой вариант.
Может быть проще расширить нужный класс минишопа и дописать дополнительный метод.
И в скором времени я освою написание полноценных компонентов и порадую вас ими)))
Звучит как угроза честно говоря
Мне кажется отдельные личности знатно так угорают над моим кодом, что же будет когда я допы начну клепать)))
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.