Перейти к содержанию

Справочник валют программы

Справочник валют — единый список валют программы лояльности: и денежных, и балльных. Деньги и баллы описываются одной записью с типом, у балльной валюты хранится курс к денежной и базовый срок жизни баллов, каждая смена курса попадает в историю и в журнал аудита. В том же разделе ведутся пары обмена между балльными валютами.

До выпуска 26.3.0 валюты велись двумя разными способами: денежные — служебной таблицей, балльные — отдельным экраном «Валюты и баллы», где курс хранился одним числом без указания денежной валюты, без истории и без проверки ссылок при удалении. Теперь это один справочник, а прежний блок из интерфейса убран.

Экран «Валюты»

Раздел «Администрирование» → «Валюты», подзаголовок — «Деньги и баллы программы: курс, история изменений и состояние валюты». У раздела две вкладки: «Валюты» и «Обмен валют» (выбранная вкладка сохраняется в адресе страницы).

Просмотр требует роль LOYALTY_DICTIONARY_READ, изменения — LOYALTY_DICTIONARY_WRITE; без права записи карточки открываются только для чтения.

Вкладка «Валюты»

Над таблицей — кнопка «Создать валюту». Колонки таблицы:

Колонка Содержание
«Валюта» Код валюты (у выключенной — приглушённый), название и символ
«Тип» «Деньги» или «Баллы»; у базовой валюты — пометка «базовая»
«Курс» У балльной — фраза «1 PTS = 0,50 ₽» и отметка «изменён …» с датой и автором; у денежной — прочерк
«Состояние» «Включена» или «Выключена»

Сортировка серверная по любой колонке, у «Типа» и «Состояния» — фильтры. Двойной клик по строке открывает карточку валюты; одиночный клик выбирает выключенную валюту для удаления («Удалить можно только выключенную валюту»). Выбранные валюты удаляются кнопкой «Удалить» на панели массовых действий — после подтверждения «Вы уверены, что хотите удалить выбранные валюты?» показывается итог по каждой валюте.

Карточка валюты:

Поле Правила
«Тип» «Деньги» — то, чем клиент платит; «Баллы» — то, что начисляет программа. После создания не меняется
«Валюта» (деньги) Выбор из встроенного перечня ISO; подставляет название, символ и точность. Уже заведённые валюты не предлагаются
«Код» (баллы) Латинские буквы A–Z, цифры и _, от 2 до 16 символов; строчные буквы становятся заглавными. Коды ISO заняты
«Название» Обязательно
«Символ», «Точность» Символ показывается после суммы; без символа — код валюты. Точность — от 0 до 6 знаков. После первой операции не меняются
«Базовая валюта» (деньги) «Через базовую валюту считается курс по умолчанию. Базовая валюта одна»
«Курс» (баллы) Фраза «1 {код} = [значение] [валюта]» с живым примером; валюта курса — включённая денежная, по умолчанию базовая
«Срок жизни баллов, дней» (баллы) Базовый срок жизни баллов валюты — см. ниже
«Включена» Переключатель; под ним — когда и кем валюта включена или выключена
«История курса» Все значения курса с датой, автором и пометкой «текущий»

Смена курса сохраняется только после подтверждения «Изменение курса валюты баллов»: окно показывает текущий и новый курс и предупреждает, что ранее сохранённые бюджеты акций не пересчитываются.

Карточку конкретной валюты можно открыть ссылкой с параметром ?currency=КОД — так ведёт ссылка «Изменить курс» с экрана «Субсчета баллов».

Состав записи

Поле Смысл
code Код валюты. Денежная — только из встроенного перечня ISO (RUB, USD, …); балльная — латиница A–Z, цифры и _, от 2 до 16 символов, и не может совпадать с кодом ISO
kind Тип: MONEY — деньги, POINTS — баллы
title Название валюты
symbol Символ валюты; необязателен и может отсутствовать
precision Точность (число знаков после запятой), от 0 до 6
enabled Включена ли валюта
isBase Базовая денежная валюта программы; ровно одна
rate, rateCurrency Курс балльной валюты и денежная валюта, в которой он выражен
pointsLifetimeDays Базовый срок жизни баллов балльной валюты, дней; null — без срока
sortOrder Порядок в списке
hasOperations По валюте уже есть операции
enabledAt, enabledBy, disabledAt, disabledBy, rateChangedAt, rateChangedBy Кто и когда включил, выключил, менял курс

При установке справочник засевается двумя записями: RUB — денежная, базовая, и PTS — балльная с курсом 1 RUB и сроком жизни баллов 365 дней. У каждой валюты есть базовый субсчёт: он создаётся вместе с валютой.

Правила

Базовая валюта

  • Базовой может быть только включённая денежная валюта, и она в программе ровно одна.
  • Назначение другой базовой снимает признак с прежней; оставить программу без базовой валюты нельзя (422 base_currency_required).
  • Базовую валюту нельзя выключить.
  • Пока есть незавершённые акции с бюджетом в деньгах, базовую валюту сменить нельзя — 409 base_currency_used_by_budget.
  • Символом и точностью базовой валюты подписываются и пересчитываются денежные суммы конфигурации программы — пороги уровней и сумма покупки в правилах списания.

Курс балльной валюты

  • Курс обязателен у каждой балльной валюты (422 currency_rate_required), неотрицателен (проверяется валидацией запроса — отрицательное значение отклоняется 400, до домена не доходит) и содержит не больше шести знаков после запятой (422 rate_scale_exceeded).
  • Балльную валюту нельзя завести, пока нет ни одной включённой денежной (422 money_currency_required): курс не в чем выразить.
  • Валютой курса может стать только включённая денежная валюта (422 currency_rate_currency_invalid). Денежную валюту нельзя выключить, пока её используют курсы включённых балльных валют (409 currency_in_use_by_rate).
  • Новый курс действует с момента сохранения — одинаково у включённой и выключенной валюты. Каждая смена значения или валюты курса пишется в историю и в журнал аудита с прежним и новым значением.
  • Нулевой курс допустим, но валюту с нулевым курсом нельзя использовать для бюджета акции (422 currency_zero_rate). Бюджеты акций в деньгах считаются по курсу валюты баллов программы (настройка points.currency): её курс нельзя обнулить, пока бюджет расходуют акции, а выражен он может быть только в базовой валюте.

Срок жизни баллов

У балльной валюты есть базовый срок жизни баллов — поле «Срок жизни баллов, дней» (pointsLifetimeDays). Подсказка поля: «Действует, если срок не задан в акции, в субсчёте или для уровня клиента». Это последнее звено цепочки, по которой определяется срок начисленного лота, — подробнее в разделе Сгорание баллов.

  • Значение — от 1 до 3 650 дней; пустое поле (null) — баллы без более точного срока не сгорают.
  • Новая балльная валюта без поля получает 365 дней. В PUT отсутствующее поле срок не меняет, явный null снимает срок.
  • Значение вне диапазона или срок у денежной валюты — 400 validation_failed.
  • Новый срок действует для новых начислений: срок уже начисленного лота записан в нём при начислении и не меняется.

При обновлении до 26.3.0 всем балльным валютам проставлено 365 дней, а валюте баллов программы — единый срок жизни баллов активной конфигурации программы, если он был задан.

Включение и выключение

  • Выключенная валюта не принимает начисления — новый лот отклоняется 422 currency_disabled. Списание и сторно остатка в такой валюте проходят: накопленное у клиентов не сгорает от административного действия.
  • Пакет ручных начислений в выключенную валюту отклоняется целиком ещё на проверке, а не построчно при выполнении.
  • В интерфейсе в выключенную валюту не заводятся новые субсчета.
  • Символ и точность валюты не меняются после первой операции (409 currency_has_operations): прошлые суммы отображались бы иначе, чем были посчитаны.

Счета клиентов в валюте

У каждого клиента в статусе «Активен» есть счёт в каждой включённой валюте — балльной и денежной — на её базовом субсчёте.

  • Новому клиенту (создание, импорт) счета открываются сразу во всех включённых валютах.
  • Создание включённой валюты и включение выключенной запускают фоновую задачу: счёт в этой валюте открывается каждому активному клиенту пачками. Заблокированным клиентам счёт не открывается — он откроется при разблокировке. Выключение валюты задачу останавливает, повторное включение начинает незавершённую задачу заново. Задача переживает перезапуск сервиса и безопасна на нескольких репликах; её параметры — loyalty.account-opening.* (по умолчанию включена).
  • План счетов нового счёта — из настройки customer.default.chart-of-accounts, иначе 2110 у балльной и 2120 у денежной валюты. Если открыть счёт не на что (нет базового субсчёта или плана счетов), заведение клиента не отказывает: счёт откроется после исправления, а метрика loyalty.accounts.opening.unopenable позволяет настроить оповещение.
  • Пока счёт не открыт, список счетов клиента (GET /v1/customers/{externalId}/accounts) всё равно показывает включённую валюту — строкой с нулями и пустым номером счёта; в карточке клиента на вкладке «Счета и балансы» вместо номера — прочерк.

При обновлении до 26.3.0 задача открытия счетов ставится разово по каждой включённой валюте.

Операции с баллами — в валюте баллов программы

Начисление, списание, резерв и баланс в карточке клиента работают с валютой баллов программы — настройкой points.currency операционной конфигурации. Счета в других включённых валютах открываются и видны в карточке клиента, но операции в новой балльной валюте пока не выполняются.

Удаление

Удалить можно только выключенную валюту (409 currency_enabled), на которую нет ссылок (409 currency_in_use). Виды ссылок возвращаются в ответе:

Вид ссылки Что означает
ACCOUNTS Есть счета в этой валюте, по которым прошла хотя бы одна проводка, либо системные счета
SUBACCOUNTS Есть субсчета, кроме базового (в том числе «Акционные баллы»); базовый — только если на нём есть счета с проводками или заданы ограничения
RATES В этой денежной валюте выражен курс балльной (сейчас или в истории курса)
REDEMPTION_LIMITS На валюту ссылается лимит списания
SYSTEM_SETTINGS Код валюты указан в настройках системы или в курсах обмена
EXCHANGE_PAIRS Валюта участвует в паре обмена

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

Обмен валют

Вкладка «Обмен валют» ведёт справочник пар обмена между балльными валютами: пара задаёт особый курс обмена в одном направлении (или в обе стороны) и ограничения на обмен.

Пары пока не применяются к операции обмена

Справочник пар хранит правила обмена, но операция обмена POST /v1/exchange их пока не читает: она считает сумму по таблице курсов обмена (GET /v1/exchange-rates), которая наполняется вне продукта. Обмена по базовому курсу без заведённого курса, минимумов, лимитов и разрешённых субсчетов при выполнении обмена ещё нет. Пояснение над таблицей («Без пары обмен между балльными валютами идёт по базовому курсу…») и подтверждение удаления пары описывают целевое поведение, а не текущее.

Над таблицей — пояснение, счётчик «Пар обмена: N, доступно: M» (по всему справочнику, без учёта фильтров) и кнопка «Добавить пару». Колонки таблицы: «Направление обмена» (с пометкой «только в одну сторону» или «обмен в обе стороны»), «Курс», «Ограничения», «Состояние» («Доступен» или «Отключён»). Сортировка серверная, по умолчанию доступные пары идут первыми; у «Состояния» — фильтр.

Карточка пары:

Поле Правила
«Из чего», «Во что» Две разные балльные валюты; после создания не меняются. Направление занято — отказ «Пара … уже есть»
«Курс» «Базовый курс», «Свой курс за единицу» или «Свой курс пачками». Рядом — справка базового курса: «Базовый курс: 1 A = x B (через RUB)»
«Значение курса» Для курса за единицу: сколько единиц получателя за 1 единицу источника, больше 0, до 6 знаков после запятой
«Сколько меняем», «Сколько получаем» Для курса пачками: целые пачки валюты-источника и сколько целых единиц получателя за пачку. Остаток меньше пачки не обменивается и остаётся у клиента
«Округление» Для базового курса и курса за единицу: «Вниз» (по умолчанию), «Математическое», «Вверх»
«Остаток» Для базового курса и курса за единицу: «Не оставлять» (по умолчанию) или «Оставлять у клиента»
«Направление» «Только в эту сторону» или «В обе стороны» — тогда появляется блок «Обратный обмен» со своими ограничениями; курс обратного обмена тот же
«Минимум за одну операцию» 0 — без ограничения; при курсе пачками кратен пачке
«Лимит обмена» «Обменять не более N … за» день, неделю, месяц или год; 0 — без лимита. Считается по календарному периоду
«Разрешённые субсчета источника» Субсчета валюты-источника (активные или закрытые); ничего не выбрано — обмениваются баллы любого субсчёта
«Обмен доступен» Состояние пары. Пару с выключенной валютой можно сохранить только отключённой

Базовый курс направления — отношение курсов двух балльных валют к базовой валюте (шесть знаков после запятой). Он не определён, если у одной из валют нет курса, курс равен нулю или выражен не в базовой валюте; тогда пару с режимом «Базовый курс» сохранить нельзя (422 exchange_rate_undefined).

Смена курса пары сохраняется после подтверждения «Изменение курса пары обмена» и пишется в журнал аудита (exchange-pair.rate.change). Удалить можно только отключённую пару; удаление — массовое, с подтверждением и итогом.

API

Метод и путь Назначение Право
GET /v1/admin/currencies Список с фильтрами kind (MONEY/POINTS) и enabled, постраничный, с сортировкой по code, kind, rate, enabled LOYALTY_DICTIONARY_READ
GET /v1/admin/currencies/{code} Карточка валюты LOYALTY_DICTIONARY_READ
GET /v1/admin/currencies/{code}/rates История курса: значение, валюта курса, автор и момент изменения LOYALTY_DICTIONARY_READ
POST /v1/admin/currencies Создать валюту LOYALTY_DICTIONARY_WRITE
PUT /v1/admin/currencies/{code} Изменить валюту, в том числе курс и срок жизни баллов LOYALTY_DICTIONARY_WRITE
DELETE /v1/admin/currencies/{code} Удалить выключенную валюту без ссылок LOYALTY_DICTIONARY_WRITE
GET /v1/currencies, GET /v1/currencies/{code} Чтение валют для интеграций и интерфейсов (в том числе pointsLifetimeDays) LOYALTY_CURRENCY_READ
GET /v1/admin/exchange-pairs Пары обмена: фильтр по состоянию, постранично, серверная сортировка LOYALTY_DICTIONARY_READ
GET /v1/admin/exchange-pairs/summary Всего пар и доступных — без учёта фильтров LOYALTY_DICTIONARY_READ
GET /v1/admin/exchange-pairs/base-rate?fromCurrency=&toCurrency= Базовый курс направления и код базовой валюты; неопределённый курс — пустой rate LOYALTY_DICTIONARY_READ
POST /v1/admin/exchange-pairs Создать пару обмена LOYALTY_DICTIONARY_WRITE
PUT /v1/admin/exchange-pairs/{id} Изменить пару целиком LOYALTY_DICTIONARY_WRITE
DELETE /v1/admin/exchange-pairs/{id} Удалить отключённую пару LOYALTY_DICTIONARY_WRITE
# Завести балльную валюту с курсом 1 RUB и сроком жизни баллов 180 дней
curl -X POST https://<host>/v1/admin/currencies \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"code":"BONUS","kind":"POINTS","title":"Бонусы","precision":2,
       "enabled":true,"rate":1,"rateCurrency":"RUB","pointsLifetimeDays":180}'
# Сменить курс — попадёт в историю и в аудит; срок жизни баллов без поля не меняется
curl -X PUT https://<host>/v1/admin/currencies/BONUS \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"title":"Бонусы","precision":2,"enabled":true,"rate":0.5,"rateCurrency":"RUB"}'

Пара обмена в запросе: fromCurrency, toCurrency, rateMode (BASE/UNIT/PACK), rate (для UNIT), packFrom, packTo (для PACK), rounding (DOWN/HALF_UP/UP), keepRemainder, bidirectional, forward и backward — ограничения направления (minAmountMinor, limitAmountMinor в минорных единицах валюты-источника, limitPeriod — DAY/WEEK/MONTH/YEAR, sourceSubaccounts — коды субсчетов), enabled. В ответе дополнительно — id и справочный baseRate.

BREAKING (26.3.0)

  • GET /v1/currencies отдаёт тип валюты kind, а display.symbol может быть пустым.
  • Неизвестный код валюты в операциях и в GET /v1/currencies/{code} — 404 currency_not_found (раньше — внутренняя ошибка).
  • Блок «Валюты и баллы» убран из интерфейса. Прежний контракт /v1/admin/point-currencies остаётся для совместимости и работает поверх нового справочника: цвет и порядок в нём больше не сохраняются, удаление включённой валюты отвечает 409 currency_enabled, валюты со ссылками — 409 currency_in_use.
  • Настройка customer.default.currency удалена — счета новому клиенту открываются во всех включённых валютах. Свойство loyalty.promotion.budget.point-currency больше не читается — бюджет акций считается по курсу валюты баллов программы.
  • Параметр preCreateAccounts при создании клиента принимается и игнорируется: счета открываются во всех включённых валютах.

Коды ошибок

Код HTTP Когда возникает
currency_not_found 404 Валюта с таким кодом не заведена
currency_code_taken 409 Код уже занят
currency_enabled 409 Удаление включённой валюты
currency_in_use 409 На валюту есть ссылки
currency_in_use_by_rate 409 Выключение денежной валюты, в которой выражен курс включённой балльной
currency_has_operations 409 Смена символа или точности после первой операции
base_currency_used_by_budget 409 Смена базовой валюты, пока акции расходуют бюджет
validation_failed 400 Срок жизни баллов вне диапазона 1–3 650 дней или у денежной валюты
currency_code_invalid 422 Код не по формату (или денежный код вне перечня ISO)
currency_code_reserved 422 Код балльной валюты совпадает с кодом ISO
currency_precision_invalid 422 Точность вне диапазона 0–6
currency_rate_required 422 У балльной валюты нет курса
rate_scale_exceeded 422 В курсе больше шести знаков после запятой (отрицательный курс отклоняется раньше, валидацией запроса, — 400)
currency_rate_currency_invalid 422 Валюта курса не найдена среди включённых денежных
money_currency_required 422 Нет ни одной включённой денежной валюты
base_currency_required 422 Программа осталась бы без базовой валюты
currency_disabled 422 Начисление в выключенную валюту
currency_zero_rate 422 Бюджет акции при нулевом курсе балльной валюты

Пары обмена:

Код HTTP Когда возникает
exchange_pair_not_found 404 Пара не найдена
exchange_pair_exists 409 Направление уже занято другой парой
exchange_pair_enabled 409 Удаление доступной (не отключённой) пары
same_currency_exchange 422 «Из чего» и «Во что» — одна валюта
exchange_pair_currency_invalid 422 Валюта пары не балльная
exchange_pair_currency_immutable 422 Попытка сменить валюты пары после создания
exchange_pair_currency_disabled 422 Пара с выключенной валютой сохраняется доступной
exchange_rate_invalid 422 Курс за единицу не больше нуля или с лишними знаками
exchange_rate_undefined 422 Режим «Базовый курс», а базовый курс направления не определён
exchange_pack_invalid 422 Пачка — не целое число от 1
exchange_minimum_not_multiple 422 Минимум при курсе пачками не кратен пачке
exchange_subaccount_invalid 422 Разрешённый субсчёт не принадлежит валюте-источнику или это черновик

Связанные разделы