package controller import ( "fmt" "net/http" "net/url" "strconv" "time" "github.com/Calcium-Ion/go-epay/epay" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/service" "github.com/QuantumNous/new-api/setting/operation_setting" "github.com/QuantumNous/new-api/setting/system_setting" "github.com/gin-gonic/gin" "github.com/samber/lo" ) type SubscriptionEpayPayRequest struct { PlanId int `json:"plan_id"` PaymentMethod string `json:"payment_method"` } func SubscriptionRequestEpay(c *gin.Context) { var req SubscriptionEpayPayRequest if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 { common.ApiErrorMsg(c, "参数错误") return } plan, err := model.GetSubscriptionPlanById(req.PlanId) if err != nil { common.ApiError(c, err) return } if !plan.Enabled { common.ApiErrorMsg(c, "套餐未启用") return } if plan.PriceAmount < 0.01 { common.ApiErrorMsg(c, "套餐金额过低") return } if !operation_setting.ContainsPayMethod(req.PaymentMethod) { common.ApiErrorMsg(c, "支付方式不存在") return } userId := c.GetInt("id") if plan.MaxPurchasePerUser > 0 { count, err := model.CountUserSubscriptionsByPlan(userId, plan.Id) if err != nil { common.ApiError(c, err) return } if count >= int64(plan.MaxPurchasePerUser) { common.ApiErrorMsg(c, "已达到该套餐购买上限") return } } callBackAddress := service.GetCallbackAddress() returnUrl, err := url.Parse(callBackAddress + "/api/subscription/epay/return") if err != nil { common.ApiErrorMsg(c, "回调地址配置错误") return } notifyUrl, err := url.Parse(callBackAddress + "/api/subscription/epay/notify") if err != nil { common.ApiErrorMsg(c, "回调地址配置错误") return } tradeNo := fmt.Sprintf("%s%d", common.GetRandomString(6), time.Now().Unix()) tradeNo = fmt.Sprintf("SUBUSR%dNO%s", userId, tradeNo) client := GetEpayClient() if client == nil { common.ApiErrorMsg(c, "当前管理员未配置支付信息") return } order := &model.SubscriptionOrder{ UserId: userId, PlanId: plan.Id, Money: plan.PriceAmount, TradeNo: tradeNo, PaymentMethod: req.PaymentMethod, CreateTime: time.Now().Unix(), Status: common.TopUpStatusPending, } if err := order.Insert(); err != nil { common.ApiErrorMsg(c, "创建订单失败") return } uri, params, err := client.Purchase(&epay.PurchaseArgs{ Type: req.PaymentMethod, ServiceTradeNo: tradeNo, Name: fmt.Sprintf("SUB:%s", plan.Title), Money: strconv.FormatFloat(plan.PriceAmount, 'f', 2, 64), Device: epay.PC, NotifyUrl: notifyUrl, ReturnUrl: returnUrl, }) if err != nil { _ = model.ExpireSubscriptionOrder(tradeNo) common.ApiErrorMsg(c, "拉起支付失败") return } c.JSON(http.StatusOK, gin.H{"message": "success", "data": params, "url": uri}) } func SubscriptionEpayNotify(c *gin.Context) { var params map[string]string if c.Request.Method == "POST" { // POST 请求:从 POST body 解析参数 if err := c.Request.ParseForm(); err != nil { _, _ = c.Writer.Write([]byte("fail")) return } params = lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string { r[t] = c.Request.PostForm.Get(t) return r }, map[string]string{}) } else { // GET 请求:从 URL Query 解析参数 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{}) } if len(params) == 0 { _, _ = c.Writer.Write([]byte("fail")) return } client := GetEpayClient() if client == nil { _, _ = c.Writer.Write([]byte("fail")) return } verifyInfo, err := client.Verify(params) if err != nil || !verifyInfo.VerifyStatus { _, _ = c.Writer.Write([]byte("fail")) return } if verifyInfo.TradeStatus != epay.StatusTradeSuccess { _, _ = c.Writer.Write([]byte("fail")) return } LockOrder(verifyInfo.ServiceTradeNo) defer UnlockOrder(verifyInfo.ServiceTradeNo) if err := model.CompleteSubscriptionOrder(verifyInfo.ServiceTradeNo, common.GetJsonString(verifyInfo)); err != nil { _, _ = c.Writer.Write([]byte("fail")) return } _, _ = c.Writer.Write([]byte("success")) } // SubscriptionEpayReturn handles browser return after payment. // It verifies the payload and completes the order, then redirects to console. func SubscriptionEpayReturn(c *gin.Context) { var params map[string]string if c.Request.Method == "POST" { // POST 请求:从 POST body 解析参数 if err := c.Request.ParseForm(); err != nil { c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail") return } params = lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string { r[t] = c.Request.PostForm.Get(t) return r }, map[string]string{}) } else { // GET 请求:从 URL Query 解析参数 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{}) } if len(params) == 0 { c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail") return } client := GetEpayClient() if client == nil { c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail") return } verifyInfo, err := client.Verify(params) if err != nil || !verifyInfo.VerifyStatus { c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail") return } if verifyInfo.TradeStatus == epay.StatusTradeSuccess { LockOrder(verifyInfo.ServiceTradeNo) defer UnlockOrder(verifyInfo.ServiceTradeNo) if err := model.CompleteSubscriptionOrder(verifyInfo.ServiceTradeNo, common.GetJsonString(verifyInfo)); err != nil { c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail") return } c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=success") return } c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=pending") }