feat(topup): add admin-only audit info to top-up logs

Thread caller IP from webhook/admin controllers through model recharge
functions and record a new RecordTopupLog entry with admin_info (server
IP, caller IP, order payment method, callback payment method, system
version). Frontend shows these fields in the expanded log row and the
IP column for admins on top-up logs, while non-admins continue to see
admin_info stripped by formatUserLogs.
This commit is contained in:
CaIon
2026-04-17 23:51:30 +08:00
parent e2807c5f95
commit 209d90e861
15 changed files with 3570 additions and 1617 deletions
+2 -2
View File
@@ -362,7 +362,7 @@ func EpayNotify(c *gin.Context) {
return
}
log.Printf("易支付回调更新用户成功 %v", topUp)
model.RecordLog(topUp.UserId, model.LogTypeTopup, fmt.Sprintf("使用在线充值成功,充值金额: %v,支付金额:%f", logger.LogQuota(quotaToAdd), topUp.Money))
model.RecordTopupLog(topUp.UserId, fmt.Sprintf("使用在线充值成功,充值金额: %v,支付金额:%f", logger.LogQuota(quotaToAdd), topUp.Money), c.ClientIP(), topUp.PaymentMethod, "epay")
}
} else {
log.Printf("易支付异常回调: %v", verifyInfo)
@@ -461,7 +461,7 @@ func AdminCompleteTopUp(c *gin.Context) {
LockOrder(req.TradeNo)
defer UnlockOrder(req.TradeNo)
if err := model.ManualCompleteTopUp(req.TradeNo); err != nil {
if err := model.ManualCompleteTopUp(req.TradeNo, c.ClientIP()); err != nil {
common.ApiError(c, err)
return
}
+1 -1
View File
@@ -353,7 +353,7 @@ func handleCheckoutCompleted(c *gin.Context, event *CreemWebhookEvent) {
log.Printf("警告:Creem回调中客户姓名为空 - 订单号: %s", referenceId)
}
err := model.RechargeCreem(referenceId, customerEmail, customerName)
err := model.RechargeCreem(referenceId, customerEmail, customerName, c.ClientIP())
if err != nil {
log.Printf("Creem充值处理失败: %s, 订单号: %s", err.Error(), referenceId)
c.AbortWithStatus(http.StatusInternalServerError)
+11 -10
View File
@@ -170,15 +170,16 @@ func StripeWebhook(c *gin.Context) {
return
}
callerIp := c.ClientIP()
switch event.Type {
case stripe.EventTypeCheckoutSessionCompleted:
sessionCompleted(event)
sessionCompleted(event, callerIp)
case stripe.EventTypeCheckoutSessionExpired:
sessionExpired(event)
case stripe.EventTypeCheckoutSessionAsyncPaymentSucceeded:
sessionAsyncPaymentSucceeded(event)
sessionAsyncPaymentSucceeded(event, callerIp)
case stripe.EventTypeCheckoutSessionAsyncPaymentFailed:
sessionAsyncPaymentFailed(event)
sessionAsyncPaymentFailed(event, callerIp)
default:
log.Printf("不支持的Stripe Webhook事件类型: %s\n", event.Type)
}
@@ -186,7 +187,7 @@ func StripeWebhook(c *gin.Context) {
c.Status(http.StatusOK)
}
func sessionCompleted(event stripe.Event) {
func sessionCompleted(event stripe.Event, callerIp string) {
customerId := event.GetObjectValue("customer")
referenceId := event.GetObjectValue("client_reference_id")
status := event.GetObjectValue("status")
@@ -201,22 +202,22 @@ func sessionCompleted(event stripe.Event) {
return
}
fulfillOrder(event, referenceId, customerId)
fulfillOrder(event, referenceId, customerId, callerIp)
}
// sessionAsyncPaymentSucceeded handles delayed payment methods (bank transfer, SEPA, etc.)
// that confirm payment after the checkout session completes.
func sessionAsyncPaymentSucceeded(event stripe.Event) {
func sessionAsyncPaymentSucceeded(event stripe.Event, callerIp string) {
customerId := event.GetObjectValue("customer")
referenceId := event.GetObjectValue("client_reference_id")
log.Printf("Stripe 异步支付成功: %s", referenceId)
fulfillOrder(event, referenceId, customerId)
fulfillOrder(event, referenceId, customerId, callerIp)
}
// sessionAsyncPaymentFailed marks orders as failed when delayed payment methods
// ultimately fail (e.g. bank transfer not received, SEPA rejected).
func sessionAsyncPaymentFailed(event stripe.Event) {
func sessionAsyncPaymentFailed(event stripe.Event, callerIp string) {
referenceId := event.GetObjectValue("client_reference_id")
log.Printf("Stripe 异步支付失败: %s", referenceId)
@@ -253,7 +254,7 @@ func sessionAsyncPaymentFailed(event stripe.Event) {
}
// fulfillOrder is the shared logic for crediting quota after payment is confirmed.
func fulfillOrder(event stripe.Event, referenceId string, customerId string) {
func fulfillOrder(event stripe.Event, referenceId string, customerId string, callerIp string) {
if len(referenceId) == 0 {
log.Println("未提供支付单号")
return
@@ -274,7 +275,7 @@ func fulfillOrder(event stripe.Event, referenceId string, customerId string) {
return
}
err := model.Recharge(referenceId, customerId)
err := model.Recharge(referenceId, customerId, callerIp)
if err != nil {
log.Println(err.Error(), referenceId)
return
+1 -1
View File
@@ -357,7 +357,7 @@ func handleWaffoPayment(c *gin.Context, wh *core.WebhookHandler, result *core.Pa
LockOrder(merchantOrderId)
defer UnlockOrder(merchantOrderId)
if err := model.RechargeWaffo(merchantOrderId); err != nil {
if err := model.RechargeWaffo(merchantOrderId, c.ClientIP()); err != nil {
log.Printf("Waffo 充值处理失败: %v, 订单: %s", err, merchantOrderId)
sendWaffoWebhookResponse(c, wh, false, err.Error())
return