package services import ( "fmt" "platform/pkg/u" "platform/web/core" m "platform/web/models" q "platform/web/queries" "time" "github.com/shopspring/decimal" "gorm.io/gen/field" ) var Resource = &resourceService{} type resourceService struct{} // CreateResourceByBalance 通过余额购买套餐 func (s *resourceService) CreateResourceByBalance(user *m.User, data *CreateResourceData) error { now := time.Now() // 获取 sku detail, err := data.TradeDetail(user) if err != nil { return core.NewServErr("获取产品支付信息失败", err) } return q.Q.Transaction(func(q *q.Query) error { // 更新用户余额 if err := User.UpdateBalance(q, user, detail.Actual.Neg(), "余额购买产品", nil); err != nil { return core.NewServErr("更新用户余额失败", err) } // 保存套餐 resource, err := s.Create(q, user.ID, now, data) if err != nil { return core.NewServErr("创建套餐失败", err) } // 生成账单 err = Bill.CreateForResource(q, user.ID, resource.ID, nil, detail) if err != nil { return core.NewServErr("生成账单失败", err) } // 核销优惠券 if detail.CouponUserId != nil { err = Coupon.UseCoupon(q, *detail.CouponUserId) if err != nil { return core.NewServErr("核销优惠券失败", err) } } return nil }) } func (s *resourceService) Create(q *q.Query, uid int32, now time.Time, data *CreateResourceData) (*m.Resource, error) { // 套餐基本信息 var resource = m.Resource{ UserID: uid, ResourceNo: u.P(ID.GenReadable("res")), Active: true, Type: data.Type, Code: data.Type.Code(), } switch data.Type { // 短效套餐 case m.ResourceTypeShort: var short = data.Short if short == nil { return nil, core.NewBizErr("短效套餐数据不能为空") } resource.Short = &m.ResourceShort{ Live: short.Live, Type: short.Mode, Quota: short.Quota, Code: data.Code(), } if short.Mode == m.ResourceModeTime { if short.Expire == nil { return nil, core.NewBizErr("包时套餐过期时间不能为空") } var duration = time.Duration(*short.Expire) * 24 * time.Hour resource.Short.ExpireAt = u.P(now.Add(duration)) } // 长效套餐 case m.ResourceTypeLong: var long = data.Long if long == nil { return nil, core.NewBizErr("长效套餐数据不能为空") } resource.Long = &m.ResourceLong{ Live: long.Live, Type: long.Mode, Quota: long.Quota, Code: data.Code(), } if long.Mode == m.ResourceModeTime { if long.Expire == nil { return nil, core.NewBizErr("包时套餐过期时间不能为空") } var duration = time.Duration(*long.Expire) * 24 * time.Hour resource.Long.ExpireAt = u.P(now.Add(duration)) } default: return nil, core.NewBizErr("不支持的套餐类型") } err := q.Resource.Create(&resource) if err != nil { return nil, core.NewServErr("创建套餐失败", err) } return &resource, nil } func (s *resourceService) Update(data *UpdateResourceData) error { if data.Active == nil { return core.NewBizErr("更新套餐失败,active 不能为空") } do := make([]field.AssignExpr, 0) if data.Active != nil { do = append(do, q.Resource.Active.Value(*data.Active)) } _, err := q.Resource. Where(q.Resource.ID.Eq(data.Id)). UpdateSimple(do...) if err != nil { return core.NewServErr("更新套餐失败", err) } return nil } type UpdateResourceData struct { core.IdReq Active *bool `json:"active"` } func (s *resourceService) CalcPrice(skuCode string, count int32, user *m.User, cuid *int32) (*m.ProductSku, *m.ProductDiscount, *m.CouponUser, decimal.Decimal, decimal.Decimal, error) { sku, err := q.ProductSku. Joins(q.ProductSku.Discount). Where(q.ProductSku.Code.Eq(skuCode), q.ProductSku.Status.Eq(int32(m.SkuStatusEnabled))). Take() if err != nil { return nil, nil, nil, decimal.Zero, decimal.Zero, core.NewServErr("产品不可用", err) } // 原价 price := sku.Price amount := price.Mul(decimal.NewFromInt32(count)) // 折扣价 discount := sku.Discount if discount == nil { return nil, nil, nil, decimal.Zero, decimal.Zero, core.NewServErr("价格查询失败", err) } discountRate := discount.Rate() if user != nil && user.DiscountID != nil { // 用户特殊优惠 uDiscount, err := q.ProductDiscount.Where(q.ProductDiscount.ID.Eq(*user.DiscountID)).Take() if err != nil { return nil, nil, nil, decimal.Zero, decimal.Zero, core.NewServErr("客户特殊价查询失败", err) } uDiscountRate := uDiscount.Rate() if uDiscountRate.Cmp(discountRate) > 0 { discountRate = uDiscountRate discount = uDiscount } } discounted := amount.Mul(discountRate) // 优惠价 coupon := (*m.CouponUser)(nil) couponApplied := discounted.Copy() if user != nil && cuid != nil { var err error coupon, err = Coupon.GetUserCoupon(user.ID, *cuid, discounted) if err != nil { return nil, nil, nil, decimal.Zero, decimal.Zero, err } couponApplied = discounted.Sub(coupon.Coupon.Amount) } // 约束到最低价格 if discounted.Cmp(sku.PriceMin) < 0 { discounted = sku.PriceMin.Copy() } if couponApplied.Cmp(sku.PriceMin) < 0 { couponApplied = sku.PriceMin.Copy() } return sku, discount, coupon, discounted, couponApplied, nil } type CreateResourceData struct { Type m.ResourceType `json:"type" validate:"required"` Short *CreateShortResourceData `json:"short,omitempty"` Long *CreateLongResourceData `json:"long,omitempty"` CouponId *int32 `json:"coupon,omitempty"` } type CreateShortResourceData struct { Live int32 `json:"live" validate:"required"` Mode m.ResourceMode `json:"mode" validate:"required"` Quota int32 `json:"quota" validate:"required"` Expire *int32 `json:"expire"` name string price *decimal.Decimal } type CreateLongResourceData struct { Live int32 `json:"live" validate:"required"` Mode m.ResourceMode `json:"mode" validate:"required"` Quota int32 `json:"quota" validate:"required"` Expire *int32 `json:"expire"` name string price *decimal.Decimal } func (data *CreateResourceData) Count() int32 { switch { default: return 0 case data.Type == m.ResourceTypeShort && data.Short != nil: return data.Short.Quota case data.Type == m.ResourceTypeLong && data.Long != nil: return data.Long.Quota } } func (data *CreateResourceData) Code() string { switch { default: return "" case data.Type == m.ResourceTypeShort && data.Short != nil: return fmt.Sprintf( "mode=%s&live=%d&expire=%d", data.Short.Mode.Code(), data.Short.Live, u.Else(data.Short.Expire, 0), ) case data.Type == m.ResourceTypeLong && data.Long != nil: return fmt.Sprintf( "mode=%s&live=%d&expire=%d", data.Long.Mode.Code(), data.Long.Live, u.Else(data.Long.Expire, 0), ) } } func (data *CreateResourceData) TradeDetail(user *m.User) (*TradeDetail, error) { sku, discount, coupon, amount, actual, err := Resource.CalcPrice(data.Code(), data.Count(), user, data.CouponId) if err != nil { return nil, err } var discountId *int32 = nil if discount != nil { discountId = &discount.ID } var couponUserId *int32 = nil if coupon != nil { couponUserId = &coupon.ID } return &TradeDetail{ data, m.TradeTypePurchase, sku.Name, amount, actual, discountId, couponUserId, }, nil } // 服务错误 type ResourceServiceErr string func (e ResourceServiceErr) Error() string { return string(e) } const ( ErrBalanceNotEnough = ResourceServiceErr("余额不足") )