Wire the Robokassa direct rail into the backend transport. POST
/api/v1/user/wallet/order (walletGate + a D36 confirmed-email gate for the
direct rail) opens a pending order and returns the signed Robokassa payment
URL. The internal, gateway-only /payments/robokassa/result endpoint verifies
the Result signature, credits the matched order exactly once via Fund (honoured
even if expired), records a succeeded payment event, and answers Robokassa's
"OK<InvId>". Add the Robokassa env config, an account HasConfirmedEmail check
(D36), the payment_events writer, and a periodic pending-order reaper. The
routes register only when a Robokassa merchant login is configured.
Add the payment-intake write path (provider-agnostic) and the Robokassa
direct-rail glue, both unit-tested; transport, wire and UI follow.
- payments: extend the ledger insert to thread order_id/provider/
provider_payment_id (spend/grant pass nil); add the order store
(create/read/expire + a pack-price loader) and the fund credit — a
fund ledger row + a guarded balance upsert + mark-paid in one tx,
idempotent on the (provider, provider_payment_id) unique index, cache
invalidated after commit. A valid callback is honoured even on an
expired order. Service CreateOrder/Fund/ExpireOrders; Money.Major for
the provider amount field.
- robokassa: build the signed hosted-payment URL (SHA-256, order id via
Shp_order, InvId unused) and verify the Result callback signature
(Password2), extracting the order and amount. Receipt/fiscalisation is
configured shop-side, so no Receipt parameter is sent.