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 { // 查询订单使用的优惠券及扣减金额 type couponRow struct { AppliedAmount int64 DiscountType int32 } var cr couponRow _ = tx.OrderCoupons.WithContext(ctx).UnderlyingDB().Raw(` SELECT oc.applied_amount AS applied_amount, sc.discount_type AS discount_type FROM order_coupons oc JOIN user_coupons uc ON uc.id = oc.user_coupon_id JOIN system_coupons sc ON sc.id = uc.coupon_id WHERE oc.order_id = ? AND oc.user_coupon_id = ? `, order.ID, order.CouponID).Scan(&cr) if cr.AppliedAmount > 0 { if cr.DiscountType == 1 { // 金额券:恢复余额 res := tx.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` UPDATE user_coupons SET balance_amount = balance_amount + ?, status = 1, used_order_id = NULL, used_at = NULL WHERE id = ? `, cr.AppliedAmount, order.CouponID) if res.RowsAffected > 0 { // 记录退还流水 _ = tx.UserCouponLedger.WithContext(ctx).Create(&model.UserCouponLedger{ UserID: userID, UserCouponID: order.CouponID, ChangeAmount: cr.AppliedAmount, BalanceAfter: 0, // 简化处理,后续可查询精确值 OrderID: order.ID, Action: "refund", CreatedAt: time.Now(), }) } } else { // 满减/折扣券:恢复状态 tx.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` UPDATE user_coupons SET status = 1, used_order_id = NULL, used_at = NULL WHERE id = ? AND status = 4 `, order.CouponID) } } } // 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 }