Ежемесячные комиссии (Monthly Fees) — руководство для поддержки
Общее описание
Для карт типа TCC (Tokenization Card) система автоматически списывает ежемесячную комиссию. Размер комиссии определяется конфигурацией BIN-а карты (поле monthlyFee). Если значение равно 0, комиссия не списывается.
Как это работает
При выпуске TCC-карты запускается Temporal-воркфлоу ManageTokenizationMonthlyFees с ID вида monthly-fees-<card_id>.
Воркфлоу работает по следующей логике:
- Получение данных карты — загружает карту из БД, определяет
card_created_atи размер комиссии из BIN-конфигурации. - Добор пропущенных комиссий (backfill) — если с момента создания карты прошло больше месяца, воркфлоу доначисляет все пропущенные комиссии за каждый месяц.
- Ожидание следующей даты — воркфлоу засыпает до следующей даты списания (ровно через месяц от
card_created_at). - Ежемесячный цикл — каждый месяц проверяет статус карты:
- Если карта
closedилиclose_pending— воркфлоу завершается. - Иначе списывает комиссию и засыпает до следующего месяца.
- Если карта
Дата списания
Дата привязана к card_created_at. Если карта создана 15 января, комиссии будут 15 февраля, 15 марта и т.д.
Идемпотентность
Каждое списание имеет уникальный ключ: <issueTx>:<YYYY-MM-DD>. Дублирование невозможно — БД отклонит повторную транзакцию.
Транзакции в БД
При каждом списании создаются 3 записи в client_account_transactions:
| Тип | Подтип | Суффикс идемпотентности | Описание |
|---|---|---|---|
pending |
issue_fee |
:hold |
Холд суммы комиссии |
fee |
issue_fee |
:issue-fee |
Списание комиссии |
deposit |
issue_fee |
:fee-credit |
Зачисление на fee-аккаунт |
Верификация после деплоя
1. Проверка каденции комиссий
Показывает все активные TCC-карты с датой последней комиссии и статусом:
SELECT
c.card_id,
c.card_created_at,
c.card_created_at + INTERVAL '1 month' AS first_fee_date,
last_fee.last_fee_at,
CASE
WHEN last_fee.last_fee_at IS NULL THEN 'NO_FEES'
WHEN NOW() - last_fee.last_fee_at > INTERVAL '31 days' THEN 'OVERDUE'
ELSE 'OK'
END AS status
FROM cards c
LEFT JOIN LATERAL (
SELECT MAX(client_account_transaction_created_at) AS last_fee_at
FROM client_account_transactions
WHERE client_account_transaction_card_id = c.card_id
AND client_account_transaction_subtype = 'issue_fee'
AND client_account_transaction_type = 'fee'
AND client_account_transaction_idemp_key NOT LIKE 'monthly-fee-return:%'
) last_fee ON true
WHERE c.card_product = 'TCC'
AND c.card_status NOT IN ('closed', 'close_pending')
ORDER BY status DESC, c.card_created_at;
Что искать:
NO_FEES— карта активна, но комиссии ни разу не списались. Проверьте, что воркфлоу запущен.OVERDUE— последняя комиссия больше 31 дня назад. Возможно, воркфлоу остановился.OK— всё в порядке.
2. Проверка дубликатов
Если были перезапуски воркфлоу, убедитесь, что нет дублей:
WITH fee_txs AS (
SELECT
client_account_transaction_card_id AS card_id,
regexp_replace(
client_account_transaction_idemp_key,
':(hold|issue-fee|fee-credit)$', ''
) AS base_key,
client_account_transaction_type AS tx_type,
COUNT(*) AS cnt
FROM client_account_transactions
WHERE client_account_transaction_subtype = 'issue_fee'
AND client_account_transaction_idemp_key NOT LIKE 'monthly-fee-return:%'
GROUP BY 1, 2, 3
)
SELECT card_id, base_key, tx_type, cnt
FROM fee_txs
WHERE cnt > 1
ORDER BY cnt DESC;
Ожидаемый результат: пустой — дубликатов быть не должно.
3. Проверка реверсов
Если запускался инструмент restart_monthly_fees --reconcile-fees, проверьте количество реверсов:
SELECT
client_account_transaction_card_id AS card_id,
COUNT(*) AS reversal_count,
SUM(client_account_transaction_amount) AS total_reversed
FROM client_account_transactions
WHERE client_account_transaction_idemp_key LIKE 'monthly-fee-return:%'
GROUP BY client_account_transaction_card_id
ORDER BY reversal_count DESC;
4. Проверка запущенных воркфлоу
Через Temporal CLI:
tctl workflow list \
--query "WorkflowType='ManageTokenizationMonthlyFees' AND ExecutionStatus='Running'" \
--print_full_paged | wc -l
Количество запущенных воркфлоу должно совпадать с числом активных TCC-карт:
SELECT COUNT(*) AS active_tcc_cards
FROM cards
WHERE card_product = 'TCC'
AND card_status NOT IN ('closed', 'close_pending');
5. История комиссий конкретной карты
Для детальной проверки по одной карте:
SELECT
client_account_transaction_idemp_key AS idemp_key,
client_account_transaction_type AS type,
client_account_transaction_amount AS amount,
client_account_transaction_created_at AS created_at
FROM client_account_transactions
WHERE client_account_transaction_card_id = '<CARD_ID>'
AND client_account_transaction_subtype = 'issue_fee'
ORDER BY client_account_transaction_created_at;
Логирование
Воркфлоу пишет структурированные логи в Temporal. Ключевые сообщения:
| Сообщение | Контекст |
|---|---|
monthly fees workflow started |
Старт воркфлоу: card_id, issue_tx, card_created_at |
backfilling missed fees |
Доначисление: количество, диапазон дат |
charging monthly fee |
Каждое списание: card_id, fee_date, amount |
skipping zero monthly fee |
BIN с нулевой комиссией |
sleeping until next charge |
Ожидание: next_charge, sleep_duration |
card closed, stopping monthly fees |
Завершение: card_id, status |
Для просмотра логов конкретного воркфлоу используйте Temporal UI или CLI:
tctl workflow show --workflow_id "monthly-fees-<card_id>"
Инструмент перезапуска
Утилита cmd/restart_monthly_fees позволяет:
- Перезапустить все воркфлоу ежемесячных комиссий (terminate + start).
- Откатить дублированные транзакции (
--reconcile-fees). - Запустить в режиме dry-run для проверки (
--dry-run).
# Только посмотреть, что будет сделано
go run ./cmd/restart_monthly_fees -cfg configs/configs.app.yaml --dry-run
# Перезапустить воркфлоу + откатить дубли
go run ./cmd/restart_monthly_fees -cfg configs/configs.app.yaml --reconcile-fees
# С фильтрацией по дате
go run ./cmd/restart_monthly_fees -cfg configs/configs.app.yaml --reconcile-fees --from 2025-01-01 --to 2025-06-01
Частые вопросы
Карта активна, но комиссия не списывается
- Проверьте, что воркфлоу запущен:
tctl workflow show --workflow_id "monthly-fees-<card_id>". - Проверьте BIN карты — возможно,
monthlyFee = 0. - Проверьте, что
card_created_atкорректна (не в будущем).
Двойное списание комиссии
Невозможно при нормальной работе из-за идемпотентности. Если видите дубликат — запустите restart_monthly_fees --reconcile-fees --dry-run для диагностики.
Воркфлоу завершился с ошибкой
Проверьте логи в Temporal UI. Типичные причины:
fee account missing— не настроен fee-аккаунт для клиента/провайдера/валюты.- Ошибка БД — временная, воркфлоу автоматически повторит.