526 lines
16 KiB
Go
526 lines
16 KiB
Go
|
|
package handler
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/json"
|
|||
|
|
"fmt"
|
|||
|
|
"math"
|
|||
|
|
"net/http"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"soul-api/internal/database"
|
|||
|
|
"soul-api/internal/model"
|
|||
|
|
|
|||
|
|
"github.com/gin-gonic/gin"
|
|||
|
|
"gorm.io/gorm"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const defaultBindingDays = 30
|
|||
|
|
|
|||
|
|
// ReferralBind POST /api/referral/bind 推荐码绑定(新绑定/续期/切换)
|
|||
|
|
func ReferralBind(c *gin.Context) {
|
|||
|
|
var req struct {
|
|||
|
|
UserID string `json:"userId"`
|
|||
|
|
ReferralCode string `json:"referralCode" binding:"required"`
|
|||
|
|
OpenID string `json:"openId"`
|
|||
|
|
Source string `json:"source"`
|
|||
|
|
}
|
|||
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|||
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "用户ID和推荐码不能为空"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
effectiveUserID := req.UserID
|
|||
|
|
if effectiveUserID == "" && req.OpenID != "" {
|
|||
|
|
effectiveUserID = "user_" + req.OpenID[len(req.OpenID)-8:]
|
|||
|
|
}
|
|||
|
|
if effectiveUserID == "" || req.ReferralCode == "" {
|
|||
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "用户ID和推荐码不能为空"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
db := database.DB()
|
|||
|
|
bindingDays := defaultBindingDays
|
|||
|
|
var cfg model.SystemConfig
|
|||
|
|
if err := db.Where("config_key = ?", "referral_config").First(&cfg).Error; err == nil {
|
|||
|
|
var config map[string]interface{}
|
|||
|
|
if _ = json.Unmarshal(cfg.ConfigValue, &config); config["bindingDays"] != nil {
|
|||
|
|
if v, ok := config["bindingDays"].(float64); ok {
|
|||
|
|
bindingDays = int(v)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var referrer model.User
|
|||
|
|
if err := db.Where("referral_code = ?", req.ReferralCode).First(&referrer).Error; err != nil {
|
|||
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "推荐码无效"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if referrer.ID == effectiveUserID {
|
|||
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "不能使用自己的推荐码"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var user model.User
|
|||
|
|
if err := db.Where("id = ?", effectiveUserID).First(&user).Error; err != nil {
|
|||
|
|
if req.OpenID != "" {
|
|||
|
|
if err := db.Where("open_id = ?", req.OpenID).First(&user).Error; err != nil {
|
|||
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "用户不存在"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "用户不存在"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
expiryDate := time.Now().AddDate(0, 0, bindingDays)
|
|||
|
|
var existing model.ReferralBinding
|
|||
|
|
err := db.Where("referee_id = ? AND status = ?", user.ID, "active").Order("binding_date DESC").First(&existing).Error
|
|||
|
|
action := "new"
|
|||
|
|
var oldReferrerID interface{}
|
|||
|
|
|
|||
|
|
if err == nil {
|
|||
|
|
if existing.ReferrerID == referrer.ID {
|
|||
|
|
action = "renew"
|
|||
|
|
db.Model(&existing).Updates(map[string]interface{}{
|
|||
|
|
"expiry_date": expiryDate,
|
|||
|
|
"binding_date": time.Now(),
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
action = "switch"
|
|||
|
|
oldReferrerID = existing.ReferrerID
|
|||
|
|
db.Model(&existing).Update("status", "cancelled")
|
|||
|
|
bindID := fmt.Sprintf("bind_%d_%s", time.Now().UnixNano(), randomStr(6))
|
|||
|
|
db.Create(&model.ReferralBinding{
|
|||
|
|
ID: bindID,
|
|||
|
|
ReferrerID: referrer.ID,
|
|||
|
|
RefereeID: user.ID,
|
|||
|
|
ReferralCode: req.ReferralCode,
|
|||
|
|
Status: refString("active"),
|
|||
|
|
ExpiryDate: expiryDate,
|
|||
|
|
BindingDate: time.Now(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
bindID := fmt.Sprintf("bind_%d_%s", time.Now().UnixNano(), randomStr(6))
|
|||
|
|
db.Create(&model.ReferralBinding{
|
|||
|
|
ID: bindID,
|
|||
|
|
ReferrerID: referrer.ID,
|
|||
|
|
RefereeID: user.ID,
|
|||
|
|
ReferralCode: req.ReferralCode,
|
|||
|
|
Status: refString("active"),
|
|||
|
|
ExpiryDate: expiryDate,
|
|||
|
|
BindingDate: time.Now(),
|
|||
|
|
})
|
|||
|
|
db.Model(&model.User{}).Where("id = ?", referrer.ID).UpdateColumn("referral_count", gorm.Expr("COALESCE(referral_count, 0) + 1"))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
msg := "绑定成功"
|
|||
|
|
if action == "renew" {
|
|||
|
|
msg = "绑定已续期"
|
|||
|
|
} else if action == "switch" {
|
|||
|
|
msg = "推荐人已切换"
|
|||
|
|
}
|
|||
|
|
c.JSON(http.StatusOK, gin.H{
|
|||
|
|
"success": true,
|
|||
|
|
"message": msg,
|
|||
|
|
"data": gin.H{
|
|||
|
|
"action": action,
|
|||
|
|
"referrer": gin.H{"id": referrer.ID, "nickname": getStringValue(referrer.Nickname)},
|
|||
|
|
"expiryDate": expiryDate,
|
|||
|
|
"bindingDays": bindingDays,
|
|||
|
|
"oldReferrerId": oldReferrerID,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func refString(s string) *string { return &s }
|
|||
|
|
func randomStr(n int) string {
|
|||
|
|
const letters = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|||
|
|
b := make([]byte, n)
|
|||
|
|
for i := range b {
|
|||
|
|
b[i] = letters[time.Now().UnixNano()%int64(len(letters))]
|
|||
|
|
}
|
|||
|
|
return string(b)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ReferralData GET /api/referral/data 获取分销数据统计
|
|||
|
|
func ReferralData(c *gin.Context) {
|
|||
|
|
userId := c.Query("userId")
|
|||
|
|
if userId == "" {
|
|||
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "用户ID不能为空"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
db := database.DB()
|
|||
|
|
|
|||
|
|
// 获取分销配置(与 soul-admin 推广设置一致)
|
|||
|
|
distributorShare := 0.9
|
|||
|
|
minWithdrawAmount := 10.0
|
|||
|
|
bindingDays := defaultBindingDays
|
|||
|
|
userDiscount := 5
|
|||
|
|
withdrawFee := 5.0
|
|||
|
|
|
|||
|
|
var cfg model.SystemConfig
|
|||
|
|
if err := db.Where("config_key = ?", "referral_config").First(&cfg).Error; err == nil {
|
|||
|
|
var config map[string]interface{}
|
|||
|
|
if err := json.Unmarshal(cfg.ConfigValue, &config); err == nil {
|
|||
|
|
if share, ok := config["distributorShare"].(float64); ok {
|
|||
|
|
distributorShare = share / 100
|
|||
|
|
}
|
|||
|
|
if minAmount, ok := config["minWithdrawAmount"].(float64); ok {
|
|||
|
|
minWithdrawAmount = minAmount
|
|||
|
|
}
|
|||
|
|
if days, ok := config["bindingDays"].(float64); ok && days > 0 {
|
|||
|
|
bindingDays = int(days)
|
|||
|
|
}
|
|||
|
|
if discount, ok := config["userDiscount"].(float64); ok {
|
|||
|
|
userDiscount = int(discount)
|
|||
|
|
}
|
|||
|
|
if fee, ok := config["withdrawFee"].(float64); ok {
|
|||
|
|
withdrawFee = fee
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 1. 查询用户基本信息
|
|||
|
|
var user model.User
|
|||
|
|
if err := db.Where("id = ?", userId).First(&user).Error; err != nil {
|
|||
|
|
c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "用户不存在"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 绑定统计
|
|||
|
|
var totalBindings int64
|
|||
|
|
db.Model(&model.ReferralBinding{}).Where("referrer_id = ?", userId).Count(&totalBindings)
|
|||
|
|
|
|||
|
|
var activeBindings int64
|
|||
|
|
db.Model(&model.ReferralBinding{}).Where(
|
|||
|
|
"referrer_id = ? AND status = 'active' AND expiry_date > ?",
|
|||
|
|
userId, time.Now(),
|
|||
|
|
).Count(&activeBindings)
|
|||
|
|
|
|||
|
|
var convertedBindings int64
|
|||
|
|
db.Model(&model.ReferralBinding{}).Where(
|
|||
|
|
"referrer_id = ? AND status = 'active' AND purchase_count > 0",
|
|||
|
|
userId,
|
|||
|
|
).Count(&convertedBindings)
|
|||
|
|
|
|||
|
|
var expiredBindings int64
|
|||
|
|
db.Model(&model.ReferralBinding{}).Where(
|
|||
|
|
"referrer_id = ? AND (status IN ('expired', 'cancelled') OR (status = 'active' AND expiry_date <= ?))",
|
|||
|
|
userId, time.Now(),
|
|||
|
|
).Count(&expiredBindings)
|
|||
|
|
|
|||
|
|
// 3. 付款统计
|
|||
|
|
var paidOrders []model.Order
|
|||
|
|
db.Where("referrer_id = ? AND status = ?", userId, "paid").Find(&paidOrders)
|
|||
|
|
|
|||
|
|
totalAmount := 0.0
|
|||
|
|
totalCommission := 0.0
|
|||
|
|
uniqueUsers := make(map[string]bool)
|
|||
|
|
for i := range paidOrders {
|
|||
|
|
totalAmount += paidOrders[i].Amount
|
|||
|
|
totalCommission += computeOrderCommission(db, &paidOrders[i], nil)
|
|||
|
|
uniqueUsers[paidOrders[i].UserID] = true
|
|||
|
|
}
|
|||
|
|
uniquePaidCount := len(uniqueUsers)
|
|||
|
|
|
|||
|
|
// 4. 访问统计
|
|||
|
|
totalVisits := int(totalBindings)
|
|||
|
|
var visitCount int64
|
|||
|
|
if err := db.Model(&model.ReferralVisit{}).
|
|||
|
|
Select("COUNT(DISTINCT visitor_id) as count").
|
|||
|
|
Where("referrer_id = ?", userId).
|
|||
|
|
Count(&visitCount).Error; err == nil {
|
|||
|
|
totalVisits = int(visitCount)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 5. 提现统计(与小程序可提现逻辑一致:可提现 = 累计佣金 - 已提现 - 待审核)
|
|||
|
|
// 待审核 = pending + processing + pending_confirm,与 /api/withdraw/pending-confirm 口径一致
|
|||
|
|
var pendingWithdraw struct{ Total float64 }
|
|||
|
|
db.Model(&model.Withdrawal{}).
|
|||
|
|
Select("COALESCE(SUM(amount), 0) as total").
|
|||
|
|
Where("user_id = ? AND status IN ?", userId, []string{"pending", "processing", "pending_confirm"}).
|
|||
|
|
Scan(&pendingWithdraw)
|
|||
|
|
|
|||
|
|
var successWithdraw struct{ Total float64 }
|
|||
|
|
db.Model(&model.Withdrawal{}).
|
|||
|
|
Select("COALESCE(SUM(amount), 0) as total").
|
|||
|
|
Where("user_id = ? AND status = ?", userId, "success").
|
|||
|
|
Scan(&successWithdraw)
|
|||
|
|
|
|||
|
|
pendingWithdrawAmount := pendingWithdraw.Total
|
|||
|
|
withdrawnFromTable := successWithdraw.Total
|
|||
|
|
|
|||
|
|
// 6. 获取活跃绑定用户列表
|
|||
|
|
var activeBindingsList []model.ReferralBinding
|
|||
|
|
db.Where("referrer_id = ? AND status = 'active' AND expiry_date > ?", userId, time.Now()).
|
|||
|
|
Order("binding_date DESC").
|
|||
|
|
Limit(20).
|
|||
|
|
Find(&activeBindingsList)
|
|||
|
|
|
|||
|
|
activeUsers := []gin.H{}
|
|||
|
|
for _, b := range activeBindingsList {
|
|||
|
|
var referee model.User
|
|||
|
|
db.Where("id = ?", b.RefereeID).First(&referee)
|
|||
|
|
|
|||
|
|
daysRemaining := int(time.Until(b.ExpiryDate).Hours() / 24)
|
|||
|
|
if daysRemaining < 0 {
|
|||
|
|
daysRemaining = 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
activeUsers = append(activeUsers, gin.H{
|
|||
|
|
"id": b.RefereeID,
|
|||
|
|
"nickname": getStringValue(referee.Nickname),
|
|||
|
|
"avatar": getStringValue(referee.Avatar),
|
|||
|
|
"daysRemaining": daysRemaining,
|
|||
|
|
"hasFullBook": getBoolValue(referee.HasFullBook),
|
|||
|
|
"bindingDate": b.BindingDate,
|
|||
|
|
"status": "active",
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 7. 获取已转化用户列表
|
|||
|
|
var convertedBindingsList []model.ReferralBinding
|
|||
|
|
db.Where("referrer_id = ? AND status = 'active' AND purchase_count > 0", userId).
|
|||
|
|
Order("last_purchase_date DESC").
|
|||
|
|
Limit(20).
|
|||
|
|
Find(&convertedBindingsList)
|
|||
|
|
|
|||
|
|
convertedUsers := []gin.H{}
|
|||
|
|
for _, b := range convertedBindingsList {
|
|||
|
|
var referee model.User
|
|||
|
|
db.Where("id = ?", b.RefereeID).First(&referee)
|
|||
|
|
|
|||
|
|
commission := 0.0
|
|||
|
|
if b.TotalCommission != nil {
|
|||
|
|
commission = *b.TotalCommission
|
|||
|
|
}
|
|||
|
|
orderAmount := commission / distributorShare
|
|||
|
|
|
|||
|
|
convertedUsers = append(convertedUsers, gin.H{
|
|||
|
|
"id": b.RefereeID,
|
|||
|
|
"nickname": getStringValue(referee.Nickname),
|
|||
|
|
"avatar": getStringValue(referee.Avatar),
|
|||
|
|
"commission": commission,
|
|||
|
|
"orderAmount": orderAmount,
|
|||
|
|
"purchaseCount": getIntValue(b.PurchaseCount),
|
|||
|
|
"conversionDate": b.LastPurchaseDate,
|
|||
|
|
"status": "converted",
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 8. 获取已过期用户列表
|
|||
|
|
var expiredBindingsList []model.ReferralBinding
|
|||
|
|
db.Where(
|
|||
|
|
"referrer_id = ? AND (status = 'expired' OR (status = 'active' AND expiry_date <= ?))",
|
|||
|
|
userId, time.Now(),
|
|||
|
|
).Order("expiry_date DESC").Limit(20).Find(&expiredBindingsList)
|
|||
|
|
|
|||
|
|
expiredUsers := []gin.H{}
|
|||
|
|
for _, b := range expiredBindingsList {
|
|||
|
|
var referee model.User
|
|||
|
|
db.Where("id = ?", b.RefereeID).First(&referee)
|
|||
|
|
|
|||
|
|
expiredUsers = append(expiredUsers, gin.H{
|
|||
|
|
"id": b.RefereeID,
|
|||
|
|
"nickname": getStringValue(referee.Nickname),
|
|||
|
|
"avatar": getStringValue(referee.Avatar),
|
|||
|
|
"bindingDate": b.BindingDate,
|
|||
|
|
"expiryDate": b.ExpiryDate,
|
|||
|
|
"status": "expired",
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 9. 获取收益明细
|
|||
|
|
var earningsDetailsList []model.Order
|
|||
|
|
db.Where("referrer_id = ? AND status = 'paid'", userId).
|
|||
|
|
Order("pay_time DESC").
|
|||
|
|
Limit(20).
|
|||
|
|
Find(&earningsDetailsList)
|
|||
|
|
|
|||
|
|
earningsDetails := []gin.H{}
|
|||
|
|
for i := range earningsDetailsList {
|
|||
|
|
e := &earningsDetailsList[i]
|
|||
|
|
var buyer model.User
|
|||
|
|
db.Where("id = ?", e.UserID).First(&buyer)
|
|||
|
|
|
|||
|
|
commission := computeOrderCommission(db, e, nil)
|
|||
|
|
earningsDetails = append(earningsDetails, gin.H{
|
|||
|
|
"id": e.ID,
|
|||
|
|
"orderSn": e.OrderSN,
|
|||
|
|
"amount": e.Amount,
|
|||
|
|
"commission": commission,
|
|||
|
|
"productType": e.ProductType,
|
|||
|
|
"productId": getStringValue(e.ProductID),
|
|||
|
|
"description": getStringValue(e.Description),
|
|||
|
|
"buyerNickname": getStringValue(buyer.Nickname),
|
|||
|
|
"buyerAvatar": getStringValue(buyer.Avatar),
|
|||
|
|
"payTime": e.PayTime,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算收益(totalCommission 已按订单逐条计算)
|
|||
|
|
estimatedEarnings := totalCommission
|
|||
|
|
availableEarnings := totalCommission - withdrawnFromTable - pendingWithdrawAmount
|
|||
|
|
if availableEarnings < 0 {
|
|||
|
|
availableEarnings = 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算即将过期用户数(7天内)
|
|||
|
|
sevenDaysLater := time.Now().Add(7 * 24 * time.Hour)
|
|||
|
|
expiringCount := 0
|
|||
|
|
for _, b := range activeBindingsList {
|
|||
|
|
if b.ExpiryDate.After(time.Now()) && b.ExpiryDate.Before(sevenDaysLater) {
|
|||
|
|
expiringCount++
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
c.JSON(http.StatusOK, gin.H{
|
|||
|
|
"success": true,
|
|||
|
|
"data": gin.H{
|
|||
|
|
// 核心可见数据
|
|||
|
|
"bindingCount": activeBindings,
|
|||
|
|
"visitCount": totalVisits,
|
|||
|
|
"paidCount": uniquePaidCount,
|
|||
|
|
"expiredCount": expiredBindings,
|
|||
|
|
|
|||
|
|
// 收益数据
|
|||
|
|
"totalCommission": round(totalCommission, 2),
|
|||
|
|
"availableEarnings": round(availableEarnings, 2),
|
|||
|
|
"pendingWithdrawAmount": round(pendingWithdrawAmount, 2),
|
|||
|
|
"withdrawnEarnings": withdrawnFromTable,
|
|||
|
|
"earnings": getFloatValue(user.Earnings),
|
|||
|
|
"pendingEarnings": getFloatValue(user.PendingEarnings),
|
|||
|
|
"estimatedEarnings": round(estimatedEarnings, 2),
|
|||
|
|
"shareRate": int(distributorShare * 100),
|
|||
|
|
"minWithdrawAmount": minWithdrawAmount,
|
|||
|
|
"bindingDays": bindingDays,
|
|||
|
|
"userDiscount": userDiscount,
|
|||
|
|
"withdrawFee": withdrawFee,
|
|||
|
|
|
|||
|
|
// 推荐码
|
|||
|
|
"referralCode": getStringValue(user.ReferralCode),
|
|||
|
|
"referralCount": getIntValue(user.ReferralCount),
|
|||
|
|
|
|||
|
|
// 详细统计
|
|||
|
|
"stats": gin.H{
|
|||
|
|
"totalBindings": totalBindings,
|
|||
|
|
"activeBindings": activeBindings,
|
|||
|
|
"convertedBindings": convertedBindings,
|
|||
|
|
"expiredBindings": expiredBindings,
|
|||
|
|
"expiringCount": expiringCount,
|
|||
|
|
"totalPaymentAmount": totalAmount,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 用户列表
|
|||
|
|
"activeUsers": activeUsers,
|
|||
|
|
"convertedUsers": convertedUsers,
|
|||
|
|
"expiredUsers": expiredUsers,
|
|||
|
|
|
|||
|
|
// 收益明细
|
|||
|
|
"earningsDetails": earningsDetails,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// round 四舍五入保留小数
|
|||
|
|
func round(val float64, precision int) float64 {
|
|||
|
|
ratio := math.Pow(10, float64(precision))
|
|||
|
|
return math.Round(val*ratio) / ratio
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MyEarnings GET /api/miniprogram/earnings 仅返回「我的收益」卡片所需数据(累计、可提现、推荐人数),用于我的页展示与刷新
|
|||
|
|
func MyEarnings(c *gin.Context) {
|
|||
|
|
userId := c.Query("userId")
|
|||
|
|
if userId == "" {
|
|||
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "用户ID不能为空"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
db := database.DB()
|
|||
|
|
var user model.User
|
|||
|
|
if err := db.Where("id = ?", userId).First(&user).Error; err != nil {
|
|||
|
|
c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "用户不存在"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
var paidOrders []model.Order
|
|||
|
|
db.Where("referrer_id = ? AND status = ?", userId, "paid").Find(&paidOrders)
|
|||
|
|
totalCommission := 0.0
|
|||
|
|
for i := range paidOrders {
|
|||
|
|
totalCommission += computeOrderCommission(db, &paidOrders[i], nil)
|
|||
|
|
}
|
|||
|
|
var pendingWithdraw struct{ Total float64 }
|
|||
|
|
db.Model(&model.Withdrawal{}).
|
|||
|
|
Select("COALESCE(SUM(amount), 0) as total").
|
|||
|
|
Where("user_id = ? AND status IN ?", userId, []string{"pending", "processing", "pending_confirm"}).
|
|||
|
|
Scan(&pendingWithdraw)
|
|||
|
|
var successWithdraw struct{ Total float64 }
|
|||
|
|
db.Model(&model.Withdrawal{}).
|
|||
|
|
Select("COALESCE(SUM(amount), 0) as total").
|
|||
|
|
Where("user_id = ? AND status = ?", userId, "success").
|
|||
|
|
Scan(&successWithdraw)
|
|||
|
|
pendingWithdrawAmount := pendingWithdraw.Total
|
|||
|
|
withdrawnFromTable := successWithdraw.Total
|
|||
|
|
availableEarnings := totalCommission - withdrawnFromTable - pendingWithdrawAmount
|
|||
|
|
if availableEarnings < 0 {
|
|||
|
|
availableEarnings = 0
|
|||
|
|
}
|
|||
|
|
c.JSON(http.StatusOK, gin.H{
|
|||
|
|
"success": true,
|
|||
|
|
"data": gin.H{
|
|||
|
|
"totalCommission": round(totalCommission, 2),
|
|||
|
|
"availableEarnings": round(availableEarnings, 2),
|
|||
|
|
"referralCount": getIntValue(user.ReferralCount),
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ReferralVisit POST /api/referral/visit 记录推荐访问(不需登录)
|
|||
|
|
func ReferralVisit(c *gin.Context) {
|
|||
|
|
var req struct {
|
|||
|
|
ReferralCode string `json:"referralCode" binding:"required"`
|
|||
|
|
VisitorOpenID string `json:"visitorOpenId"`
|
|||
|
|
VisitorID string `json:"visitorId"`
|
|||
|
|
Source string `json:"source"`
|
|||
|
|
Page string `json:"page"`
|
|||
|
|
}
|
|||
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|||
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "推荐码不能为空"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
db := database.DB()
|
|||
|
|
var referrer model.User
|
|||
|
|
if err := db.Where("referral_code = ?", req.ReferralCode).First(&referrer).Error; err != nil {
|
|||
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "推荐码无效"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
source := req.Source
|
|||
|
|
if source == "" {
|
|||
|
|
source = "miniprogram"
|
|||
|
|
}
|
|||
|
|
visitorID := req.VisitorID
|
|||
|
|
if visitorID == "" {
|
|||
|
|
visitorID = ""
|
|||
|
|
}
|
|||
|
|
vOpenID := req.VisitorOpenID
|
|||
|
|
vPage := req.Page
|
|||
|
|
err := db.Create(&model.ReferralVisit{
|
|||
|
|
ReferrerID: referrer.ID,
|
|||
|
|
VisitorID: strPtrOrNil(visitorID),
|
|||
|
|
VisitorOpenID: strPtrOrNil(vOpenID),
|
|||
|
|
Source: strPtrOrNil(source),
|
|||
|
|
Page: strPtrOrNil(vPage),
|
|||
|
|
}).Error
|
|||
|
|
if err != nil {
|
|||
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "已处理"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "访问已记录"})
|
|||
|
|
}
|
|||
|
|
func strPtrOrNil(s string) *string {
|
|||
|
|
if s == "" {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
return &s
|
|||
|
|
}
|