feat(admin): manual full-order refund + ledger CSV export
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 22s
CI / ui (pull_request) Has been skipped
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m53s

Closes the admin / reports / catalog work. Each fund row on the /_gm finance
panel gains a Refund action (payments.RefundOrderFull): a full-order refund the
operator records after refunding on the rail — a refund ledger row + a floor-0
chip revoke (never negative, D27), idempotent (a second refund reports
already-refunded). A ledger CSV export (/_gm/ledger.csv, payments.LedgerExport)
streams the whole append-only ledger for tax + reconciliation.

Tests: refund an order in full (chips revoked, a refund row), an idempotent
second refund, the CSV export shape; CSRF-guarded.
This commit is contained in:
Ilia Denisov
2026-07-10 06:46:18 +02:00
parent ec5c6afa23
commit 1bf612a087
8 changed files with 242 additions and 13 deletions
@@ -60,6 +60,7 @@ func (s *Server) registerConsole(router *gin.Engine) {
gm.POST("/users/:id/remove-email", s.consoleRemoveEmail)
gm.POST("/users/:id/grant", s.consoleGrant)
gm.POST("/users/:id/grant-product", s.consoleGrantProduct)
gm.POST("/users/:id/refund", s.consoleRefund)
gm.POST("/users/:id/delete", s.consoleDeleteUser)
gm.GET("/reasons", s.consoleReasons)
gm.POST("/reasons", s.consoleCreateReason)
@@ -111,6 +112,7 @@ func (s *Server) registerConsole(router *gin.Engine) {
gm.POST("/catalog/:id", s.consoleUpdateProduct)
gm.POST("/catalog/:id/archive", s.consoleArchiveProduct)
gm.POST("/catalog/:id/delete", s.consoleDeleteProductAction)
gm.GET("/ledger.csv", s.consoleLedgerExport)
}
}