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:
@@ -282,6 +282,26 @@ The native Android app is a Capacitor 8 wrapper of the `ui` SPA, scaffolded unde
|
||||
security list shared by both hops either lives in `pkg/` or is enforced backend-side. The YooKassa
|
||||
sender allowlist is enforced in the backend, with the gateway forwarding the real peer IP as
|
||||
`X-Forwarded-For` — do not duplicate the CIDR list into the gateway.
|
||||
- **«Мой налог» (НПД) has NO test environment.** Every call lands on the owner's real tax
|
||||
account, so the rail is verified against an `httptest` stub plus one real purchase the owner
|
||||
makes himself. Its API (`lknpd.nalog.ru`) is reverse-engineered — no contract, no sandbox.
|
||||
- **`POST /api/v1/income` is NOT idempotent.** A timeout does not mean nothing was filed. The
|
||||
only recovery is searching the taxpayer's income list by the exact `services[].name`, so that
|
||||
name is frozen in the DB *before* the call and carries a marker from the **tail** of the order
|
||||
id. Do not "simplify" it to the head: our order ids are UUIDv7, whose leading hex digits are a
|
||||
millisecond clock — the first eight are shared by every order in the same ~65 seconds, which
|
||||
would make two purchases indistinguishable to the probe. An unresolved outcome halts the
|
||||
whole queue on purpose; a blind retry is the one thing that must never happen.
|
||||
- **The receipt's time zone decides the tax month.** The ledger is UTC; `BACKEND_MYNALOG_TZ`
|
||||
(default `Europe/Moscow`) is what a payment is filed under, so a 23:30 UTC payment on the last
|
||||
of the month belongs to the *next* one.
|
||||
- **A bank card is an электронное средство платежа**, so ст. 14 ч. 3 ФЗ-422 wants the receipt
|
||||
at settlement; the до-9-го-числа delay applies only to settlements *without* an ЭСП. Do not
|
||||
"relax" the cadence on the strength of the 9th — the automatic mode satisfies both readings,
|
||||
which is why the ambiguity never had to be settled.
|
||||
- **A bank does not auto-register income for an ИП на НПД** unless that service is explicitly
|
||||
enabled with the bank. Money landing on the account is not a чек. VK payouts are therefore a
|
||||
separate, still-manual stream (and VK is a legal entity: 6%, its INN in the receipt).
|
||||
- **Robokassa is retired but wired.** The direct rail resolves YooKassa → else Robokassa, and no
|
||||
deployment sets the Robokassa credentials. Reviving it is a credentials change, not a code change;
|
||||
`backend/internal/robokassa/README.md` holds the retired vars, cabinet URLs and the known gap
|
||||
|
||||
Reference in New Issue
Block a user