feat(payments): report income to «Мой налог»
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Failing after 24s
CI / ui (pull_request) Successful in 1m17s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Failing after 0s
CI / deploy (pull_request) Has been skipped
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Failing after 24s
CI / ui (pull_request) Successful in 1m17s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Failing after 0s
CI / deploy (pull_request) Has been skipped
The direct rail runs on НПД, where the provider neither files with the tax service nor issues a receipt — so nobody was doing it. This registers each rouble purchase, annuls its receipt on a refund, and hands the buyer the receipt by email. Two properties of the (unofficial) lknpd API shape the design. Registering an income takes no idempotency key, so an error does not mean nothing happened: the service name is frozen before the call and carries a marker from the tail of the order id, and after a failure the taxpayer's income list is searched for that exact name. Found means filed; not found halts the queue for a human, because declaring an income twice is as wrong as not declaring it. And faults are classified rather than logged: a token is renewed silently, a throttle backs off, an outage retries, but three unfixable rejections take the rail out of service — a changed format must not become thousands of requests overnight. The console button and the worker share one RunBatch. Automatic mode is armed from the console, not from configuration, so the operator can watch a run go through by hand first. A daily watchdog runs whether or not it is armed, since the case it exists for is the export being off. An idle queue issues no call at all — not even an authentication. No payment path changed: the purchase letter rides the existing payment-event outbox on its own cursor, the receipt and annulment letters ride the export row. Decisions D53-D60.
This commit is contained in:
@@ -1376,6 +1376,7 @@ link — misses the event; while an add-email confirmation is pending the client
|
||||
| Manual account block (suspension) | backend: a per-request gate refuses a blocked account on every `/api/v1/user/*` route except the block-status probe with **403 `account_blocked`**; the operator blocks/unblocks from the admin console (§11) |
|
||||
| User feedback gate | backend rejects a guest or a `feedback_banned` account from submitting; the **gateway** also rejects a guest's `feedback.submit` (the `Op.NonGuest` flag + `is_guest` from session resolve) with **`guest_forbidden`** before any backend call; attachments are served `nosniff` with a download disposition for non-images (§15) |
|
||||
| Admin authentication | a single Basic-Auth gate on `/_gm/*`, forwarded **verbatim** to the backend's server-rendered admin console (and, in the deployed contour, routing `/_gm/grafana/*` to Grafana). In the deploy the **caddy** owns this gate (§13); a local non-caddy run uses the gateway's own `GATEWAY_ADMIN_*` proxy, which the per-IP admin limiter class guards ahead of its Basic-Auth — the caddy-fronted path has no limiter (stock caddy), an accepted gap. The backend trusts the proxy (no admin principal) and guards its state-changing POSTs with a **same-origin** check — the console's CSRF defence. No operator identity is tracked |
|
||||
| Tax-cabinet credential at rest | the backend seals the «Мой налог» **refresh token** with AES-GCM under `BACKEND_MYNALOG_KEY` before storing it (`payments.mynalog_session`), so a database dump or a backup carries no usable credential. The cabinet login and password are typed into the admin console, used once and **never persisted**; an unset key leaves the whole export rail unregistered. The console's error page prints provider errors verbatim, so the client never puts credentials in an error string |
|
||||
| backend ↔ gateway ↔ validator trust | the network (only gateway may reach backend; the validator and the gateway's admin bot-link relay serve unauthenticated gRPC on the trusted internal segment) |
|
||||
| remote bot ↔ gateway (bot-link) | **mutual TLS**: a private CA signs the gateway server cert and the bot client cert, and each verifies the other. The bot dials out (no inbound port, no static IP), so the channel is guarded solely by mTLS — the bot client key is as sensitive as the token (§13) |
|
||||
|
||||
@@ -1543,6 +1544,13 @@ The gateway exposes the bot-link on a dedicated mTLS gRPC listener
|
||||
(`GATEWAY_BOTLINK_ADDR`, internal-only in the test contour, published in prod) plus a
|
||||
plaintext relay (`GATEWAY_BOTLINK_RELAY_ADDR`) the backend admin console calls.
|
||||
|
||||
The **backend** egresses directly (no sidecar) to the payment provider and, for the
|
||||
professional-income tax export (`docs/PAYMENTS.md` §12), to `lknpd.nalog.ru` — a Russian
|
||||
government host, so the prod main host's own Russian egress is what that path depends on. The
|
||||
export is dormant unless `BACKEND_MYNALOG_KEY` is set, and even when armed it makes no outbound
|
||||
call at all while its queue is empty: the queue is a local query, and nothing authenticates until
|
||||
there is something to file.
|
||||
|
||||
The full contour (`deploy/docker-compose.yml`) runs one `gateway`, one `backend`,
|
||||
one Postgres, the static `landing`, the Telegram `validator` and `bot` (+ the bot's VPN
|
||||
sidecar — the `bot`+`vpn` pair is gated to a `telegram-local` compose profile so the prod
|
||||
|
||||
+60
-5
@@ -454,11 +454,7 @@ Receipts are automatic **through the provider**, and differ by rail:
|
||||
return is foreseeable: НПД carries an annual income ceiling, and losing the regime puts 54-ФЗ back
|
||||
in force.
|
||||
|
||||
Everything an automated «Мой налог» submission needs is already recorded — the order id, the
|
||||
provider payment id, the amount and currency, the credit time, the sold-pack snapshot, and refunds
|
||||
as their own ledger rows, all exportable as CSV. The one thing it will additionally need is a
|
||||
per-operation "already reported" marker for its own idempotency; that belongs to the submission
|
||||
work, not here.
|
||||
Reporting each rouble operation to «Мой налог» is automated — see below.
|
||||
- **VK** — VK processes Votes through the tax authority itself; nothing to do.
|
||||
- **TG Stars** — no tax side (for a RU self-employed, Stars are not legally withdrawable = not
|
||||
НПД income; accepted, no receipt issued).
|
||||
@@ -466,6 +462,65 @@ Receipts are automatic **through the provider**, and differ by rail:
|
||||
**ОРД** (ad marking) for VK ads is handled on VK's side. (Not legal advice — the owner
|
||||
confirms the exact НПД scheme with a tax advisor.)
|
||||
|
||||
### Reporting to «Мой налог» (D53-D60)
|
||||
|
||||
Rouble income taken through the direct rail is registered with the tax service by us, because
|
||||
nobody else does it: the provider neither files nor issues a receipt under this regime. The rail
|
||||
covers `kind='fund'`, `provider='yookassa'`, currency `RUB` — the store rails are out of scope,
|
||||
since the ledger records their price in the store's own currency and holds no rouble amount at all.
|
||||
|
||||
**Where it runs.** `backend/internal/mynalog` is the API client (no database), `backend/internal/
|
||||
mynalogsync` the orchestration, `payments.mynalog_receipt` the per-income progress, and
|
||||
`/_gm/mynalog` the operator surface. The console button and the background worker call the same
|
||||
`RunBatch`; there is deliberately no second implementation of the part that decides what a failed
|
||||
registration means. The whole rail is dormant without `BACKEND_MYNALOG_KEY`.
|
||||
|
||||
**The API is unofficial** — `lknpd.nalog.ru`, no published contract, no sandbox. Two consequences
|
||||
run through the design:
|
||||
|
||||
- **Registering an income is not idempotent.** There is no idempotency key, so an error does not
|
||||
mean nothing happened. The service name is therefore frozen in the database *before* the call and
|
||||
carries a marker taken from the **tail** of the order id (a UUIDv7's leading digits are a
|
||||
millisecond clock and would be shared by every order in the same minute). After a failure the
|
||||
income list is searched for that exact name: found means filed, not found means **unresolved** —
|
||||
which halts the queue until a human decides, because the alternatives are declaring an income
|
||||
twice or not at all. One run at a time, on a Postgres advisory lock, for the same reason.
|
||||
- **Faults are classified, not just logged.** A 401 is renewed silently; a 429 backs off; a 5xx or
|
||||
timeout retries later and only alerts after a day; any other 4xx is unfixable by repetition, and
|
||||
three in a row take the rail out of service — a changed format must not become thousands of
|
||||
requests overnight. Exceeding the annual НПД ceiling is separated out, because it means the regime
|
||||
no longer applies.
|
||||
|
||||
**Timing.** Every moment is sent in the taxpayer's zone (`BACKEND_MYNALOG_TZ`, default
|
||||
`Europe/Moscow`), since the offset decides which tax month a near-midnight payment falls into; the
|
||||
receipt carries the date the money arrived, not the date it was filed. The automatic mode ticks
|
||||
every 15 minutes and does nothing — including not authenticating — when the queue is empty. A
|
||||
separate daily watchdog runs **whether or not** the automatic mode does, and escalates unfiled
|
||||
income from a closed month on the 1st, the 5th and the 9th. (ст. 14 ч. 3 ФЗ-422 allows until the
|
||||
9th only for settlements *not* involving an electronic means of payment; a card payment is one, so
|
||||
the receipt is strictly due at settlement. The automatic mode satisfies both readings.)
|
||||
|
||||
**Manual fallback.** `/_gm/mynalog.csv` lists exactly what would be sent — time, service name,
|
||||
amount — plus the ledger id, so a receipt filed by hand can have its number entered back on the
|
||||
page. That number is what keeps a hand-filed income annullable on a refund, which is why it is
|
||||
asked for rather than a plain "done" tick.
|
||||
|
||||
**Refunds.** The tax service has no refund concept: annulling the receipt *is* the refund. A refund
|
||||
that lands before the income was ever filed is ruled out locally as `not_required` — nothing filed,
|
||||
nothing annulled, no call made. A partial refund is out of scope on both sides (§ Refunds).
|
||||
|
||||
**The buyer hears about it three times**, in Russian, driven by durable queues rather than by the
|
||||
payment path: the purchase confirmation (which says plainly that it is *not* the fiscal receipt and
|
||||
that one follows), the receipt itself with its printable link, and — after a refund — a notice that
|
||||
the receipt has been annulled. The first rides the existing `payments.payment_events` outbox on its
|
||||
own `mailed_at` cursor; the other two ride columns on the export row. The address is the D36
|
||||
confirmed-email anchor a direct purchase already requires.
|
||||
|
||||
**Credentials.** The cabinet login and password are typed into the console, used once and never
|
||||
stored: only the refresh token they yield is kept, sealed with AES-GCM under
|
||||
`BACKEND_MYNALOG_KEY` (our own random 32 bytes, not a tax-service credential) so it does not travel
|
||||
in a database dump. A lost key is a nuisance, not a loss — the operator signs in again.
|
||||
|
||||
## 13. Distribution (native Android)
|
||||
|
||||
- **RuStore** — an external payment gate is allowed (0%); native = clean `direct` context.
|
||||
|
||||
@@ -345,9 +345,68 @@ web+PWA / native Android+iOS через Capacitor). Владелец (самоз
|
||||
(тег 1212), способ расчёта `full_payment` (тег 1214), доставка на email-якорь D36,
|
||||
`tax_system_code` не шлём. Возврат к чекам предсказуем: у НПД годовой потолок дохода 2,4 млн ₽, и
|
||||
его превышение возвращает 54-ФЗ — тогда это правка переменной, а не кода.
|
||||
Связка с «Мой налог» — **отдельная задача**. Всё нужное для неё уже хранится (идентификатор заказа
|
||||
и платежа провайдера, сумма, валюта, время зачисления, снимок пакета, возвраты); не хватает только
|
||||
отметки «операция уже отправлена» для идемпотентности выгрузки — её заводит та задача.
|
||||
Связка с «Мой налог» — **отдельная задача**, закрыта решениями D53-D60 ниже.
|
||||
|
||||
## Выгрузка в «Мой налог» (интервью с владельцем, 2026-07-28)
|
||||
|
||||
- **D53. Охват выгрузки — только рубли ЮKassa.** Выгружаем `kind='fund'`, `provider='yookassa'`,
|
||||
валюта `RUB`. VK-Голоса и Telegram Stars вне охвата **не по решению, а по факту**: журнал операций
|
||||
хранит их цену в `VOTE`/`XTR` и рублёвой суммы не знает вообще — она приходит выплатой платформы за
|
||||
вычетом её комиссии. Плюс VK — юрлицо, то есть ставка 6% и ИНН плательщика в чеке, а это другой тип
|
||||
чека. Stars владелец не выводит, поэтому спор о моменте признания дохода по ним не открываем.
|
||||
Доход от rewarded-рекламы (`vk_ads`) денег в журнале не имеет и тоже вне охвата.
|
||||
- **D54. Пароль вводится, хранится только refresh-токен, зашифрованный.** Логин (ИНН) и пароль
|
||||
вводятся в форме `/_gm/mynalog`, используются один раз и **никуда не пишутся**; на диск ложится
|
||||
только выданный refresh-токен, запечатанный AES-GCM на `BACKEND_MYNALOG_KEY`. Ключ — наши
|
||||
собственные случайные 32 байта, к учётным данным налоговой отношения не имеющие; он нужен потому,
|
||||
что refresh-токен — долгоживущий доступ к личному кабинету ФНС, и в дампе базы (а бэкапы уезжают
|
||||
в S3) ему открытым текстом не место. Потеря ключа не катастрофа: токен перестаёт расшифровываться,
|
||||
и консоль просит войти заново. Вариант «логин/пароль в секретах Gitea» отклонён: это пароль от
|
||||
налоговой, а не от магазина, и радиус поражения несопоставим.
|
||||
- **D55. Автоматический режим есть, но включается галочкой в консоли, а не конфигом.** Кнопка и
|
||||
воркер зовут один и тот же `RunBatch` — второй реализации нет. Автомат по умолчанию выключен:
|
||||
владелец сначала прогоняет рельс руками и убеждается, что чек выглядит правильно, и только потом
|
||||
включает. Тик — 15 минут; при пустой очереди прогон **не авторизуется** и наружу не ходит.
|
||||
Ограничение прогона — **бюджет времени** (13 минут у воркера, 100 секунд у кнопки), а не счётчик
|
||||
чеков: счётчик ничего не защищает, защищают пауза 2 секунды между вызовами и предохранители.
|
||||
- **D56. Неустановленный исход останавливает очередь.** Регистрация дохода не идемпотентна: ключа
|
||||
идемпотентности у API нет, поэтому ошибка не означает, что чек не создан. Название услуги
|
||||
замораживается в базе **до** запроса и несёт маркер из **хвоста** идентификатора заказа (старшие
|
||||
разряды UUIDv7 — миллисекундные часы, одинаковые у всех заказов одной минуты, и такой маркер сделал
|
||||
бы две покупки неразличимыми). После сбоя ищем чек по этому точному названию: нашли — записываем,
|
||||
не нашли — статус «неизвестно», и **новые чеки не отправляются**, пока человек не разберёт.
|
||||
Аннулирования при этом продолжают идти: они только убирают доход. Один прогон за раз — advisory-лок
|
||||
Postgres; строка, застрявшая в `sending` после падения процесса, под локом заведомо осиротевшая и
|
||||
переводится в «неизвестно», а не переотправляется.
|
||||
- **D57. Сбои классифицируются на пять классов.** `401` — молча переавторизуемся (запрос отклонён до
|
||||
обработки, поэтому единственный сбой, который можно повторить без зонда); `429` — отступаем; `5xx`
|
||||
и таймаут — мягко, письмо только если сервис не поднялся за сутки; прочие `4xx` — жёстко, и три
|
||||
подряд снимают рельс с эксплуатации с письмом, потому что смена формата API отклоняет всё подряд и
|
||||
без этого превратилась бы в тысячи запросов за ночь; превышение годового лимита НПД выделено
|
||||
отдельным классом (оно подчиняет предыдущий, чтобы проверка «это неисправимо?» его не пропустила) —
|
||||
это уже не техническая ошибка, а потеря режима.
|
||||
- **D58. Сумма — полная, дата — фактическая, пояс — налогоплательщика.** В чек идёт то, что заплатил
|
||||
покупатель: на НПД расходы не вычитаются, комиссия ЮKassa — расход владельца. Время операции — это
|
||||
момент платежа, а не момент выгрузки, и отправляется в поясе `BACKEND_MYNALOG_TZ` (по умолчанию
|
||||
`Europe/Moscow`), потому что смещение решает, в какой налоговый месяц попадёт околополуночный
|
||||
платёж. Название услуги: `Внутриигровая валюта: "Фишка", NN шт. (ID: xxxxxxxx)`, не длиннее 128
|
||||
символов — при усечении жертвуем описанием, маркер неприкосновенен.
|
||||
- **D59. Надзиратель за фискальным периодом работает всегда.** Отдельный суточный цикл, независимый
|
||||
от автоматического режима — именно потому, что случай, ради которого он существует, это выключенный
|
||||
или сломавшийся автомат. Эскалация по незакрытому прошлому месяцу: 1-е число — предупреждение, с
|
||||
5-го — тревога ежедневно, с 9-го — «срок вышел». Правовая рамка: ст. 14 ч. 3 ФЗ-422 даёт отсрочку
|
||||
до 9-го числа только для расчётов, **не связанных** с электронными средствами платежа; оплата
|
||||
картой — ЭСП, поэтому строгое прочтение требует чек в момент расчёта. Автоматический режим
|
||||
удовлетворяет обеим трактовкам, поэтому спор решать не потребовалось.
|
||||
- **D60. Покупателю уходят три письма, и ни одно не трогает платёжный путь.** Подтверждение покупки
|
||||
(сразу, с прямой оговоркой, что это **не** чек и что чек придёт отдельно), фискальный чек ссылкой
|
||||
после регистрации и уведомление об аннулировании после возврата — иначе у покупателя осталась бы
|
||||
ссылка на мёртвый чек. Только по-русски, локализация не нужна. Первое письмо едет по уже
|
||||
существующему outbox'у `payments.payment_events` на собственном курсоре `mailed_at`, два других — по
|
||||
колонкам строки выгрузки; поэтому в коде зачисления и возврата не потребовалось менять ни строки, а
|
||||
сбой релея откладывает письмо, а не теряет его. Адрес — подтверждённый email-якорь D36, который
|
||||
прямая покупка требует и так. Возврат, случившийся раньше выгрузки, закрывается как `not_required`:
|
||||
ни чека, ни аннулирования, ни обращения к сервису.
|
||||
|
||||
## Заметки к оформлению документов
|
||||
|
||||
@@ -363,9 +422,10 @@ web+PWA / native Android+iOS через Capacitor). Владелец (самоз
|
||||
ведёт к пропускам. Текст — только для фиксации решённого и пояснений. Усилить
|
||||
feedback-память `prefer-interview-mode` после plan mode.
|
||||
|
||||
## Все развилки закрыты (D1-D52)
|
||||
## Все развилки закрыты (D1-D60)
|
||||
|
||||
Интервью завершено. Дальше — оформление документов и реализация по релизам.
|
||||
D53-D60 добавлены отдельным интервью 2026-07-28 по выгрузке в «Мой налог».
|
||||
|
||||
**Дополнение 2026-07-14 (интервью с владельцем).** D41 ревизована (владелец переходит на
|
||||
ИП: 54-ФЗ через облачную кассу Robokassa вместо авточека НПД); добавлены D42-D44 — сплит
|
||||
|
||||
+61
-4
@@ -446,10 +446,7 @@ Stars, общая сумма по ним не значила бы ничего)
|
||||
расчёта `full_payment` (тег 1214), доставка на email-якорь D36. Переключатель нужен потому, что
|
||||
возврат к чекам предсказуем: у НПД есть годовой потолок дохода, и потеря режима возвращает 54-ФЗ.
|
||||
|
||||
Всё, что потребуется автоматической отправке в «Мой налог», уже записано — идентификатор заказа,
|
||||
идентификатор платежа провайдера, сумма и валюта, время зачисления, снимок проданного пакета и
|
||||
возвраты отдельными строками журнала, всё выгружается в CSV. Не хватать будет только отметки «эта
|
||||
операция уже отправлена» для идемпотентности самой выгрузки; ей место в той задаче, а не здесь.
|
||||
Отправка каждой рублёвой операции в «Мой налог» автоматизирована — см. ниже.
|
||||
- **VK** — VK сам процессит Голоса через налоговую; делать нечего.
|
||||
- **TG Stars** — налоговой стороны нет (для РФ-самозанятого Stars легально невыводимы = не
|
||||
доход НПД; принимаем, чек не формируем).
|
||||
@@ -457,6 +454,66 @@ Stars, общая сумма по ним не значила бы ничего)
|
||||
**ОРД** (маркировка рекламы) по VK-рекламе — на стороне VK. (Не юридическая консультация —
|
||||
владелец сверяет точную схему НПД с налоговым консультантом.)
|
||||
|
||||
### Выгрузка в «Мой налог» (D53-D60)
|
||||
|
||||
Рублёвый доход прямого рельса регистрируем в налоговой мы, потому что больше некому: провайдер при
|
||||
этом режиме ни отчёта не подаёт, ни чека не формирует. Охват — `kind='fund'`,
|
||||
`provider='yookassa'`, валюта `RUB`; магазинные рельсы вне охвата, так как журнал операций хранит их
|
||||
цену в валюте магазина и рублёвой суммы там нет вообще.
|
||||
|
||||
**Где живёт.** `backend/internal/mynalog` — клиент API (без базы), `backend/internal/mynalogsync` —
|
||||
оркестрация, `payments.mynalog_receipt` — состояние по каждому приходу, `/_gm/mynalog` — экран
|
||||
оператора. Кнопка в консоли и фоновый воркер зовут один и тот же `RunBatch`; второй реализации той
|
||||
части, которая решает, что значит неудавшаяся регистрация, сознательно не существует. Без
|
||||
`BACKEND_MYNALOG_KEY` рельс спит целиком.
|
||||
|
||||
**API неофициальный** — `lknpd.nalog.ru`, опубликованного контракта нет, тестовой среды нет. Отсюда
|
||||
два следствия, пронизывающих всю конструкцию:
|
||||
|
||||
- **Регистрация дохода не идемпотентна.** Ключа идемпотентности нет, поэтому ошибка не означает, что
|
||||
ничего не произошло. Название услуги замораживается в базе **до** запроса и несёт маркер из
|
||||
**хвоста** идентификатора заказа (старшие разряды UUIDv7 — миллисекундные часы, они одинаковы у
|
||||
всех заказов одной минуты). После сбоя список доходов ищется по этому точному названию: нашли —
|
||||
значит зарегистрировано, не нашли — **исход неизвестен**, и очередь встаёт до решения человека,
|
||||
потому что альтернативы — задвоить доход или потерять его. По той же причине один прогон за раз,
|
||||
на advisory-локе Postgres.
|
||||
- **Сбои классифицируются, а не просто логируются.** 401 обновляем молча; 429 — отступаем; 5xx и
|
||||
таймаут — повторим позже, письмо только через сутки; любой другой 4xx повтором не лечится, и три
|
||||
подряд снимают рельс с эксплуатации — смена формата не должна превращаться в тысячи запросов за
|
||||
ночь. Превышение годового лимита НПД выделено отдельно: это уже не техническая ошибка, а потеря
|
||||
режима.
|
||||
|
||||
**Время.** Каждый момент отправляется в часовом поясе налогоплательщика (`BACKEND_MYNALOG_TZ`, по
|
||||
умолчанию `Europe/Moscow`): смещение решает, в какой налоговый месяц попадёт околополуночный платёж.
|
||||
В чеке стоит дата поступления денег, а не дата выгрузки. Автоматический режим просыпается раз в 15
|
||||
минут и при пустой очереди не делает ничего — в том числе не авторизуется. Отдельный суточный
|
||||
надзиратель работает **независимо** от автоматического режима и эскалирует незакрытый прошлый месяц
|
||||
1-го, 5-го и 9-го числа. (Ст. 14 ч. 3 ФЗ-422 даёт отсрочку до 9-го только для расчётов, **не**
|
||||
связанных с электронными средствами платежа; оплата картой — ЭСП, поэтому чек строго в момент
|
||||
расчёта. Автоматический режим удовлетворяет обеим трактовкам.)
|
||||
|
||||
**Ручной фолбэк.** `/_gm/mynalog.csv` отдаёт ровно то, что было бы отправлено — время, название
|
||||
услуги, сумму — плюс идентификатор строки журнала, чтобы номер выданного вручную чека можно было
|
||||
внести обратно на странице. Именно этот номер оставляет ручной приход аннулируемым при возврате,
|
||||
поэтому спрашивается он, а не галочка «сделано».
|
||||
|
||||
**Возвраты.** У налоговой нет понятия возврата: аннулирование чека **и есть** возврат. Возврат,
|
||||
пришедший раньше, чем приход был выгружен, закрывается локально как `not_required` — ничего не
|
||||
регистрируем, ничего не аннулируем, наружу не ходим. Частичный возврат вне охвата с обеих сторон
|
||||
(см. раздел про возвраты).
|
||||
|
||||
**Покупатель узнаёт трижды**, по-русски, из долговечных очередей, а не из платёжного пути:
|
||||
подтверждение покупки (в котором прямо сказано, что это **не** фискальный чек и что чек придёт
|
||||
отдельно), сам чек со ссылкой на печатную форму и — после возврата — уведомление об аннулировании.
|
||||
Первое едет по существующему outbox'у `payments.payment_events` на собственном курсоре `mailed_at`,
|
||||
два других — по колонкам строки выгрузки. Адрес — тот самый подтверждённый email-якорь D36, который
|
||||
прямая покупка требует и так.
|
||||
|
||||
**Учётные данные.** Логин и пароль от кабинета вводятся в консоли, используются один раз и нигде не
|
||||
хранятся: на диск кладётся только полученный refresh-токен, запечатанный AES-GCM на
|
||||
`BACKEND_MYNALOG_KEY` (наши собственные случайные 32 байта, а не секрет налоговой) — чтобы он не
|
||||
уезжал в дампе базы. Потеря ключа — неудобство, а не потеря: оператор входит заново.
|
||||
|
||||
## 13. Дистрибуция (native Android)
|
||||
|
||||
- **RuStore** — внешний платёжный гейт разрешён (0%); native = чистый контекст `direct`.
|
||||
|
||||
Reference in New Issue
Block a user