Справочник валют программы¶
Справочник валют — единый список валют программы лояльности: и денежных, и балльных. Деньги и баллы описываются одной записью с типом, у балльной валюты хранится курс к денежной и базовый срок жизни баллов, каждая смена курса попадает в историю и в журнал аудита. В том же разделе ведутся пары обмена между балльными валютами.
До выпуска 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 | Разрешённый субсчёт не принадлежит валюте-источнику или это черновик |
Связанные разделы¶
- Операционная конфигурация — какая валюта представляет баллы и системные счета.
- Субсчета баллов — субсчета заводятся внутри валюты баллов.
- Сгорание баллов — как базовый срок валюты участвует в сроке лота.