package services import ( "context" "database/sql" "encoding/json" "fmt" "github.com/gofiber/fiber/v2" "github.com/shopspring/decimal" bill2 "platform/web/domains/bill" resource2 "platform/web/domains/resource" trade2 "platform/web/domains/trade" g "platform/web/globals" "platform/web/globals/orm" m "platform/web/models" q "platform/web/queries" "time" ) var Resource = &resourceService{} type resourceService struct{} func (s *resourceService) CreateResource(uid int32, now time.Time, ser *CreateResourceSerializer) error { data, err := ser.ToData() if err != nil { return err } var name = data.GetName() var amount = data.GetPrice() // 保存交易信息 err = q.Q.Transaction(func(q *q.Query) error { // 检查用户 user, err := q.User. Where(q.User.ID.Eq(uid)). Take() if err != nil { return err } // 检查余额 if user.Balance.Cmp(amount) < 0 { return fiber.NewError(fiber.StatusBadRequest, "余额不足") } // 保存套餐 resource, err := createResource(q, uid, now, data) if err != nil { return err } // 生成账单 bill := m.Bill{ UserID: uid, ResourceID: resource.ID, BillNo: ID.GenReadable("bil"), Info: "购买套餐 - " + name, Type: int32(bill2.TypeConsume), Amount: amount, } err = q.Bill. Omit(q.Bill.TradeID, q.Bill.RefundID). Create(&bill) if err != nil { return err } // 更新用户余额 _, err = q.User.UpdateSimple(q.User.Balance.Value(user.Balance.Sub(amount))) if err != nil { return err } if err != nil { return err } return nil }, &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) if err != nil { return err } return nil } func (s *resourceService) PrepareResource(uid int32, now time.Time, method trade2.Method, ser *CreateResourceSerializer) (*TransactionPrepareResult, error) { data, err := ser.ToData() if err != nil { return nil, err } name := data.GetName() amount := data.GetPrice() // 保存到数据库 var result *TransactionPrepareResult err = q.Q.Transaction(func(q *q.Query) error { var err error // 生成交易订单 result, err = Transaction.PrepareTransaction(q, uid, now, &TransactionPrepareData{ Subject: "购买套餐 - " + name, Amount: amount, ExpireAt: time.Now().Add(30 * time.Minute), Type: trade2.TypeRecharge, Method: method, }) if err != nil { return err } // 保存请求缓存 resourceSerializer := CreateResourceSerializer{} if err := resourceSerializer.ByData(data); err != nil { return err } err = g.Redis.Set(context.Background(), result.TradeNo, &CreateResourceCache{ Uid: uid, TradeId: result.Trade.ID, BillId: result.Bill.ID, Method: method, CreateResourceSerializer: resourceSerializer, }, 30*time.Minute).Err() if err != nil { return err } return nil }) if err != nil { return nil, err } return result, nil } func (s *resourceService) CompleteResource(tradeNo string, now time.Time, opResult ...*TransactionVerifyResult) error { // 获取请求缓存 reqStr, err := g.Redis.Get(context.Background(), tradeNo).Result() if err != nil { return err } cache := new(CreateResourceCache) if err := json.Unmarshal([]byte(reqStr), cache); err != nil { return err } // 检查交易结果 var rs *TransactionVerifyResult if len(opResult) > 0 { rs = opResult[0] } else { var err error rs, err = Transaction.VerifyTransaction(&TransactionVerifyData{ TradeNo: tradeNo, Method: cache.Method, }) if err != nil { return err } } data, err := cache.ToData() if err != nil { return err } // 保存交易信息 err = q.Q.Transaction(func(q *q.Query) error { // 保存套餐 resource, err := createResource(q, cache.Uid, now, data) if err != nil { return err } // 更新账单 _, err = q.Bill.Debug(). Select(q.Bill.ResourceID). Updates(&m.Bill{ ID: cache.BillId, ResourceID: resource.ID, }) if err != nil { return err } // 完成交易 _, err = Transaction.CompleteTransaction(q, &TransactionCompleteData{ TradeNo: tradeNo, TransactionVerifyResult: *rs, }) if err != nil { return err } // 删除缓存 err = g.Redis.Del(context.Background(), tradeNo).Err() if err != nil { return err } return nil }) if err != nil { return err } return nil } func (s *resourceService) CancelResource(tradeNo string, now time.Time, opRevoked ...bool) error { // 删除请求缓存 cacheStr, err := g.Redis.GetDel(context.Background(), tradeNo).Result() if err != nil { return err } cache := new(CreateResourceCache) if err := json.Unmarshal([]byte(cacheStr), cache); err != nil { return err } // 取消交易 if len(opRevoked) <= 0 { err = Transaction.RevokeTransaction(tradeNo, cache.Method) if err != nil { return err } } // 更新订单状态 err = Transaction.FinishTransaction(q.Q, tradeNo, now) if err != nil { return err } return nil } func createResource(q *q.Query, uid int32, now time.Time, data CreateResourceData) (*m.Resource, error) { // 套餐基本信息 var resource = m.Resource{ UserID: uid, ResourceNo: ID.GenReadable("res"), Active: true, } switch data := data.(type) { // 短效套餐 case *CreateShortResourceData: var duration = time.Duration(data.Expire) * 24 * time.Hour resource.Type = int32(resource2.TypeShort) resource.Short = &m.ResourceShort{ Type: data.Mode, Live: data.Live, Quota: data.Quota, Expire: orm.LocalDateTime(now.Add(duration)), DailyLimit: data.DailyLimit, } // 长效套餐 case *CreateLongResourceData: var duration = time.Duration(data.Expire) * 24 * time.Hour resource.Type = int32(resource2.TypeLong) resource.Long = &m.ResourceLong{ Type: data.Mode, Live: data.Live, Quota: data.Quota, Expire: orm.LocalDateTime(now.Add(duration)), DailyLimit: data.DailyLimit, } default: return nil, fmt.Errorf("不支持的套餐类型") } err := q.Resource.Create(&resource) if err != nil { return nil, err } return &resource, nil } type CreateResourceData interface { GetName() string GetPrice() decimal.Decimal } type CreateShortResourceData struct { Live int32 `json:"live" validate:"required min=180"` Mode int32 `json:"mode" validate:"required"` Expire int32 `json:"expire"` DailyLimit int32 `json:"daily_limit" validate:"min=2000"` Quota int32 `json:"quota" validate:"min=10000"` name string price *decimal.Decimal } func (data *CreateShortResourceData) GetName() string { if data.name == "" { var mode string switch data.Mode { case 1: mode = "包时" case 2: mode = "包量" } data.name = fmt.Sprintf("短效动态%s %d 天", mode, data.Live) } return data.name } func (data *CreateShortResourceData) GetPrice() decimal.Decimal { if data.price == nil { var factor int32 switch data.Mode { case 1: factor = data.DailyLimit * data.Expire case 2: factor = data.Quota } var base = data.Live if base == 180 { base = 150 } var dec = decimal.Decimal{}. Add(decimal.NewFromInt32(base * factor)). Div(decimal.NewFromInt(30000)) data.price = &dec } return *data.price } type CreateLongResourceData struct { Live int32 `json:"live" validate:"required oneof=1,4,8,12,24"` Mode int32 `json:"mode" validate:"required oneof=1,2"` Expire int32 `json:"expire"` DailyLimit int32 `json:"daily_limit" validate:"min=100"` Quota int32 `json:"quota" validate:"min=500"` name string price *decimal.Decimal } func (data *CreateLongResourceData) GetName() string { if data.name == "" { var mode string switch data.Mode { case 1: mode = "包时" case 2: mode = "包量" } data.name = fmt.Sprintf("长效动态%s %d 天", mode, data.Live) } return data.name } func (data *CreateLongResourceData) GetPrice() decimal.Decimal { if data.price == nil { var factor int32 = 0 switch resource2.Mode(data.Mode) { case resource2.ModeTime: factor = data.Expire * data.DailyLimit case resource2.ModeCount: factor = data.Quota } var base int32 switch data.Live { case 1: base = 30 case 4: base = 80 case 8: base = 120 case 12: base = 180 case 24: base = 350 } // data.price = big.NewRat(int64(base*factor), 100) var dec = decimal.Decimal{}. Add(decimal.NewFromInt32(base * factor)). Div(decimal.NewFromInt(100)) data.price = &dec } return *data.price } type CreateResourceCache struct { Uid int32 `json:"uid"` TradeId int32 `json:"trade_id"` BillId int32 `json:"bill_id"` Method trade2.Method `json:"method"` CreateResourceSerializer } type CreateResourceSerializer struct { Type resource2.Type `json:"type" validate:"required"` Short *CreateShortResourceData `json:"short,omitempty"` Long *CreateLongResourceData `json:"long,omitempty"` } func (s *CreateResourceSerializer) ToData() (CreateResourceData, error) { switch s.Type { case resource2.TypeShort: return s.Short, nil case resource2.TypeLong: return s.Long, nil } return nil, fmt.Errorf("不支持的套餐类型") } func (s *CreateResourceSerializer) ByData(data CreateResourceData) error { switch data := data.(type) { case *CreateShortResourceData: s.Type = resource2.TypeShort s.Short = data case *CreateLongResourceData: s.Type = resource2.TypeLong s.Long = data default: return fmt.Errorf("不支持的套餐类型") } return nil }