|
@@ -2,6 +2,7 @@ package controller
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"bytes"
|
|
"bytes"
|
|
|
|
|
+ "context"
|
|
|
"crypto/hmac"
|
|
"crypto/hmac"
|
|
|
"crypto/sha256"
|
|
"crypto/sha256"
|
|
|
"encoding/hex"
|
|
"encoding/hex"
|
|
@@ -9,10 +10,10 @@ import (
|
|
|
"errors"
|
|
"errors"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/common"
|
|
|
|
|
+ "github.com/QuantumNous/new-api/logger"
|
|
|
"github.com/QuantumNous/new-api/model"
|
|
"github.com/QuantumNous/new-api/model"
|
|
|
"github.com/QuantumNous/new-api/setting"
|
|
"github.com/QuantumNous/new-api/setting"
|
|
|
"io"
|
|
"io"
|
|
|
- "log"
|
|
|
|
|
"net/http"
|
|
"net/http"
|
|
|
"time"
|
|
"time"
|
|
|
|
|
|
|
@@ -20,10 +21,7 @@ import (
|
|
|
"github.com/thanhpk/randstr"
|
|
"github.com/thanhpk/randstr"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-const (
|
|
|
|
|
- PaymentMethodCreem = "creem"
|
|
|
|
|
- CreemSignatureHeader = "creem-signature"
|
|
|
|
|
-)
|
|
|
|
|
|
|
+const CreemSignatureHeader = "creem-signature"
|
|
|
|
|
|
|
|
var creemAdaptor = &CreemAdaptor{}
|
|
var creemAdaptor = &CreemAdaptor{}
|
|
|
|
|
|
|
@@ -37,9 +35,9 @@ func generateCreemSignature(payload string, secret string) string {
|
|
|
// 验证Creem webhook签名
|
|
// 验证Creem webhook签名
|
|
|
func verifyCreemSignature(payload string, signature string, secret string) bool {
|
|
func verifyCreemSignature(payload string, signature string, secret string) bool {
|
|
|
if secret == "" {
|
|
if secret == "" {
|
|
|
- log.Printf("Creem webhook secret not set")
|
|
|
|
|
|
|
+ logger.LogWarn(context.Background(), fmt.Sprintf("Creem webhook secret 未配置 test_mode=%t signature=%q body=%q", setting.CreemTestMode, signature, payload))
|
|
|
if setting.CreemTestMode {
|
|
if setting.CreemTestMode {
|
|
|
- log.Printf("Skip Creem webhook sign verify in test mode")
|
|
|
|
|
|
|
+ logger.LogInfo(context.Background(), fmt.Sprintf("Creem webhook 验签已跳过 reason=test_mode signature=%q body=%q", signature, payload))
|
|
|
return true
|
|
return true
|
|
|
}
|
|
}
|
|
|
return false
|
|
return false
|
|
@@ -66,13 +64,13 @@ type CreemAdaptor struct {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (*CreemAdaptor) RequestPay(c *gin.Context, req *CreemPayRequest) {
|
|
func (*CreemAdaptor) RequestPay(c *gin.Context, req *CreemPayRequest) {
|
|
|
- if req.PaymentMethod != PaymentMethodCreem {
|
|
|
|
|
- c.JSON(200, gin.H{"message": "error", "data": "不支持的支付渠道"})
|
|
|
|
|
|
|
+ if req.PaymentMethod != model.PaymentMethodCreem {
|
|
|
|
|
+ c.JSON(http.StatusOK, gin.H{"message": "error", "data": "不支持的支付渠道"})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if req.ProductId == "" {
|
|
if req.ProductId == "" {
|
|
|
- c.JSON(200, gin.H{"message": "error", "data": "请选择产品"})
|
|
|
|
|
|
|
+ c.JSON(http.StatusOK, gin.H{"message": "error", "data": "请选择产品"})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -80,8 +78,8 @@ func (*CreemAdaptor) RequestPay(c *gin.Context, req *CreemPayRequest) {
|
|
|
var products []CreemProduct
|
|
var products []CreemProduct
|
|
|
err := json.Unmarshal([]byte(setting.CreemProducts), &products)
|
|
err := json.Unmarshal([]byte(setting.CreemProducts), &products)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- log.Println("解析Creem产品列表失败", err)
|
|
|
|
|
- c.JSON(200, gin.H{"message": "error", "data": "产品配置错误"})
|
|
|
|
|
|
|
+ logger.LogError(c.Request.Context(), fmt.Sprintf("Creem 产品配置解析失败 user_id=%d error=%q", c.GetInt("id"), err.Error()))
|
|
|
|
|
+ c.JSON(http.StatusOK, gin.H{"message": "error", "data": "产品配置错误"})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -95,7 +93,7 @@ func (*CreemAdaptor) RequestPay(c *gin.Context, req *CreemPayRequest) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if selectedProduct == nil {
|
|
if selectedProduct == nil {
|
|
|
- c.JSON(200, gin.H{"message": "error", "data": "产品不存在"})
|
|
|
|
|
|
|
+ c.JSON(http.StatusOK, gin.H{"message": "error", "data": "产品不存在"})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -112,29 +110,28 @@ func (*CreemAdaptor) RequestPay(c *gin.Context, req *CreemPayRequest) {
|
|
|
Amount: selectedProduct.Quota, // 充值额度
|
|
Amount: selectedProduct.Quota, // 充值额度
|
|
|
Money: selectedProduct.Price, // 支付金额
|
|
Money: selectedProduct.Price, // 支付金额
|
|
|
TradeNo: referenceId,
|
|
TradeNo: referenceId,
|
|
|
- PaymentMethod: PaymentMethodCreem,
|
|
|
|
|
|
|
+ PaymentMethod: model.PaymentMethodCreem,
|
|
|
CreateTime: time.Now().Unix(),
|
|
CreateTime: time.Now().Unix(),
|
|
|
Status: common.TopUpStatusPending,
|
|
Status: common.TopUpStatusPending,
|
|
|
}
|
|
}
|
|
|
err = topUp.Insert()
|
|
err = topUp.Insert()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- log.Printf("创建Creem订单失败: %v", err)
|
|
|
|
|
- c.JSON(200, gin.H{"message": "error", "data": "创建订单失败"})
|
|
|
|
|
|
|
+ logger.LogError(c.Request.Context(), fmt.Sprintf("Creem 创建充值订单失败 user_id=%d trade_no=%s product_id=%s error=%q", id, referenceId, selectedProduct.ProductId, err.Error()))
|
|
|
|
|
+ c.JSON(http.StatusOK, gin.H{"message": "error", "data": "创建订单失败"})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 创建支付链接,传入用户邮箱
|
|
// 创建支付链接,传入用户邮箱
|
|
|
- checkoutUrl, err := genCreemLink(referenceId, selectedProduct, user.Email, user.Username)
|
|
|
|
|
|
|
+ checkoutUrl, err := genCreemLink(c.Request.Context(), referenceId, selectedProduct, user.Email, user.Username)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- log.Printf("获取Creem支付链接失败: %v", err)
|
|
|
|
|
- c.JSON(200, gin.H{"message": "error", "data": "拉起支付失败"})
|
|
|
|
|
|
|
+ logger.LogError(c.Request.Context(), fmt.Sprintf("Creem 创建支付链接失败 user_id=%d trade_no=%s product_id=%s error=%q", id, referenceId, selectedProduct.ProductId, err.Error()))
|
|
|
|
|
+ c.JSON(http.StatusOK, gin.H{"message": "error", "data": "拉起支付失败"})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- log.Printf("Creem订单创建成功 - 用户ID: %d, 订单号: %s, 产品: %s, 充值额度: %d, 支付金额: %.2f",
|
|
|
|
|
- id, referenceId, selectedProduct.Name, selectedProduct.Quota, selectedProduct.Price)
|
|
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem 充值订单创建成功 user_id=%d trade_no=%s product_id=%s product_name=%q quota=%d money=%.2f", id, referenceId, selectedProduct.ProductId, selectedProduct.Name, selectedProduct.Quota, selectedProduct.Price))
|
|
|
|
|
|
|
|
- c.JSON(200, gin.H{
|
|
|
|
|
|
|
+ c.JSON(http.StatusOK, gin.H{
|
|
|
"message": "success",
|
|
"message": "success",
|
|
|
"data": gin.H{
|
|
"data": gin.H{
|
|
|
"checkout_url": checkoutUrl,
|
|
"checkout_url": checkoutUrl,
|
|
@@ -149,20 +146,19 @@ func RequestCreemPay(c *gin.Context) {
|
|
|
// 读取body内容用于打印,同时保留原始数据供后续使用
|
|
// 读取body内容用于打印,同时保留原始数据供后续使用
|
|
|
bodyBytes, err := io.ReadAll(c.Request.Body)
|
|
bodyBytes, err := io.ReadAll(c.Request.Body)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- log.Printf("read creem pay req body err: %v", err)
|
|
|
|
|
- c.JSON(200, gin.H{"message": "error", "data": "read query error"})
|
|
|
|
|
|
|
+ logger.LogError(c.Request.Context(), fmt.Sprintf("Creem 支付请求读取失败 error=%q", err.Error()))
|
|
|
|
|
+ c.JSON(http.StatusOK, gin.H{"message": "error", "data": "read query error"})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 打印body内容
|
|
|
|
|
- log.Printf("creem pay request body: %s", string(bodyBytes))
|
|
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem 支付请求已收到 user_id=%d body=%q", c.GetInt("id"), string(bodyBytes)))
|
|
|
|
|
|
|
|
// 重新设置body供后续的ShouldBindJSON使用
|
|
// 重新设置body供后续的ShouldBindJSON使用
|
|
|
c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
|
c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
|
|
|
|
|
|
|
err = c.ShouldBindJSON(&req)
|
|
err = c.ShouldBindJSON(&req)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- c.JSON(200, gin.H{"message": "error", "data": "参数错误"})
|
|
|
|
|
|
|
+ c.JSON(http.StatusOK, gin.H{"message": "error", "data": "参数错误"})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
creemAdaptor.RequestPay(c, &req)
|
|
creemAdaptor.RequestPay(c, &req)
|
|
@@ -230,35 +226,37 @@ type CreemWebhookEvent struct {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func CreemWebhook(c *gin.Context) {
|
|
func CreemWebhook(c *gin.Context) {
|
|
|
|
|
+ if !isCreemWebhookEnabled() {
|
|
|
|
|
+ logger.LogWarn(c.Request.Context(), fmt.Sprintf("Creem webhook 被拒绝 reason=webhook_disabled path=%q client_ip=%s", c.Request.RequestURI, c.ClientIP()))
|
|
|
|
|
+ c.AbortWithStatus(http.StatusForbidden)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 读取body内容用于打印,同时保留原始数据供后续使用
|
|
// 读取body内容用于打印,同时保留原始数据供后续使用
|
|
|
bodyBytes, err := io.ReadAll(c.Request.Body)
|
|
bodyBytes, err := io.ReadAll(c.Request.Body)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- log.Printf("读取Creem Webhook请求body失败: %v", err)
|
|
|
|
|
|
|
+ logger.LogError(c.Request.Context(), fmt.Sprintf("Creem webhook 读取请求体失败 path=%q client_ip=%s error=%q", c.Request.RequestURI, c.ClientIP(), err.Error()))
|
|
|
c.AbortWithStatus(http.StatusBadRequest)
|
|
c.AbortWithStatus(http.StatusBadRequest)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 获取签名头
|
|
// 获取签名头
|
|
|
signature := c.GetHeader(CreemSignatureHeader)
|
|
signature := c.GetHeader(CreemSignatureHeader)
|
|
|
-
|
|
|
|
|
- // 打印关键信息(避免输出完整敏感payload)
|
|
|
|
|
- log.Printf("Creem Webhook - URI: %s", c.Request.RequestURI)
|
|
|
|
|
- if setting.CreemTestMode {
|
|
|
|
|
- log.Printf("Creem Webhook - Signature: %s , Body: %s", signature, bodyBytes)
|
|
|
|
|
- } else if signature == "" {
|
|
|
|
|
- log.Printf("Creem Webhook缺少签名头")
|
|
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem webhook 收到请求 path=%q client_ip=%s signature=%q body=%q", c.Request.RequestURI, c.ClientIP(), signature, string(bodyBytes)))
|
|
|
|
|
+ if signature == "" {
|
|
|
|
|
+ logger.LogWarn(c.Request.Context(), fmt.Sprintf("Creem webhook 缺少签名 path=%q client_ip=%s body=%q", c.Request.RequestURI, c.ClientIP(), string(bodyBytes)))
|
|
|
c.AbortWithStatus(http.StatusUnauthorized)
|
|
c.AbortWithStatus(http.StatusUnauthorized)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 验证签名
|
|
// 验证签名
|
|
|
if !verifyCreemSignature(string(bodyBytes), signature, setting.CreemWebhookSecret) {
|
|
if !verifyCreemSignature(string(bodyBytes), signature, setting.CreemWebhookSecret) {
|
|
|
- log.Printf("Creem Webhook签名验证失败")
|
|
|
|
|
|
|
+ logger.LogWarn(c.Request.Context(), fmt.Sprintf("Creem webhook 验签失败 path=%q client_ip=%s signature=%q body=%q", c.Request.RequestURI, c.ClientIP(), signature, string(bodyBytes)))
|
|
|
c.AbortWithStatus(http.StatusUnauthorized)
|
|
c.AbortWithStatus(http.StatusUnauthorized)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- log.Printf("Creem Webhook签名验证成功")
|
|
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem webhook 验签成功 path=%q client_ip=%s", c.Request.RequestURI, c.ClientIP()))
|
|
|
|
|
|
|
|
// 重新设置body供后续的ShouldBindJSON使用
|
|
// 重新设置body供后续的ShouldBindJSON使用
|
|
|
c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
|
c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
|
@@ -266,19 +264,19 @@ func CreemWebhook(c *gin.Context) {
|
|
|
// 解析新格式的webhook数据
|
|
// 解析新格式的webhook数据
|
|
|
var webhookEvent CreemWebhookEvent
|
|
var webhookEvent CreemWebhookEvent
|
|
|
if err := c.ShouldBindJSON(&webhookEvent); err != nil {
|
|
if err := c.ShouldBindJSON(&webhookEvent); err != nil {
|
|
|
- log.Printf("解析Creem Webhook参数失败: %v", err)
|
|
|
|
|
|
|
+ logger.LogError(c.Request.Context(), fmt.Sprintf("Creem webhook 解析失败 path=%q client_ip=%s error=%q body=%q", c.Request.RequestURI, c.ClientIP(), err.Error(), string(bodyBytes)))
|
|
|
c.AbortWithStatus(http.StatusBadRequest)
|
|
c.AbortWithStatus(http.StatusBadRequest)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- log.Printf("Creem Webhook解析成功 - EventType: %s, EventId: %s", webhookEvent.EventType, webhookEvent.Id)
|
|
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem webhook 解析成功 event_type=%s event_id=%s request_id=%s order_id=%s order_status=%s", webhookEvent.EventType, webhookEvent.Id, webhookEvent.Object.RequestId, webhookEvent.Object.Order.Id, webhookEvent.Object.Order.Status))
|
|
|
|
|
|
|
|
// 根据事件类型处理不同的webhook
|
|
// 根据事件类型处理不同的webhook
|
|
|
switch webhookEvent.EventType {
|
|
switch webhookEvent.EventType {
|
|
|
case "checkout.completed":
|
|
case "checkout.completed":
|
|
|
handleCheckoutCompleted(c, &webhookEvent)
|
|
handleCheckoutCompleted(c, &webhookEvent)
|
|
|
default:
|
|
default:
|
|
|
- log.Printf("忽略Creem Webhook事件类型: %s", webhookEvent.EventType)
|
|
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem webhook 忽略事件 event_type=%s event_id=%s", webhookEvent.EventType, webhookEvent.Id))
|
|
|
c.Status(http.StatusOK)
|
|
c.Status(http.StatusOK)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -287,7 +285,7 @@ func CreemWebhook(c *gin.Context) {
|
|
|
func handleCheckoutCompleted(c *gin.Context, event *CreemWebhookEvent) {
|
|
func handleCheckoutCompleted(c *gin.Context, event *CreemWebhookEvent) {
|
|
|
// 验证订单状态
|
|
// 验证订单状态
|
|
|
if event.Object.Order.Status != "paid" {
|
|
if event.Object.Order.Status != "paid" {
|
|
|
- log.Printf("订单状态不是已支付: %s, 跳过处理", event.Object.Order.Status)
|
|
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem 订单状态未支付,忽略处理 request_id=%s order_id=%s order_status=%s", event.Object.RequestId, event.Object.Order.Id, event.Object.Order.Status))
|
|
|
c.Status(http.StatusOK)
|
|
c.Status(http.StatusOK)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -295,7 +293,7 @@ func handleCheckoutCompleted(c *gin.Context, event *CreemWebhookEvent) {
|
|
|
// 获取引用ID(这是我们创建订单时传递的request_id)
|
|
// 获取引用ID(这是我们创建订单时传递的request_id)
|
|
|
referenceId := event.Object.RequestId
|
|
referenceId := event.Object.RequestId
|
|
|
if referenceId == "" {
|
|
if referenceId == "" {
|
|
|
- log.Println("Creem Webhook缺少request_id字段")
|
|
|
|
|
|
|
+ logger.LogWarn(c.Request.Context(), fmt.Sprintf("Creem webhook 缺少 request_id event_id=%s order_id=%s", event.Id, event.Object.Order.Id))
|
|
|
c.AbortWithStatus(http.StatusBadRequest)
|
|
c.AbortWithStatus(http.StatusBadRequest)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -303,40 +301,35 @@ func handleCheckoutCompleted(c *gin.Context, event *CreemWebhookEvent) {
|
|
|
// Try complete subscription order first
|
|
// Try complete subscription order first
|
|
|
LockOrder(referenceId)
|
|
LockOrder(referenceId)
|
|
|
defer UnlockOrder(referenceId)
|
|
defer UnlockOrder(referenceId)
|
|
|
- if err := model.CompleteSubscriptionOrder(referenceId, common.GetJsonString(event)); err == nil {
|
|
|
|
|
|
|
+ if err := model.CompleteSubscriptionOrder(referenceId, common.GetJsonString(event), model.PaymentMethodCreem); err == nil {
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem 订阅订单处理成功 trade_no=%s creem_order_id=%s", referenceId, event.Object.Order.Id))
|
|
|
c.Status(http.StatusOK)
|
|
c.Status(http.StatusOK)
|
|
|
return
|
|
return
|
|
|
} else if err != nil && !errors.Is(err, model.ErrSubscriptionOrderNotFound) {
|
|
} else if err != nil && !errors.Is(err, model.ErrSubscriptionOrderNotFound) {
|
|
|
- log.Printf("Creem订阅订单处理失败: %s, 订单号: %s", err.Error(), referenceId)
|
|
|
|
|
|
|
+ logger.LogError(c.Request.Context(), fmt.Sprintf("Creem 订阅订单处理失败 trade_no=%s creem_order_id=%s error=%q", referenceId, event.Object.Order.Id, err.Error()))
|
|
|
c.AbortWithStatus(http.StatusInternalServerError)
|
|
c.AbortWithStatus(http.StatusInternalServerError)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 验证订单类型,目前只处理一次性付款(充值)
|
|
// 验证订单类型,目前只处理一次性付款(充值)
|
|
|
if event.Object.Order.Type != "onetime" {
|
|
if event.Object.Order.Type != "onetime" {
|
|
|
- log.Printf("暂不支持的订单类型: %s, 跳过处理", event.Object.Order.Type)
|
|
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem 暂不支持该订单类型,忽略处理 request_id=%s creem_order_id=%s order_type=%s", referenceId, event.Object.Order.Id, event.Object.Order.Type))
|
|
|
c.Status(http.StatusOK)
|
|
c.Status(http.StatusOK)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 记录详细的支付信息
|
|
|
|
|
- log.Printf("处理Creem支付完成 - 订单号: %s, Creem订单ID: %s, 支付金额: %d %s, 客户邮箱: <redacted>, 产品: %s",
|
|
|
|
|
- referenceId,
|
|
|
|
|
- event.Object.Order.Id,
|
|
|
|
|
- event.Object.Order.AmountPaid,
|
|
|
|
|
- event.Object.Order.Currency,
|
|
|
|
|
- event.Object.Product.Name)
|
|
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem 支付完成回调 trade_no=%s creem_order_id=%s amount_paid=%d currency=%s product_name=%q customer_email=%q customer_name=%q", referenceId, event.Object.Order.Id, event.Object.Order.AmountPaid, event.Object.Order.Currency, event.Object.Product.Name, event.Object.Customer.Email, event.Object.Customer.Name))
|
|
|
|
|
|
|
|
// 查询本地订单确认存在
|
|
// 查询本地订单确认存在
|
|
|
topUp := model.GetTopUpByTradeNo(referenceId)
|
|
topUp := model.GetTopUpByTradeNo(referenceId)
|
|
|
if topUp == nil {
|
|
if topUp == nil {
|
|
|
- log.Printf("Creem充值订单不存在: %s", referenceId)
|
|
|
|
|
|
|
+ logger.LogWarn(c.Request.Context(), fmt.Sprintf("Creem 充值订单不存在 trade_no=%s creem_order_id=%s", referenceId, event.Object.Order.Id))
|
|
|
c.AbortWithStatus(http.StatusBadRequest)
|
|
c.AbortWithStatus(http.StatusBadRequest)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if topUp.Status != common.TopUpStatusPending {
|
|
if topUp.Status != common.TopUpStatusPending {
|
|
|
- log.Printf("Creem充值订单状态错误: %s, 当前状态: %s", referenceId, topUp.Status)
|
|
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem 充值订单状态非 pending,忽略处理 trade_no=%s status=%s creem_order_id=%s", referenceId, topUp.Status, event.Object.Order.Id))
|
|
|
c.Status(http.StatusOK) // 已处理过的订单,返回成功避免重复处理
|
|
c.Status(http.StatusOK) // 已处理过的订单,返回成功避免重复处理
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -347,21 +340,20 @@ func handleCheckoutCompleted(c *gin.Context, event *CreemWebhookEvent) {
|
|
|
|
|
|
|
|
// 防护性检查,确保邮箱和姓名不为空字符串
|
|
// 防护性检查,确保邮箱和姓名不为空字符串
|
|
|
if customerEmail == "" {
|
|
if customerEmail == "" {
|
|
|
- log.Printf("警告:Creem回调中客户邮箱为空 - 订单号: %s", referenceId)
|
|
|
|
|
|
|
+ logger.LogWarn(c.Request.Context(), fmt.Sprintf("Creem 回调客户邮箱为空 trade_no=%s creem_order_id=%s", referenceId, event.Object.Order.Id))
|
|
|
}
|
|
}
|
|
|
if customerName == "" {
|
|
if customerName == "" {
|
|
|
- log.Printf("警告:Creem回调中客户姓名为空 - 订单号: %s", referenceId)
|
|
|
|
|
|
|
+ logger.LogWarn(c.Request.Context(), fmt.Sprintf("Creem 回调客户姓名为空 trade_no=%s creem_order_id=%s", referenceId, event.Object.Order.Id))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
err := model.RechargeCreem(referenceId, customerEmail, customerName, c.ClientIP())
|
|
err := model.RechargeCreem(referenceId, customerEmail, customerName, c.ClientIP())
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- log.Printf("Creem充值处理失败: %s, 订单号: %s", err.Error(), referenceId)
|
|
|
|
|
|
|
+ logger.LogError(c.Request.Context(), fmt.Sprintf("Creem 充值处理失败 trade_no=%s creem_order_id=%s client_ip=%s error=%q", referenceId, event.Object.Order.Id, c.ClientIP(), err.Error()))
|
|
|
c.AbortWithStatus(http.StatusInternalServerError)
|
|
c.AbortWithStatus(http.StatusInternalServerError)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- log.Printf("Creem充值成功 - 订单号: %s, 充值额度: %d, 支付金额: %.2f",
|
|
|
|
|
- referenceId, topUp.Amount, topUp.Money)
|
|
|
|
|
|
|
+ logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem 充值成功 trade_no=%s creem_order_id=%s quota=%d money=%.2f client_ip=%s", referenceId, event.Object.Order.Id, topUp.Amount, topUp.Money, c.ClientIP()))
|
|
|
c.Status(http.StatusOK)
|
|
c.Status(http.StatusOK)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -379,7 +371,7 @@ type CreemCheckoutResponse struct {
|
|
|
Id string `json:"id"`
|
|
Id string `json:"id"`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func genCreemLink(referenceId string, product *CreemProduct, email string, username string) (string, error) {
|
|
|
|
|
|
|
+func genCreemLink(ctx context.Context, referenceId string, product *CreemProduct, email string, username string) (string, error) {
|
|
|
if setting.CreemApiKey == "" {
|
|
if setting.CreemApiKey == "" {
|
|
|
return "", fmt.Errorf("未配置Creem API密钥")
|
|
return "", fmt.Errorf("未配置Creem API密钥")
|
|
|
}
|
|
}
|
|
@@ -388,7 +380,7 @@ func genCreemLink(referenceId string, product *CreemProduct, email string, usern
|
|
|
apiUrl := "https://api.creem.io/v1/checkouts"
|
|
apiUrl := "https://api.creem.io/v1/checkouts"
|
|
|
if setting.CreemTestMode {
|
|
if setting.CreemTestMode {
|
|
|
apiUrl = "https://test-api.creem.io/v1/checkouts"
|
|
apiUrl = "https://test-api.creem.io/v1/checkouts"
|
|
|
- log.Printf("使用Creem测试环境: %s", apiUrl)
|
|
|
|
|
|
|
+ logger.LogInfo(ctx, fmt.Sprintf("Creem 使用测试环境 api_url=%s", apiUrl))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 构建请求数据,确保包含用户邮箱
|
|
// 构建请求数据,确保包含用户邮箱
|
|
@@ -424,8 +416,7 @@ func genCreemLink(referenceId string, product *CreemProduct, email string, usern
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
req.Header.Set("x-api-key", setting.CreemApiKey)
|
|
req.Header.Set("x-api-key", setting.CreemApiKey)
|
|
|
|
|
|
|
|
- log.Printf("发送Creem支付请求 - URL: %s, 产品ID: %s, 用户邮箱: %s, 订单号: %s",
|
|
|
|
|
- apiUrl, product.ProductId, email, referenceId)
|
|
|
|
|
|
|
+ logger.LogInfo(ctx, fmt.Sprintf("Creem 支付请求已发送 api_url=%s product_id=%s email=%q trade_no=%s", apiUrl, product.ProductId, email, referenceId))
|
|
|
|
|
|
|
|
// 发送请求
|
|
// 发送请求
|
|
|
client := &http.Client{
|
|
client := &http.Client{
|
|
@@ -443,7 +434,7 @@ func genCreemLink(referenceId string, product *CreemProduct, email string, usern
|
|
|
return "", fmt.Errorf("读取响应失败: %v", err)
|
|
return "", fmt.Errorf("读取响应失败: %v", err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- log.Printf("Creem API resp - status code: %d, resp: %s", resp.StatusCode, string(body))
|
|
|
|
|
|
|
+ logger.LogInfo(ctx, fmt.Sprintf("Creem API 响应已收到 trade_no=%s status_code=%d body=%q", referenceId, resp.StatusCode, string(body)))
|
|
|
|
|
|
|
|
// 检查响应状态
|
|
// 检查响应状态
|
|
|
if resp.StatusCode/100 != 2 {
|
|
if resp.StatusCode/100 != 2 {
|
|
@@ -460,6 +451,6 @@ func genCreemLink(referenceId string, product *CreemProduct, email string, usern
|
|
|
return "", fmt.Errorf("Creem API resp no checkout url ")
|
|
return "", fmt.Errorf("Creem API resp no checkout url ")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- log.Printf("Creem 支付链接创建成功 - 订单号: %s, 支付链接: %s", referenceId, checkoutResp.CheckoutUrl)
|
|
|
|
|
|
|
+ logger.LogInfo(ctx, fmt.Sprintf("Creem 支付链接创建成功 trade_no=%s response_id=%s checkout_url=%q", referenceId, checkoutResp.Id, checkoutResp.CheckoutUrl))
|
|
|
return checkoutResp.CheckoutUrl, nil
|
|
return checkoutResp.CheckoutUrl, nil
|
|
|
}
|
|
}
|