[СДЕЛАЙ САМ] SendIt и MiniShop2 - заказ в 1 клик - быстро, просто и бесплатно.

Приветствую, решил зафиксировать для себя и поделится с сообществом, прежде всего с новичками, актуальным способом быстро и без боли добавить в интернет-магазин на базе MiniShop2 функцию «Заказать в 1 клик.»

Функционирование модальных окон обеспечивает Bootstrap. Уточняю этот момент, потому в JS, который будет представлен далее, используются методы открытия и закрытия модального окна, и если вы используете самописные модальные окна или фреймворки отличные от Bootstrap эти методы нужно будет заменить на аналогичные из ваших библиотек.

Инструкция

1. Скачиваем и устанавливаем из репозитория modstore компоненты SendIt и MiniShop2, если ещё этого не сделали.

2. Везде где нужно иметь возможность быстрого заказа, добавляем кнопку
<button class="btn btn-warning" data-product-id="{$id}" type="button" data-bs-toggle="modal" data-bs-target="#oneClickModal"> Купить в один клик  </button>
У кнопки обязательно должен быть атрибут data-product-id со значение ID добавляемого товара.

3. Создаем модальное окно с вызовом сниппета AjaxFormItLogin, в котором укажем чанк формы, хук создающий заказ, правила валидации и текст уведомления о наличие ошибок валидации.
<div class="modal fade" id="oneClickModal">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Купить в один клик</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <form data-si-form="fastOrderForm" data-si-preset="fast_order">
                    <input type="hidden" name="delivery" value="1">
                    <input type="hidden" name="payment" value="1">
                    <input type="hidden" name="options[]" value="">
                    <input type="hidden" name="id" value="">
                    <div class="mb-3">
                        <label class="form-label">Имя получателя</label>
                        <input type="text" class="form-control" name="receiver">
                    </div>
                    <div class="mb-3">
                        <label class="form-label">Email</label>
                        <input type="email" class="form-control" name="email">
                    </div>
                    <div class="mb-3">
                        <label class="form-label">Количество</label>
                        <input type="number" class="form-control" name="count" value="1" min="1">
                    </div>
                    <button type="submit" class="btn btn-primary">Заказать</button>
                </form>
            </div>
        </div>
    </div>
</div>
Отмечу, что SendIt в данном случае нужен только для валидации полей и отправки формы, при желании можно написать свою реализацию этих функций или использовать другое решение.

4. Создаем чанк пресет fast_order
'fast_order' => [
        'hooks' => 'oneClickHook',
        'validate' => 'receiver:required,email:email:required,id:required',
        'successMessage' => 'Заказ оформлен!',
        'fieldNames' => 'receiver==Получатель,email==Почта,id==ID товара',
        'validationErrorMessage' => 'Исправьте ошибки!'
    ]
В форме обязательно должны быть поля с именами delivery, payment и id. Поскольку мы делаем форму быстрого заказа выбор способов оплаты и доставки я не предоставляю, однако, если такая необходимость есть в вашем проекте, вы можете предоставить пользователю выбор. При этом имейте в виду, если для различных способов доставки доступны различные способы оплаты, логику их взаимодействия писать придётся самостоятельно, так как скрипты MiniShop2, отвечающие за это, тут не подключаются.

5. Создаём хук, или, другими словами, сниппет, oneClickHook со следующим кодом:
<?php
$values = $hook->getValues();
$ms2 = $modx->getService('minishop2');
$ms2->initialize('web');
$ms2->cart->clean();
$count = (float)$values['count'] ?: 1;
$ms2->cart->add((int)$values['id'], $count, $values['options']); // добавляем товар в корзину
$ms2->order->config['json_response'] = true; // просим вернуть нам json
// добавляем поля в заказ
$ms2->order->add('receiver', $values['receiver']); 
$ms2->order->add('email', $values['email']);
$ms2->order->add('delivery', $values['delivery']);
$ms2->order->add('payment', $values['payment']);
$ms2->order->submit(); // отправляем заказ
Обратите внимание ни какой код после отправки заказа выполнен не будет, т.к. мы попросили MiniShop2 вернуть нам json, он сделает всё, что должен, и принудительно завершит работу. При этом все встроенные механизмы этого модуля отработают, а именно: будут отправлены письма клиенту и менеджеру, если вы не отключили эту функцию, будет сформирована ссылка на оплату.

6. Напишем JS, который обработает ответ сервера: закроет модалку, покажет уведомление, сделает переадресацию на оплату, если нужно. Так же этот скрипт заполнит поле с ID товара в нашей форме при открытии модального окна.
document.addEventListener('DOMContentLoaded', () => {
    const oneCLickModal = document.getElementById('oneClickModal')
    oneCLickModal.addEventListener('show.bs.modal', function (e) {
        oncLickModal.querySelector('[name="id"]').value =  e.relatedTarget.dataset.productId;
    });

    document.addEventListener('si:send:success', (e) => {
        const {action, target, result, headers, Sending} = e.detail;
        const modal = bootstrap.Modal.getInstance(oneCLickModal);
        modal.hide();
        if(result.data.redirect){           
            setTimeout(() =>{
                window.location.href = result.data.redirect
            }, 3000);
        }
    });
});
7. Укажем в системной настройке ms2_email_manager куда отправлять уведомления о новом заказе и всё готово.

Я потратил 30 минут на реализацию этого функционала, вы же теперь успеете не только всё сделать, но и чайку попить)))

ДРУГОЙ ВАРИАНТ РЕАЛИЗАЦИИ МОЖНО ПОСМОТРЕТЬ ТУТ
Артур Шевченко
09 февраля 2023, 12:03
modx.pro
10
1 208
+13
Поблагодарить автора Отправить деньги

Комментарии: 52

Максим
09 февраля 2023, 16:35
0
Все сделал по инструкции, так же в pdotools_fenom_parser и установил значение ДА
Но в модальном окне выдает следующее:
Array ( [corePath] => /home/p/paldrivepm/public_html/core/components/ajaxformitlogin/ [assetsUrl] => /assets/components/ajaxformitlogin/ [actionUrl] => /assets/components/ajaxformitlogin/action.php [formSelector] => afl_form_1051631402 [json_response] => 1 [fileUplodedProgressMsg] => Загружено: [fileUplodedSuccessMsg] => Данные полностью загружены на сервер! [fileUplodedErrorMsg] => Произошла ошибка при загрузке данных на сервер! [ajaxErrorMsg] => Форма не была отправлена! Свяжитесь с администратором. [notifySettingsPath] => assets/components/ajaxformitlogin/js/message_settings.json [frontend_js] => [[+assetsUrl]]js/default.js [notifyClassPath] => ./aflizitoast.class.js [notifyClassName] => AflIziToast [form] => @FILE chunks/forms/oneclickorder.tpl [snippet] => FormIt [hooks] => oneClickHook [emailTo] => [emailFrom] => [emailSubject] => [emailTpl] => aflExampleEmail [successMessage] => Форма успешно отправлена! Менеджер свяжется с Вами в течение 5 минут. [clearFieldsOnSuccess] => 1 [transmittedParams] => ["success" => "", "error" => "aliases"] [aliases] => email==Email [validate] => receiver:required,email:email:required,id:required [showUploadProgress] => 1 [spamProtection] => 1 [validationErrorMessage] => Исправьте ошибки! [secret] => hjNKTgZS [secret.vTextContains] => Кажется Вы робот. Если это не так, обновите страницу. [pageId] => 4 [metrics] => [counterId] => )
И не совсем понял, chunks/forms/oneclickorder.tpl нужно в корне сайта создавать?
    Сергей
    09 февраля 2023, 17:49
    0
    не обязательно использовать файловый чанк, можешь прописать обычный
      Артур Шевченко
      09 февраля 2023, 18:55
      0
      Проверьте пути к чанкам. Ну и посмотрите в журнале ошибок.
        Максим
        09 февраля 2023, 19:47
        0
        Все заработало, только подскажите, пожалуйста (перерыл все), КАК мне выводить ID товара в атрибут кнопки — data-product-id="{$id}" (это — {$id} не выводит).
          Артур Шевченко
          09 февраля 2023, 21:21
          +1
          Ну для этого надо конечно документацию изучать, на предмет того, как выводить плейсхолдеры. Тут есть много вариантов, навскидку 4
          {$id}
          {$_modx->resource.id}
          [[+id]]
          [[*id]]
            Максим
            09 февраля 2023, 21:25
            0
            Спасибо огромное.
            Для карточки товара помогло — [[*id]]
            Для списка с товарами — {$id}
      Максим
      09 февраля 2023, 23:00
      0
      Артур, если будет возможность помогите еще с одним вопросом.
      Как вывести название товара в модальном окне в карточке (странице) товара — понятно, просто указать [[*pagetitle]] и все.
      Подскажите, а как можно вывести название товара в модальное окно в списке (категории) товаров (pagetitle выводит название ресурса категории)?
        Константин Ильин
        10 февраля 2023, 09:17
        +1
        Навскидку так вроде.
        Кнопке добавить data-pagetitle
        <button  ...  data-pagetitle="{$pagetitle | htmlent}"  ...>
        В js
        oncLickModal.addEventListener('show.bs.modal', function (e) {
            .....
            // Добавляем эту строку, ставим pagetitle в .modal-title
            oncLickModal.querySelector('.modal-title').innerText =  e.relatedTarget.dataset.pagetitle;
        
        });
          Алексей
          10 февраля 2023, 14:29
          0
          Добрый день. Только делал недавно.
          Это ссылка
          <a href="#modal-order" onclick="getEmpId(this)" data-name="{$pagetitle}" class="pt uk-button uk-button-primary uk-width-1-1" uk-toggle>Запросить наличие</a>
          Это js
          function getEmpId(element) {
              var empId = element.dataset.name
              document.querySelector("input[name=title]").value = empId
          }
          Это input
          <input type="text" id="af_title" name="title" value="" placeholder="" class="uk-input" readonly/>
            Максим
            11 февраля 2023, 13:49
            0
            Спасибо огромное — работает!
          Максим
          12 февраля 2023, 15:58
          0
          На главной выводится список товаров, в том числе параметром optionFilters:
          [[!msProducts?
                            &parents=`2`
                            &depth=`2`
                            &limit=`6`
                            &tpl=`tpl.msProducts.row.mainpage`
                            &optionFilters=`{"sklad:>":0}`
                            &sortby=`publishedon`
                            &sortdir=`DESC`
                          ]]
          Если на этой странице поместить вывод формы в модальное окно
          {'!AjaxFormItLogin' | snippet: [
                              'form' => 'tpl.oneclickorder',
                              'hooks' => 'oneClickHook',
                              'validate' => 'receiver:required,email:email:required,phone:tel:required,id:required',                    
                              'validationErrorMessage' => 'Заполните обязательные поля!'                   
            ]}
          то происходит ошибка
          [2023-02-12 15:56:37] (ERROR @ /home/p/paldrivepm/public_html/core/components/pdotools/model/pdotools/pdotools.class.php : 999) Unexpected token ':' in dab80369dc94a301e23cc1a737f03eac line 196, near '{"sklad:>":' <- there
          и главная страница неотображается
          Если убрать optionFilters, все работает.
          Можете подсказать куда копать?
            Артур Шевченко
            12 февраля 2023, 21:55
            0
            Конечно, это называется белый экран смерти сайта. Вы включили шаблонизатор Fenom на страницах и он пытает распарсить {«sklad:>»:0}, но не понимает что. Поставьте после { пробел и будет вам счастье.
            Maks
            14 февраля 2023, 14:23
            0
            const modal = bootstrap.Modal.getInstance(oncLickModal);
            Что то никак не хочет работать. Здесь нет ошибки? oncLickModal это правильно?
              Артур Шевченко
              14 февраля 2023, 14:42
              0
              Скорее всего есть, наверное должно быть oneClickModal
                Maks
                14 февраля 2023, 15:45
                0
                Не понятно как тогда отписывались что работает у людей если там ошибка. Или нашли но молчали.
              Maks
              14 февраля 2023, 15:46
              0
              Тем не менее что так что так не хочет работать. Бутстрап должен быть подключен или необязательно?
                Артур Шевченко
                14 февраля 2023, 17:50
                0
                Обязательно должен быть подключен он отвечает за работу модалки.
                Maks
                14 февраля 2023, 16:04
                0
                И хук кстати тоже не работает.
                  Maks
                  14 февраля 2023, 16:14
                  0
                  Жаль что не работает, нужная вещь/ Хотелось вашим дополнением заменить много что было реализовано ранее. Тем самым снести разные там modalertify, ajaxform
                    Максим
                    14 февраля 2023, 17:04
                    0
                    И у меня НЕ пашет… ID в модалку не залетает. В кнопке ID подтягивается.
                    Кстати, модалка в примере под Bootstrap 5. Меняйте классы под себя.
                      Артур Шевченко
                      14 февраля 2023, 17:48
                      0
                      Значит так, опечатка действительно есть, но поскольку опечатка во всём скрипте одинаковая и повторяется, то скрипт работает. Более того перед тем как написать заметку, я проверил работу всего о чём тут написано на реальном сайте. Если у вас что-то не работает полагаю, что вы где-то допустили ошибку.
                        Maks
                        14 февраля 2023, 18:12
                        0
                        На работающей форме проверил не работает хук. Заявки не отправляются в заказы.
                          Maks
                          14 февраля 2023, 18:24
                          0
                          Все ок заказы отправляет. Нет закрывается окно и после отправки нет уведомления что отправлено.
                          Подключил 5 бутстрап.
                        Максим
                        14 февраля 2023, 18:35
                        0
                        Пример реализации автором написан под bootstrap 5! Если 3 или 4, то надо адаптировать кнопку, окно и скрипт под себя. У меня пока затык со скриптом…
                          Maks
                          14 февраля 2023, 18:45
                          0
                          Я и подключил 5 бутстрап
                          Maks
                          14 февраля 2023, 18:36
                          0
                          В консоли ошибка
                          Uncaught TypeError: bootstrap.Modal.getInstance is not a function
                            Maks
                            14 февраля 2023, 18:53
                            0
                            Теперь выводит
                            Uncaught TypeError: can't access property «hide», modal is null
                            Maks
                            14 февраля 2023, 19:15
                            0
                            В общем проверил хук на другой форме где все уведомления работали. Форма отправляется заказ летит в заказы, а вот окно что все отправлено не появляется. Наверное хук не дает этого сделать.
                              Maks
                              14 февраля 2023, 19:27
                              0
                              Короче говоря методом тыка выяснил что после этого хука никакие другие хуки не срабатывают. Так же и уведомления не показываются после отправки формы.
                                Артур Шевченко
                                14 февраля 2023, 20:21
                                0
                                Хуки после oneClickHook выполнятся не будут потому, что минишоп завершает скрипт методом die(). Я в заметке отметил этот момент
                                Обратите внимание ни какой код после отправки заказа выполнен не будет, т.к. мы попросили MiniShop2 вернуть нам json, он сделает всё, что должен, и принудительно завершит работу.
                                А вот уведомления должны показываться, если нет ошибок в JS.
                                  Maks
                                  14 февраля 2023, 20:36
                                  0
                                  Я его последним ставлю все работает только уведомлений нет.
                                Максим
                                14 февраля 2023, 21:11
                                0
                                Специально у себя протестил.
                                Подключил js bootstrap 5, вставил все как в примере.
                                По итогу заказ с модалки улетает, но модалка не закрывается и нет уведомлений. Но это баг вероятнее всего из-за уже подключенных скриптов bs4. Если Артур поможет адаптировать скрипт под bs4, то можно было бы выложить пример для bootstrap 4. Сам не силен в скриптах.
                                  Maks
                                  14 февраля 2023, 21:14
                                  0
                                  У меня есть форма. Работала без данного хука прекрасно, все окно при ошибке и при успешной отправке появлялись. Когда подключаю хук то при незаполнении формы появляется сообщение что не заполнена, а при успешной отправке она отправляет данные только не появляется уведомление что все отправлено. Так что не думаю что здесь проблема в бутстрап.
                                Тодор
                                20 февраля 2023, 15:20
                                +1
                                Если не ошибаюсь, то етот код добавит в уже существующе корзину товар, после чего оформляет заказ. Но заказ в 1 клик ето покупка только поточного товара. По етомо после инициализации минишопа я бы еще добавил:
                                $tmp = $ms2->cart->get();
                                $ms2->cart->clean();
                                а после оформления заказа, вернуть назад прежнее содержимое корзини
                                $ms2->cart->set($tmp);
                                  Артур Шевченко
                                  20 февраля 2023, 15:40
                                  0
                                  Всё верно, корзину надо очищать перед оформлением, а вот вернуть то состояние которое было не выйдет, так как после выполнения метода submit() никакой код не выполнится.
                                    Тодор
                                    20 февраля 2023, 16:38
                                    0
                                    Я использу вот такой снипет для отладки ответов после submit():
                                    <?php
                                    $formFields = $hook->getValues();
                                    $count = (float) $formFields['count'];
                                    $id = (int) $formFields['id'];
                                    if($id <= 0){
                                        $hook->addError( 'id', "Неправильное ID товара" );
                                        return false;
                                    }
                                    if ($miniShop2 = $modx->getService('miniShop2')) {
                                        $miniShop2->initialize($modx->context->key, array(
                                            'json_response' => true,
                                            'max_count' => 1000,
                                            'allow_deleted' => false,
                                            'allow_unpublished' => false
                                        ));
                                        $tmp = $miniShop2->cart->get();
                                        $miniShop2->cart->clean();
                                        $miniShop2->cart->add( $id, $count, $formFields['options']);
                                        $miniShop2->order->add( 'receiver', $formFields['receiver'] );
                                        $miniShop2->order->add( 'email', $formFields['email'] );
                                        $miniShop2->order->add( 'delivery', $formFields['delivery']); // id метода доставки
                                        $miniShop2->order->add( 'payment', $formFields['payment']); // id метода оплаты
                                        if($response = $miniShop2->order->submit()){
                                            if($response['success'] != 1){
                                                $response = json_decode($response, 1);
                                                $hook->addError('receiver', $response['message']);
                                                if(!empty($response['data'])){
                                                    foreach($response['data'] as $field){
                                                        $hook->addError($field, $response['message']);
                                                    }
                                                }
                                                return false;
                                            }
                                            $miniShop2->cart->set($tmp);
                                            return true; // Успешное оформление заказа
                                        }else{
                                            $hook->addError('receiver', 'Ошибка при оформлении заказа');
                                            return false;
                                        }
                                    }else{
                                        $hook->addError("receiver", "Проблема инициализации магазина");
                                        return false;
                                    }
                                      Артур Шевченко
                                      20 февраля 2023, 17:16
                                      0
                                      Ну не знаю. Факт в том, что в моём варианте с minishop2 >4.0.0 код после $miniShop2->order->submit() не выполняется.
                                        Николай Савин
                                        20 февраля 2023, 17:25
                                        0
                                        Почему не выполняется? Должен возвращаться $response, в случае если включен json_response и существует метод оплаты. Я прекрасно реализую ecoomerce методы ПОСЛЕ submit
                                          Артур Шевченко
                                          20 февраля 2023, 17:32
                                          0
                                          ХЗ, я думал из-за die(). Проверь на досуге где косяк.
                                            Николай Савин
                                            20 февраля 2023, 17:39
                                            0
                                            Ну перед die же echo json_encode
                                            Ответ гарантированно должен быть и проверенно работает
                                              Артур Шевченко
                                              20 февраля 2023, 18:04
                                              0
                                              Ну вот у меня всё что возвращает submit() сразу на фронт улетает, а весь код в хуке после submit() почему-то не работает. При этом если поменять echo на return, то всё норм. Может я что-то не так делал, может что-то не так в AjaxFormitLogin, я не знаю. Надо ещё раз проверить.
                                  Максим
                                  04 августа 2023, 08:42
                                  0
                                  Артур, может есть вариант как при реализации сделать отправку заказа именно 1 товара? Суть: когда в корзине лежит уже 10 товаров и посетитель на другом товаре оформляет купить в один клик, то в админку прилетеает не один товар, а 10+1. Заранее спасибо!
                                    Николай Савин
                                    04 августа 2023, 09:06
                                    +1
                                    Ты кстати уже не первый, кто об этом спрашиваешь. Я начинаю задумываться о реализации вариантов корзин.
                                    Но пока на данный момент решение может быть только следующим (примерно):
                                    1. Временно сохраняем текущую корзину в сессию через $ms2->cart->get();
                                    2. Очищаем корзину
                                    3. Добавляем в нее новый товар
                                    4. Создаем заказ.
                                    5. Если не пустая сессия с сохраненной корзиной закидываем ее опять в корзину через $ms2->cart->set($_SESSION['tmp_cart'])
                                      Семён Кудрявцев
                                      04 августа 2023, 10:14
                                      +1
                                      Можно позаимствовать реализацию у онлайнтрейда — www.onlinetrade.ru/basket.html,
                                      очень удобно создаешь сколько угодно тебе корзин, называешь их как тебе надо, типа — присмотрел к др, подраки на нг, ит.д
                                      Лежат себе и кушать не просят, актуализируются автоматически.
                                      Если сделать грамотно, очень удобно будет. Корзины хранятся в бд, доступны менеджерам из админки, в любой момент могут их посмотреть, помочь клиенту дособрать, или оформить любую из корзин.
                                        Николай Савин
                                        04 августа 2023, 10:16
                                        0
                                        Да хороший наглядный пример. Ты как всегда на высоте Семен. Спасибо.
                                        Максим
                                        04 августа 2023, 10:18
                                        0
                                        Спасибо Николай за наводку. Не силен в бэке, по наитию сделал так. Заказ улетает, но корзина не восстанавливается с товарами из старой сессии. После отправки заказа корзина становится пустой:

                                        $values = $hook->getValues();
                                        $ms2 = $modx->getService('minishop2');
                                        $ms2->initialize('web');
                                        
                                        $count = (float)$values['count'] ?: 1;
                                        
                                        // Step 1: Temporarily save the current cart to the session
                                        $_SESSION['tmp_cart'] = $ms2->cart->get();
                                        
                                        // Step 2: Clear the cart
                                        $ms2->cart->clean();
                                        
                                        // Step 3: Add the specific product to the cart with the desired quantity
                                        $ms2->cart->add((int)$values['id'], $count, $values['options']);
                                        
                                        $ms2->order->config['json_response'] = true; // Ask to return JSON
                                        
                                        // Add fields to the order
                                        $ms2->order->add('receiver', $values['receiver']);
                                        $ms2->order->add('email', $values['email']);
                                        $ms2->order->add('phone', $values['phone']);
                                        $ms2->order->add('delivery', $values['delivery']);
                                        $ms2->order->add('payment', $values['payment']);
                                        $ms2->order->add('comment', $values['comment']);
                                        
                                        // Step 4: Create a new order
                                        $ms2->order->submit();
                                        
                                        // Step 5: Add other cart items from the session back to the cart
                                        if (isset($_SESSION['tmp_cart']) && is_array($_SESSION['tmp_cart']['products'])) {
                                            $ms2->cart->set($_SESSION['tmp_cart']);
                                        }
                                          Николай Савин
                                          04 августа 2023, 11:17
                                          0
                                          Конечно не восстанавливается, потому что метод submit редиректит на страницу спасибо или в платежную систему.
                                          Если у вас страница спасибо, то я бы на ней вызвал сниппет восстановления корзины.
                                          Если у вас редирект на платежку — то тут сложнее. Корзину лучше бы писать куда-то в профиль пользователя и после его возвращения восстанавливать ее. На сессии лучше не надеяться.
                                            Максим
                                            04 августа 2023, 12:57
                                            0
                                            У меня купить в один клик в виде модалки, после субмита появляется просто сообщение в виде спасибо. А в случае заказа через корзину, да, грузится страница мол ваш заказ такой-то, спасибо.
                                            Через профиль пользователя не выйдет, т.к. заказы оформляются от анонимов. Пробовал через кэш, но пока не выходит. Видимо надо кэшировать куда-то в файл и потом забирать от туда же.
                                            Артур Шевченко
                                            04 августа 2023, 20:56
                                            0
                                            Я же чёрным по белому написал
                                            Обратите внимание ни какой код после отправки заказа выполнен не будет, т.к. мы попросили MiniShop2 вернуть нам json, он сделает всё, что должен, и принудительно завершит работу.
                                            Есть надежда, что когда-нибудь @Николай Савин уберёт die() из обработчика заказа)))
                                        Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                                        52