mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 10:54:00 +00:00
✨ fix(subscription): finalize payments, log billing, and clean up dead code
Complete subscription orders by creating a matching top-up record and writing billing logs Add Epay return handler to verify and finalize browser callbacks Require Stripe/Creem webhook configuration before starting subscription payments Show subscription purchases in topup history with clearer labels/methods Remove unused subscription helper, legacy Creem webhook struct, and unused topup fields Simplify subscription self API payload to active/all lists only
This commit is contained in:
@@ -68,17 +68,10 @@ func GetSubscriptionSelf(c *gin.Context) {
|
||||
activeSubscriptions = []model.SubscriptionSummary{}
|
||||
}
|
||||
|
||||
// For backward compatibility, also return the first active subscription as "subscription"
|
||||
var summary *model.SubscriptionSummary
|
||||
if len(activeSubscriptions) > 0 {
|
||||
summary = &activeSubscriptions[0]
|
||||
}
|
||||
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"billing_preference": pref,
|
||||
"subscription": summary, // backward compatibility (first active)
|
||||
"subscriptions": activeSubscriptions, // all active subscriptions
|
||||
"all_subscriptions": allSubscriptions, // all subscriptions including expired
|
||||
"billing_preference": pref,
|
||||
"subscriptions": activeSubscriptions, // all active subscriptions
|
||||
"all_subscriptions": allSubscriptions, // all subscriptions including expired
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/QuantumNous/new-api/setting"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/thanhpk/randstr"
|
||||
)
|
||||
@@ -46,6 +47,10 @@ func SubscriptionRequestCreemPay(c *gin.Context) {
|
||||
common.ApiErrorMsg(c, "该套餐未配置 CreemProductId")
|
||||
return
|
||||
}
|
||||
if setting.CreemWebhookSecret == "" && !setting.CreemTestMode {
|
||||
common.ApiErrorMsg(c, "Creem Webhook 未配置")
|
||||
return
|
||||
}
|
||||
|
||||
userId := c.GetInt("id")
|
||||
user, _ := model.GetUserById(userId, false)
|
||||
|
||||
@@ -50,7 +50,7 @@ func SubscriptionRequestEpay(c *gin.Context) {
|
||||
userId := c.GetInt("id")
|
||||
|
||||
callBackAddress := service.GetCallbackAddress()
|
||||
returnUrl, _ := url.Parse(system_setting.ServerAddress + "/console/topup")
|
||||
returnUrl, _ := url.Parse(callBackAddress + "/api/subscription/epay/return")
|
||||
notifyUrl, _ := url.Parse(callBackAddress + "/api/subscription/epay/notify")
|
||||
|
||||
tradeNo := fmt.Sprintf("%s%d", common.GetRandomString(6), time.Now().Unix())
|
||||
@@ -124,3 +124,31 @@ func SubscriptionEpayNotify(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// SubscriptionEpayReturn handles browser return after payment.
|
||||
// It verifies the payload and completes the order, then redirects to console.
|
||||
func SubscriptionEpayReturn(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 {
|
||||
c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=fail")
|
||||
return
|
||||
}
|
||||
verifyInfo, err := client.Verify(params)
|
||||
if err != nil || !verifyInfo.VerifyStatus {
|
||||
c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=fail")
|
||||
return
|
||||
}
|
||||
if verifyInfo.TradeStatus == epay.StatusTradeSuccess {
|
||||
LockOrder(verifyInfo.ServiceTradeNo)
|
||||
defer UnlockOrder(verifyInfo.ServiceTradeNo)
|
||||
_ = model.CompleteSubscriptionOrder(verifyInfo.ServiceTradeNo, jsonString(verifyInfo))
|
||||
c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=success")
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=pending")
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@ func SubscriptionRequestStripePay(c *gin.Context) {
|
||||
common.ApiErrorMsg(c, "Stripe 未配置或密钥无效")
|
||||
return
|
||||
}
|
||||
if setting.StripeWebhookSecret == "" {
|
||||
common.ApiErrorMsg(c, "Stripe Webhook 未配置")
|
||||
return
|
||||
}
|
||||
|
||||
userId := c.GetInt("id")
|
||||
user, _ := model.GetUserById(userId, false)
|
||||
|
||||
@@ -65,12 +65,10 @@ func GetTopUpInfo(c *gin.Context) {
|
||||
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"`
|
||||
Amount int64 `json:"amount"`
|
||||
}
|
||||
|
||||
func GetEpayClient() *epay.Client {
|
||||
|
||||
@@ -227,16 +227,6 @@ type CreemWebhookEvent struct {
|
||||
} `json:"object"`
|
||||
}
|
||||
|
||||
// 保留旧的结构体作为兼容
|
||||
type CreemWebhookData struct {
|
||||
Type string `json:"type"`
|
||||
Data struct {
|
||||
RequestId string `json:"request_id"`
|
||||
Status string `json:"status"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func CreemWebhook(c *gin.Context) {
|
||||
// 读取body内容用于打印,同时保留原始数据供后续使用
|
||||
bodyBytes, err := io.ReadAll(c.Request.Body)
|
||||
|
||||
Reference in New Issue
Block a user