Files
zhengchiliu b94dd6dffb feat(order): 重构退款全流程并新增申请退款中状态
- 订单状态新增「6 申请退款中」,用户申请退款原子流转并落待审核退款流水
- 退款失败回退订单至已支付并恢复被取消的权益发放任务
- 新增虚拟支付退款查单兜底任务,反查超时订单并自动订正
- 会员包来源额度不叠加,扣减/回退/回收口径统一为只取生效一份
- 退款回收流水按吊销前后有效剩余差值记账,避免无效回收记录
- 同步更新 README 与 Stitch 设计文档
2026-09-16 03:44:09 +08:00

70 lines
3.2 KiB
Go

package mysqldao
import (
"context"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type BenefitRevokeTaskDao struct{ db *gorm.DB }
func NewBenefitRevokeTaskDao() *BenefitRevokeTaskDao {
return &BenefitRevokeTaskDao{db: global.MysqlEngine}
}
func (d *BenefitRevokeTaskDao) GetByOrderNo(ctx context.Context, orderNo string) (*mysqlmodel.BenefitRevokeTask, error) {
var task mysqlmodel.BenefitRevokeTask
err := d.db.WithContext(ctx).Where("order_no = ?", orderNo).First(&task).Error
return &task, err
}
func (d *BenefitRevokeTaskDao) ListPending(ctx context.Context, limit int) ([]mysqlmodel.BenefitRevokeTask, error) {
var list []mysqlmodel.BenefitRevokeTask
err := d.db.WithContext(ctx).Where("status = ?", 0).Order("id ASC").Limit(limit).Find(&list).Error
return list, err
}
func (d *BenefitRevokeTaskDao) Claim(ctx context.Context, id int64) (bool, error) {
r := d.db.WithContext(ctx).Model(&mysqlmodel.BenefitRevokeTask{}).Where("id = ? AND status IN ?", id, []int8{0, 2}).Update("status", 3)
return r.RowsAffected > 0, r.Error
}
func (d *BenefitRevokeTaskDao) Update(ctx context.Context, id int64, updates map[string]any) error {
return d.db.WithContext(ctx).Model(&mysqlmodel.BenefitRevokeTask{}).Where("id = ?", id).Updates(updates).Error
}
func (d *BenefitRevokeTaskDao) RecoverStale(ctx context.Context, before int64) error {
return d.db.WithContext(ctx).Model(&mysqlmodel.BenefitRevokeTask{}).Where("status = ? AND updated_at < ?", 3, before).Update("status", 0).Error
}
// RefundSettleWithRevokeTask 原子写入退款订单终态、取消未执行发放并创建回收任务。
// 前置状态:2=已支付(正常退款)、3=已取消但已付款(竞态兜底)、6=申请退款中(用户已提交退款申请)。
func (d *BenefitRevokeTaskDao) RefundSettleWithRevokeTask(ctx context.Context, orderNo string, refundAmount, refundTime int64) (bool, error) {
settled := false
err := d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var order mysqlmodel.Order
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("order_no = ?", orderNo).First(&order).Error; err != nil {
return err
}
if order.OrderStatus == 5 {
return nil
}
if order.OrderStatus != 2 && order.OrderStatus != 3 && order.OrderStatus != 6 {
return nil
}
if err := tx.Model(&mysqlmodel.Order{}).Where("id = ?", order.ID).Updates(map[string]any{"order_status": 5, "refund_time": refundTime, "refund_amount": refundAmount}).Error; err != nil {
return err
}
if err := tx.Model(&mysqlmodel.BenefitGrantTask{}).Where("order_no = ? AND status IN ?", orderNo, []int8{0, 2}).Update("status", 4).Error; err != nil {
return err
}
// 2 / 6 表示权益已按订单发放过,需创建回收任务;3 从未发放,跳过避免误扣其它来源的会员天数/额度。
if order.OrderStatus == 2 || order.OrderStatus == 6 {
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&mysqlmodel.BenefitRevokeTask{OrderNo: orderNo, UserID: order.UserID, MaxRetry: 10}).Error; err != nil {
return err
}
}
settled = true
return nil
})
return settled, err
}