bindbox-game/internal/service/user/orders_action.go

110 lines
3.3 KiB
Go
Executable File

package user
import (
"context"
"errors"
"time"
"bindbox-game/internal/repository/mysql/dao"
"bindbox-game/internal/repository/mysql/model"
"gorm.io/gorm/clause"
)
// CancelOrder 取消订单
func (s *service) CancelOrder(ctx context.Context, userID int64, orderID int64, reason string) (*model.Orders, error) {
var updatedOrder *model.Orders
err := s.writeDB.Transaction(func(tx *dao.Query) error {
// 1. 查询订单
order, err := tx.Orders.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where(tx.Orders.ID.Eq(orderID), tx.Orders.UserID.Eq(userID)).First()
if err != nil {
return err
}
if order == nil {
return errors.New("order not found")
}
// 2. 校验状态
if order.Status != 1 {
return errors.New("order cannot be cancelled")
}
// 3. 退还积分
if order.PointsAmount > 0 {
refundReason := "cancel_order"
if reason != "" {
refundReason = refundReason + ":" + reason
}
// order.PointsAmount 已经是分单位,直接退还
pointsToRefund := order.PointsAmount
// Update User Points
existing, _ := tx.UserPoints.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where(tx.UserPoints.UserID.Eq(userID)).First()
if existing == nil {
if err := tx.UserPoints.WithContext(ctx).Omit(tx.UserPoints.ValidEnd).Create(&model.UserPoints{UserID: userID, Points: pointsToRefund}); err != nil {
return err
}
} else {
if _, err := tx.UserPoints.WithContext(ctx).Where(tx.UserPoints.ID.Eq(existing.ID)).Updates(map[string]any{"points": existing.Points + pointsToRefund}); err != nil {
return err
}
}
// Log
led := &model.UserPointsLedger{UserID: userID, Action: "refund_points", Points: pointsToRefund, RefTable: "orders", RefID: order.OrderNo, Remark: refundReason}
if err := tx.UserPointsLedger.WithContext(ctx).Create(led); err != nil {
return err
}
}
// 4. 退还优惠券(恢复预扣的余额和状态)
if order.CouponID > 0 {
var oc struct {
AppliedAmount int64
}
// 获取该订单实际扣减的优惠券金额
_ = tx.OrderCoupons.WithContext(ctx).Where(tx.OrderCoupons.OrderID.Eq(order.ID), tx.OrderCoupons.UserCouponID.Eq(order.CouponID)).Scan(&oc)
// 执行原子回退:增加余额 + 重置状态 + 清除占用订单
res := tx.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(`
UPDATE user_coupons
SET balance_amount = balance_amount + ?,
status = 1,
used_order_id = 0,
used_at = NULL
WHERE id = ? AND used_order_id = ?
`, oc.AppliedAmount, order.CouponID, order.ID)
if res.RowsAffected > 0 {
// 记录退还流水
_ = tx.UserCouponLedger.WithContext(ctx).Create(&model.UserCouponLedger{
UserID: userID,
UserCouponID: order.CouponID,
ChangeAmount: oc.AppliedAmount,
OrderID: order.ID,
Action: "cancel_refund",
CreatedAt: time.Now(),
})
}
}
// 5. 更新订单状态
updates := map[string]any{
tx.Orders.Status.ColumnName().String(): 3,
tx.Orders.CancelledAt.ColumnName().String(): time.Now(),
}
if _, err := tx.Orders.WithContext(ctx).Where(tx.Orders.ID.Eq(order.ID)).Updates(updates); err != nil {
return err
}
updatedOrder = order
updatedOrder.Status = 3
return nil
})
if err != nil {
return nil, err
}
return updatedOrder, nil
}