eef90a152e
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m0s
Split the single Robokassa direct rail into one merchant shop per channel (web / android; ios later), chosen by the trusted X-Platform subtype, while every shop still credits the one `direct` wallet — merchant-account separation for accounting and receipts, not a new wallet. - config: a channel-keyed shops registry (web seeds from the legacy vars or _WEB_*, android from _ANDROID_*); an empty shop leaves the rail dormant; per-shop validation. - intake: the order picks its shop by subtype (unknown falls back to web); the per-shop Result callback is verified by that shop's own Password2 at /pay/robokassa/result/<channel> (the gateway extracts the channel; Caddy's /pay/* glob already forwards it — no Caddyfile change). - persistence: an additive `shop` column on the order (migration 00015), recorded from the payment context, surfaced per entry in the admin report. - standalone apps sign in by email only, so a direct purchase keeps its email anchor. - docs: PAYMENTS (+ru) topology, deploy env vars + compose mapping, the decisions log (D41 revised for the ИП / 54-ФЗ move; D42-D44), the plan. Contour-safe: dormant until shops are configured; the migration is additive (no wipe); no client wire change. Fiscalization (Receipt/Email) and the gateway `direct/android` subtype follow when the ИП / RuStore are live.
66 lines
2.1 KiB
Go
66 lines
2.1 KiB
Go
package payments
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Statement is an account's full financial picture for the admin console: chip balances per funding
|
|
// segment, benefits per origin, the recorded refund risk, and the append-only ledger history
|
|
// (newest first). Read straight from the materialized tables + the ledger, uncached — an admin-only,
|
|
// rare view, not a hot path.
|
|
type Statement struct {
|
|
Segments []SegmentChips
|
|
Benefits []OriginBenefit
|
|
Risk RiskInfo
|
|
Ledger []LedgerEntry
|
|
}
|
|
|
|
// SegmentChips is one funding segment's chip balance.
|
|
type SegmentChips struct {
|
|
Source Source
|
|
Chips int
|
|
}
|
|
|
|
// OriginBenefit is one origin's benefit state: the hint wallet, the ad-free term (zero AdsPaidUntil
|
|
// when none) and the lifetime ad-free flag.
|
|
type OriginBenefit struct {
|
|
Origin Source
|
|
Hints int
|
|
AdsPaidUntil time.Time
|
|
AdsForever bool
|
|
}
|
|
|
|
// RiskInfo is the account's recorded refund risk: whether it is abuse-flagged and the unrecoverable
|
|
// chip loss accumulated by floor-0 refunds. Present is false when the account has no risk row.
|
|
type RiskInfo struct {
|
|
Present bool
|
|
Abuse bool
|
|
LossChips int
|
|
}
|
|
|
|
// LedgerEntry is one append-only ledger row projected for the report. The string ids are empty when
|
|
// the column is NULL; Shop is the direct-rail merchant channel the referenced order used (empty for
|
|
// other rails or order-less rows); Snapshot is the raw purchase/refund JSON (empty when none).
|
|
type LedgerEntry struct {
|
|
Kind string
|
|
Source string
|
|
Origin string
|
|
ChipsDelta int
|
|
ProductID string
|
|
OrderID string
|
|
Provider string
|
|
ProviderPaymentID string
|
|
Shop string
|
|
Snapshot string
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
// AccountStatement assembles the account's financial picture (segments, benefits, risk, full ledger
|
|
// history) for the admin console, straight from the materialized tables and the ledger (uncached).
|
|
func (s *Service) AccountStatement(ctx context.Context, accountID uuid.UUID) (Statement, error) {
|
|
return s.store.accountStatement(ctx, accountID)
|
|
}
|