package controller import ( "fmt" "log" "net/url" "one-api/common" "one-api/logger" "one-api/model" "one-api/service" "one-api/setting" "one-api/setting/operation_setting" "one-api/setting/system_setting" "strconv" "sync" "time" "github.com/Calcium-Ion/go-epay/epay" "github.com/gin-gonic/gin" "github.com/samber/lo" "github.com/shopspring/decimal" ) func GetTopUpInfo(c *gin.Context) { // 获取支付方式 payMethods := operation_setting.PayMethods // 如果启用了 Stripe 支付,添加到支付方法列表 if setting.StripeApiSecret != "" && setting.StripeWebhookSecret != "" && setting.StripePriceId != "" { // 检查是否已经包含 Stripe hasStripe := false for _, method := range payMethods { if method["type"] == "stripe" { hasStripe = true break } } if !hasStripe { stripeMethod := map[string]string{ "name": "Stripe", "type": "stripe", "color": "rgba(var(--semi-purple-5), 1)", "min_topup": strconv.Itoa(setting.StripeMinTopUp), } payMethods = append(payMethods, stripeMethod) } } data := gin.H{ "enable_online_topup": operation_setting.PayAddress != "" && operation_setting.EpayId != "" && operation_setting.EpayKey != "", "enable_stripe_topup": setting.StripeApiSecret != "" && setting.StripeWebhookSecret != "" && setting.StripePriceId != "", "pay_methods": payMethods, "min_topup": operation_setting.MinTopUp, "stripe_min_topup": setting.StripeMinTopUp, "amount_options": operation_setting.GetPaymentSetting().AmountOptions, "discount": operation_setting.GetPaymentSetting().AmountDiscount, } common.ApiSuccess(c, data) } type EpayRequest struct { Amount int64 `json:"amount"` PaymentMethod string `json:"payment_method"` TopUpCode string `json:"top_up_code"` } type AmountRequest struct { Amount int64 `json:"amount"` TopUpCode string `json:"top_up_code"` } func GetEpayClient() *epay.Client { if operation_setting.PayAddress == "" || operation_setting.EpayId == "" || operation_setting.EpayKey == "" { return nil } withUrl, err := epay.NewClient(&epay.Config{ PartnerID: operation_setting.EpayId, Key: operation_setting.EpayKey, }, operation_setting.PayAddress) if err != nil { return nil } return withUrl } func getPayMoney(amount int64, group string) float64 { dAmount := decimal.NewFromInt(amount) if !common.DisplayInCurrencyEnabled { dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit) dAmount = dAmount.Div(dQuotaPerUnit) } topupGroupRatio := common.GetTopupGroupRatio(group) if topupGroupRatio == 0 { topupGroupRatio = 1 } dTopupGroupRatio := decimal.NewFromFloat(topupGroupRatio) dPrice := decimal.NewFromFloat(operation_setting.Price) // apply optional preset discount by the original request amount (if configured), default 1.0 discount := 1.0 if ds, ok := operation_setting.GetPaymentSetting().AmountDiscount[int(amount)]; ok { if ds > 0 { discount = ds } } dDiscount := decimal.NewFromFloat(discount) payMoney := dAmount.Mul(dPrice).Mul(dTopupGroupRatio).Mul(dDiscount) return payMoney.InexactFloat64() } func getMinTopup() int64 { minTopup := operation_setting.MinTopUp if !common.DisplayInCurrencyEnabled { dMinTopup := decimal.NewFromInt(int64(minTopup)) dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit) minTopup = int(dMinTopup.Mul(dQuotaPerUnit).IntPart()) } return int64(minTopup) } func RequestEpay(c *gin.Context) { var req EpayRequest err := c.ShouldBindJSON(&req) if err != nil { c.JSON(200, gin.H{"message": "error", "data": "参数错误"}) return } if req.Amount < getMinTopup() { c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", getMinTopup())}) return } id := c.GetInt("id") group, err := model.GetUserGroup(id, true) if err != nil { c.JSON(200, gin.H{"message": "error", "data": "获取用户分组失败"}) return } payMoney := getPayMoney(req.Amount, group) if payMoney < 0.01 { c.JSON(200, gin.H{"message": "error", "data": "充值金额过低"}) return } if !operation_setting.ContainsPayMethod(req.PaymentMethod) { c.JSON(200, gin.H{"message": "error", "data": "支付方式不存在"}) return } callBackAddress := service.GetCallbackAddress() returnUrl, _ := url.Parse(system_setting.ServerAddress + "/console/log") notifyUrl, _ := url.Parse(callBackAddress + "/api/user/epay/notify") tradeNo := fmt.Sprintf("%s%d", common.GetRandomString(6), time.Now().Unix()) tradeNo = fmt.Sprintf("USR%dNO%s", id, tradeNo) client := GetEpayClient() if client == nil { c.JSON(200, gin.H{"message": "error", "data": "当前管理员未配置支付信息"}) return } uri, params, err := client.Purchase(&epay.PurchaseArgs{ Type: req.PaymentMethod, ServiceTradeNo: tradeNo, Name: fmt.Sprintf("TUC%d", req.Amount), Money: strconv.FormatFloat(payMoney, 'f', 2, 64), Device: epay.PC, NotifyUrl: notifyUrl, ReturnUrl: returnUrl, }) if err != nil { c.JSON(200, gin.H{"message": "error", "data": "拉起支付失败"}) return } amount := req.Amount if !common.DisplayInCurrencyEnabled { dAmount := decimal.NewFromInt(int64(amount)) dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit) amount = dAmount.Div(dQuotaPerUnit).IntPart() } topUp := &model.TopUp{ UserId: id, Amount: amount, Money: payMoney, TradeNo: tradeNo, CreateTime: time.Now().Unix(), Status: "pending", } err = topUp.Insert() if err != nil { c.JSON(200, gin.H{"message": "error", "data": "创建订单失败"}) return } c.JSON(200, gin.H{"message": "success", "data": params, "url": uri}) } // tradeNo lock var orderLocks sync.Map var createLock sync.Mutex // LockOrder 尝试对给定订单号加锁 func LockOrder(tradeNo string) { lock, ok := orderLocks.Load(tradeNo) if !ok { createLock.Lock() defer createLock.Unlock() lock, ok = orderLocks.Load(tradeNo) if !ok { lock = new(sync.Mutex) orderLocks.Store(tradeNo, lock) } } lock.(*sync.Mutex).Lock() } // UnlockOrder 释放给定订单号的锁 func UnlockOrder(tradeNo string) { lock, ok := orderLocks.Load(tradeNo) if ok { lock.(*sync.Mutex).Unlock() } } func EpayNotify(c *gin.Context) { params := lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string { r[t] = c.Request.URL.Query().Get(t) return r }, map[string]string{}) client := GetEpayClient() if client == nil { log.Println("易支付回调失败 未找到配置信息") _, err := c.Writer.Write([]byte("fail")) if err != nil { log.Println("易支付回调写入失败") return } } verifyInfo, err := client.Verify(params) if err == nil && verifyInfo.VerifyStatus { _, err := c.Writer.Write([]byte("success")) if err != nil { log.Println("易支付回调写入失败") } } else { _, err := c.Writer.Write([]byte("fail")) if err != nil { log.Println("易支付回调写入失败") } log.Println("易支付回调签名验证失败") return } if verifyInfo.TradeStatus == epay.StatusTradeSuccess { log.Println(verifyInfo) LockOrder(verifyInfo.ServiceTradeNo) defer UnlockOrder(verifyInfo.ServiceTradeNo) topUp := model.GetTopUpByTradeNo(verifyInfo.ServiceTradeNo) if topUp == nil { log.Printf("易支付回调未找到订单: %v", verifyInfo) return } if topUp.Status == "pending" { topUp.Status = "success" err := topUp.Update() if err != nil { log.Printf("易支付回调更新订单失败: %v", topUp) return } //user, _ := model.GetUserById(topUp.UserId, false) //user.Quota += topUp.Amount * 500000 dAmount := decimal.NewFromInt(int64(topUp.Amount)) dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit) quotaToAdd := int(dAmount.Mul(dQuotaPerUnit).IntPart()) err = model.IncreaseUserQuota(topUp.UserId, quotaToAdd, true) if err != nil { log.Printf("易支付回调更新用户失败: %v", topUp) return } log.Printf("易支付回调更新用户成功 %v", topUp) model.RecordLog(topUp.UserId, model.LogTypeTopup, fmt.Sprintf("使用在线充值成功,充值金额: %v,支付金额:%f", logger.LogQuota(quotaToAdd), topUp.Money)) } } else { log.Printf("易支付异常回调: %v", verifyInfo) } } func RequestAmount(c *gin.Context) { var req AmountRequest err := c.ShouldBindJSON(&req) if err != nil { c.JSON(200, gin.H{"message": "error", "data": "参数错误"}) return } if req.Amount < getMinTopup() { c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", getMinTopup())}) return } id := c.GetInt("id") group, err := model.GetUserGroup(id, true) if err != nil { c.JSON(200, gin.H{"message": "error", "data": "获取用户分组失败"}) return } payMoney := getPayMoney(req.Amount, group) if payMoney <= 0.01 { c.JSON(200, gin.H{"message": "error", "data": "充值金额过低"}) return } c.JSON(200, gin.H{"message": "success", "data": strconv.FormatFloat(payMoney, 'f', 2, 64)}) }