feat(billing): 人工退款入口 —— paid 单冲销积分+置 refunded(幂等)
PAYMENT_DESIGN §5 承诺却只留了 OrderRefunded 常量、无入口。补齐全链路: - store.RefundOrder:订单 CAS(paid→refunded) 为主闸(重复退 changed=false 幂等), 同事务记 adjust 负分录(ref=订单号)+ 回退物化余额。照抄 MarkOrderPaid 双闸范式; 新增 idx_ledger_refund_ref 部分唯一索引(kind='adjust' AND ref<>'')做账本级兜底, 与 grant 索引对称、不与 admin 手工校正(ref 空)冲突。 - 积分若已消费,回退后余额可为负(人工退款预期,账本仍自洽,后续消费被硬拦截)。 - handler AdminRefundOrder + POST /admin/orders/:id/refund(admin 组已挂 Audit 留痕); 真渠道钱款原路退回需 admin 另在商户后台操作,本地仅冲销积分与订单态(不接自动退款 API)。 - admin 订单流加「退款」按钮(仅 paid 单可见,二次确认+填原因)。 - 测试:RefundOrder 冲销+幂等、只退 paid 两个不变量测试(sqlite 真 DB,余额=账本之和)。 gateway build/vet/test 全绿,admin tsc 干净。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -144,6 +144,7 @@ func New(db *store.Postgres, cache *store.Redis, bus *nats.Bus, blobStore *blob.
|
||||
admin.PUT("/payment/wechat", h.AdminSaveWechatPay)
|
||||
admin.GET("/orders", h.AdminOrders) // 全平台充值订单流 + 状态计数
|
||||
admin.GET("/orders/reconcile", h.AdminReconcile) // 日终对账:paid 单 ↔ 账本 grant
|
||||
admin.POST("/orders/:id/refund", h.AdminRefundOrder) // 人工退款:置 refunded + adjust 负分录 + 回退余额(审计)
|
||||
// 多租户成员管理(平台运维口径)
|
||||
admin.GET("/tenants", h.AdminTenants) // 租户目录(成员数+余额)
|
||||
admin.POST("/tenants", h.AdminCreateTenant) // 新建租户(可选指定 owner)
|
||||
|
||||
Reference in New Issue
Block a user