feat: add coupon support to orders and create user_coupons model
- Added CouponID field to Order model to track used coupons. - Updated order query generation to include CouponID. - Introduced UserCoupon model to manage user coupon associations. - Implemented query methods for UserCoupon to facilitate CRUD operations. - Updated query context and default query setup to include UserCoupon.
This commit is contained in:
84
backend/app/services/coupon.go
Normal file
84
backend/app/services/coupon.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"quyun/v2/app/errorx"
|
||||
"quyun/v2/database/models"
|
||||
)
|
||||
|
||||
// @provider
|
||||
type coupon struct{}
|
||||
|
||||
// Validate checks if a coupon can be used for an order and returns the discount amount
|
||||
func (s *coupon) Validate(ctx context.Context, userID, userCouponID, amount int64) (int64, error) {
|
||||
uc, err := models.UserCouponQuery.WithContext(ctx).Where(models.UserCouponQuery.ID.Eq(userCouponID)).First()
|
||||
if err != nil {
|
||||
return 0, errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
|
||||
}
|
||||
if uc.UserID != userID {
|
||||
return 0, errorx.ErrUnauthorized.WithMsg("无权使用该优惠券")
|
||||
}
|
||||
if uc.Status != "unused" {
|
||||
return 0, errorx.ErrBusinessLogic.WithMsg("优惠券已使用或失效")
|
||||
}
|
||||
|
||||
c, err := models.CouponQuery.WithContext(ctx).Where(models.CouponQuery.ID.Eq(uc.CouponID)).First()
|
||||
if err != nil {
|
||||
return 0, errorx.ErrRecordNotFound.WithMsg("优惠券信息缺失")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if !c.StartAt.IsZero() && now.Before(c.StartAt) {
|
||||
return 0, errorx.ErrBusinessLogic.WithMsg("优惠券尚未生效")
|
||||
}
|
||||
if !c.EndAt.IsZero() && now.After(c.EndAt) {
|
||||
return 0, errorx.ErrBusinessLogic.WithMsg("优惠券已过期")
|
||||
}
|
||||
|
||||
if amount < c.MinOrderAmount {
|
||||
return 0, errorx.ErrBusinessLogic.WithMsg("未达到优惠券使用门槛")
|
||||
}
|
||||
|
||||
var discount int64
|
||||
if c.Type == "fix_amount" {
|
||||
discount = c.Value
|
||||
} else if c.Type == "discount" {
|
||||
discount = (amount * c.Value) / 100
|
||||
if c.MaxDiscount > 0 && discount > c.MaxDiscount {
|
||||
discount = c.MaxDiscount
|
||||
}
|
||||
}
|
||||
|
||||
// Discount cannot exceed order amount
|
||||
if discount > amount {
|
||||
discount = amount
|
||||
}
|
||||
|
||||
return discount, nil
|
||||
}
|
||||
|
||||
// MarkUsed marks a user coupon as used (intended to be called inside a transaction)
|
||||
func (s *coupon) MarkUsed(ctx context.Context, tx *models.Query, userCouponID, orderID int64) error {
|
||||
now := time.Now()
|
||||
// Update User Coupon
|
||||
info, err := tx.UserCoupon.WithContext(ctx).Where(tx.UserCoupon.ID.Eq(userCouponID), tx.UserCoupon.Status.Eq("unused")).Updates(&models.UserCoupon{
|
||||
Status: "used",
|
||||
OrderID: orderID,
|
||||
UsedAt: now,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.RowsAffected == 0 {
|
||||
return errorx.ErrBusinessLogic.WithMsg("优惠券核销失败")
|
||||
}
|
||||
|
||||
// Update Coupon used quantity (Optional, but good for stats)
|
||||
// We need CouponID from uc
|
||||
uc, _ := tx.UserCoupon.WithContext(ctx).Where(tx.UserCoupon.ID.Eq(userCouponID)).First()
|
||||
_, _ = tx.Coupon.WithContext(ctx).Where(tx.Coupon.ID.Eq(uc.CouponID)).UpdateSimple(tx.Coupon.UsedQuantity.Add(1))
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user