Spaces:
Build error
Build error
| 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)}) | |
| } | |