2,451

(3 ответов, оставленных в Вопросы по работе с системой)

Добрый день
Ну это скорее частный для Вас случай. Поэтому лучше это реализовать отдельным модулем для Вас.
Вы можете почитать вот эту статью - о создании блок контроллера. И создать свой собственный блок контроллер.
За рекомендуемые товары отвечает /modules/catalog/controller/block/recommended.inc.php
Вы можете из него себе переделать вывод, чтобы он выполнял нужные Вам функции.

Есть ли возможность прикреплять Рекомендуемые целой категорией?

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

2,452

(10 ответов, оставленных в Вопросы по работе с системой)

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

2,453

(6 ответов, оставленных в Вопросы по работе с системой)

Если ссылку сделать вот так:

<a href="#" class="inDialog">ASDAF</a>

то всплывает текущая карточка товара.

Естественно. Это урл сам на себя просто с пустым якарем.


<a href="/templates/luchski/test.html" class="inDialog">TEST</a></p>

Дело вот в чём, inDialog ожидает json, который содержит поле html. Его выдают все страницы системы, если к ним обращаться через AJAX. Если вы хотите, открыть сторонний файл html. То тогда вот так:

<a href="/templates/default/test.html" onclick="$(this).colorbox();">Открыть</a>

И будет Вам счастье. smile

2,454

(10 ответов, оставленных в Вопросы по работе с системой)

да, последнее обновление по лицензии

Например, версия "Каталога товаров" - 2.0.0.121

Ольга и Сергей. Напишите на support@readyscript.ru доступы в админку, FTP и описание процесса тестирования и мы у Вас проверим.

Всем привет.
Довольно часто, приходится слышать от украинских покупателей, что хотелось бы кроме Robokassa ещё видеть у себя в способах оплаты Interkass-у. Недавно мы это реализовали и сделали отдельным модулем. Но в большинстве случаев люди немножко не понимают, как сделать им тоже самое для своего сайта, если заказчик им заказал интеграцию с каким-либо сервисом. Ну вот сегодня настал тот день, когда я раскрою все тайны и всё станет я думаю яснее.

Тем кому лень всё изучать даю прямую ссылку на готовый модуль smile



Скачать готовый модуль интеграции интеркассы для ReadyScript - распаковать в папку /modules/. В Веб-сайт->Настройка модулей сбросить кэш, перезагрузить страницу и установить модуль.



Задача сделать свой собственный дополнительный модуль, который позволит проводить платежи через Interkassa.
Вот как это можно сделать?
Смотрим документацию здесь по этому сервису. Всё довольно просто.

Предварительно для разработки нужно будет включить ошибки. Смотрим сюда.

Шаг 1. Сделаем свой собственный модуль.

В папке modules создаём папку с названием Вашего модуля(interkassa в нашем случае) и в ней создаём папку config
В этой папке создаём файл file.inc.php с данными описывающими модуль и пишем следующее содержимое:

<?php
namespace Interkassa\Config;
use \RS\Orm\Type;

/**
* Конфигурационный файл модуля
*/
class File extends \RS\Orm\ConfigObject
{ 
    
    /**
    * Возвращает значения свойств модуля по-умолчанию
    * 
    * @return array
    */
    public static function getDefaultValues()
    {
        return array(
            'name' => t('Интеркасса'), //Название нашего модуля
            'description' => t('Платёжная система - Интеркасса'), //Описание модуля
            'version' => '1.0.0.0', //Версия вашего модуля
            'author' => 'Сведения об авторе', //Сведения об авторе
        );
    }     
    
}

После чего заходим в админ панель в  "Веб-сайт" -> "Настройка модулей" и на этой странице сбрасываем кэш.
Появится наш модуль с названием "Интеркасса". Заходим в наш модуль. И нажимаем "установить модуль".
Всё модуль в системе зарегистрирован.


Шаг 2. Создание файла с хуком для добавления своего типа оплаты

В системе существует так называемая система хуков, которая позволяет встраиваться в различные места системы и выполнять свои функции. Документация по хукам находится здесь. Всё основано на том, что нужно указать в файле handlers свой нужный символьный идентификатор хука и потом создать public static function c таким же названием, только вырезав из него символы "." (точка) и "-" тире. Идём на страницу этой документации и спускаемся в низ до таблицы. Для нашего случая нам нужен хук payment.gettypes.
Т.е. нам нужно пропиcать bind этого алиаса хука и создать функцию с таким же названием вырезав лишнее.

Итак алиас будет выглядеть так:

function init()
    {
        $this
            ->bind('payment.gettypes');
    }

и функция соотвественно:

/**
    * Добавляем новый вид оплаты - Интеркасса
    * 
    * @param array $list - массив уже существующих типов оплаты
    * @return array
    */
    public static function paymentGetTypes($list)

Почему $list и как узнать что передаётся в хук? Смотрим внимательно в документацию по хукам в колонку "Тип параметра" напротив строки с "payment.gettypes".

В функцию paymentGetTypes поступает массив с типами оплаты со всей системы, а мы его просто дополним, добавив новый элемент в массив. Элементом массива будет класс который мы создадим и именно он будет обрабатывать все запросы от Interkassa.

Итак cоздаём файл handlers.inc.php в папке config. Пишем в него следующее содержимое:

<?php
namespace Interkassa\Config;
use \RS\Orm\Type as OrmType;

/**
* Класс предназначен для объявления событий, которые будет прослушивать данный модуль и обработчиков этих событий.
*/
class Handlers extends \RS\Event\HandlerAbstract
{
    function init()
    {
        $this
            ->bind('payment.gettypes');
    }
    
    /**
    * Добавляем новый вид оплаты - Интеркасса
    * 
    * @param array $list - массив уже существующих типов оплаты, который собирается со всей системы
    * @return array
    */
    public static function paymentGetTypes($list)
    {
        $list[] = new \Interkassa\Model\PaymentType\Interkassa(); //Интеркасса. Класс который мы создадим и будет обрабатывать все запросы к интеркассе.   
        return $list;
    }
}

Шаг 3. Создаём класс для интеркассы, который хранит все сведения.

Все классы модулей оплат вшитые в дистрибутив находятся в /modules/shop/model/paymenttype/.
Проще всего создавать свой класс оплаты на основе уже имеющихся в этой папке. Например для interkassa я переделывал из robokassa, т.к. алгоритм примерно схож.

Итак создаём файл в папке с нашим модулем по пути:
/modules/interkassa/model/paymenttype/interkassa.inc.php

Имя класса будет Interkassa, которая будет наследником абстрактного класса оплат \Shop\Model\PaymentType\AbstractType

<?php
namespace Interkassa\Model\PaymentType;
use \RS\Orm\Type;
use \Shop\Model\Orm\Transaction;

/**
* Способ оплаты - Interkassa
*/
class Interkassa extends \Shop\Model\PaymentType\AbstractType

Пропишем в соответствии с документацией interkassa наши константы

const
        API_URL     = "https://sci.interkassa.com/", //URL Api для взаимодействия
        PAYWAYS_URL = "https://api.interkassa.com/v1/paysystem-input-payway", //URL Api для получения полниго списка типов оплат
        IP_DIAPOZON = "85.10.225."; //Диапозон IP адресов интеркассы с которых должны приходить запросы

Разберём следующие стандартные функции создающего нами класса:

Функция getTitle() должна возвращать имя Вашего способа оплаты в Вашем модуле.

/**
    * Возвращает название расчетного модуля (типа доставки)
    * 
    * @return string
    */
    function getTitle()
    {
        return t('Интеркасса');
    }

Функция getDescription() должна возвращать описание вашего типа оплаты

/**
    * Возвращает описание типа оплаты. Возможен HTML
    * 
    * @return string
    */
    function getDescription()
    {
        return t('Оплата через агрегатор платежей "Интеркасса"');
    }

Функция getShortName() должна возвращать короткое имя (Именно на латиницей!) Вашего типа оплаты. Это нужно для того чтобы переключатся между типами оплаты и именно это название пойдёт как идентификатор Вашего типа оплаты внутри системы. Должно быть уникально по сравнению с теми что уже есть в системе.

/**
* Возвращает идентификатор данного типа оплаты. (только англ. буквы)
* 
* @return string
*/
function getShortName()
{
     return 'interkassa';
}

Функция isPostQuery() должна возвращать булевое значение. Если true, то пользователь будет перенаправлен на систему оплаты POST запросом. Если эту функцию не указывать, то пользователь будет переходить GET запросом на страницу оплаты.
Зачем это нужно? Дело вот в чем.
Когда пользователь совершает оплату и переходит на последнюю страницу (Завершение заказа), то ему предоставляется кнопка, которая либо возвращает пользователя на главную страницу либо даёт сформированную ссылку на систему оплаты. Эта функция отвечает как раз каким способом(GET или POST запросом) пользователь перейдёт на сайт системы оплаты.
В нашем случаем нам нужно POST поэтому:

/**
* Отправка данных с помощью POST?
* 
*/
function isPostQuery()
{
      return true;
}

Функция getFormObject() должна возвращать дополнительную форму для способа оплаты. Дело в том, что в способе оплаты по умолчанию уже есть набор постоянных полей. Но в соответствии с документациями часто требуется указать дополнительные параметры которые должен заполнить. Например Api ключ и Api пароль как в нашем случае. Поэтому при выборе класса оплаты будет ajax запросом подгружена форма с полями, которые мы укажем.
А также бывает часто нужно вывести дополнительную информацию для пользователя, чтобы он её вставил в настройках. Например для нашего случая в настройках интеркассы нужно ввести url которые будут обрабатывать приходящие запросы(будет рассмотрено ниже).
В этой функции реализуется возврат массива ORM объектов полей. Подробнее об этих полях можно почитать в документации.

Итак на основе документации по интеркассе получим следующее:

    /**
    * Возвращает ORM объект для генерации формы или null
    * 
    * @return \RS\Orm\FormObject | null
    */
    function getFormObject()
    {
        $properties = new \RS\Orm\PropertyIterator(array(
            'ik_co_id' => new Type\String(array(
                'maxLength' => 255,
                'description' => t('Checkout ID - индетификатор кассы'),
                
            )),
            'secret_key' => new Type\String(array(
                'description' => t('Секретный ключ'),
                'hint' => t('Указан на странице Вашей кассы'),
                'template' => '%interkassa%/form/payment/interkassa/secret_key.tpl'
            )),
            'test_key' => new Type\String(array(
                'description' => t('Тестовый ключ'),
                'hint' => t('Указан на странице Вашей кассы')
            )),
            'language' => new Type\String(array(
                'maxLength' => 5,
                'description' => t('Язык интерфейса'),
                'listFromArray' => array(array(
                    0    => t('Определяется Интеркассой'),
                    'ru' => t('Русский'),
                    'ua' => t('Украинский'),
                    'en' => t('Английский'),
                ))
            )),
            'ik_pw_via' => new Type\String(array(
                'maxLength' => 255,
                'description' => t('Тип оплаты:'),
                'default' => 0,
                'list' => array(array($this,'getPayways')),
            )),
            '__help__' => new Type\Mixed(array(
                'description' => t(''),
                'visible' => true,  
                'template' => '%interkassa%/form/payment/interkassa/help.tpl'
            )), 
        ));
        
        return new \RS\Orm\FormObject($properties);
    }

Поле __help__ как раз служит для получения отрендеренного шаблона с данными для вставки в настройки системы оплаты (Шаблон в ключе 'template').
%interkassa% - означает, что путь для поиска шаблона будет строится из папки с шаблонами модуля interkassa, а именно - /modules/interkassa/view/

Также приведём код в файлах шаблонов которые нам понадобятся для полей где указан ключ 'template'.

В этом шаблоне будут указаны 3 ссылки для вставки в настройки интеркассы. Url надо формировать в соотвествии с маршрутами в системе для оплаты.
Маршруты для этих урлов можно посмотреть в /modules/shop/config/handlers.inc.php в методе getRoutes.

В шаблоне сгенерировать урл можно конструкцией

{$router->getUrl('shop-front-onlinepay', [Act=>result, PaymentType=>$payment_type->getShortName()], true)}
//в итоге будет
//http://ВАШДОМЕН/onlinepay/interkassa/result/
//т.е. http://ВАШДОМЕН/onlinepay/Ваш класс/метод/

/modules/interkassa/view/form/payment/interkassa/help.tpl:

<h3>Настройка аккаунта Интеркасса</h3>


<p>Укажите алгоритм подписи у Вашей кассы - <b>MD5</b></p>

<p>Укажите эти URL в настройках Вашей кассы:</p>  

<b>URL ожидания проведения платежа: </b><br>
<a target="_blank" href="{$router->getUrl('shop-front-onlinepay', [Act=>result, PaymentType=>$payment_type->getShortName()], true)}">
    {$router->getUrl('shop-front-onlinepay', [Act=>result, PaymentType=>$payment_type->getShortName()], true)}
</a>

<br><br>

<b>URL взаимодействия платежа: </b><br>
<a target="_blank" href="{$router->getUrl('shop-front-onlinepay', [Act=>result, PaymentType=>$payment_type->getShortName()], true)}">
    {$router->getUrl('shop-front-onlinepay', [Act=>result, PaymentType=>$payment_type->getShortName()], true)}
</a>

<br><br>

<b>URL успешной оплаты: </b><br>
<a target="_blank" href="{$router->getUrl('shop-front-onlinepay', [Act=>success, PaymentType=>$payment_type->getShortName()], true)}">
    {$router->getUrl('shop-front-onlinepay', [Act=>success, PaymentType=>$payment_type->getShortName()], true)}
</a>

<br><br>

<b>URL неуспешной оплаты: </b><br>
<a target="_blank" href="{$router->getUrl('shop-front-onlinepay', [Act=>fail, PaymentType=>$payment_type->getShortName()], true)}">
    {$router->getUrl('shop-front-onlinepay', [Act=>fail, PaymentType=>$payment_type->getShortName()], true)}
</a>

<p>Укажите методы для всех URL - <b>POST</b></p>


/modules/interkassa/view/form/payment/interkassa/secret_key.tpl:

Это шаблон интересен тем, что здесь используется POST AJAX запрос в зависимости значения поля с секретным ключом и возвращаемый результат вставляется в соответствующие поле(выпадающий список).
Зачем? Дело в том, что в соответсвии с документацией пользователь при построеннии запроса на оплату может указывать канал оплаты, либо не указывать его и тогда ему будет доступен выбоор всех каналов оплат в интеркассе.

Для построения такого запроса нам нужно сформированить запрос на тотже url где мы находимся в админке но с параметром ?do=userAct.

Получить такой url можно так:

{$router->getAdminUrl('userAct')}

Также надо передать следующие параметры:
userAct - Метод который будет выполнятся в нашем классе и вернёт информацию (В нашем случае staticGetPaywaysByCheckoutID)
paymentObj - название латиницей класса типа оплаты в системе
interkassa - название латиницей папки вашего модуля
params - объект с параметрами дополнительными для передачи в наш статический метод

Обработка приходящего запроса будет в методе actionUserAct() в файле:
/modules/shop/controller/admin/paymentctrl.inc.php

В итоге выглядит так:

data     : {
                      'userAct'     : 'staticGetPaywaysByCheckoutID',
                      'paymentObj'  : 'interkassa',
                      'module'      : 'interkassa',
                      'params'      :{
                          'checkout_id' : $(".formbox input[name='data[ik_co_id]']").val(),
                          'secret_key'  : val
                      }
}

Полный текст файла

<div id="interkassaSecretKey" data-url="{$router->getAdminUrl('userAct')}" data-default-title="{t('-Все типы оплат-')}">
   {include file=$field->getOriginalTemplate()} 
</div>

<script type="text/javascript">
   $.allReady(function() {
       var aj;
       
       /**
       * Назначаем действие на текстовое поле с id кассы, если будет введен текст, 
       * то шлём запрос на сервер, чтобы он нам вернул нам доступные пути оплаты через интеркассу
       *
       */
       $("#interkassaSecretKey input[name='data[secret_key]']").off('keyup').on('keyup',function(){
           
           var val    = $(this).val();
           var select = $("select[name='data[ik_pw_via]']");
           
           
           if (val.length<16){ //Если символов не хватаем в списке будет пункт "Все"
              select.empty().append('<option value="0">'+ $("#interkassaSecretKey").data('defaultTitle')+'</option>'); 
           }else{ //Если есть ключ пробуем запросить пути оплаты для данной кассы
              if (typeof(aj)=='object'){
                 aj.abort(); 
              }  
              aj = $.ajaxQuery({
                  type     : "POST",
                  url      : $("#interkassaSecretKey").data('url'),
                  data     : {
                      'userAct'     : 'staticGetPaywaysByCheckoutID',
                      'paymentObj'  : 'interkassa',
                      'module'      : 'interkassa',
                      'params'      :{
                          'checkout_id' : $(".formbox input[name='data[ik_co_id]']").val(),
                          'secret_key'  : val
                      }
                  },
                  dataType : "json",
                  success  : function(responce){
                     select.empty();
                     
                     if (responce['data']['list'].length>0){
                        //Добавим возможные значения
                        $(responce['data']['list']).each(function(i){
                           select.append('<option value="'+responce['data']['list'][i]['key']+'">'+responce['data']['list'][i]['value']+'</option>'); 
                        });  
                        
                     }
                     
                  }
              });
              
           }
       });
   });
</script>

А метод в нашем классе будет таким:

/**
    * Получает возможные пути оплаты для привязанной к вам кассы
    * 
    * @param array|string $data - id кассы или массив параметров, среди которых, должен быть ключ "checkout_id"
    */
    public static function staticGetPaywaysByCheckoutID($data)
    {
        $_this = new self();
        
        $_this->setOption('secret_key',$data['secret_key']);
        $_this->setOption('ik_co_id',$data['checkout_id']);
        $data = $_this->getPayways();
        
        $payways = array();
        foreach ($data as $key=>$value){
           $payways[] = array(
              'key' => $key,  
              'value' => $value,  
           ); 
        }
        
        return array('list' => $payways);
    }

Функция canOnlinePay() должна возвращать булевое значение. Если true, то значит что это тип оплаты через внешнюю систему оплаты и должен на последнем шаге заказа оправить пользователя на url оплаты через систему оплаты. Если false, то этот тип оплаты не перенаправляет пользователя на систему оплаты, а перенаправляет на главную станицу. При этом наш класс оплаты формирует документы для обработки или отправки. Примерами служит тип оплаты "Счёт" и "Квитанция ПД4" для оплаты. Тем кому нужна реализация именно таких типов оплат без перехода на страницу внешних сервисов смотрите файлы:

/modules/shop/model/paymenttype/bill.inc.php и /modules/shop/model/paymenttype/formpd4.inc.php

/**
* Возвращает true, если данный тип поддерживает проведение платежа через интернет
* 
* @return bool
*/
function canOnlinePay()
    {
        return true;
    }


Шаг 4. Создаём основные функции в нашем классе для обмена информации.

Есть 5 основных функции для нашего класса, которые являются обязательными. Дело в том, что в процессе оплаты через онлайн сервисы оплаты требуется обмен информацией с системой оплаты и Вашим сайтом. Обмен происходит по определённым url в системе. Всего из 3:

1. url успешной оплаты
2. url не успешной оплаты
3. url который нужен для обмена информацией с системой онлайн оплаты и сайтом. Например, сведения, что оплата прошла успешно и нужно зарегистрировать изменения или нужно подтверждение от нашего сайта, которое скажет что оплатить данный счёт можно. Всё зависит от API системы онлайн оплаты к которой подключаемся.

В процессе обмена информацией между сайтом и системой онлайн оплаты обязательно должен передаваться id транзакции, который система онлайн оплаты возвращает. Он может быть передан как дополнительным параметром от системы Вашему сайту, либо одном из основным, в зависимости от API системы онлайн оплаты к которой подключаемся. А мы в своём случае должны получить данный id транзакции чтобы понять, какая сейчас транзакции происходит.


Идём по порядку по функциям:

1. getTransactionIdFromRequest(\RS\Http\Request $request)
При создании транзакции на онлайн оплату(происходит всегда, когда пользователь переходит на сайт оплаты нажимая кнопку завершить заказ). Транзакции присваивается уникальный идентификатор. Как его получить будет рассмотрено ниже. Удалять транзакции из базы вручную нельзя категорически, т.к. каждая новая последующая транзакция обладает своей уникальной подписью, которая основывается на всех предыдущих подписях транзакций. Если всё же вы так сделали, то придётся удалить все транзакции из системы в соответствующей таблице.


 
Эта функция(getTransactionIdFromRequest) служит для того, чтобы указать в каком параметре во входящих запросах от системы онлайн оплаты нам пришёл id транзации на оплату. В нашем случае придёт запрос со значением параметра с именем "ik_pm_no".

    /**
    * Возвращает ID заказа исходя из REQUEST-параметров соотвествующего типа оплаты
    * Используется только для Online-платежей
    * 
    * @return mixed
    */
    function getTransactionIdFromRequest(\RS\Http\Request $request)
    {
        return $request->request('ik_pm_no', TYPE_INTEGER, false);
    }

2. getPayUrl(\Shop\Model\Orm\Transaction $transaction)
Эта функция подготавливает ссылку  с параметрами, по которой перейдёт пользователь на систему оплаты для совершения платежа. Ссылка будет присвоена кнопке "Завершить заказ" на последней стадии оформления заказа.
В общем главная функция этой функции это составить правильный урл с параметрами для перенаправления пользователя на url оплаты.

Получить id транзакции можно так:

$inv_id = $transaction->id; //Получаем id транзакции

Также для получения параметров из дополнительных полей, которые мы создавали в функции getFormObject() можно делать так:

$field = $this->getOption('Имя поля',Значение поля по умолчанию, если не задано(не обязательный параметр));

//Т.е. получение значения поля формы с именем "ik_co_id"
$params['ik_co_id'] = $this->getOption('ik_co_id');

//Или с параметром по умолчанию
$way        = $this->getOption('ik_pw_via',0); //Тип канала оплаты

Если нам нужен GET запрос, то нужно сформировать просто строку с url. Если это POST запрос для системы, то мы возвращаем url API системы онлайн оплаты. И с помощью метода $this->addPostParams($params); добавим параметры для POST запроса.

В итоге для нашего POST запроса будет так:

/**
    * Возвращает URL для перехода на сайт сервиса оплаты
    * 
    * @param \Shop\Model\Orm\Transaction $transaction - ORM объект транзакции
    * @return string
    */
    function getPayUrl(\Shop\Model\Orm\Transaction $transaction)
    {
        
        $order      = $transaction->getOrder(); //Данные о заказе
        /**
        * @var mixed
        */
        $user       = $order->getUser(); //Пользователь который должен оплатить
        
        $inv_id     = $transaction->id;
        $out_summ   = round($transaction->cost, 2);
        $in_cur     = $this->getPaymentCurrency();
        $way        = $this->getOption('ik_pw_via',0); //Тип канала оплаты

        $params = array();
        $params['ik_co_id'] = $this->getOption('ik_co_id'); //ID кассы
        $params['ik_pm_no'] = $inv_id;
        $params['ik_cur']   = $in_cur;
        $params['ik_am']    = $out_summ;
        $params['ik_am_t']  = "invoice"; //Выбор способа оплаты будет на стороне интеркассы
        $params['ik_desc']  = t("Оплата заказа №").$order['order_num'];
        if ( $language = $this->getLanguage() ) {
           $params['ik_loc'] = $language; 
        }
        $params['ik_cli']  = $user['e_mail']; //Контакты покупателя
        
        //принудительно указываем метод post и url
        $router            = \RS\Router\Manager::obj();
        
        //Обработать process|payway|payways|payways_calc
        $params['ik_act'] = 'payways'; 
        if ($way){ //Если нужно отобразить все типы оплат
            $params['ik_act']    = 'payway';
            $params['ik_pw_via'] = $way; 
        }   
        
        $params['ik_int']  = 'web';     //Формат ответов json|web
        $params['ik_sign'] = $this->getParamsSign($params);
        
        $this->addPostParams($params); //Добавляем параметры для POST запроса
        
        return self::API_URL; //url пост запроса
    }


3. onResult(\Shop\Model\Orm\Transaction $transaction, \RS\Http\Request $request)
Эта фукция самая пожалуй главная, на неё должны приходить все запросы на обработку от системы. Например в нашем случае мы при помощи неё проверяем пришедшие данные из интеркассы, подтверждаем воможность оплаты данного платежа и проверяем статус оплаты. См. документацию интекассы

В итоге имеем:

  
    /**
    * Обработка запросов от интеркассы
    * 
    * @param \Shop\Model\Orm\Transaction $transaction - объект транзакции
    * @param \RS\Http\Request $request - объект запросов
    * @return string
    */
    function onResult(\Shop\Model\Orm\Transaction $transaction, \RS\Http\Request $request)
    {
        
        if (!$this->checkIPsDiapozon()){
           $exception = new \Shop\Model\PaymentType\ResultException(t('Неправильный диапозон IP адресов'));
           $exception->setResponse('Wrong IPs');
           throw $exception; 
        }
        
        if (!$this->checkMainParams($transaction, $request)){
           $exception = new \Shop\Model\PaymentType\ResultException(t('Главные параметры указаные в настройках интеркассы не прошли проверку'));
           $exception->setResponse('Wrong main params');
           throw $exception;  
        }
        
        //Смотрим текущий статус
        $status = $request->request('ik_inv_st',TYPE_STRING,0);
        
        switch($status){
            case "success":
                return 'OK'.$transaction->id;
                break;
            case "process":
                $exception = new \Shop\Model\PaymentType\ResultException(t('Неудачно совершённый платёж'));
                $exception->setResponse('Payment status progress');
                $exception->setUpdateTransaction(false);
                throw $exception;  
                break;
            case "waitAccept": //Если долгое ожидание проведения платежа, то пользователь перенаправляется к нам
                \RS\Application\Application::getInstance()->headers->addHeader('Location',$request->getSelfAbsoluteHost());
                break;
            case "fail":
            default:
               $exception = new \Shop\Model\PaymentType\ResultException(t('Неудачно совершённый платёж'));
               $exception->setResponse('Payment failed');
               throw $exception;   
               break;
        }
        
        return 'OK'.$transaction->id;
    }

В методе присутвует вызов исключений \Shop\Model\PaymentType\ResultException он очень важен т.к. он позволяет генерировать исключения, чтобы они сохранялись в системе, а также отдавали информацию для платёжной системе об итоге обработки их запроса.
Рассмотрим на прямом примере:

//Создаём класс исключения и в параметре передаём какой текст сохранится в системе у этой транзакции
$exception = new \Shop\Model\PaymentType\ResultException(t('Неудачно совершённый платёж'));
//Генерируем ответ на запрос для интеркассы
$exception->setResponse('Payment status progress');
//Устанавливаем флаг того, что статус транзакции не обновлять при сбошенном обновлении иначе платёж не удастся 
$exception->setUpdateTransaction(false);
//Бросаем исключение
throw $exception;  

Теперь поясню по результатам, которые надо возвращать с помощью этой функции(onResult):

1. В этом методе если будет брошено исключение, то статус транзакции установится в fail(неуспешно) и для нового платежа надо будет генерировать новое обращение к системе. Нужно, чтобы поставить статус что оплата провалилась. В интеркассу улетит сообщение $exception->setResponse

2. Если будет брошено исключение, но при этом будет использоваться функция $exception->setUpdateTransaction(false); и в аргумете будет false, то будет брошено исключение не влияющее на транзакцию, повторное обращение к сайту с этим платежом будет возможно и статус у транзакции не изменится. Например нужно, чтобы просто вернуть информацию о платеже системе онлайн оплаты, но статус транзакции при этом ещё не должен быть изменён.

3. Если исключение не будет брошено, а просто будет возвращён текст:

//Например
return 'OK'.$transaction->id;

То в систему онлайн оплаты улетит OKid транзакции и статут транзакции установится в успешный.



4. onFail(\Shop\Model\Orm\Transaction $transaction, \RS\Http\Request $request)
Эта функция не обязательна, т.к. имеется в родительском классе. По сути она показывает страницу с не успешным статусом оплаты на Вашем сайте. Для нашего случая внутри функции устанавливается ещё и статус транзакции.

/**
    * Вызывается при открытии страницы неуспешного проведения платежа 
    * Используется только для Online-платежей
    * 
    * @param \Shop\Model\Orm\Transaction $transaction
    * @param \RS\Http\Request $request
    * @return void 
    */
    function onFail(\Shop\Model\Orm\Transaction $transaction, \RS\Http\Request $request)
    {
        $transaction['status'] = $transaction::STATUS_FAIL;
        $transaction->update();
    }

5. onSuccess(\Shop\Model\Orm\Transaction $transaction, \RS\Http\Request $request)
Эта функция не обязательна, т.к. имеется в родительском классе. По сути она показывает страницу с успешным статусом оплаты на Вашем сайте.


Итого получаем такой файл:

<?php
namespace Interkassa\Model\PaymentType;
use \RS\Orm\Type;
use \Shop\Model\Orm\Transaction;

/**
* Способ оплаты - Interkassa
*/
class Interkassa extends \Shop\Model\PaymentType\AbstractType
{
    const
        API_URL     = "https://sci.interkassa.com/", //URL Api для взаимодействия
        PAYWAYS_URL = "https://api.interkassa.com/v1/paysystem-input-payway", //URL Api для получения полниго списка типов оплат
        IP_DIAPOZON = "85.10.225."; //Диапозон IP адресов интеркассы с которых должны приходить запросы
    
    /**
    * Возвращает название расчетного модуля (типа доставки)
    * 
    * @return string
    */
    function getTitle()
    {
        return t('Интеркасса');
    }
    
    /**
    * Возвращает описание типа оплаты. Возможен HTML
    * 
    * @return string
    */
    function getDescription()
    {
        return t('Оплата через агрегатор платежей "Интеркасса"');
    }
    
    /**
    * Возвращает идентификатор данного типа оплаты. (только англ. буквы)
    * 
    * @return string
    */
    function getShortName()
    {
        return 'interkassa';
    }
    
    /**
    * Отправка данных с помощью POST?
    * 
    */
    function isPostQuery()
    {
        return true;
    }
    
    /**
    * Возвращает ORM объект для генерации формы или null
    * 
    * @return \RS\Orm\FormObject | null
    */
    function getFormObject()
    {
        $properties = new \RS\Orm\PropertyIterator(array(
            'ik_co_id' => new Type\String(array(
                'maxLength' => 255,
                'description' => t('Checkout ID - индетификатор кассы'),
                
            )),
            'secret_key' => new Type\String(array(
                'description' => t('Секретный ключ'),
                'hint' => t('Указан на странице Вашей кассы'),
                'template' => '%interkassa%/form/payment/interkassa/secret_key.tpl'
            )),
            'test_key' => new Type\String(array(
                'description' => t('Тестовый ключ'),
                'hint' => t('Указан на странице Вашей кассы')
            )),
            'language' => new Type\String(array(
                'maxLength' => 5,
                'description' => t('Язык интерфейса'),
                'listFromArray' => array(array(
                    0    => t('Определяется Интеркассой'),
                    'ru' => t('Русский'),
                    'ua' => t('Украинский'),
                    'en' => t('Английский'),
                ))
            )),
            'ik_pw_via' => new Type\String(array(
                'maxLength' => 255,
                'description' => t('Тип оплаты:'),
                'default' => 0,
                'list' => array(array($this,'getPayways')),
            )),
            '__help__' => new Type\Mixed(array(
                'description' => t(''),
                'visible' => true,  
                'template' => '%interkassa%/form/payment/interkassa/help.tpl'
            )), 
        ));
        
        return new \RS\Orm\FormObject($properties);
    }
    
    
    /**
    * Возвращает true, если данный тип поддерживает проведение платежа через интернет
    * 
    * @return bool
    */
    function canOnlinePay()
    {
        return true;
    }
    
    /**
    * Возвращает URL для перехода на сайт сервиса оплаты
    * 
    * @param Transaction $transaction - ORM объект транзакции
    * @return string
    */
    function getPayUrl(\Shop\Model\Orm\Transaction $transaction)
    {
        
        $order      = $transaction->getOrder(); //Данные о заказе
        /**
        * @var mixed
        */
        $user       = $order->getUser(); //Пользователь который должен оплатить
        
        $inv_id     = $transaction->id;
        $out_summ   = round($transaction->cost, 2);
        $in_cur     = $this->getPaymentCurrency();
        $way        = $this->getOption('ik_pw_via',0); //Тип канала оплаты

        $params = array();
        $params['ik_co_id'] = $this->getOption('ik_co_id'); //ID кассы
        $params['ik_pm_no'] = $inv_id;
        $params['ik_cur']   = $in_cur;
        $params['ik_am']    = $out_summ;
        $params['ik_am_t']  = "invoice"; //Выбор способа оплаты будет на стороне интеркассы
        $params['ik_desc']  = t("Оплата заказа №").$order['order_num'];
        if ( $language = $this->getLanguage() ) {
           $params['ik_loc'] = $language; 
        }
        $params['ik_cli']  = $user['e_mail']; //Контакты покупателя
        
        //принудительно указываем метод post и url
        $router            = \RS\Router\Manager::obj();
        
        //Обработать process|payway|payways|payways_calc
        $params['ik_act'] = 'payways'; 
        if ($way){ //Если нужно отобразить все типы оплат
            $params['ik_act']    = 'payway';
            $params['ik_pw_via'] = $way; 
        }   
        
        $params['ik_int']  = 'web';     //Формат ответов json|web
        $params['ik_sign'] = $this->getParamsSign($params); 
        
        $this->addPostParams($params); //Добавляем параметры для POST запроса
        
        return self::API_URL; //url пост запроса
    }
    

    
    /**
    * Получает все варианты оплаты 
    * 
    * @param string $checkout_id - id кассы
    */
    function getPayways()
    {
        
        $params = array();
        
        $params['ik_co_id'] = $this->getOption('ik_co_id','');
        $params['ik_pm_no'] = 'PAYWAYS_1';
        $params['ik_am']    = 1;
        $params['ik_desc']  = 'Look up my payways';
        $params['ik_act']   = 'payways';
        $params['ik_int']   = 'json';
        $params['ik_sign']  = $this->getParamsSign($params);
        
        // Create a stream
        $opts = array(
            'http'=>array(
                'method'=>"GET",
            )
        );
        
        $context = stream_context_create($opts);
        $params = http_build_query($params);
        // Получим оплаты, которые привязаны к кассе
        $data = json_decode(file_get_contents(self::API_URL."?".$params, false, $context));

        // Подготовим массив
        $payways = array(0 => t('-Все типы оплат-'));
        
        if (isset($data->resultMsg) && $data->resultMsg=="Success"){ //Если всё прошло успешно и мы получили типы путей оплаты
            foreach($data->resultData->paywaySet as $way){
                $payways[$way->als] = mb_strtoupper($way->ser)." - ".mb_strtoupper($way->curAls);
            }
        }
        asort($payways);

        return $payways;
    }
    
    /**
    * Получает возможные пути оплаты для привязанной к вам кассы
    * 
    * @param array|string $data - id кассы или массив параметров, среди которых, должен быть ключ "checkout_id"
    */
    public static function staticGetPaywaysByCheckoutID($data)
    {
        $_this = new self();
        
        $_this->setOption('secret_key',$data['secret_key']);
        $_this->setOption('ik_co_id',$data['checkout_id']);
        $data = $_this->getPayways();
        
        $payways = array();
        foreach ($data as $key=>$value){
           $payways[] = array(
              'key' => $key,  
              'value' => $value,  
           ); 
        }
        
        return array('list' => $payways);
    }
    
    /**
    * Получает нужную подпись для разных режимов
    * 
    * @param boolean $test - флаг, что нужно использовать тестовый ключ
    */
    private function getRightSign($test = false)
    {
        if ($test && ($this->getOption('ik_pw_via',false) == "test_interkassa_test_xts")){
           file_put_contents(__DIR__.'/file.txt',date('Y-m-d H:i:s')."\n"."345435\n",FILE_APPEND);  
           return $this->getOption('test_key',''); 
        }
        return $this->getOption('secret_key','');
    }
    
    /**
    * Получает подпись для платежа формируемая по правилам Интеркассы
    * 
    * @param array $params - массив параметров
    * @param boolean $test - флаг, что нужно использовать тестовый ключ
    */
    private function getParamsSign( $params, $test = false )
    {
        unset($params['ik_sign']); //Удаляем из данных строку подписи 
        ksort($params, SORT_STRING); //Сортируем по ключам в алфовитном порядке
        array_push($params, $this->getRightSign($test)); //Конкатинируем символом ":"
        return base64_encode(md5(implode($params, ":"), true)); //MD5 в бинарном виде в кодированном base64
    }
    
    /**
    * Получает язык в котором будет представлен интерфейс Интеркассы
    */
    private function getLanguage()
    {
       return $this->getOption('language',0) ? $this->getOption('language',0) : false; 
    }
    
    /**
    * Проверяет диапозон IP адреса. Проверяет IP адреса интеркассы
    * 
    * @return boolean
    */ 
    private function checkIPsDiapozon()
    {
       $ip = $_SERVER['REMOTE_ADDR']; 
       if (stripos($ip, self::IP_DIAPOZON)===false){
          return false; 
       } 
       return true;
    }
    
    /**
    * Получает трех символьный код базовой валюты в которой ведётся оплата
    * 
    */
    private function getPaymentCurrency()
    {
       /**
       * @var \Catalog\Model\Orm\Currency
       */
       $currency = \RS\Orm\Request::make()
                        ->from(new \Catalog\Model\Orm\Currency())
                        ->where(array(
                           'public'  => 1,
                           'is_base'  => 1,
                        ))
                        ->object(); 
       return $currency ? $currency->title : false;
    }
    
    
    
    /**
    * Проверяем подпись запроса
    * 
    * @param string $sign - подпись запроса
    */
    private function checkSign($sign, $test = false)
    {
        $ik = array();
        foreach ($_REQUEST as $key=>$value){
           if (stripos('ik_')!==false){
              $ik[$key] = $value;  
           }
        }
        $my_sign = $this->getParamsSign($ik, $test); //Получаем нами сформированную подпись
        // Проверка корректности подписи
        return $my_sign == $sign;
    }
    
    /**
    * Проверяет основные параметры приходящие от интеркассы сравнивая с теми, что уснавлены в настройках системы
    * 
    * @param \Shop\Model\Orm\Transaction $transaction - объект транзакции
    * @param \RS\Http\Request $request - объект запросов
    */
    private function checkMainParams(\Shop\Model\Orm\Transaction $transaction, \RS\Http\Request $request)
    {
        $ik_co_id  = $request->request('ik_co_id',TYPE_STRING,'');
        $ik_am     = $request->request('ik_am',TYPE_STRING,'');
        $ik_inv_st = $request->request('ik_inv_st',TYPE_STRING,'');
        $ik_sign   = $request->request('ik_sign',TYPE_STRING,'');
        
        if ( $ik_co_id != $this->getOption('ik_co_id','') ) {
            return 'ID кассы';
        }
        if ( $ik_am != round($transaction->cost, 2) ) {
            return 'Сумма платежа';
        }
        if (!in_array($ik_inv_st,array('process','success','fail','waitAccept'))) {
            return 'параметр ik_inv_st='.$ik_inv_st;
        }
        $ik = array();
        foreach ($_REQUEST as $key=>$value){
           if (stripos('ik_')!==false){
              $ik[$key] = $value;  
           }
        }
        $test = ($request->request('ik_pw_via',TYPE_STRING,'') == "test_interkassa_test_xts");
        if (!$this->checkSign($ik_sign, $test)){
            return 'Неправильная подпись. Параметр ik_sign='.$ik_sign." ".$this->getParamsSign($ik);
        }
        return true;
    }
    
    /**
    * Возвращает ID заказа исходя из REQUEST-параметров соотвествующего типа оплаты
    * Используется только для Online-платежей
    * 
    * @return mixed
    */
    function getTransactionIdFromRequest(\RS\Http\Request $request)
    {
        return $request->request('ik_pm_no', TYPE_INTEGER, false);
    }
    
    /**
    * Обработка запросов от интеркассы
    * 
    * @param \Shop\Model\Orm\Transaction $transaction - объект транзакции
    * @param \RS\Http\Request $request - объект запросов
    * @return string
    */
    function onResult(\Shop\Model\Orm\Transaction $transaction, \RS\Http\Request $request)
    {
        
        if (!$this->checkIPsDiapozon()){
           $exception = new \Shop\Model\PaymentType\ResultException(t('Неправильный диапозон IP адресов'));
           $exception->setResponse('Wrong IPs');
           throw $exception; 
        }
        
        if (!$this->checkMainParams($transaction, $request)){
           $exception = new \Shop\Model\PaymentType\ResultException(t('Главные параметры указаные в настройках интеркассы не прошли проверку'));
           $exception->setResponse('Wrong main params');
           throw $exception;  
        }
        
        //Смотрим текущий статус
        $status = $request->request('ik_inv_st',TYPE_STRING,0);
        
        switch($status){
            case "success":
                return 'OK'.$transaction->id;
                break;
            case "process":
                $exception = new \Shop\Model\PaymentType\ResultException(t('Неудачно совершённый платёж'));
                $exception->setResponse('Payment status progress');
                $exception->setUpdateTransaction(false);
                throw $exception;  
                break;
            case "waitAccept": //Если долгое ожидание проведения платежа, то пользователь перенаправляется к нам
                \RS\Application\Application::getInstance()->headers->addHeader('Location',$request->getSelfAbsoluteHost());
                break;
            case "fail":
            default:
               $exception = new \Shop\Model\PaymentType\ResultException(t('Неудачно совершённый платёж'));
               $exception->setResponse('Payment failed');
               throw $exception;   
               break;
        }
        
        return 'OK'.$transaction->id;
    }
    
    /**
    * Вызывается при открытии страницы неуспешного проведения платежа 
    * Используется только для Online-платежей
    * 
    * @param \Shop\Model\Orm\Transaction $transaction
    * @param \RS\Http\Request $request
    * @return void 
    */
    function onFail(\Shop\Model\Orm\Transaction $transaction, \RS\Http\Request $request)
    {
        $transaction['status'] = $transaction::STATUS_FAIL;
        $transaction->update();
    }
    
}

Как отлаживать всё это дело?
Я лично все запросы которые пришли на url пишу в файл. Например так:

//Пишет в текущую папку в файл info.txt весь массив запроса
file_put_contents(__DIR__."/info.txt","\n".var_export($_REQUEST,true),FILE_APPEND);

2,456

(6 ответов, оставленных в Вопросы по работе с системой)

А чем Вас наш colorboх не устраивает? Если вы пользуетесь нашей темой оформления, то вы можете ссылке указать класс inDialog. У Вас откроется ссылка в всплывающем окне.
В theme.js должна быть строка $('.inDialog').openInDialog();
А сама функция openInDialog лежит в common.js

2,457

(6 ответов, оставленных в Вопросы по работе с системой)

Насколько я помню lightbox, то там как и с фотками подсовываете просто урл у ссылки на нужный html документ.

2,458

(10 ответов, оставленных в Вопросы по работе с системой)

Опишите пожалуйста по шагам, как тестировали и что делали пожалуйста. И лучше если в support@readyscript.ru. Мы буем тестировать и если что выпустим обновление с исправлением.

2,459

(6 ответов, оставленных в Вопросы по работе с системой)

Да нет в том то и дело, просто хотелось понять для какой темы требуется.
Вам видимо придётся свой модуль дописать, который будет ещё и возвращать в json доступные фильтры. Но это довольно не тривиальная задача. Попробуйте посмотреть вот эту статью по созданию блок контроллера и на основе блок контроллера фильтров сделать свой блок контроллер.
Блок контроллер находится по пути:
/modules/catalog/controller/block/sidefilters.inc.php

Либо можно подменить существующий блок контроллер своим файлом. Создав в тойже папки файл
/modules/catalog/controller/block/sidefilters.my.inc.php

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

Либо можно нам такую разработку заказать на этой странице, но задача не простая сразу скажу, т.к. там будет множество кейсов.

Либо можно как-то пересмотреть структуру поместив в разные папки майки и футболки и давать прямые ссылки на них. Я думаю это будет самое малозатратное.

2,460

(6 ответов, оставленных в Вопросы по работе с системой)

Вы используете стандартную тему оформления?

2,461

(11 ответов, оставленных в Вопросы по работе с системой)

А у меня не получилось почему-то настроить Email уведомления магазина. Можно по шагам для совсем чайников?
С какой почты будет отправляться?
Поле "От " заполнила.

И восстановление пароля тоже на почту не приходит.

А вы вообще от куда и куда слать будете письмо? Я имею ввиду почтовые ящики и какой у вас домен с которого отправляете?
Можете часть букв закрыть "*"

Не за что)

Ну что получилось?

2,464

(2 ответов, оставленных в Вопросы по работе с системой)

Нашёл решение проще:

Удалите папки /modules/exchange, /modules/partnership, /cache
В файле /package.inc.php заменить строку Shop.Full на Shop.Middle

2,465

(2 ответов, оставленных в Сайт ReadyScript)

Кстати возможна и подобная проблема Схожая тема.

2,466

(6 ответов, оставленных в Вопросы по работе с системой)

А Вы справа от полей мета-тегов на вопросик наведитесь. И кстати {title} пишется внутри без пробелов.

Ну пока это не предусмотрено, но решение логичное. Напишите пожалуйста нам в сервис пожелание, мы когда доберёмся сделаем.
Ссылка на пожелания 

2,468

(2 ответов, оставленных в Вопросы по работе с системой)

Обратный путь не предусмотрен насколько я знаю, только в большую сторону можно. Вам надо будет поставить в другую папку на сервере Маркет версию. Например сделав поддомен.
А из первой папки с шаблонами скопировать свой шаблон в папку с шаблонами второй папки.
Что касается данные товаров. То Вы можете выгрузить все данные с помощью CSV, фотографии все хранятся в папке /storage/photo/original(Её тоже скопировать). Затем загрузить CSV на другом поддомене и все Ваши товары будут проимпортированны.
В конце просто переименуете папки между собой. А поддомен который будет ссылаться на уже первую папку вместо второй просто удалите.
Примерно так.

Добрый день.
Ну чтож, тут есть сразу не сколько решений. Давайте Рассмотрим всё по порядку:

Вариант 1
Нами к smarty(шаблонозатор системы) написан плагин, который позволяет вызывать статический метод  у любого класса системы прямо в шаблоне, главное чтобы это был метод public static
Т.е. допустим у Вас имеется свой собственный класс по пути \Custom\Model\CustomApi и метод loadInfo. Чтобы его вызвать нужно воспользоваться следующей конструкцией:

В шаблоне

{* $list - список Ваших объектов *}
{$list}

{* Вызов метода loadInfo в \Custom\Model\CustomApi *}
{static_call var=new_list callback=['\Custom\Model\CustomApi','loadInfo'] params=[$list]}

Таким образом мы сделаем аналог вызова функции вот так:

$new_list = \Custom\Model\CustomApi::loadInfo($list);

Т.е. в шаблоне все данные, которые будут возвращены в шаблон, возвратятся в $new_list

В Вашем классе надо будет сделать так

/**
*
* @param array $list - массив входящих объектов
*/
public static function loadInfo($list){
    //Пройдёмся в цикле по списку
    foreach ($list as $k=>$item){
       //Получим объект товара сделав запрос к таблице товаров через \RS\ORM\Request 
       $product = \RS\ORM\Request::make()
                             ->from(new \Catalog\Model\Orm\Product()) //Укажем что запрос будет в таблицу товаров
                             ->where(array(
                                   'id' => $item['product_id'] 
                             ))
                             ->object(); //Получим объект \Catalog\Model\Orm\Product
       
       //Ну или вот так ещё короче $product= new \Catalog\Model\Orm\Product($item['product_id']);
        
       $list['product_title'] = $product['title']; //Создаём новый ключ массива, куда и запишем значение названия товара
       //Или можно записать весь объект
       $list['product'] = $product;
       return $list;
    }
}

Получить ссылку на товар можно тогда так:

{$new_list.0.product->getUrl()}
{*
   Аналог
   $new_list[0]['product']->getUrl();
*}

Подробнее метод static_call описани в документации


Вариант 2
Если используете блок контроллер или front контроллер, то можно прямо в контроллере подгрузить эти сведения о товаре. Например через join.
Рассмотрим на примере:
Допустим Вы запросом получаете сведения из одной таблицы, и вам нужно прикрепить данные из другой таблицы.

//Получим список комплектаций с подгруженным полем из товара
$list = \RS\Orm\Request::make()
                 ->select('O.*, P.title as product_title') //Выбрать только все поля таблицы комплектаций и одно поле товара
                 ->from(new \Catalog\Model\Orm\Offer(),'O')  //Запрос к такблице комплектаций с алиасом O
                 ->join(new \Catalog\Model\Orm\Product(),'O.product_id=P.id','P') //Join к таблице товаров с алиасом P
                 ->where("O.product_id>0") // WHERE  O.product_id>0
                 ->objects();

2,470

(7 ответов, оставленных в Вопросы по работе с системой)

У вас на хостинге стоит ограничение в 2 МБ на загрузку файлов. Обратитесь в хостинг для увеличения этого параметра post_max_size и upload_max_filesize.

2,471

(2 ответов, оставленных в Сайт ReadyScript)

Видимо у Вас совсем слабый хостинг. Скажите(хостингу) чтобы поставили хотя бы 5 минут для Вашего хостинга. max_execution_time

Ну конечно. У меня Текущая комплектация системы: Shop.Full. Версия ядра: 2.0.0.132.
Скачайте новый дистрибутив. Или обновитесь.
Там уже функционала ещё целая тонна добавилась.

Проверил у себя. Всё показывается. Может вы что-то не так делаете?

Ааа, если демо. Тогда должен быть последний. Ну если конечно вы сегодня скачали, а не месяцы назад. Сейчас посмотрю и у себя.

А у вас случайно шаблон не старый? Я просто посмотрел, вроде должно отображаться.