This is the multi-page printable view of this section.
Click here to print.
Return to the regular view of this page.
Шаблоны
Шаблоны позволяют генерировать как однострочные команды, так и целые блоки текста.
Эти механизмы являются основой построения взаимодействий SHM.
Шаблоны рендерятся локально (сервером SHM), результат рендера может быть отправлен/выполнен с помощью Транспорта.
Шаблоны служат:
Введение
Синтаксис: {{ ОБЪЕКТ.ДАННЫЕ }}
Например, с помощью объекта user можно получить доступ к данным пользователя:
user.id - идентификатор пользователя
user.login - логин пользователя
user.balance - баланс пользователя
Используя эти функции мы можем написать такой шаблон:
Уважаемый клиент.
Ваш логин: {{ user.login }}
Ваш баланс: {{ user.balance }} руб.
В шаблонах поддерживаются условия и циклы, примеры использования вы можете увидеть здесь: Прогноз оплаты
Больше информации о шаблонизаторе Вы можете узнать здесь
Объекты и функции
Ниже приведен список методов для работы с SHM через шаблоны.
Достуность тех или иных методов зависит от контекста применения шаблона.
Подробнее о доступности методов описано здесь.
Пользователь
| Метод |
Описание |
| user.id() |
Идентификатор пользователя (получить/установить) |
| user.switch( USER_ID ) |
Переключение пользователя на указанного (смена контекста) |
| user.login |
Логин пользователя |
| user.login2 |
Логин пользователя (дополнительный) |
| user.email |
Email пользователя. Последовательно проверяет: settings пользователя → login → login2 → профиль. Возвращает первый валидный адрес. |
| user.balance |
Баланс пользователя |
| user.credit |
Кредитный лимит пользователя |
| user.dogovor |
Договор пользователя |
| user.full_name |
ФИО пользователя |
| user.settings |
Получить settings пользователя |
| user.get_bonus |
Получить кол-во бонусов |
| user.income_percent |
Получить процент партнерских бонусов |
| user.add_bonus( КОЛ-ВО, КОММЕНТ ) |
Начисление бонусов |
| user.set_settings( foo = 1 ) |
Сохранить в settings пользователя произвольные данные |
| user.services |
Ссылка на услуги пользователя |
| user.gen_session.id |
Специальная функция для генерации идентификатора сессии |
| user.set_new_passwd |
Смена пароля пользователя. Вернет новый пароль |
| user.make_autopayment( SUM ) |
Выполняет автоплатеж на сумму SUM, если для пользователя доступен и разрешен автоплатеж |
| user.pays. |
Ссылка на платежи пользователя |
| user.delete |
Удаление пользователя (с нулевым балансом, без услуг) |
| user.items() |
Получить всех пользователей |
| new_user = user.reg( login = ‘LOGIN’, password = ‘PLAIN_PASSWORD’, partner_id = ‘USER_PARTNER_ID’ ) |
Регистрация нового пользователя |
Услуги пользователя
| Метод |
Описание |
| us.id() |
Идентификатор пользовательской услуги (получить/установить) |
| us.name |
Имя пользовательской услуги |
| us.created |
Дата создания пользовательской услуги |
| us.expire |
Дата истечения пользовательской услуги |
| us.status |
Статус пользовательской услуги |
| us.settings |
Получить параметры пользовательской услуги |
| us.set_settings( foo = 1 ) |
Сохранить в settings услуги пользователя произвольные данные |
| us.set( FIELD = VALUE ) |
Установка поля FIELD в значение VALUE. Пример: us.set( next = 123 ) |
| us.child_by_category(CATEGORY). |
Ссылка на дочернюю услугу определенной категории |
| us.finish( money_back = 1 ) |
Завершение услуги с возвратом средств (биллинг продлит или заблокирует услугу в зависимости от наличия средств) |
| us.block |
Принудительная блокировка услуги пользователя |
| us.activate |
Активация услуги пользователя после блокировки |
| us.delete |
Удаление заблокированной услуги пользователя |
| us.gen_store_pass |
Специальная функция для генерации и сохранения пароля в settings |
| us.parent. |
Ссылка на родительскую услугу пользователя |
| us.top_parent. |
Ссылка на самую верхнюю услугу пользователя |
| us.service. |
Ссылка на каталог услуг |
| us.withdraw. |
Ссылка на списание услуги |
| us.event( EVENT_NAME ) |
Повторное выполнение стандартного события для услуги |
| us.make_custom_event( name = NAME, title = TITLE, … ) |
Создание пользовательского события для услуги |
| us.add_period_by_money( СУММА ) |
Продлевает активную услугу на период, соответствующий переданной сумме: рассчитывает количество месяцев исходя из стоимости услуги (с учётом скидки), обновляет дату истечения, списывает СУММА с баланса пользователя и создаёт событие продления. Возвращает 1 при успехе, undef — если услуга не активна, сумма ≤ 0, отсутствует способ списания или рассчитанный период равен нулю. |
| us.items() |
Получение списка услуг пользователя |
Каталог услуг
| Метод |
Описание |
| service.id() |
Идентификатор пользовательской услуги (получить/установить) |
| service.name |
Название услуги |
| service.cost |
Базовая стоимость услуги |
| service.period |
Период услуги |
| service.category |
Категория услуги |
| service.server |
Ссылка на сервер услуги |
| service.id( N ).name |
Получение имени услуги c идентификатором N |
| service.id( N ). |
Получение произвольного поля услуги c идентификатором N |
| service.price_list() |
Возвращает прайс-лист услуг с расчетом скидок и бонусов |
| service.price_list_check_allow_to_order |
Проверка, доступна ли услуга для регистрации в текущем контексте пользователя |
| service.settings |
Получить settings услуги |
| service.set_settings( foo = 1 ) |
Сохранить в settings услуги произвольные данные |
| service.is_ever_used |
Проверяет, использовалась ли услуга когда-либо (включая текущие активные/блокированные/завершенные состояния) |
| service.was_previously_used |
Проверяет, использовалась ли услуга ранее и сейчас удалена (status = REMOVED) |
| service.is_currently_used |
Проверяет, используется ли услуга сейчас (любой статус, кроме REMOVED) |
| service.withdraw. |
Ссылка на объект списания |
| service.reg( service_id = N, check_allow_to_order = 1 ) |
Регистрирует услугу клиенту с идентификатором N |
| service.items() |
Получение списка услуг из каталога |
Платежи
| Метод |
Описание |
| pay.id() |
Получить/установить id платежа |
| pay.date |
Дата и время платежа |
| pay.money |
Cумма платежа |
| pay.pay_system_id |
Имя платежной системы |
| pay.comment |
Данные платежа |
| pay.last |
Получить ссылку на последний платеж |
| pay.forecast |
Возвращает JSON прогноза оплат услуг |
| pay.paysystems |
Получить список платежных систем |
| pay.items() |
Получение списка платежей |
Бонусы
| Метод |
Описание |
| bonus.id |
Идентификатор бонуса |
| bonus.date |
Дата создания бонуса |
| bonus.bonus |
Кол-во бонусов |
| bonus.comment |
Комментарий |
| bonus.amount |
Кол-во бонусов (псевдоним для user.get_bonus) |
| bonus.items() |
Получение списка бонусов |
Списания
| Метод |
Описание |
| wd.id() |
Получить/установить id списания |
| wd.create_date |
Дата создания списания |
| wd.withdraw_date |
Дата списания списания |
| wd.cost |
Сумма |
| wd.discount |
Скидка |
| wd.bonus |
Кол-во бонусов |
| wd.months |
Период услуги |
| wd.total |
Итоговая стоимость |
| wd.service_id |
идентификатор каталога услуг |
| wd.user_service_id |
идентификатор услуги пользователя |
| wd.qnt |
Кол-во единиц товара |
| wd.items() |
Получение списка списаний |
Сервера
| Метод |
Описание |
| server.id() |
Получить/установить id сервера |
| server.name |
Имя сервера |
| server.host |
Host сервера |
| server.transport |
Транспорт сервера |
| server.settings |
Получение settings текущего сервера |
| server.set_settings( foo = 1 ) |
Сохранить в settings сервера произвольные данные |
| server.group. |
Ссылка на группу сервера |
| server.servers_by_group_id( N ) |
Получение списка серверов из группы N |
| server.items() |
Получение списка серверов |
Группы серверов
| Метод |
Описание |
| sg.id() |
Получить/установить id группы серверов |
| sg.name |
Имя группы серверов |
| sg.type |
Способ выбора серверов (random,by-one,evenly) |
| sg.transport |
Транспорт группы (local,ssh,http…) |
| sg.settings |
settings группы серверов |
| sg.set_settings( foo = 1 ) |
Сохранить в settings группы серверов произвольные данные |
| sg.items() |
Получение списка групп серверов |
Шаблоны
| Метод |
Описание |
| tpl.id |
Получить/установить id шаблона |
| tpl.id( NAME ).parse( usi = 123 ) |
Выполнить шаблон с именем NAME для пользовательской услуги с идентификаторм 123 |
| tpl.data |
Данные шаблона |
| tpl.settings |
Получить settings шаблона |
Хранилище
| Метод |
Описание |
| storage.save( NAME, DATA ) |
Сохранить данные DATA в хранилище с ключом NAME |
| storage.load( NAME ) |
Получить данные из хранилища с ключом NAME |
| storage.del( NAME ) |
Удалить данные из хранилища с ключом NAME |
| storage.items() |
Получение списка данных |
Конфигурация
| Метод |
Описание |
| config.NAME |
Получить данные NAME из конфигурации |
Telegram
| Метод |
Описание |
| telegram.bot(TEMPLATE, CMD, [ARGS]) |
Выполнить CMD с аргументами ARGS из шаблона TEMPLATE |
Задачи
Вспомогательные ф-ии
| Метод |
Описание |
| params |
Аргументы вызова шаблона (http query string) |
| event_name |
Переменная содержит название текущего события |
| toJson() |
Функция преобразования объекта в JSON |
| toQueryString() |
Функция преобразования объектов в Query string |
| ref() |
Функция для преобразования данных в массив (устарела) |
| list_for_api() |
Функция для получения списков данных из объекта (устарела) |
| filter() |
Метод для точечной выборки данных (для items) |
| list.sort_by_key( foo = ‘asc’, bar = ‘desc’ ) |
Виртуальный метод списка. Сортирует массив хешей/объектов по нескольким полям. Поддерживаемые направления: asc, desc. |
| list.pluck( ‘foo’ ) |
Виртуальный метод списка. Возвращает массив значений поля foo из каждого элемента списка. |
| report |
Управление HTTP-статусом и заголовками ответа |
| misc |
Вспомогательные ф-ии |
Примеры
1 - Вспомогательные функции и методы
misc
Объект misc предоставляет доступ к вспомогательным функциям из модуля Core::Utils. Эти функции помогают работать с датами, строками, JSON, файлами и другими полезными операциями прямо в шаблонах.
Работа с датами и временем
now()
Возвращает текущую дату и время в формате “YYYY-MM-DD HH:MM:SS”.
Синтаксис:
{{ misc.now }}
Пример:
Текущее время: {{ misc.now }}
Результат: Текущее время: 2025-10-20 15:30:45
Преобразует Unix timestamp в строку с датой.
Параметры:
timestamp - Unix timestamp (необязательный, по умолчанию текущее время)
format - формат даты (необязательный, по умолчанию “%Y-%m-%d %H:%M:%S”)
Синтаксис:
{{ misc.utime_to_string(1698756000) }}
{{ misc.utime_to_string(1698756000, "%d.%m.%Y") }}
string_to_utime(date_string)
Преобразует строку с датой в Unix timestamp.
Параметры:
date_string - строка с датой в формате “YYYY-MM-DD HH:MM:SS”
Синтаксис:
{{ misc.string_to_utime("2025-01-01 12:00:00") }}
parse_date(date_string)
Разбирает строку с датой и возвращает хеш с компонентами.
Параметры:
date_string - строка с датой (необязательный, по умолчанию текущая дата)
Синтаксис:
{{ SET date_parts = misc.parse_date("2025-12-31 23:59:59") }}
Год: {{ date_parts.year }}, Месяц: {{ date_parts.month }}
add_date_time(date, options)
Добавляет указанное количество времени к дате.
Параметры:
date - исходная дата (необязательный, по умолчанию текущая дата)
options - хеш с параметрами: year, month, day, hour, min, sec
Синтаксис:
{{ misc.add_date_time(misc.now, { day = 7 }) }}
{{ misc.add_date_time("2025-01-01 00:00:00", { month = 1, day = 15 }) }}
add_period(date, period)
Добавляет период к дате, используя компактный формат записи.
Параметры:
date — исходная дата в формате "YYYY-MM-DD HH:MM:SS"
period — строка вида "<число><единица>", где единица:
d — дни
m — месяцы
y — годы
H — часы
M — минуты
Возвращает: новую дату в формате "YYYY-MM-DD HH:MM:SS", или исходную дату если формат периода не распознан.
Синтаксис:
{{ misc.add_period(misc.now, "7d") }}
{{ misc.add_period(misc.now, "1m") }}
{{ misc.add_period(misc.now, "1y") }}
{{ misc.add_period(misc.now, "12H") }}
{{ misc.add_period(misc.now, "30M") }}
Примеры:
Через неделю: {{ misc.add_period(misc.now, "7d") }}
Через месяц: {{ misc.add_period(misc.now, "1m") }}
Через год: {{ misc.add_period(misc.now, "1y") }}
{{# Удобно для вычисления дат окончания подписки #}}
{{ SET expire = misc.add_period(service.created, "1m") }}
Действует до: {{ expire }}
start_of_month(date)
Возвращает начало месяца для указанной даты.
Синтаксис:
{{ misc.start_of_month("2025-03-15 14:30:00") }}
Результат: 2025-03-01 00:00:00
start_of_day(date)
Возвращает начало дня для указанной даты.
Синтаксис:
{{ misc.start_of_day("2025-03-15 14:30:00") }}
Результат: 2025-03-15 00:00:00
end_of_month(date)
Возвращает конец месяца для указанной даты.
Синтаксис:
{{ misc.end_of_month("2025-02-15 14:30:00") }}
Результат: 2025-02-28 23:59:59
days_in_months(date)
Возвращает количество дней в месяце для указанной даты.
Синтаксис:
{{ misc.days_in_months("2025-02-15") }}
Результат: 28
Форматирует разность между указанной датой и текущим временем в читаемом виде.
Параметры:
target_date - целевая дата в формате “YYYY-MM-DD HH:MM:SS”
Синтаксис:
{{ misc.format_time_diff("2025-12-31 23:59:59") }}
Результат: 2 месяца, 11 дней, 8 часов
Работа с JSON
encode_json(data, options)
Преобразует данные в JSON строку с UTF-8 кодировкой.
Параметры:
data - данные для преобразования
options - хеш с параметрами (pretty - для красивого форматирования)
Синтаксис:
{{ misc.encode_json({ name = "Test", value = 123 }) }}
{{ misc.encode_json(data, { pretty = 1 }) }}
encode_json_perl(data, options)
Преобразует данные в JSON строку с внутренней кодировкой Perl (для совместимости с кириллицей).
Синтаксис:
{{ misc.encode_json_perl({ name = "Тест", value = 123 }) }}
decode_json(json_string)
Преобразует JSON строку в структуру данных.
Синтаксис:
{{ SET data = misc.decode_json('{"name":"Test","value":123}') }}
{{ data.name }}
Работа со строками и кодированием
html_escape(string)
Экранирует HTML символы в строке.
Синтаксис:
{{ misc.html_escape('<script>alert("XSS")</script>') }}
Результат: <script>alert("XSS")</script>
encode_base64(string)
Кодирует строку в Base64.
Синтаксис:
{{ misc.encode_base64("Hello World") }}
decode_base64(encoded_string)
Декодирует строку из Base64.
Синтаксис:
{{ misc.decode_base64("SGVsbG8gV29ybGQ=") }}
encode_base64url(string)
Кодирует строку в Base64 URL-safe формат.
Синтаксис:
{{ misc.encode_base64url("Hello World!") }}
decode_base64url(encoded_string)
Декодирует строку из Base64 URL-safe формата.
Синтаксис:
{{ misc.decode_base64url("SGVsbG8gV29ybGQh") }}
to_query_string(hash)
Преобразует хеш в query string для URL.
Синтаксис:
{{ misc.to_query_string({ name = "test", value = "123" }) }}
Результат: name=test&value=123
Криптографические функции
sha256(data)
Вычисляет SHA-256 и возвращает бинарный результат.
Синтаксис:
{{ misc.sha256("hello") }}
sha256_hex(data)
Вычисляет SHA-256 и возвращает hex-строку.
Синтаксис:
{{ misc.sha256_hex("hello") }}
sha512(data)
Вычисляет SHA-512 и возвращает бинарный результат.
Синтаксис:
{{ misc.sha512("hello") }}
sha512_hex(data)
Вычисляет SHA-512 и возвращает hex-строку.
Синтаксис:
{{ misc.sha512_hex("hello") }}
hmac_sha256(data, key)
Вычисляет HMAC-SHA256 и возвращает бинарный результат.
Синтаксис:
{{ misc.hmac_sha256("payload", "secret") }}
hmac_sha256_hex(data, key)
Вычисляет HMAC-SHA256 и возвращает hex-строку.
Синтаксис:
{{ misc.hmac_sha256_hex("payload", "secret") }}
hmac_sha512(data, key)
Вычисляет HMAC-SHA512 и возвращает бинарный результат.
Синтаксис:
{{ misc.hmac_sha512("payload", "secret") }}
hmac_sha512_hex(data, key)
Вычисляет HMAC-SHA512 и возвращает hex-строку.
Синтаксис:
{{ misc.hmac_sha512_hex("payload", "secret") }}
Для вывода в текстовых шаблонах обычно используйте методы с суффиксом _hex.
Генерация данных
passgen(length)
Генерирует случайный пароль.
Параметры:
length - длина пароля (необязательный, по умолчанию 10)
Синтаксис:
{{ misc.passgen(12) }}
{{ misc.passgen() }}
uuid_gen()
Генерирует UUID.
Синтаксис:
{{ misc.uuid_gen() }}
Результат: 123e4567-e89b-12d3-a456-426614174000
get_random_value(value)
Возвращает случайное значение из массива или само значение, если это не массив.
Синтаксис:
{{ misc.get_random_value(["red", "green", "blue"]) }}
{{ misc.get_random_value("single_value") }}
Работа с массивами и хешами
hash_merge(hash1, hash2, ...)
Объединяет несколько хешей в один.
Синтаксис:
{{ SET merged = misc.hash_merge(hash1, hash2, hash3) }}
uniq_by_key(array, key)
Возвращает уникальные элементы массива по указанному ключу.
Синтаксис:
{{ SET unique_items = misc.uniq_by_key(items, "id") }}
Валидация
is_email(email)
Проверяет, является ли строка корректным email адресом.
Синтаксис:
{{ IF misc.is_email("test@example.com") }}
Корректный email
{{ END }}
is_host(hostname)
Проверяет, является ли строка корректным hostname или IP адресом.
Синтаксис:
{{ IF misc.is_host("example.com") }}
Корректный хост
{{ END }}
is_ip_allowed(ip, networks)
Проверяет, входит ли IP адрес в список разрешенных сетей.
Синтаксис:
{{ IF misc.is_ip_allowed("192.168.1.1", ["192.168.1.0/24", "10.0.0.0/8"]) }}
IP разрешен
{{ END }}
ip_rate_limit(key, rps, options)
Ограничивает частоту запросов с одного IP адреса. Использует Redis для хранения счётчиков.
Параметры:
key — уникальный идентификатор ограничения (например, имя действия)
rps — лимит в формате "количество/интервал", где интервал в секундах
options — необязательный хеш:
ip — IP адрес (необязательный, по умолчанию IP текущего пользователя)
penalty — если 1, продлевает таймер при превышении лимита (по умолчанию 0)
Возвращает: 1 если лимит превышен, 0 если запрос разрешён, undef если Redis недоступен или параметры некорректны.
Синтаксис:
{{ IF misc.ip_rate_limit("login", "5/60") }}
Слишком много попыток. Попробуйте позже.
{{ END }}
Примеры:
{{# Не более 10 запросов в 30 секунд #}}
{{ IF misc.ip_rate_limit("search", "10/30") }}
Превышен лимит запросов
{{ END }}
{{# С penalty: таймер сбрасывается при каждом превышении #}}
{{ IF misc.ip_rate_limit("api_call", "100/3600", { penalty = 1 }) }}
Доступ заблокирован
{{ END }}
{{# Для конкретного IP #}}
{{ IF misc.ip_rate_limit("upload", "3/60", { ip = user.ip }) }}
{{ r = report.status(429) }}
{{ r = report.add_error('Лимит загрузок исчерпан') }}
{{ STOP }}
{{ END }}
Важно: Функция требует работающего Redis. Если Redis недоступен, функция возвращает undef и ограничения не применяются.
Работа с периодами
parse_period(period)
Разбирает строку периода в формате “месяцы.дни.часы”.
Параметры:
period - строка в формате “M.DD.HH”
Синтаксис:
{{ SET period = misc.parse_period("3.15.12") }}
Месяцы: {{ period.months }}, Дни: {{ period.days }}, Часы: {{ period.hours }}
Полезные функции
dots_str_to_sql(string)
Преобразует строку с точками в SQL запрос для JSON полей.
Синтаксис:
{{ SET sql_info = misc.dots_str_to_sql("settings.api.key") }}
Поле: {{ sql_info.field }}, Запрос: {{ sql_info.query }}
get_user_ip()
Возвращает IP адрес пользователя.
Синтаксис:
{{ misc.get_user_ip() }}
Примеры использования
Работа с датами
Сегодня: {{ misc.now }}
Завтра: {{ misc.add_date_time(misc.now, { day = 1 }) }}
Начало месяца: {{ misc.start_of_month(misc.now) }}
Генерация паролей и UUID
Новый пароль: {{ misc.passgen(16) }}
UUID для записи: {{ misc.uuid_gen() }}
Работа с JSON
{{ SET user_data = { name = "Иван", age = 30 } }}
JSON: {{ misc.encode_json_perl(user_data, { pretty = 1 }) }}
Валидация данных
{{ IF misc.is_email(user.email) }}
Email корректен: {{ user.email }}
{{ ELSE }}
Некорректный email
{{ END }}
Важные замечания
Кодировка и локализация
- Используйте
encode_json_perl() для данных с кириллицей в шаблонах
encode_json() подходит для API с UTF-8 кодировкой
- Функция
format_time_diff() возвращает текст на русском языке
Безопасность
- Всегда используйте
html_escape() для пользовательских данных перед выводом в HTML
- Функции валидации (
is_email, is_host) обеспечивают базовую проверку формата
Производительность
- UUID генерация читает системный файл
/proc/sys/kernel/random/uuid
Совместимость
- Все функции даты работают с форматом “YYYY-MM-DD HH:MM:SS”
- Base64 функции совместимы со стандартом RFC 4648
- JSON функции поддерживают вложенные структуры данных
2 - Использование методов
Ряд методов, описанных в разделе Шаблоны не будут работать в ряде случаев.
Работа с идентификаторами
Практически все методы поддерживают работу с идентификаторами (id).
Например, user.id вернет идентификатор текущего пользователя, а us.id вернет идентификатор текущей пользовательской услуги.
Если метод не возвращает свой id, то значит он небыл инициализирован, и следует “добираться” до метода либо через услугу пользователя, либо
через явное указание идентификатора: id( N ). Например, в ряде случаев, server.id не вернет свой идентификатор, однако: us.service.server.id - вернет.
Помимо получения идентификатора, его можно и установить, пример:
user.id(123) - вернет объект для пользователя с идентификатором 123.
user.login - вернет login текущего пользователя
user.id(123).login - вернет login пользователя 123
us.name - вернет название текущей услуги пользователя (если определена)
us.id(99).name - вернет название услуги пользователя с идентификатором 99
Контекст применения шаблонов
| Метод |
Контекст |
| us.* |
События |
| us.id( N ).* |
Везде |
| us.withdraw |
Для платных услуг |
| us.service.server |
Для услуг, у которых определен сервер (server_id в settings услуги пользователя) |
| server.* |
События, Задачи, INIT сервера |
| server.id( N ) |
Везде |
| task |
События, Задачи |
3 - Платежные системы
Описание
SHM имеет встроенный модуль paysystems, позволяющий получать список доступных платежных систем.
Получить список платежных систем можно:
- Через API:
/shm/v1/user/pay/paysystems
- Из шаблонов:
pay.paysystems( АРГУМЕНТЫ )
Аргументы
| Параметр |
Описание |
| user_id |
Идентификатор пользователя |
| amount |
Сумма платежа. По-умолчанию в неё записывается значение total из “Прогноз оплаты” |
| pp |
Proposed Payment (Предлагаемый платеж). Установите этот параметр в 1, если хотите, чтобы сумма платежа (amount) была установлена в ссылку shm_url |
| paysystem |
Укажите платежную систему, если хотите получить ссылку только для одной конкретной платежной системы |
Описание полей
Пример ответа:
{
"TZ": "Europe/Moscow",
"data": [
{
"amount": 123,
"forecast": 0,
"name": "yookassa",
"paysystem": "yookassa",
"shm_url": "/shm/pay_systems/yookassa.cgi?action=create&user_id=1&ts=1706539331&amount=",
"user_id": 1,
"weight": 10
},
{
"amount": 123,
"forecast": 0,
"name": "ЮMoney",
"paysystem": "yoomoney",
"shm_url": "/shm/pay_systems/yoomoney.cgi?action=create&user_id=1&ts=1706539331&amount=",
"user_id": 1,
"weight": 0
}
],
"date": "Mon Jan 29 17:42:11 2024",
"items": 0,
"limit": 25,
"offset": 0,
"version": "0.10.0"
}
Где:
paysystem - Платежная система
name - Произвольное название платежной системы (для отображения)
amount - Сумма платежа. По-умолчанию заполняется из “Прогноз оплаты”, если он положительный, или из переданного аргумента amount. В остальных случаях - пустой.
forecast - “Прогноз оплаты”
weight - “вес”. Используется для сортировки платежных систем
shm_url - Ссылка для выставления счета в платежной системе. Если флаг pp не установлен, то amount в ссылке остается пустой. Это сделано для удобства использования ссылки.
4 - Фильтры
filter()
Фильтры используются для точечной выборки данных из БД
Например, мы можем получить список пользователей с положительным балансом так:
{{ FOR u IN user.filter( balance = isPositive ).items }}
...
{{ END }}
Синтаксис фильтрации
Важные правила синтаксиса
Кавычки для ключей:
- Кавычки НЕ обязательны для обычных ключей:
status = 1, balance = gt(0)
- Кавычки ОБЯЗАТЕЛЬНЫ только для ключей, содержащие спец. символы:
'-or', '-and', =, !=, a.b …
Запятые:
- В однострочном синтаксисе запятые обязательны:
filter( status = 1, balance = gt(0) )
- В многострочном синтаксисе запятые НЕ нужны при разделении переносом строки
Основные правила записи
Простые значения можно записывать напрямую без использования специальных функций:
{{ FOR u IN user.filter( status = 1 ).items }}
Пользователь {{ u.login }} со статусом 1
{{ END }}
Это эквивалентно использованию eq():
{{ FOR u IN user.filter( status = eq(1) ).items }}
То же самое, что и выше
{{ END }}
Строковые значения также можно записывать напрямую:
{{ FOR u IN user.filter( role = "admin" ).items }}
Администраторы
{{ END }}
Когда использовать функции сравнения
Функции сравнения (eq(), ne(), gt(), etc.) нужны в следующих случаях:
1. Для явного указания типа сравнения:
{{ FOR u IN user.filter( balance = isPositive ).items }}
Пользователи с положительнрым балансом
{{ END }}
2. Для операторов отличных от равенства:
{{ FOR u IN user.filter( age = gt(18) ).items }}
Пользователи старше 18 лет
{{ END }}
3. Для текстовых полей, когда нужно точное совпадение:
{{ FOR u IN user.filter( login = eq("admin") ).items }}
Точно "admin", а не LIKE 'admin' (LIKE регистронезависимый)
{{ END }}
4. В JSON полях для лучшей читаемости:
{{ FOR u IN user.filter( settings = { status = ne(0) } ).items }}
Пользователи с активными настройками
{{ END }}
Краткие формы записи
| Полная форма |
Краткая форма |
Описание |
status = eq(1) |
status = 1 |
Равенство |
enabled = eq(true) |
enabled = true |
Boolean значения |
role = eq("admin") |
role = "admin" |
Строки (для точного сравнения) |
name = eq("") |
name = "" |
Пустая строка |
Специальные случаи
Текстовые поля с LIKE:
# Автоматический LIKE поиск
{{ FOR u IN user.filter( name = "%Петр%" ).items }}
# Точное сравнение
{{ FOR u IN user.filter( name = eq("Петр") ).items }}
Boolean значения:
# Все эти записи эквивалентны
{{ FOR u IN user.filter( enabled = true ).items }}
{{ FOR u IN user.filter( enabled = eq(true) ).items }}
{{ FOR u IN user.filter( enabled = 1 ).items }}
{{ FOR u IN user.filter( enabled = eq(1) ).items }}
NULL значения:
# Простая запись
{{ FOR u IN user.filter( manager_id = null ).items }}
{{ FOR u IN user.filter( manager_id = { '!=' = null } ).items }}
# С функцией
{{ FOR u IN user.filter( manager_id = isNull ).items }}
{{ FOR u IN user.filter( manager_id = isNotNull ).items }}
Многострочный синтаксис
В многострочных фильтрах действуют особые правила:
1. Запятые не требуются при разделении ключ-значение переносом строки:
{{ FOR u IN user.filter(
balance = gt(0)
status = ne(0)
description = isNotEmpty
).items }}
Фильтр без запятых
{{ END }}
2. Сравнение однострочного и многострочного синтаксиса:
# Однострочный (с запятыми)
{{ FOR u IN user.filter( status = 1, balance = gt(0), role = "admin" ).items }}
# Многострочный (без запятых)
{{ FOR u IN user.filter(
status = 1
balance = gt(0)
role = "admin"
).items }}
Принципы работы фильтрации
Логический оператор “И” (AND)
По умолчанию все условия, перечисленные через запятую, объединяются логическим оператором “И”:
{{ FOR u IN user.filter(
balance = gt(0)
status = 1
created = ge('2024-01-01')
).items }}
Пользователь {{ u.login }} соответствует ВСЕМ условиям одновременно
{{ END }}
Это эквивалентно SQL: WHERE balance > 0 AND status = 1 AND created >= '2024-01-01'
Логический оператор “ИЛИ” (OR)
Для реализации логики “ИЛИ” есть несколько способов:
Способ 1: Использование специального ключа -or
{{ FOR u IN user.filter( '-or' = { balance = 0, login = "test" } ).items }}
Пользователь {{ u.login }} с нулевым балансом ИЛИ логином "test"
{{ END }}
Можно комбинировать -or с обычными условиями:
{{ FOR u IN user.filter(
status = 1
'-or' = {
balance = gt(1000)
settings = { vip = true }
}
).items }}
Активный пользователь с балансом больше 1000 ИЛИ VIP статусом
{{ END }}
Способ 2: Объединение результатов нескольких фильтров
{{ SET positive_users = user.filter( balance = gt(0) ).items }}
{{ SET premium_users = user.filter( status = eq(2) ).items }}
{{ FOR u IN positive_users.merge(premium_users).unique }}
Пользователь {{ u.login }} имеет положительный баланс ИЛИ премиум статус
{{ END }}
Способ 3: Массивы значений для одного поля
{{ FOR u IN us.filter( status = [STATUS_ACTIVE,STATUS_BLOCK,STATUS_WAIT_FOR_PAY] ).items }}
Услуги пользователя с указанными статусами
{{ END }}
Автоматическое использование LIKE для текстовых полей
Для текстовых полей в базе данных автоматически применяется оператор LIKE при использовании символов подстановки %. Это позволяет выполнять гибкий поиск по шаблонам.
Принцип работы
Когда вы указываете значение для текстового поля, система автоматически:
- Определяет тип поля в структуре таблицы
- Если поле имеет текстовый тип (
text, varchar, etc.) И значение содержит символы %, применяет оператор LIKE
- Если символов
% нет, используется точное сравнение =
Примеры LIKE поиска
Поиск по подстроке в имени:
{{ FOR u IN user.filter(full_name = "%Иван%").items }}
Найден пользователь {{ u.login }} с именем содержащим "Иван"
{{ END }}
Генерирует SQL: WHERE full_name LIKE '%Иван%'
Поиск по началу логина:
{{ FOR u IN user.filter(login = "admin%").items }}
Найден пользователь {{ u.login }} начинающийся с "admin"
{{ END }}
Генерирует SQL: WHERE login LIKE 'admin%'
Поиск по окончанию email:
{{ FOR u IN user.filter(email = "%@company.com").items }}
Пользователи с корпоративным email
{{ END }}
Генерирует SQL: WHERE email LIKE '%@company.com'
Точное совпадение для текстовых полей
Если вам нужно точное совпадение, НЕ используйте символы %:
{{ FOR u IN user.filter(login = "admin").items }}
Пользователь с точным логином "admin"
{{ END }}
Генерирует SQL: WHERE login LIKE 'admin'
Альтернативно, можно использовать оператор eq():
{{ FOR u IN user.filter(login = eq("admin")).items }}
Пользователь с точным логином "admin"
{{ END }}
Генерирует SQL: WHERE login = 'admin'
Кастомные LIKE паттерны
Для более сложных паттернов поиска используйте явное указание LIKE операторов:
{{ FOR u IN user.filter(email = { '-like' = "%@gmail.com" }).items }}
Пользователи с email на Gmail
{{ END }}
{{ FOR u IN user.filter(login = { '-not_like' = "test%" }).items }}
Пользователи, логин которых НЕ начинается с "test"
{{ END }}
Смешанные условия с LIKE
{{ FOR u IN user.filter(
full_name = "%Петр%"
email = "%@company.com"
status = eq(1)
).items }}
Активные пользователи с именем содержащим "Петр" и корпоративным email
{{ END }}
В этом примере:
full_name LIKE '%Петр%' (LIKE из-за символов %)
email LIKE '%@company.com' (LIKE из-за символов %)
status = 1 (точное сравнение для числового поля)
Важно: Символы % нужно добавлять самостоятельно для активации LIKE поиска!
Работа с JSON полями
Важно: Для работы с JSON полями используйте формат json_поле = { 'ключ' = значение }, а НЕ 'json_поле.ключ' = значение.
Для фильтрации по полям внутри JSON используйте хеш с ключами JSON полей:
{{ FOR u IN user.filter(settings = { notifications = true }).items }}
Пользователь {{ u.login }} с включенными уведомлениями
{{ END }}
Для вложенных JSON структур:
{{ FOR u IN user.filter( profile = { 'preferences.theme' = 'dark' }).items }}
Пользователь {{ u.login }} с темной темой
{{ END }}
Проверка существования JSON поля
Для проверки существования поля в JSON используйте имя поля в точечной нотации:
{{ FOR u IN user.filter(settings = 'notifications').items }}
Пользователь {{ u.login }} у которого есть поле notifications в settings
{{ END }}
Для вложенных полей:
{{ FOR u IN user.filter(settings = 'profile.avatar').items }}
Пользователь {{ u.login }} у которого есть поле profile.avatar в settings
{{ END }}
Проверка отсутствия JSON поля
Для проверки отсутствия поля в JSON используйте префикс !:
{{ FOR u IN user.filter(settings = '!notifications').items }}
Пользователь {{ u.login }} у которого НЕТ поля notifications в settings
{{ END }}
Для вложенных полей:
{{ FOR u IN user.filter(settings = '!profile.avatar').items }}
Пользователь {{ u.login }} у которого НЕТ поля profile.avatar в settings
{{ END }}
Проверка значений в JSON полях
Для проверки конкретных значений используйте хеш с точечной нотацией:
{{ FOR u IN user.filter(settings = { notifications = true, theme = 'dark' }).items }}
Пользователь {{ u.login }} с темной темой и включенными уведомлениями
{{ END }}
С операторами сравнения:
{{ FOR u IN user.filter(settings = { max_items = gt(10), priority = le(5) }).items }}
Пользователь {{ u.login }} с max_items > 10 и priority <= 5 в настройках
{{ END }}
Комбинирование JSON условий
{{ FOR u IN user.filter(
settings = 'api_key'
settings = { notifications = true, auto_backup = ne(false) }
).items }}
Пользователь {{ u.login }} с API ключом, уведомлениями и автобэкапом
{{ END }}
Проверка JSON полей с специальными функциями
{{ FOR u IN user.filter(settings = { api_key = isNotNull }).items }}
Пользователь {{ u.login }} с настроенным API ключом
{{ END }}
{{ FOR u IN user.filter(profile = { avatar = isEmpty }).items }}
Пользователь {{ u.login }} без аватара в профиле
{{ END }}
{{ FOR u IN user.filter(settings = { notifications = isTrue }).items }}
Пользователь {{ u.login }} где notifications = true или 1
{{ END }}
{{ FOR u IN user.filter(settings = { archived = isFalse }).items }}
Пользователь {{ u.login }} где archived = false или 0
{{ END }}
Практические примеры синтаксиса
Сравнение различных подходов
Числовые значения:
# Простая запись (рекомендуется для равенства)
{{ FOR u IN user.filter( user_id = 123 ).items }}
# Явная запись (когда нужна ясность)
{{ FOR u IN user.filter( user_id = eq(123) ).items }}
# Операторы сравнения (обязательно через функции)
{{ FOR u IN user.filter( user_id = gt(1000) ).items }}
Строковые значения:
# Точное совпадение
{{ FOR u IN us.filter( status = STATUS_ACTIVE ).items }}
# LIKE поиск (с символами %)
{{ FOR u IN us.filter( status = "%ACTIVE%" ).items }}
# Явное точное совпадение (рекомендуется)
{{ FOR u IN us.filter( status = eq(STATUS_ACTIVE) ).items }}
Boolean значения:
# Простая запись (рекомендуется)
{{ FOR u IN user.filter( enabled = true ).items }}
{{ FOR u IN user.filter( deleted = false ).items }}
# Числовая запись (также работает)
{{ FOR u IN user.filter( enabled = 1 ).items }}
{{ FOR u IN user.filter( deleted = 0 ).items }}
# Специальные флаги для обычных полей
{{ FOR u IN user.filter( enabled = isTrue ).items }}
{{ FOR u IN user.filter( deleted = isFalse ).items }}
# Для JSON полей с mixed boolean/number
{{ FOR u IN user.filter( settings = { notifications = isTrue } ).items }}
{{ FOR u IN user.filter( settings = { archived = isFalse } ).items }}
JSON поля:
# Простые значения в JSON
{{ FOR u IN user.filter( settings = { theme = 'dark' } ).items }}
# Функции сравнения в JSON
{{ FOR u IN user.filter( settings = { max_items = gt(10) } ).items }}
# Специальные функции в JSON
{{ FOR u IN user.filter( settings = { api_key = isNotNull } ).items }}
Рекомендации по выбору синтаксиса
Используйте простую запись когда:
- Проверяете равенство:
block = 1
- Работаете с boolean:
enabled = true
- Нужно совпадение строки без учета регистра:
role = "admin"
- Нужно совпадение части строки:
role = "adm%"
Используйте функции когда:
- Нужны операторы сравнения:
age = gt(18)
- Работаете с NULL:
field = isNull
- Нужны специальные проверки:
name = isEmpty
- Нужно точное совпадение строки:
role = eq("admin")
Специальные функции для фильтрации
Проверка на NULL и пустые значения
isNull
Проверяет, что поле равно NULL:
{{ FOR u IN user.filter( avatar = isNull ).items }}
Пользователь {{ u.login }} без аватара
{{ END }}
isNotNull
Проверяет, что поле не равно NULL:
{{ FOR u IN user.filter( avatar = isNotNull ).items }}
Пользователь {{ u.login }} с аватаром
{{ END }}
isEmpty
Проверяет, что поле пустое (NULL или пустая строка):
{{ FOR u IN user.filter( description = isEmpty ).items }}
Пользователь {{ u.login }} без описания
{{ END }}
isNotEmpty
Проверяет, что поле не пустое:
{{ FOR u IN user.filter( description = isNotEmpty ).items }}
Пользователь {{ u.login }} с описанием
{{ END }}
isTrue
Проверяет, что поле истинно. Работает для обычных и JSON полей, считает истинными значения true и 1:
{{ FOR u IN user.filter( enabled = isTrue ).items }}
Пользователь {{ u.login }} с включенным флагом enabled
{{ END }}
{{ FOR u IN user.filter( settings = { notifications = isTrue } ).items }}
Пользователь {{ u.login }} с включенными уведомлениями
{{ END }}
isFalse
Проверяет, что поле ложно. Работает для обычных и JSON полей, считает ложными значения false и 0:
{{ FOR u IN user.filter( deleted = isFalse ).items }}
Пользователь {{ u.login }} с выключенным флагом deleted
{{ END }}
{{ FOR u IN user.filter( settings = { archived = isFalse } ).items }}
Пользователь {{ u.login }} с выключенным флагом archived
{{ END }}
Числовые сравнения
lt(значение) (<)
Меньше указанного значения:
{{ FOR u IN user.filter( balance = lt(100) ).items }}
Пользователь {{ u.login }} с балансом меньше 100
{{ END }}
gt(значение) (>)
Больше указанного значения:
{{ FOR u IN user.filter( balance = gt(1000) ).items }}
Пользователь {{ u.login }} с балансом больше 1000
{{ END }}
le(значение) (<=)
Меньше или равно указанному значению:
{{ FOR u IN user.filter( balance = le(0) ).items }}
Пользователь {{ u.login }} с нулевым или отрицательным балансом
{{ END }}
ge(значение) (>=)
Больше или равно указанному значению:
{{ FOR u IN user.filter( balance = ge(500) ).items }}
Пользователь {{ u.login }} с балансом от 500
{{ END }}
eq(значение) (==)
Равно указанному значению:
{{ FOR u IN user.filter( status = eq(1) ).items }}
Активный пользователь {{ u.login }}
{{ END }}
ne(значение) (!=)
Не равно указанному значению:
{{ FOR u IN user.filter( status = ne(0) ).items }}
Пользователь {{ u.login }} (не заблокирован)
{{ END }}
between(мин, макс) ([..])
Значение в диапазоне (включительно):
{{ FOR u IN user.filter( balance = between(100, 1000) ).items }}
Пользователь {{ u.login }} с балансом от 100 до 1000
{{ END }}
Проверка знака числа
isPositive (>0)
Проверяет, что значение больше нуля (положительное):
{{ FOR u IN user.filter( balance = isPositive ).items }}
Пользователь {{ u.login }} с положительным балансом
{{ END }}
isNegative (<0)
Проверяет, что значение меньше нуля (отрицательное):
{{ FOR u IN user.filter( balance = isNegative ).items }}
Пользователь {{ u.login }} с отрицательным балансом
{{ END }}
isNonNegative (>=0)
Проверяет, что значение больше или равно нулю (неотрицательное):
{{ FOR u IN user.filter( balance = isNonNegative ).items }}
Пользователь {{ u.login }} с неотрицательным балансом
{{ END }}
isNonPositive (<=0)
Проверяет, что значение меньше или равно нулю (неположительное):
{{ FOR u IN user.filter( balance = isNonPositive ).items }}
Пользователь {{ u.login }} с неположительным балансом
{{ END }}
Сложные примеры фильтрации
Комбинирование разных типов условий
{{ FOR u IN user.filter(
balance = gt(0)
status = ne(0)
description = isNotEmpty
settings = { notifications = true }
created = between('2024-01-01', '2024-12-31')
).items }}
Активный пользователь {{ u.login }} с положительным балансом, описанием,
включенными уведомлениями, созданный в 2024 году
{{ END }}
Фильтрация с использованием JSON и специальных функций
{{ FOR s IN service.filter(
enabled = true
price = ge(100)
config = { auto_renewal = false }
description = isNotEmpty
).items }}
Активная услуга {{ s.name }} стоимостью от 100 без автопродления
{{ END }}
Комбинирование AND и OR условий
{{ FOR u IN user.filter(
status = eq(1)
balance = ge(0)
'-or' = {
profile = { type = 'premium' }
created = ge('2024-01-01')
settings = { notifications = true }
}
).items }}
Активный пользователь с неотрицательным балансом И (премиум профиль ИЛИ создан в 2024 ИЛИ включены уведомления)
{{ END }}
Сложная JSON фильтрация
{{ FOR u IN user.filter(
settings = 'api_key'
settings = '!deprecated_features'
settings = {
max_sessions = gt(1)
auto_logout = true
'security.two_factor' = ne(false)
}
profile = { 'preferences.language' = ['ru', 'en'] }
).items }}
Пользователь {{ u.login }} с API ключом, без устаревших функций,
с настройками безопасности и поддержкой русского или английского языка
{{ END }}
Специальные значения
Также доступны специальные константы:
null - значение NULL
true - логическое истина (1)
false - логическое ложь (0)
{{ FOR s IN service.filter( enabled = true ).items }}
Активная услуга {{ s.name }}
{{ END }}
5 - Функции
toJson()
Преобразует объект в JSON.
Данную ф-ию удобно использовать для отладки запросов, когда не очевидно, что вернет тот или иной метод, для просмотра полей и т.п.
Пример 1:
Смотрим, что вернет метод user.items:
{{ toJson( user.items.first ) }}
Результат:
{
"balance": 123.45,
"block": 0,
"bonus": 0,
"can_overdraft": 0,
"created": "2024-01-08 15:18:16",
"credit": 0,
"discount": 0,
"full_name": "Admin",
"last_login": "2024-01-22 20:52:53",
"login": "admin",
"user_id": 1
}
Пример 2:
Строим JSON объект. Удобно для использования в Telegram bot-е, в HTTP запросах и т.п.:
{{
toJson(
a = user.id
b = 2
c = [3,4,5]
)
}}
Результат:
{"a":1,"b":2,"c":[3,4,5]}
toQueryString()
Преобразование объекта в Query String:
Пример:
{{
toQueryString(
a = user.id
b = 2
c = "hello world"
)
}}
Результат:
a=1&b=2&c=hello%20world
list_for_api() УСТАРЕЛ. Используйте метод items()
Метод для получения списка данных объекта.
Без аргументов выдаст первые 25 строк данных.
Аргументы:
| Параметр |
Описание |
| admin |
Установка этого параметра в 1 позволяет получить данные всех клиентов |
| limit |
Кол-во отдаваемых данных. По-умолчанию: 25. (0 - без лимитов) |
| offset |
Индекс начала смещения списка. По-умолчанию: 0 |
| filter |
Используется для поиска данных по определенным полям |
| sort_field |
Поле для сортировки (по-умолчанию ключевое поле) |
| sort_direction |
Порядок сортировки: asc, desc (по-умолчанию desc) |
Пример 1:
Выведем всех пользователей:
{{ arr = user.items }}
{{ FOR item IN arr }}
User id: {{ item.user_id }}, Login: {{ item.login }}, Balance: {{ item.balance }}
{{ END }}
Результат:
User id: 1, Login: admin, Balance: 0
User id: 22, Login: danuk, Balance: 224.44
User id: 34, Login: Dima, Balance: 0
User id: 117, Login: xims, Balance: 200
Пример 2:
Выведем список всех услуг с категорией начинающиеся на web, и период которых равен 1 месяцу:
{{ arr = service.filter( category = 'web%', period => 1 ).items }}
{{ FOR item IN arr }}
Service id: {{ item.service_id }}, Name: {{ item.name }}, Cost: {{ item.cost }}
{{ END }}
Результат:
Service id: 111, Name: Web хостинг, Cost: 0
Service id: 110, Name: Тариф X-MAX, Cost: 300
Service id: 5, Name: Web хостинг LITE, Cost: 0
Сортировка
Существует два типа сортировки:
- Сортировка на уровне запросов
- Сортировка на уровне шаблона
При использовании пагинации важно сортировать результаты на уровне запросов.
Сортировка на уровне запросов
Сортировка на уровне запросов осуществляется непосредственно в БД.
Пример выдачи результатов отсортированных по возрастанию по полю name:
{{ arr = service.sort('name').items }}
Пример выдачи результатов отсортированных по убыванию по полю name:
{{ arr = service.rsort('name').items }}
6 - Report
report
Объект report позволяет управлять HTTP-ответом из шаблона:
- задавать HTTP-статус;
- добавлять HTTP-заголовки;
- формировать ошибку для JSON-ответа API.
Используйте report в HTTP-контексте (например, в шаблонах API). В задачах/уведомлениях HTTP-заголовки не применяются.
Важно: без вызова report.add_error(...) заголовки и статус из report не применяются. Вызов add_error обязателен (сообщение можно не передавать).
report.status(CODE)
Устанавливает HTTP-статус ответа.
Синтаксис:
Пример:
{{ IF !user.id }}
{{ report.status(401) }}
{{ report.add_error("UNAUTHORIZED") }}
{{ END }}
Устанавливает HTTP-заголовки ответа.
Заголовки будут отправлены только если в report добавлена ошибка через report.add_error(...).
Синтаксис:
{{ report.headers( "X-Request-Id" = "abc-123" ) }}
Пример:
{{
report.headers(
"Cache-Control" = "no-store"
"X-User-Id" = user.id
)
}}
report.add_error([MESSAGE])
Добавляет сообщение об ошибке в ответ API.
MESSAGE не обязателен. Метод можно вызвать без аргументов, чтобы просто перевести ответ в error-режим.
Именно этот вызов активирует ветку error-ответа, где применяются:
- HTTP-статус из
report.status(...);
- HTTP-заголовки из
report.headers(...).
Если есть ошибки, API возвращает JSON вида:
{
"status": 400,
"error": "MESSAGE"
}
Если до этого указан report.status(...), будет использован этот код.
Типовой шаблон ошибки API
{{ IF !user.id }}
{{ report.status(401) }}
{{ report.headers( "WWW-Authenticate" = "Bearer" ) }}
{{ report.add_error("AUTH_REQUIRED") }}
{{ END }}
Результат:
- HTTP статус:
401
- HTTP заголовок:
WWW-Authenticate: Bearer
- JSON тело:
{ "status": 401, "error": "AUTH_REQUIRED" }
Пример без текста ошибки
{{ report.status(429) }}
{{ report.headers( "Retry-After" = "60", status = 429 ) }}
{{ report.add_error() }}
Результат:
- HTTP статус:
429
- HTTP заголовок:
Retry-After: 60
- JSON тело содержит поле
status и пустое поле error.
Пример HTTP редиректа
{{ report.headers( status = 302, location = "https://example.com/success" ) }}
{{ report.add_error() }}
Результат:
- HTTP статус:
302
- HTTP заголовок:
Location: https://example.com/success
- Ответ будет обработан клиентом как редирект (при поддержке редиректов клиентом).
7 - Работа с задачами (task)
Объект task предоставляет методы для управления выполнением задач в шаблонах.
task.answer()
Метод task.answer() используется для явного указания результата выполнения задачи из шаблона. Это особенно важно при использовании транспорта LOCAL, когда код выполняется непосредственно на сервере SHM.
Синтаксис
{{ task.answer( status = STATUS, msg = MESSAGE ) }}
Параметры
| Параметр |
Описание |
Обязательный |
status |
Статус выполнения задачи |
Да |
msg |
Сообщение о результате выполнения |
Нет |
Статусы задач
| Статус |
Описание |
TASK_SUCCESS |
Задача выполнена успешно |
TASK_FAIL |
Задача завершилась с ошибкой |
TASK_STUCK |
Задача застряла (требует ручного вмешательства) |
Примеры использования
Успешное выполнение
{{ user.add_bonus( 100, 'Акция' ) }}
{{ task.answer(
status = TASK_SUCCESS
msg = 'Бонусы начислены успешно'
) }}
Выполнение с ошибкой
{{ IF user.balance < 0 }}
{{ task.answer(
status = TASK_FAIL
msg = 'Недостаточно средств на балансе'
) }}
{{ END }}
Условное выполнение
{{ IF us.status == 'ACTIVE' }}
{{ us.block }}
{{ task.answer(
status = TASK_SUCCESS
msg = 'Услуга успешно заблокирована'
) }}
{{ ELSE }}
{{ task.answer(
status = TASK_FAIL
msg = 'Услуга не активна'
) }}
{{ END }}
Обработка списка пользователей
{{ count = 0 }}
{{ FOR u IN user.items }}
{{ us_list = u.us.filter( service_id = 5, status = 'ACTIVE' ).items }}
{{ IF us_list.size }}
{{ u.add_bonus( 100, 'Акция' ) }}
{{ count = count + 1 }}
{{ END }}
{{ END }}
{{ task.answer(
status = TASK_SUCCESS
msg = 'Начислено бонусов ' _ count _ ' пользователям'
) }}
Когда использовать task.answer()
Метод task.answer() следует использовать в следующих случаях:
- Шаблоны с транспортом LOCAL - когда шаблон выполняется локально на сервере SHM
- Явное управление результатом - когда необходимо точно указать статус выполнения задачи
- Условная логика - когда результат зависит от условий внутри шаблона
- Задачи автоматизации - для отчетности о выполнении массовых операций
Особенности
- Если в шаблоне не вызван
task.answer(), система автоматически определит статус выполнения
- Параметр
msg сохраняется в логах и может быть использован для отладки
- Статус
STUCK требует ручного вмешательства администратора
- Можно передавать дополнительные параметры для расширенной информации о результате
spool.add()
Метод spool.add() позволяет добавлять задачи в очередь выполнения (spool) непосредственно из шаблонов. Это мощный инструмент для создания цепочек задач и выполнения пользовательских шаблонов.
Синтаксис
{{ result = spool.add(
event = EVENT_OBJECT
settings = SETTINGS_OBJECT
prio = PRIORITY
) }}
Параметры
| Параметр |
Описание |
Обязательный |
event |
Объект события с параметрами выполнения |
Да |
settings |
Объект с настройками задачи |
Да |
prio |
Приоритет выполнения задачи (по умолчанию: 100) |
Нет |
Объект event
| Поле |
Описание |
Обязательное |
name |
Название события (например, “UPDATE”, “CREATE”) |
Да |
title |
Описание задачи (для логов и интерфейса) |
Нет |
settings.transport |
Транспорт для выполнения (local, ssh, http) |
Да |
Объект settings
| Поле |
Описание |
Обязательное |
template_id |
Идентификатор шаблона для выполнения |
Да |
user_service_id |
ID услуги пользователя (если нужен контекст) |
Нет |
server_id |
ID сервера для выполнения |
Нет |
Приоритет задач
Приоритет (prio) определяет порядок выполнения задач в очереди:
- Меньшее значение = выше приоритет = задача выполнится раньше
- Большее значение = ниже приоритет = задача выполнится позже
- По умолчанию: 100
Примеры значений:
1 - очень высокий приоритет (критичные задачи)
50 - высокий приоритет
100 - обычный приоритет (по умолчанию)
200 - низкий приоритет
500 - очень низкий приоритет (фоновые задачи)
Примеры использования
Выполнение пользовательского шаблона
Создание задачи для выполнения собственного шаблона с транспортом LOCAL:
{{ result = spool.add(
event = {
name = "UPDATE"
title = "Установка бесплатного тарифа"
settings = { transport = 'local' }
}
settings = {
template_id = 'my_template'
user_service_id = us.id
}
) }}
Задача с высоким приоритетом
Создание задачи, которая должна выполниться в первую очередь:
{{ result = spool.add(
event = {
name = "URGENT_UPDATE"
title = "Срочное обновление конфигурации"
settings = { transport = 'local' }
}
settings = {
template_id = 'urgent_config_update'
user_service_id = us.id
}
prio = 10
) }}
Фоновая задача с низким приоритетом
Создание фоновой задачи для статистики:
{{ result = spool.add(
event = {
name = "STATS"
title = "Сбор статистики"
settings = { transport = 'local' }
}
settings = {
template_id = 'collect_stats'
}
prio = 500
) }}
Массовое создание задач для пользователей
Создание задач для каждого пользователя с активной услугой:
{{ FOR u IN user.items }}
{{ us_list = u.us.filter( service_id = 5, status = 'ACTIVE' ).items }}
{{ IF us_list.size }}
{{ result = spool.add(
event = {
name = "BONUS_AWARD"
title = "Начисление бонусов пользователю " _ u.login
settings = { transport = 'local' }
}
settings = {
template_id = 'award_bonus'
user_service_id = us_list.first.id
}
) }}
{{ END }}
{{ END }}
Цепочка задач
Создание последовательности задач с разными приоритетами:
{{# Первая задача - подготовка данных }}
{{ result1 = spool.add(
event = {
name = "PREPARE"
title = "Подготовка данных"
settings = { transport = 'local' }
}
settings = {
template_id = 'prepare_data'
user_service_id = us.id
}
prio = 10
) }}
{{# Вторая задача - обработка (выполнится после первой) }}
{{ result2 = spool.add(
event = {
name = "PROCESS"
title = "Обработка данных"
settings = { transport = 'local' }
}
settings = {
template_id = 'process_data'
user_service_id = us.id
}
prio = 20
) }}
{{# Третья задача - отправка уведомления (выполнится последней) }}
{{ result3 = spool.add(
event = {
name = "NOTIFY"
title = "Отправка уведомления"
settings = { transport = 'local' }
}
settings = {
template_id = 'send_notification'
user_service_id = us.id
}
prio = 30
) }}
Возвращаемое значение
Метод возвращает объект добавленной задачи с полями:
id - идентификатор созданной задачи
status - статус задачи (обычно TASK_NEW)
- другие поля задачи
Особенности
- Задачи выполняются асинхронно в порядке приоритета
- Задачи с одинаковым приоритетом выполняются в порядке добавления (по ID)
- Можно создавать задачи для выполнения на других серверах, указав
server_id
- Поле
title сохраняется в логах и отображается в административном интерфейсе
- Задачи можно отслеживать через административный интерфейс SHM
us.make_custom_event()
Метод us.make_custom_event() - это удобный способ создания пользовательских событий для услуги. Он автоматически добавляет контекст услуги (user_service_id и server_id) и создает задачу в очереди выполнения.
Синтаксис
{{ result = us.make_custom_event(
name = EVENT_NAME
title = DESCRIPTION
transport = TRANSPORT
template_id = TEMPLATE_ID
prio = PRIORITY
delay = DELAY_SECONDS
settings = SETTINGS_OBJECT
) }}
Параметры
| Параметр |
Описание |
Обязательный |
По умолчанию |
name |
Название события |
Нет |
‘custom’ |
title |
Описание события для логов |
Нет |
‘custom event’ |
transport |
Транспорт для выполнения (local, ssh, http) |
Нет* |
Из сервера услуги |
template_id |
Идентификатор шаблона для выполнения |
Нет* |
Из сервера услуги |
prio |
Приоритет выполнения задачи |
Нет |
100 |
delay |
Задержка выполнения в секундах |
Нет |
0 |
settings |
Дополнительные настройки задачи |
Нет |
{} |
* Хотя бы один из параметров (transport + template_id или сервер услуги) должен быть указан.
Автоматически добавляемые параметры
Метод автоматически добавляет:
user_service_id - ID текущей услуги
server_id - ID сервера из настроек услуги (если указан)
Приоритет и задержка
- Приоритет (
prio): меньшее значение = выше приоритет (по умолчанию 100)
- Задержка (
delay): время в секундах до начала выполнения задачи
Примеры использования
Базовое использование
Создание пользовательского события для отправки промокода:
{{ result = us.make_custom_event(
name = 'PROMOCODE_SEND'
title = 'Отправка промокода'
transport = 'local'
template_id = 'alp_promo_send'
prio = 100
settings = {
promo_code = 'SUMMER2024'
discount = 20
}
) }}
Событие с высоким приоритетом
Создание срочной задачи для немедленного выполнения:
{{ result = us.make_custom_event(
name = 'URGENT_UPDATE'
title = 'Срочное обновление конфигурации'
transport = 'local'
template_id = 'urgent_config'
prio = 10
) }}
Отложенное событие
Создание задачи, которая выполнится через 1 час (3600 секунд):
{{ result = us.make_custom_event(
name = 'REMINDER'
title = 'Напоминание об оплате'
transport = 'local'
template_id = 'payment_reminder'
delay = 3600
settings = {
reminder_type = 'payment_due'
}
) }}
Выполнение для нескольких услуг
Создание событий для всех активных услуг определенной категории:
{{ FOR service IN user.us.filter( status = 'ACTIVE', service_id = 5 ).items }}
{{ result = service.make_custom_event(
name = 'NOTIFICATION'
title = 'Уведомление для услуги ' _ service.name
transport = 'local'
template_id = 'service_notification'
settings = {
message = 'Ваша услуга успешно продлена'
}
) }}
{{ END }}
Событие с дополнительными настройками
Передача сложных данных в settings:
{{ result = us.make_custom_event(
name = 'DATA_SYNC'
title = 'Синхронизация данных'
transport = 'local'
template_id = 'data_sync'
prio = 200
settings = {
sync_type = 'incremental'
sources = ['db1', 'db2']
options = {
verify = true
backup = true
}
}
) }}
Цепочка событий с задержками
Создание последовательности событий с разными задержками:
{{# Сразу - подготовка }}
{{ r1 = us.make_custom_event(
name = 'PREPARE'
title = 'Подготовка данных'
transport = 'local'
template_id = 'prepare'
prio = 10
) }}
{{# Через 5 минут - обработка }}
{{ r2 = us.make_custom_event(
name = 'PROCESS'
title = 'Обработка данных'
transport = 'local'
template_id = 'process'
prio = 20
delay = 300
) }}
{{# Через 10 минут - уведомление }}
{{ r3 = us.make_custom_event(
name = 'NOTIFY'
title = 'Отправка уведомления'
transport = 'local'
template_id = 'notify'
prio = 30
delay = 600
) }}
Использование в событиях
Создание дополнительного события при активации услуги:
{{# В шаблоне события ACTIVATE }}
{{ IF us.service.id == 5 }}
{{ result = us.make_custom_event(
name = 'WELCOME_EMAIL'
title = 'Отправка приветственного письма'
transport = 'local'
template_id = 'welcome_email'
delay = 60
) }}
{{ END }}
Возвращаемое значение
Метод возвращает объект созданной задачи или undef при ошибке.
Отличия от spool.add()
us.make_custom_event() - это упрощенная обертка над spool.add(), специально созданная для работы с событиями услуг пользователя.
Сравнительная таблица
| Характеристика |
us.make_custom_event() |
spool.add() |
| Контекст |
Автоматически добавляет user_service_id и server_id |
Требует явного указания всех параметров |
| Синтаксис |
Плоский список параметров |
Вложенная структура (event + settings) |
| Использование |
Для событий конкретной услуги |
Для любых задач (общие или специфичные) |
| Сервер |
Берется из настроек услуги |
Нужно указывать явно в settings.server_id |
| Параметр задержки |
delay (в секундах) |
delayed (в секундах) + executed |
| Транспорт |
Напрямую в параметрах |
В event.settings.transport |
| Гибкость |
Ограниченная (стандартные сценарии) |
Полный контроль над всеми аспектами |
| Простота |
Высокая (меньше кода) |
Средняя (больше параметров) |
Примеры эквивалентного кода
Одно и то же действие выполненное разными способами:
С помощью us.make_custom_event() (упрощенно):
{{ result = us.make_custom_event(
name = 'PROMOCODE_SEND'
title = 'Отправка промокода'
transport = 'local'
template_id = 'alp_promo_send'
prio = 100
) }}
Тот же результат через spool.add() (полный контроль):
{{ result = spool.add(
event = {
name = "PROMOCODE_SEND"
title = "Отправка промокода"
settings = { transport = 'local' }
}
settings = {
template_id = 'alp_promo_send'
user_service_id = us.id
server_id = us.server.id
}
prio = 100
) }}
Как видно, us.make_custom_event() автоматически подставляет user_service_id и server_id, что делает код короче и проще.
Когда использовать
Используйте us.make_custom_event() когда:
- ✅ Работаете в контексте услуги пользователя (в шаблонах событий, автоматизациях)
- ✅ Услуга привязана к серверу и нужен его контекст
- ✅ Нужна простота и скорость разработки
- ✅ Создаете стандартные события для услуг
- ✅ Хотите меньше кода и автоматическое заполнение контекста
Используйте spool.add() когда:
- ✅ Создаете задачи не связанные с конкретной услугой
- ✅ Нужно создать задачу для другого пользователя или услуги
- ✅ Требуется нестандартная структура события
- ✅ Создаете системные или административные задачи
- ✅ Нужен полный контроль над всеми параметрами события
- ✅ Работаете вне контекста услуги (например, в задачах автоматизации для всех пользователей)
Особенности
- Если не указан
transport и template_id, они будут взяты из настроек сервера услуги
- Метод автоматически добавляет
user_service_id текущей услуги
server_id добавляется автоматически, если услуга связана с сервером
- Задержка (
delay) полезна для отложенных уведомлений и напоминаний
- Дополнительные данные в
settings доступны в шаблоне через task.settings
us.event()
Метод us.event() позволяет повторно запустить выполнение стандартных событий для услуги пользователя. Это полезно когда нужно повторить выполнение события, например, после изменения настроек или при необходимости пересоздания ресурсов.
Синтаксис
{{ us.event( EVENT_NAME ) }}
Параметры
| Параметр |
Описание |
Обязательный |
EVENT_NAME |
Название стандартного события |
Да |
Доступные события
| Константа |
Значение |
Описание |
EVENT_CREATE |
CREATE |
Создание услуги |
EVENT_ACTIVATE |
ACTIVATE |
Активация услуги |
EVENT_PROLONGATE |
PROLONGATE |
Продление услуги |
EVENT_BLOCK |
BLOCK |
Блокировка услуги |
EVENT_REMOVE |
REMOVE |
Удаление услуги |
EVENT_CHANGED |
CHANGED |
Изменение услуги |
EVENT_CHANGED_TARIFF |
CHANGED_TARIFF |
Изменение тарифа |
Статус PROGRESS и выполнение событий
Для повторного выполнения большинства событий услуга должна находиться в статусе STATUS_PROGRESS или в соответствующем событию статусе.
Важно: Статус PROGRESS позволяет запускать любое событие, что делает его универсальным для повторного выполнения.
Правила выполнения событий по статусам
| Событие |
Разрешенные статусы |
EVENT_CREATE |
INIT, NOT PAID, PROGRESS |
EVENT_PROLONGATE |
ACTIVE, PROGRESS |
EVENT_BLOCK |
ACTIVE, PROGRESS |
EVENT_ACTIVATE |
BLOCK, PROGRESS |
EVENT_REMOVE |
ACTIVE, BLOCK, PROGRESS |
EVENT_CHANGED |
Любой (кроме REMOVED) |
EVENT_CHANGED_TARIFF |
Любой (кроме REMOVED) |
Примеры использования
Повторное выполнение события CREATE
Классический случай - необходимость пересоздать услугу (например, после изменения параметров):
{{# Переводим услугу в статус PROGRESS }}
{{ us.set( status = STATUS_PROGRESS ) }}
{{# Запускаем событие CREATE повторно }}
{{ us.event( EVENT_CREATE ) }}
Повторная активация услуги
Повторная активация может потребоваться для обновления конфигурации:
{{ us.set( status = STATUS_PROGRESS ) }}
{{ us.event( EVENT_ACTIVATE ) }}
Принудительное обновление конфигурации
Использование события CHANGED для обновления без изменения статуса:
{{# CHANGED можно вызвать без изменения статуса }}
{{ us.event( EVENT_CHANGED ) }}
Повторное создание после изменения настроек
Изменяем настройки и пересоздаем услугу:
{{# Изменяем настройки услуги }}
{{ us.set_settings(
new_param = 'value'
updated = true
) }}
{{# Переводим в PROGRESS и пересоздаем }}
{{ us.set( status = STATUS_PROGRESS ) }}
{{ us.event( EVENT_CREATE ) }}
Пересоздание всех услуг определенной категории
Массовое пересоздание услуг:
{{ FOR service IN user.us.filter( service_id = 5, status = 'ACTIVE' ).items }}
{{ service.set( status = STATUS_PROGRESS ) }}
{{ service.event( EVENT_CREATE ) }}
{{ END }}
Событие с проверкой текущего статуса
Безопасное выполнение события с проверкой:
{{ IF us.status == 'ACTIVE' }}
{{ us.set( status = STATUS_PROGRESS ) }}
{{ us.event( EVENT_PROLONGATE ) }}
{{ ELSE }}
{{ task.answer(
status = TASK_FAIL
msg = 'Услуга не активна, невозможно выполнить продление'
) }}
{{ END }}
Цепочка событий
Выполнение нескольких событий последовательно:
{{# Сначала CHANGED для обновления статуса }}
{{ us.event( EVENT_CHANGED ) }}
{{# Затем пересоздаем }}
{{ us.set( status = STATUS_PROGRESS ) }}
{{ us.event( EVENT_CREATE ) }}
Типичные сценарии использования
-
Пересоздание услуги после изменения шаблона - когда изменился шаблон создания и нужно применить изменения к существующим услугам
-
Обновление конфигурации - после изменения настроек услуги требуется повторное выполнение события активации
-
Исправление ошибок - если при создании произошла ошибка, можно исправить параметры и запустить событие заново
-
Миграция данных - при переносе услуг между серверами или изменении инфраструктуры
-
Синхронизация состояния - когда нужно синхронизировать состояние услуги с внешней системой
Важные особенности
- Метод
event() автоматически создает задачи в spool для выполнения события
- Если у услуги есть дочерние услуги в статусе PROGRESS, родительская услуга также перейдет в PROGRESS
- События
CHANGED и CHANGED_TARIFF не требуют специального статуса и могут вызываться напрямую
- При выполнении события услуга автоматически переходит в статус PROGRESS, а после выполнения - в целевой статус
- Приоритет задач для событий обычно установлен в 10 (высокий приоритет)
Предупреждения
⚠️ Внимание: Повторное выполнение событий может привести к дублированию ресурсов или конфликтам. Используйте этот метод осознанно и убедитесь, что ваши шаблоны событий корректно обрабатывают повторное выполнение.
⚠️ Статус REMOVED: События нельзя выполнить для удаленных услуг (статус REMOVED).
Связанные темы
8 - Автоматизации
8.1 - Подсчет доходов за месяц
Ниже приведен пример шаблона для подсчета поступления средств за Январь 2024 года:
{{ sum = user.pays.filter( date = '2024-01-%' ).sum( all_users = 1 ).money }}
Итого за Январь: {{ sum }} руб.
8.2 - Рассылки
SHM умеет делать рассылки по всем пользователям.
Для того, чтобы сделать рассылку, необходимо:
- В кабинете Администратора выбрать пункт меню: “Задачи -> Текущие задачи”, далее нажать кнопку “ADD”.
- Выберите для кого сделать рассылку: для одного пользователя или для всех (при тестировании используйте одного конкретного пользователя)
- Выберите группу серверов, которая будет использоваться для рассылки
- Выберите необходимый шаблон
- Создайте задачу
- Контролируйте исполнение задчи в “Текущие задачи”
В случае, если после рендера шаблон получается пустой, то такое сообщение отправлено не будет.
Этот свойство используется для выборочной отправки сообщений.
Примеры шаблонов
Шаблон для всех клиентов
Уважаемый {{ user.full_name }}!
Это тестовое сообщение.
Ваш личный кабинет находится по адресу: {{ config.cli.url }}
Шаблон для пользователей Telegram
Если рассылку делать с помощью транспорта Телеграм, то данная проверка в
шаблоне смысла не имеет, так как транспорт Телеграм сам проверяет это поле.
{{ IF user.settings.telegram.login }}
Вы получили это сообщение потому, что у Вас есть Telegram.
Ваш Telegram логин: {{ user.settings.telegram.login }}
{{ END }}
Шаблон для услуг в определенной категории
Данный шаблон будет отправлен только клиентам, у которых есть услуги в категории test.
Категорию услуг можно указать и через маску, например так: te%.
{{ IF user.services.filter( category = 'test' ).items }}
Уважаемый {{ user.full_name }}!
Вы получили это сообщение потому, что у Вас есть услуга в категории test.
{{ END }}
Информация об услугах в определенной категории
Показываем сообщение для каждой услуги в категории test.
{{ user_services = user.services.filter( category = 'test' ).items }}
{{ IF user_services }}
Услуги в категории test найдены в количестве: {{ user_services.size }} штук.
{{ FOR item IN user_services }}
Услуга: {{ item.name }}
Категория: {{ item.category }}
Статус: {{ item.status }}
Действует до: {{ item.expire }}
Цена: {{ item.withdraws.cost }}
Кол-во: {{ item.withdraws.qnt }}
Cкидка: {{ item.withdraws.discount }}%
Итого: {{ item.cost }}
{{ END }}
{{ END }}
Активная услуга в определенной категории
Перебираем (ищем) все услуги пользователя с категорией test, и проверяем, что статус активный.
Как только активная услуга будет найдена, то пишем сообщение и прекращаем перебор (LAST).
Таким образом, будет выведено только одно сообщение, даже если услуг несколько.
Если нужно выводить сообщения для каждой услуги, необходимо убрать LAST.
{{ FOR item IN user.services.filter( category = 'test' ).items }}
{{ IF item.status == 'ACTIVE' }}
Вы видите это сообщение, потому что у вас есть активная услуга в категории test
{{ LAST }}
{{ END }}
{{ END }}
Уведомление клиентам без услуг в определенной категории
{{ IF user.services.filter( category = 'test' ).items.empty }}
Вы видите это сообщение, потому что у вас нет ниодной услуги в категории test
{{ END }}
Подробнее о создании и настройке шаблонов можно прочитать здесь
8.3 - Удаление услуг при блокировке
Для удаления услуг при блокировке создайте шаблон вида:
{{ IF us.status == 'BLOCK' }}
{{ us.delete }}
{{ END }}
и добавьте его к событию CHANGED для нужной категории услуг.
Каждый раз, когда услуга будет переходить в статус BLOCK, SHM будет удалять её.
8.4 - Массовое начисление бонусов
Бывают случаи, когда необходимо начислить бонусы всем клиентам. Здесь приведены примеры, как это сделать.
Массовое начисление бонусов клиентам с указанной активной услугой:
Следующий код начислит всем клиентам с активной услугой 5 по 100 бонусов:
{{ FOR u IN user.items }}
{{ us_list = u.us.filter( service_id = 5, status = 'ACTIVE' ).items }}
{{ IF us_list.size }}
{{ u.add_bonus( 100, 'Акция' ) }}
{{ END }}
{{ END }}
8.5 - Массовое обновление тарифов
Бывают случаи, когда нужно обновить тарифы всем клиентам. Здесь приведены примеры, как это сделать.
Обновление стоимости текущей услуги (тарифа)
Просто изменить стоимость услуги в Каталоге не достаточно.
Необходимо для таких услуг пользователя установить “следующую” услугу в “текущую”:
{{ FOR u IN user.items }}
{{ FOR us IN u.us.items }}
{{ us.set(next = us.service_id) }}
{{ END }}
{{ END }}
Массовая смена услуг (тарифов)
Например, мы хотим сменить (со следующего учетного периода) услугу 5 на 6, делается это так:
{{ FOR u IN user.items }}
{{ FOR us IN u.us.filter( service_id = 5 ).items }}
{{ us.set(next = 6) }}
{{ END }}
{{ END }}
9 - Уведомления клиентам
Для отправки уведомлений клиентам создайте соответствующее событие и привяжите к нему шаблон.
Уведомления будут отправлены с помощью выбранного транспорта. Например, Вы можете отправить уведомление EMAIL, и/или Telegram (настраивается в событии).
9.1 - Прогноз оплаты
Описание
SHM имеет встроенный модуль forecast, позволяющий получать информацию о прогнозе оплаты услуг.
forecast находит все услуги (кроме уже заблокированных), дата истечения которых менее чем 3 дня (days), и ещё неоплаченные услуги, и вычисляет стоимость их продления.
Получить прогноз оплаты можно следующими способами:
- Через Web для текущего авторизованного пользователя:
/shm/v1/user/pay/forecast
- Через Web для пользователя с ID 123:
/shm/v1/user/pay/forecast?user_id=123 (нужно быть авторизованным под администратором)
- Из шаблонов:
user.pays.forecast
Настройки
| Параметр |
Описание |
| days |
Кол-во дней для прогноза (3 дня по-умолчанию) |
| blocked |
Учитывать заблокированные услуги (0 - по-умолчанию НЕТ) |
Пример команды в шаблонах:
{{ forecast = user.pays.forecast(days = 10, blocked = 1) }}
Пример шаблона
Метод user.pays.forecast возвращает JSON вида:
{
"items": [
{
"name": "Регистрация домена в зоне .RU",
"service_id": 11,
"user_service_id": 2949,
"usi": 2949,
"cost": 590,
"discount": 0,
"months": 12,
"qnt": 1,
"total": 590,
"expire": "2017-07-29 12:39:46",
"status": "ACTIVE",
"next": {
"name": "Продление домена в зоне .RU",
"service_id": 12,
"cost": 890,
"discount": 0,
"months": 12,
"qnt": 1,
"total": 890
}
}
],
"balance": -21.56,
"bonuses": 100,
"dept": 21.56,
"total": 768.44
}
Мы можем построить шаблон письма о прогнозе опаты услуг следующим образом:
Уважаемый {{ user.full_name }}
{{ forecast = user.pays.forecast }}
{{ ap = user.make_autopayment( forecast.total ) }}
{{ IF ap == 1 }}
Выполнен автоплатеж в размере: {{ forecast.total }}.
Ваши услуги будут продлены автоматически.
{{ ELSE }}
Уведомляем Вас о сроках действия услуг:
{{ FOR item IN forecast.items }}
- Услуга: {{ item.name }}
{{ IF item.expire }}
Истекает: {{ item.expire }}
{{ IF item.service_id != item.next.service_id }}
Следующая услуга: {{ item.next.name }}
Стоимость: {{ item.next.total }}
{{ ELSE }}
Стоимость продления: {{ item.next.total }}
{{ END }}
{{ ELSE }}
Стоимость: {{ item.total }}
{{ END }}
{{ END }}
{{ IF forecast.dept }}
Погашение задолженности: {{ forecast.dept }}
{{ END }}
Итого к оплате: {{ forecast.total }} руб.
{{ END }}
Подробнее о создании и настройке шаблонов можно прочитать здесь
9.2 - Уведомление о зачислении платежа
Описание
Создайте шаблон со следующим содержимым и привяжите его к событию PAYMENT:
{{ IF pay.money == 0 }}
Ошибка, платеж не прошел
{{ ELSE }}
Зачислен платеж на сумму: {{ pay.money }} руб.
Ваш баланс: {{ user.balance }} руб.
{{ END }}
10 - HTTP (API)
SHM позволяет использовать шаблоны для внешнего использования
Поддерживаемые методы:
HTTP адрес
Доступ к шаблону с именем my_template можно получить следующим способами:
Для пользователей
Доступ с авторизацией:
/shm/v1/template/my_template?format=html&foo=1&bar=hello
Пример для curl
curl -s -u 'admin:admin' http://127.0.0.1:8081/shm/v1/template/my_template
Более детально о вариантах аутентификации можно почитать здесь
Для публичного использования
/shm/v1/public/my_template?format=html&foo=1&bar=hello
Для того, чтобы шаблон работал без авторизации необходимо прописать в его settings параметр:
allow_public: true
В публичных шаблонах переменная user не устанавливается автоматически, но её можно установить самостоятельно:
-
Указать идентификатор вручную (статически):
в шаблоне: {{ user = user.switch( 123 ) }}
-
Указать идентификатор через HTTP запрос:
/shm/v1/public/my_template?uid=123
в шаблоне: {{ user = user.switch( request.params.uid ) }}
Пример для curl
curl -s http://127.0.0.1:8081/shm/v1/public/my_template?uid=123
Пример шаблона для проверки существования пользователя:
{{ IF user.switch( request.params.uid ).id }}
...
{{ END }}
Аргументы
В строку запроса можно добавить любые аргументы. Внутри самого шаблона к ним можно обратиться через переменную request.params
{{ foo = request.params.foo }}
Ниже приведен список специальных аргументов:
plain (text/plain)
html (text/html)
json (application/json)
other (application/octet-stream)
qrcode
user_id - зарезервирован
В случае работы с шаблоном из под прав администратора можно явно указать идентификатор пользователя
HTTP заголовки
В шаблонах можно читать заголовки. Их удобно использовать для различных проверок
{{ headers = request.headers }}
Пример шаблона для проверки заголовка x-webhook-secret:
{{ IF request.headers.x-webhook-secret == 'something-very-very-secret' }}
...
{{ END }}
Примеры
10.1 - Вывод данных в формате HTML
Пример шаблона (my_template) для формирования данных в HTML:
<table border=1>
{{ FOR u IN user.items }}
<tr>
<td>{{ u.user_id }}</td>
<td>{{ u.login }}</td>
<td>{{ u.balance }}</td>
</tr>
{{ END }}
</table>
Пример вызова шаблона для curl:
curl -s http://127.0.0.1:8081/shm/v1/public/my_template?format=html
Необходимо прописать в settings шаблона параметр: allow_public: true
Результат работы HTML шаблона лучше всего смотреть в браузере:
http://127.0.0.1:8081/shm/v1/public/my_template?format=html
10.2 - Вывод данных в формате JSON
Для формирования JSON объекта удобно использовать следующий синтаксис:
Пример шаблона (my_template) для формирования данных в JSON:
{{ u = user.id( request.params.uid ) }}
{{
toJson(
user_id = u.id
login = u.login
balance = u.balance
last_payment = u.pays.last
forecast = u.pays.forecast.total
)
}}
Пример вызова шаблона для curl:
curl -s http://127.0.0.1:8081/shm/v1/public/my_template?format=json&uid=123
Необходимо прописать в settings шаблона параметр: allow_public: true
Пример результата:
{
"user_id": "123",
"login": "danuk",
"balance": -21.56,
"last_payment": {
"comment": null,
"date": "2016-01-04 20:33:35",
"id": 2,
"money": 455,
"pay_system_id": "manual",
"user_id": 123
},
"forecast": 1013.45
}
11 - Прайс лист
Описание
SHM имеет встроенный модуль price_list, позволяющий получать список доступных услуг для регистрации клиентам.
Получить список услуг для регистрации (прайс лист) можно:
- Через API:
/shm/v1/service/price_list
- Из шаблонов:
service.price_list( АРГУМЕНТЫ )
service.price_list() возвращает массив услуг каталога с уже рассчитанной стоимостью,
скидкой и применимыми бонусами для текущего пользователя.
Метод используется как базовый источник данных для построения выпадающего списка
доступных услуг в личном кабинете пользователя и для проверки разрешенных услуг при их регистрации
Метод учитывает:
- фильтрацию доступных для заказа услуг
- составные услуги (
is_composite)
- пользовательскую скидку
- доступные бонусы
- ограничение
order_only_once (если услуга уже была использована)
Сигнатура
{{ arr = service.price_list() }}
{{ arr = service.price_list(service_id = N) }}
{{ arr = service.price_list(filter = { category = 'web%' }) }}
Аргументы
| Параметр |
Описание |
| service_id |
Вернуть данные только по одной услуге с указанным ID |
| filter |
Дополнительная фильтрация списка услуг |
Возвращаемые поля
Каждый элемент массива содержит стандартные поля услуги и дополнительные вычисленные поля:
| Поле |
Описание |
| cost |
Итоговая стоимость услуги (для составной услуги - с учетом дочерних) |
| discount |
Примененный процент скидки |
| cost_discount |
Размер скидки в деньгах |
| real_cost |
Стоимость после скидки, до применения бонусов |
| real_cost_with_bonuses |
Стоимость после применения бонусов |
| cost_bonus |
Сколько бонусов будет списано |
| partial_renew |
Признак частичного продления (из config.allow_partial_period) |
Примеры
Пример 1: вывести прайс-лист
{{ arr = service.price_list() }}
{{ FOR item IN arr }}
- {{ item.name }}: {{ item.real_cost_with_bonuses }}
{{ END }}
Пример 2: получить цену конкретной услуги
{{ arr = service.price_list(service_id = 5) }}
{{ IF arr.size }}
Стоимость: {{ arr.first.real_cost_with_bonuses }}
{{ END }}
Пример 3: сортировка и выборка нужных полей
{{ arr = service.price_list().sort_by_key('real_cost_with_bonuses' => 'asc') }}
{{ prices = arr.pluck('real_cost_with_bonuses') }}
{{ prices.join(', ') }}
Изменение стандартного поведения через шаблон
Стандартную логику формирования списка услуг можно переопределить шаблоном.
Для этого включите шаблонный режим в конфигурации биллинга:
- параметр
billing.price_list_template_id должен содержать ID шаблона
Шаблон должен вернуть JSON-массив идентификаторов услуг из каталога (service_id) в том порядке, в котором необходимо отобразить их клиенту.
Пример шаблона:
{{ services = [] }}
{{ FOR service IN user.us.services.items }}
{{ NEXT UNLESS service.allow_to_order }}
{{# NEXT IF service.is_ever_used }}
{{# NEXT IF service.was_previously_used }}
{{# NEXT IF service.is_currently_used }}
{{# добавьте сюда любую другую логику для исключения услуг из списка }}
{{ services.push( service ) }}
{{ END }}
{{ toJson( services.sort_by_key( cost = 'asc', category = 'asc' ).pluck('id') ) }}
Примечание:
service.price_list() и service.api_price_list() будут использовать результат этого шаблона
как источник списка услуг.
Проверка доступности услуги для заказа
service.price_list_check_allow_to_order проверяет, входит ли текущая услуга в
актуальный список услуг для регистрации пользователя.
Данную проверку можно выполнить и так: service.price_list(service_id = N), но price_list_check_allow_to_order работает значительно быстрее, так как не вычисляет скидки и бонусы
Метод возвращает:
1 - услуга доступна для заказа
0 - услуга недоступна для заказа
Логика проверки опирается на тот же источник, что и service.price_list():
- стандартная фильтрация каталога (
allow_to_order, deleted, filter и т.д.)
- либо список из шаблона при включенном
billing.price_list_template_id
Пример использования:
{{ IF service.id(5).price_list_check_allow_to_order }}
Услуга доступна для регистрации
{{ ELSE }}
Услуга недоступна для регистрации
{{ END }}
Этот метод используется в процессе регистрации услуги при check_allow_to_order = 1.
12 - Выполнение скриптов
Шаблоны позволяют генерировать как однострочные команды, так и целые блоки текста.
Эти механизмы являются основой построения взаимодействий SHM.
13 - Telegram bot
Шаблон для Telegram Bot-а
Это двух-уровневый шаблон. Сначала используются теги <% ... %> для нахождения
нужной секции шаблона, соответствующей команды. После нахождения нужной секции
шаблонизатор будет использовать теги вида: {{ ... }}.
Для сопоставления команды пользователя/бота используется внутренняя переменная cmd.
Так, при наборе команды /balance будет найдена секция: <% CASE '/balance' %>.
Команда USER_NOT_FOUND является встроенной в SHM, и вызывается автоматичеки, когда SHM не может найти у себя этого пользователя.
В каждой секции мы можем писать реальные команды Telegram. Например, команда sendMessage отправляет сообщение в Telegram.
Вы можете использовать эту команду в соответсвии с документацией Telegram.
Для отправки команд в API Telegram используйте метод tg_api.
Для того, чтобы лучше понять, как строятся всевозможные кнопочки, читайте документацию Telegram. В этом шаблоне всего-лишь описаны вызовы этих методов.
Пример шаблона:
<% SWITCH cmd %>
<% CASE 'USER_NOT_FOUND' %>
{{ tg_api( shmRegister = { callback_data = "/menu" } ) }}
<% CASE ['/start', '/menu'] %>
{{ tg_api( sendMessage = {
text = "Я Ваш тестовый Telegram Bot"
reply_markup = {
inline_keyboard = [
[
{
text = "Баланс"
callback_data = "/balance"
}
]
]
}
}
)
}}
<% CASE '/balance' %>
{{ tg_api( deleteMessage = { message_id = message.message_id } ) }}
{{ tg_api( sendMessage = {
text = "Баланс: " _ user.balance
reply_markup = {
inline_keyboard = [
[
{
text = "Назад"
callback_data = "/menu"
}
]
]
}
}
)
}}
<% CASE %>
{{ tg_api( sendMessage = { text = 'Я не знаю команды: ' _ cmd } ) }}
<% END %>
Стандартные методы Telegram
В SHM можно использовать любые методы Telegram.
Полный перечень доступных методов и синтаксис их использования
можно посмотреть на официальном сайте документации Telegram https://core.telegram.org/bots/api#available-methods
chat_id - заполняется автоматически, но при необходимости его можно указать
Встроенные переменные SHM
cmd - специальная переменная SHM в которую записывается значение, полученное из Telegram, в зависимости от типа:
message.text для команд введеных пользователем, и callback_query.data для callback_data. Если команда пользователя начинается
с символа /, то такая команда будет усечена до первого слова.
args - массив аргументов команды, разделитель - пробел.
start_args - именованный массив аргументов для команды start
message - полное сообщение от Telegram (json объект)
Примеры парсинга cmd и args:
| Источник |
Команда |
cmd |
args |
| Пользователь |
/test 1 2 3 |
/test |
[1,2,3] |
| Пользователь |
test 1 2 3 |
test 1 2 3 |
[1,2,3] |
| callback_data |
/test 1 2 3 |
/test |
[1,2,3] |
| callback_data |
test 1 2 3 |
test |
[1,2,3] |
args - это массив. Для получения значений из него используйте синтаксис: args.N, где N - индекс элемента.
Например, получить первый элемент массива можно так: args.0
Передача дополнительных параметров при старте бота
При старте бота может понадобится передача каких-либо данных, например для отслеживания рекламных акций (utm метрики),
или для создания партнерской ссылки.
ВНИМАНИЕ: суммарная длина параметров не может превышать 64 символа
Генерация партнерской ссылки
Для генерации партнерской ссылки можно использовать следующий шаблон:
https://t.me/myshm_bot?start={{ toBase64Url(toQueryString( pid = user.id )) }}
где myshm_bot - имя бота (замените на свой)
Генерация ссылки с произвольными параметрами
https://t.me/myshm_bot?start={{ toBase64Url(toQueryString(
utm_source = 'google'
utm_medium = 'telegram'
foo = 'bar'
))
}}
где myshm_bot - имя бота (замените на свой)
Переменные utm_ автоматически сохраняются в settings пользователя
Чтение переданных параметров
Вы можете прочитать переданный при старте бота параметр с помощью специальной переменной: start_args, пример:
Источник: {{ start_args.utm_source }}
Встроенные методы SHM
shmRegister
Метод позволяет зарегистрировать нового клиента
"shmRegister": {
"callback_data": "/menu",
"error": "ОШИБКА"
}
shmServiceOrder
Метод для регистрации новых услуг. Пример использования:
"shmServiceOrder": {
"service_id": "{{ args.0 }}",
"expire": "2014-09-25 10:11:12",
"parent": 123,
"check_exists": 1,
"check_exists_unpaid": 1,
"check_category": "test-%",
"callback_data": "/menu",
"cb_not_enough_money": "/pay",
"error": "ОШИБКА"
}
где: service_id - ID услуги,
expire - установка произвольной даты истечения услуги (опционально),
parent - ID родительской услуги (опционально),
check_exists - проверка существования услуги (опционально),
check_exists_unpaid - проверка существования неоплаченной услуги (опционально),
check_category - проверка существования услуги по категории (опционально),
callback_data - команда для случая успешного заказа услуги,
cb_not_enough_money - команда для случая нехватки средств для активации услуги.
Если используется один из флагов: check_exists, check_exists_unpaid, check_category и услуга найдена,
то регистрация новой услуги не осуществляется, а метод вернет первую найденную услугу (callback_data или cb_not_enough_money).
shmServiceDelete
Метод для удаления услуг пользователя. Пример использования:
"shmServiceDelete": {
"usi": "{{ args.0 }}",
"callback_data": "/menu",
"error": "ОШИБКА"
}
где: usi - ID услуги пользователя
uploadDocumentFromStorage
Метод загружает данные из Storage и отправляет их в виде файла
"uploadDocumentFromStorage": {
"name": "{{ args.0 }}",
"filename": "{{ args.0 }}.conf"
}
uploadPhotoFromStorage
Метод загружает данные из Storage и отправляет их в виде картинки (QR code)
"uploadPhotoFromStorage": {
"name": "{{ args.0 }}",
"format": "qr_code_png"
}
shmRedirectCallback
Метод вызывает указанную команду (cmd)
"shmRedirectCallback": {
"callback_data": "/help"
}
Приём платежей
Для приёма платежей в Telegram удобно использовать дополнительный шаблон (web_app).
Шаблон используется для возможности ввода произвольной суммы и выбора настроенных платежных систем SHM.
- Настройте одну или несколько платежных систем
- Скачайте шаблон и сохраните в SHM под названием
tg_payments_webapp
- Сделайте шаблон публичным: пропишите в его
settings параметр: allow_public: true
- В Шаблоне своего бота используйте конструкцию вида:
<% CASE '/payment' %>
{{ tg_payment_webapp="$config.api.url/shm/v1/public/tg_payments_webapp?format=html&user_id=$user.id&profile=$tpl.id" }}
{{ tg_api(
sendMessage = {
text = "Оплата покупки",
reply_markup = {
inline_keyboard = [
[{
text = "Оплатить..."
web_app = { url = tg_payment_webapp }
}]
]
}
}
)
}}
Дополнительно можно передавать:
email, например: &email={{ user.settings.email }}
ack_email, например: &ack_email=1 - появится поле ввода email
13.1 - Пагинация
Пример шаблона для постраничного вывода списка услуг
{{
limit = 5
data = []
}}
<% SWITCH cmd %>
<% CASE '/list' %>
{{
offset = args.0 || 0
items = user.services.limit( limit, offset ).items;
}}
{{ FOR item IN items }}
{{ data.push(
[{
"text" = 'Услуга: ' _ item.name _ ' ID: ' _ item.user_service_id
"callback_data" = '/service ' _ item.user_service_id
}]
)
}}
{{ END }}
{{ data.push(
[{
"text" = 'Еще...'
"callback_data" = '/list ' _ (limit + offset)
}]
) IF items.size == limit
}}
{
"sendMessage": {
"text": "Список услуг",
"reply_markup" : {
"inline_keyboard": {{ toJson( data ) }}
}
}
}
<% END %>