| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- package model
- import (
- "errors"
- "fmt"
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/logger"
- "github.com/shopspring/decimal"
- "gorm.io/gorm"
- )
- type TopUp struct {
- Id int `json:"id"`
- UserId int `json:"user_id" gorm:"index"`
- Amount int64 `json:"amount"`
- Money float64 `json:"money"`
- TradeNo string `json:"trade_no" gorm:"unique;type:varchar(255);index"`
- PaymentMethod string `json:"payment_method" gorm:"type:varchar(50)"`
- CreateTime int64 `json:"create_time"`
- CompleteTime int64 `json:"complete_time"`
- Status string `json:"status"`
- }
- func (topUp *TopUp) Insert() error {
- var err error
- err = DB.Create(topUp).Error
- return err
- }
- func (topUp *TopUp) Update() error {
- var err error
- err = DB.Save(topUp).Error
- return err
- }
- func GetTopUpById(id int) *TopUp {
- var topUp *TopUp
- var err error
- err = DB.Where("id = ?", id).First(&topUp).Error
- if err != nil {
- return nil
- }
- return topUp
- }
- func GetTopUpByTradeNo(tradeNo string) *TopUp {
- var topUp *TopUp
- var err error
- err = DB.Where("trade_no = ?", tradeNo).First(&topUp).Error
- if err != nil {
- return nil
- }
- return topUp
- }
- func Recharge(referenceId string, customerId string) (err error) {
- if referenceId == "" {
- return errors.New("未提供支付单号")
- }
- var quota float64
- topUp := &TopUp{}
- refCol := "`trade_no`"
- if common.UsingPostgreSQL {
- refCol = `"trade_no"`
- }
- err = DB.Transaction(func(tx *gorm.DB) error {
- err := tx.Set("gorm:query_option", "FOR UPDATE").Where(refCol+" = ?", referenceId).First(topUp).Error
- if err != nil {
- return errors.New("充值订单不存在")
- }
- if topUp.Status != common.TopUpStatusPending {
- return errors.New("充值订单状态错误")
- }
- topUp.CompleteTime = common.GetTimestamp()
- topUp.Status = common.TopUpStatusSuccess
- err = tx.Save(topUp).Error
- if err != nil {
- return err
- }
- quota = topUp.Money * common.QuotaPerUnit
- err = tx.Model(&User{}).Where("id = ?", topUp.UserId).Updates(map[string]interface{}{"stripe_customer": customerId, "quota": gorm.Expr("quota + ?", quota)}).Error
- if err != nil {
- return err
- }
- return nil
- })
- if err != nil {
- return errors.New("充值失败," + err.Error())
- }
- RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用在线充值成功,充值金额: %v,支付金额:%d", logger.FormatQuota(int(quota)), topUp.Amount))
- return nil
- }
- func GetUserTopUps(userId int, pageInfo *common.PageInfo) (topups []*TopUp, total int64, err error) {
- // Start transaction
- tx := DB.Begin()
- if tx.Error != nil {
- return nil, 0, tx.Error
- }
- defer func() {
- if r := recover(); r != nil {
- tx.Rollback()
- }
- }()
- // Get total count within transaction
- err = tx.Model(&TopUp{}).Where("user_id = ?", userId).Count(&total).Error
- if err != nil {
- tx.Rollback()
- return nil, 0, err
- }
- // Get paginated topups within same transaction
- err = tx.Where("user_id = ?", userId).Order("id desc").Limit(pageInfo.GetPageSize()).Offset(pageInfo.GetStartIdx()).Find(&topups).Error
- if err != nil {
- tx.Rollback()
- return nil, 0, err
- }
- // Commit transaction
- if err = tx.Commit().Error; err != nil {
- return nil, 0, err
- }
- return topups, total, nil
- }
- // GetAllTopUps 获取全平台的充值记录(管理员使用)
- func GetAllTopUps(pageInfo *common.PageInfo) (topups []*TopUp, total int64, err error) {
- tx := DB.Begin()
- if tx.Error != nil {
- return nil, 0, tx.Error
- }
- defer func() {
- if r := recover(); r != nil {
- tx.Rollback()
- }
- }()
- if err = tx.Model(&TopUp{}).Count(&total).Error; err != nil {
- tx.Rollback()
- return nil, 0, err
- }
- if err = tx.Order("id desc").Limit(pageInfo.GetPageSize()).Offset(pageInfo.GetStartIdx()).Find(&topups).Error; err != nil {
- tx.Rollback()
- return nil, 0, err
- }
- if err = tx.Commit().Error; err != nil {
- return nil, 0, err
- }
- return topups, total, nil
- }
- // SearchUserTopUps 按订单号搜索某用户的充值记录
- func SearchUserTopUps(userId int, keyword string, pageInfo *common.PageInfo) (topups []*TopUp, total int64, err error) {
- tx := DB.Begin()
- if tx.Error != nil {
- return nil, 0, tx.Error
- }
- defer func() {
- if r := recover(); r != nil {
- tx.Rollback()
- }
- }()
- query := tx.Model(&TopUp{}).Where("user_id = ?", userId)
- if keyword != "" {
- like := "%%" + keyword + "%%"
- query = query.Where("trade_no LIKE ?", like)
- }
- if err = query.Count(&total).Error; err != nil {
- tx.Rollback()
- return nil, 0, err
- }
- if err = query.Order("id desc").Limit(pageInfo.GetPageSize()).Offset(pageInfo.GetStartIdx()).Find(&topups).Error; err != nil {
- tx.Rollback()
- return nil, 0, err
- }
- if err = tx.Commit().Error; err != nil {
- return nil, 0, err
- }
- return topups, total, nil
- }
- // SearchAllTopUps 按订单号搜索全平台充值记录(管理员使用)
- func SearchAllTopUps(keyword string, pageInfo *common.PageInfo) (topups []*TopUp, total int64, err error) {
- tx := DB.Begin()
- if tx.Error != nil {
- return nil, 0, tx.Error
- }
- defer func() {
- if r := recover(); r != nil {
- tx.Rollback()
- }
- }()
- query := tx.Model(&TopUp{})
- if keyword != "" {
- like := "%%" + keyword + "%%"
- query = query.Where("trade_no LIKE ?", like)
- }
- if err = query.Count(&total).Error; err != nil {
- tx.Rollback()
- return nil, 0, err
- }
- if err = query.Order("id desc").Limit(pageInfo.GetPageSize()).Offset(pageInfo.GetStartIdx()).Find(&topups).Error; err != nil {
- tx.Rollback()
- return nil, 0, err
- }
- if err = tx.Commit().Error; err != nil {
- return nil, 0, err
- }
- return topups, total, nil
- }
- // ManualCompleteTopUp 管理员手动完成订单并给用户充值
- func ManualCompleteTopUp(tradeNo string) error {
- if tradeNo == "" {
- return errors.New("未提供订单号")
- }
- refCol := "`trade_no`"
- if common.UsingPostgreSQL {
- refCol = `"trade_no"`
- }
- var userId int
- var quotaToAdd int
- var payMoney float64
- err := DB.Transaction(func(tx *gorm.DB) error {
- topUp := &TopUp{}
- // 行级锁,避免并发补单
- if err := tx.Set("gorm:query_option", "FOR UPDATE").Where(refCol+" = ?", tradeNo).First(topUp).Error; err != nil {
- return errors.New("充值订单不存在")
- }
- // 幂等处理:已成功直接返回
- if topUp.Status == common.TopUpStatusSuccess {
- return nil
- }
- if topUp.Status != common.TopUpStatusPending {
- return errors.New("订单状态不是待支付,无法补单")
- }
- // 计算应充值额度:
- // - Stripe 订单:Money 代表经分组倍率换算后的美元数量,直接 * QuotaPerUnit
- // - 其他订单(如易支付):Amount 为美元数量,* QuotaPerUnit
- if topUp.PaymentMethod == "stripe" {
- dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
- quotaToAdd = int(decimal.NewFromFloat(topUp.Money).Mul(dQuotaPerUnit).IntPart())
- } else {
- dAmount := decimal.NewFromInt(topUp.Amount)
- dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
- quotaToAdd = int(dAmount.Mul(dQuotaPerUnit).IntPart())
- }
- if quotaToAdd <= 0 {
- return errors.New("无效的充值额度")
- }
- // 标记完成
- topUp.CompleteTime = common.GetTimestamp()
- topUp.Status = common.TopUpStatusSuccess
- if err := tx.Save(topUp).Error; err != nil {
- return err
- }
- // 增加用户额度(立即写库,保持一致性)
- if err := tx.Model(&User{}).Where("id = ?", topUp.UserId).Update("quota", gorm.Expr("quota + ?", quotaToAdd)).Error; err != nil {
- return err
- }
- userId = topUp.UserId
- payMoney = topUp.Money
- return nil
- })
- if err != nil {
- return err
- }
- // 事务外记录日志,避免阻塞
- RecordLog(userId, LogTypeTopup, fmt.Sprintf("管理员补单成功,充值金额: %v,支付金额:%f", logger.FormatQuota(quotaToAdd), payMoney))
- return nil
- }
- func RechargeCreem(referenceId string, customerEmail string, customerName string) (err error) {
- if referenceId == "" {
- return errors.New("未提供支付单号")
- }
- var quota int64
- topUp := &TopUp{}
- refCol := "`trade_no`"
- if common.UsingPostgreSQL {
- refCol = `"trade_no"`
- }
- err = DB.Transaction(func(tx *gorm.DB) error {
- err := tx.Set("gorm:query_option", "FOR UPDATE").Where(refCol+" = ?", referenceId).First(topUp).Error
- if err != nil {
- return errors.New("充值订单不存在")
- }
- if topUp.Status != common.TopUpStatusPending {
- return errors.New("充值订单状态错误")
- }
- topUp.CompleteTime = common.GetTimestamp()
- topUp.Status = common.TopUpStatusSuccess
- err = tx.Save(topUp).Error
- if err != nil {
- return err
- }
- // Creem 直接使用 Amount 作为充值额度(整数)
- quota = topUp.Amount
- // 构建更新字段,优先使用邮箱,如果邮箱为空则使用用户名
- updateFields := map[string]interface{}{
- "quota": gorm.Expr("quota + ?", quota),
- }
- // 如果有客户邮箱,尝试更新用户邮箱(仅当用户邮箱为空时)
- if customerEmail != "" {
- // 先检查用户当前邮箱是否为空
- var user User
- err = tx.Where("id = ?", topUp.UserId).First(&user).Error
- if err != nil {
- return err
- }
- // 如果用户邮箱为空,则更新为支付时使用的邮箱
- if user.Email == "" {
- updateFields["email"] = customerEmail
- }
- }
- err = tx.Model(&User{}).Where("id = ?", topUp.UserId).Updates(updateFields).Error
- if err != nil {
- return err
- }
- return nil
- })
- if err != nil {
- return errors.New("充值失败," + err.Error())
- }
- RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用Creem充值成功,充值额度: %v,支付金额:%.2f", quota, topUp.Money))
- return nil
- }
|