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:
@@ -75,6 +75,11 @@ func OpenPostgres(dsn string) *Postgres {
|
||||
if err := db.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_ledger_grant_ref ON sundynix_credit_ledger (kind, ref) WHERE kind = 'grant' AND ref <> ''`).Error; err != nil {
|
||||
log.Printf("[store] 账本 grant/ref 唯一索引创建失败(重复入账兜底闸缺位): %v", err)
|
||||
}
|
||||
// 退款幂等兜底闸:adjust 分录带 ref(=订单号) 唯一——防重复退款冲销。
|
||||
// 部分索引:admin 手工校正(GrantCredits 负数)ref 为空,不受约束;与 grant 双闸对称。
|
||||
if err := db.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_ledger_refund_ref ON sundynix_credit_ledger (kind, ref) WHERE kind = 'adjust' AND ref <> ''`).Error; err != nil {
|
||||
log.Printf("[store] 账本 adjust/ref 唯一索引创建失败(重复退款兜底闸缺位): %v", err)
|
||||
}
|
||||
registerTenantScope(db) // 多租户:受租户模型的查询/创建自动按上下文注入 tenant_id(统一强制隔离)
|
||||
log.Println("[store] postgres connected & migrated (雪花 id + 软删 规约)")
|
||||
return &Postgres{db: db}
|
||||
|
||||
Reference in New Issue
Block a user