Зачем автоматизировать платежи
Финансовые операции — идеальный кандидат для автоматизации через cron: они регулярные, предсказуемые, имеют чёткое расписание. Аренда 1-го числа, зарплата 5-го и 25-го, налоги — каждый месяц одно и то же. Ручная работа = человеческие ошибки + траты времени бухгалтера. Cron + банковский API = надёжная автоматика.
НО: финансовые cron'ы требуют большей осторожности чем обычные. Цена ошибки — реальные деньги. В этом гайде — best practices для финансовых cron-задач.
Шаблоны для разных операций
Безопасность финансов
1. Idempotency — защита от двойного списания
-- Таблица для idempotency CREATE TABLE billing_runs ( id UUID PRIMARY KEY DEFAULT uuidv7(), user_id BIGINT NOT NULL, period_start DATE NOT NULL, period_end DATE NOT NULL, amount NUMERIC(10,2), status TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), -- ВАЖНО: уникальный индекс UNIQUE (user_id, period_start, period_end) ); -- При биллинге: INSERT INTO billing_runs (user_id, period_start, period_end, amount, status) VALUES (42, '2026-05-01', '2026-05-31', 1500.00, 'pending') ON CONFLICT (user_id, period_start, period_end) DO NOTHING RETURNING *; -- Если RETURNING пустой — этот платёж уже был, пропускаем. -- Если RETURNING содержит запись — это новая, делаем charge.
2. Транзакции и rollback
async function billUser(userId, amount) {
await db.transaction(async (tx) => {
// 1. Создаём pending запись
const run = await tx.insertBillingRun(userId, amount, 'pending');
// 2. Делаем списание через Stripe / банк API
let chargeResult;
try {
chargeResult = await stripe.charges.create({
amount: amount * 100,
currency: 'rub',
customer: user.stripeCustomerId,
idempotency_key: `billing_${run.id}`, // ВАЖНО!
});
} catch (e) {
await tx.updateBillingRun(run.id, 'failed', e.message);
throw e;
}
// 3. Записываем успех
await tx.updateBillingRun(run.id, 'success', chargeResult.id);
// 4. Отправляем чек клиенту
await sendReceipt(userId, run, chargeResult);
});
}3. Алерты и мониторинг
#!/bin/bash
# /opt/scripts/billing.sh
set -e
trap 'on_error $LINENO' ERR
on_error() {
curl -X POST "$SLACK_WEBHOOK" -d \
"{\"text\":\"💳 Billing failed at line $1\"}"
exit 1
}
# Запуск биллинга
node /app/billing.js
# Проверка результатов
PROCESSED=$(psql -t -c "SELECT count(*) FROM billing_runs \
WHERE created_at > NOW() - INTERVAL '5 minutes' \
AND status = 'success'")
if [ "$PROCESSED" -lt 100 ]; then
curl -X POST "$SLACK_WEBHOOK" -d \
"{\"text\":\"⚠️ Только $PROCESSED биллинг-проверок. Ожидалось 100+\"}"
fi
echo "Billing OK: $PROCESSED users processed"Любая финансовая операция должна быть идемпотентной. Если запрос повторяется (network retry, ручной перезапуск, cron rerun), результат должен быть тот же — без двойного списания. Используйте idempotency keys.— Stripe Engineering — Idempotency in distributed systems
Российская специфика
Производственный календарь
Перед автоматическим запуском проверяйте — рабочий ли это день. Используйте API isdayoff.ru:
#!/bin/bash # Проверка: рабочий ли сегодня день в РФ TODAY=$(date +%Y-%m-%d) IS_HOLIDAY=$(curl -s "https://isdayoff.ru/$TODAY?cc=ru") if [ "$IS_HOLIDAY" = "1" ]; then echo "Today is a holiday, skipping payroll" exit 0 fi # Иначе — запускаем зарплатный процесс node /app/payroll.js
Перенос на предшествующий рабочий день
По ТК РФ если день выплаты зарплаты — выходной/праздник, выплата делается ЗА предыдущий рабочий день. Например, 1 января (праздник) → выплата 30 декабря.
function adjustPayrollDate(targetDate) {
let date = new Date(targetDate);
while (await isWeekendOrHoliday(date)) {
date.setDate(date.getDate() - 1);
}
return date;
}
// Пример: 5 января 2026 (понедельник, но 1-8 — каникулы)
// Перенос на 30 декабря 2025Налоговый календарь 2026
- 28 января — НДС за 4 квартал 2025
- 15 февраля — страховые взносы за январь
- 28 февраля — НДС за январь
- 28 марта — налог на прибыль за 2025
- 28 апреля — НДС за 1 квартал 2026
- 15 числа каждого месяца — страховые взносы за прошлый месяц
Audit log
Каждая финансовая операция должна логироваться: кто, что, когда, сумма, результат. Это требование 152-ФЗ для ПДн и 115-ФЗ для финопераций.
CREATE TABLE billing_audit_log ( id BIGSERIAL PRIMARY KEY, occurred_at TIMESTAMPTZ DEFAULT NOW(), actor_id BIGINT, -- кто (system / user_id) action TEXT NOT NULL, -- charge / refund / void user_id BIGINT, -- кому amount NUMERIC(10,2), currency TEXT, payment_method TEXT, -- card / bank / yandex external_id TEXT, -- charge ID в Stripe status TEXT, meta JSONB -- дополнительные данные ); -- Хранить минимум 5 лет (рекомендация финрегуляторов) -- Архивировать старые записи в холодное хранилище
- Stripe Documentation — Idempotency. Stripe. stripe.com/docs/api/idempotent_requests. 2024.
- Производственный календарь РФ — КонсультантПлюс. КонсультантПлюс. consultant.ru/law/ref/calendar. 2026.
- isdayoff.ru API — выходные дни РФ. isdayoff.ru. isdayoff.ru/desc/api.html. 2024.
- ТК РФ — Статья 136. Порядок выплаты заработной платы. Трудовой кодекс РФ. consultant.ru/document/cons_doc_LAW_34683. 2024.
