package handler import ( "crypto/rand" "encoding/hex" "encoding/json" "errors" "fmt" "net/http" "net/url" "strconv" "strings" "time" "xboard-go/internal/database" "xboard-go/internal/model" "xboard-go/internal/service" "xboard-go/pkg/utils" "github.com/gin-gonic/gin" "gorm.io/gorm" "gorm.io/gorm/clause" ) func UserCommConfig(c *gin.Context) { data := buildGuestConfig() data["is_telegram"] = boolToInt(service.MustGetBool("telegram_bot_enable", false)) data["telegram_discuss_link"] = service.MustGetString("telegram_discuss_link", "") data["stripe_pk"] = service.MustGetString("stripe_pk_live", "") data["withdraw_methods"] = service.MustGetString("commission_withdraw_method", "[]") data["withdraw_close"] = boolToInt(service.MustGetBool("withdraw_close_enable", false)) data["currency"] = service.MustGetString("currency", "CNY") data["currency_symbol"] = service.MustGetString("currency_symbol", "CNY") data["commission_distribution_enable"] = boolToInt(service.MustGetBool("commission_distribution_enable", false)) data["commission_distribution_l1"] = service.MustGetString("commission_distribution_l1", "0") data["commission_distribution_l2"] = service.MustGetString("commission_distribution_l2", "0") data["commission_distribution_l3"] = service.MustGetString("commission_distribution_l3", "0") Success(c, data) } func UserTransfer(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } var req struct { TransferAmount int64 `json:"transfer_amount"` } if err := c.ShouldBindJSON(&req); err != nil || req.TransferAmount <= 0 { Fail(c, http.StatusBadRequest, "invalid transfer amount") return } err := database.DB.Transaction(func(tx *gorm.DB) error { var locked model.User if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", user.ID).First(&locked).Error; err != nil { return err } if req.TransferAmount > int64(locked.CommissionBalance) { return errors.New("insufficient commission balance") } updates := map[string]any{ "commission_balance": int64(locked.CommissionBalance) - req.TransferAmount, "balance": int64(locked.Balance) + req.TransferAmount, "updated_at": time.Now().Unix(), } return tx.Model(&model.User{}).Where("id = ?", locked.ID).Updates(updates).Error }) if err != nil { Fail(c, http.StatusBadRequest, err.Error()) return } Success(c, true) } func UserGetQuickLoginURL(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } Success(c, quickLoginURL(c, user.ID, c.PostForm("redirect"), c.Query("redirect"))) } func PassportGetQuickLoginURL(c *gin.Context) { token := strings.TrimSpace(c.GetHeader("Authorization")) if token == "" { token = strings.TrimSpace(c.PostForm("auth_data")) } if token == "" { var req struct { AuthData string `json:"auth_data"` Redirect string `json:"redirect"` } _ = c.ShouldBindJSON(&req) token = strings.TrimSpace(req.AuthData) if token != "" { c.Set("quick_login_redirect", req.Redirect) } } token = strings.TrimSpace(strings.TrimPrefix(token, "Bearer ")) if token == "" { Fail(c, http.StatusUnauthorized, "authorization is required") return } claims, err := utils.VerifyToken(token) if err != nil { Fail(c, http.StatusUnauthorized, "token expired or invalid") return } redirect := strings.TrimSpace(c.Query("redirect")) if redirect == "" { redirect = strings.TrimSpace(c.PostForm("redirect")) } if redirect == "" { if value, exists := c.Get("quick_login_redirect"); exists { redirect, _ = value.(string) } } Success(c, quickLoginURL(c, claims.UserID, redirect)) } func PassportLoginWithMailLink(c *gin.Context) { var req struct { Email string `json:"email" binding:"required,email"` Redirect string `json:"redirect"` } if err := c.ShouldBindJSON(&req); err != nil { Fail(c, http.StatusBadRequest, "invalid request body") return } var user model.User if err := database.DB.Where("email = ?", strings.ToLower(strings.TrimSpace(req.Email))).First(&user).Error; err != nil { Fail(c, http.StatusBadRequest, "user not found") return } Success(c, gin.H{ "url": quickLoginURL(c, user.ID, req.Redirect), "email": user.Email, "expires_in": 900, }) } func PassportPV(c *gin.Context) { code := strings.TrimSpace(c.PostForm("invite_code")) if code == "" { var req struct { InviteCode string `json:"invite_code"` } _ = c.ShouldBindJSON(&req) code = strings.TrimSpace(req.InviteCode) } if code != "" { _ = database.DB.Model(&model.InviteCode{}). Where("code = ?", code). UpdateColumn("pv", gorm.Expr("pv + ?", 1)).Error } Success(c, true) } func UserNoticeFetch(c *gin.Context) { current := parsePositiveInt(c.DefaultQuery("current", "1"), 1) pageSize := 5 query := database.DB.Model(&model.Notice{}).Where("`show` = ?", 1).Order("sort ASC").Order("id DESC") var total int64 query.Count(&total) var notices []model.Notice if err := query.Offset((current - 1) * pageSize).Limit(pageSize).Find(¬ices).Error; err != nil { Fail(c, http.StatusInternalServerError, "failed to fetch notices") return } Success(c, gin.H{ "data": notices, "total": total, }) } func UserTrafficLog(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } now := time.Now() startDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).Unix() var records []model.StatUser if err := database.DB.Where("user_id = ? AND record_at >= ?", user.ID, startDate). Order("record_at DESC"). Find(&records).Error; err != nil { Fail(c, http.StatusInternalServerError, "failed to fetch traffic log") return } items := make([]gin.H, 0, len(records)) for _, record := range records { items = append(items, gin.H{ "user_id": record.UserID, "u": record.U, "d": record.D, "record_at": record.RecordAt, "server_rate": 1, }) } Success(c, items) } func UserInviteSave(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } limit := service.MustGetInt("invite_gen_limit", 5) var count int64 database.DB.Model(&model.InviteCode{}). Where("user_id = ? AND status = ?", user.ID, 0). Count(&count) if int(count) >= limit { Fail(c, http.StatusBadRequest, "the maximum number of creations has been reached") return } inviteCode := model.InviteCode{ UserID: user.ID, Code: randomAlphaNum(8), Status: false, CreatedAt: time.Now().Unix(), UpdatedAt: time.Now().Unix(), } if err := database.DB.Create(&inviteCode).Error; err != nil { Fail(c, http.StatusInternalServerError, "failed to create invite code") return } Success(c, true) } func UserInviteFetch(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } commissionRate := service.MustGetInt("invite_commission", 10) if user.CommissionRate != nil { commissionRate = *user.CommissionRate } var codes []model.InviteCode _ = database.DB.Where("user_id = ? AND status = ?", user.ID, 0).Order("id DESC").Find(&codes).Error var totalInvited int64 var confirmedCommission int64 var pendingCommission int64 database.DB.Model(&model.User{}).Where("invite_user_id = ?", user.ID).Count(&totalInvited) database.DB.Model(&model.CommissionLog{}).Where("invite_user_id = ? AND get_amount > 0", user.ID).Select("COALESCE(SUM(get_amount), 0)").Scan(&confirmedCommission) database.DB.Model(&model.Order{}).Where("status = ? AND commission_status = ? AND invite_user_id = ?", 3, 0, user.ID).Select("COALESCE(SUM(commission_balance), 0)").Scan(&pendingCommission) if service.MustGetBool("commission_distribution_enable", false) { pendingCommission = int64(float64(pendingCommission) * float64(service.MustGetInt("commission_distribution_l1", 100)) / 100) } Success(c, gin.H{ "codes": codes, "stat": []int64{ totalInvited, confirmedCommission, pendingCommission, int64(commissionRate), int64(user.CommissionBalance), }, }) } func UserInviteDetails(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } current := parsePositiveInt(c.DefaultQuery("current", "1"), 1) pageSize := parsePositiveInt(c.DefaultQuery("page_size", "10"), 10) if pageSize < 10 { pageSize = 10 } query := database.DB.Model(&model.CommissionLog{}). Where("invite_user_id = ? AND get_amount > 0", user.ID). Order("created_at DESC") var total int64 query.Count(&total) var logs []model.CommissionLog if err := query.Offset((current - 1) * pageSize).Limit(pageSize).Find(&logs).Error; err != nil { Fail(c, http.StatusInternalServerError, "failed to fetch invite details") return } items := make([]gin.H, 0, len(logs)) for _, item := range logs { items = append(items, gin.H{ "id": item.ID, "order_amount": item.OrderAmount, "trade_no": item.TradeNo, "get_amount": item.GetAmount, "created_at": item.CreatedAt, }) } Success(c, gin.H{ "data": items, "total": total, }) } func UserOrderFetch(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } query := database.DB.Preload("Plan").Preload("Payment").Where("user_id = ?", user.ID).Order("created_at DESC") if status := strings.TrimSpace(c.Query("status")); status != "" { query = query.Where("status = ?", status) } var orders []model.Order if err := query.Find(&orders).Error; err != nil { Fail(c, http.StatusInternalServerError, "failed to fetch orders") return } Success(c, normalizeOrders(orders)) } func UserOrderDetail(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } tradeNo := strings.TrimSpace(c.Query("trade_no")) if tradeNo == "" { Fail(c, http.StatusBadRequest, "trade_no is required") return } var order model.Order if err := database.DB.Preload("Plan").Preload("Payment"). Where("user_id = ? AND trade_no = ?", user.ID, tradeNo). First(&order).Error; err != nil { Fail(c, http.StatusBadRequest, "order does not exist or has been paid") return } if order.Plan == nil && order.PlanID != nil { Fail(c, http.StatusBadRequest, "subscription plan does not exist") return } data := normalizeOrder(order) data["try_out_plan_id"] = service.MustGetInt("try_out_plan_id", 0) if ids := parseIntSlice(order.SurplusOrderIDs); len(ids) > 0 { var surplusOrders []model.Order _ = database.DB.Where("id IN ?", ids).Order("id DESC").Find(&surplusOrders).Error data["surplus_orders"] = normalizeOrders(surplusOrders) } Success(c, data) } func UserOrderCheck(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } tradeNo := strings.TrimSpace(c.Query("trade_no")) if tradeNo == "" { Fail(c, http.StatusBadRequest, "trade_no is required") return } var order model.Order if err := database.DB.Select("status").Where("user_id = ? AND trade_no = ?", user.ID, tradeNo).First(&order).Error; err != nil { Fail(c, http.StatusBadRequest, "order does not exist") return } Success(c, order.Status) } func UserOrderGetPaymentMethod(c *gin.Context) { var methods []model.Payment if err := database.DB.Select("id", "name", "payment", "icon", "handling_fee_fixed", "handling_fee_percent"). Where("enable = ?", 1). Order("sort ASC"). Find(&methods).Error; err != nil { Fail(c, http.StatusInternalServerError, "failed to fetch payment methods") return } Success(c, methods) } func UserOrderCancel(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } var req struct { TradeNo string `json:"trade_no"` } if err := c.ShouldBindJSON(&req); err != nil || strings.TrimSpace(req.TradeNo) == "" { Fail(c, http.StatusUnprocessableEntity, "invalid parameter") return } var order model.Order if err := database.DB.Where("trade_no = ? AND user_id = ?", req.TradeNo, user.ID).First(&order).Error; err != nil { Fail(c, http.StatusBadRequest, "order does not exist") return } if order.Status != 0 { Fail(c, http.StatusBadRequest, "you can only cancel pending orders") return } if err := database.DB.Model(&model.Order{}).Where("id = ?", order.ID). Updates(map[string]any{"status": 2, "updated_at": time.Now().Unix()}).Error; err != nil { Fail(c, http.StatusBadRequest, "cancel failed") return } Success(c, true) } func UserOrderSave(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } var req struct { PlanID int `json:"plan_id" binding:"required"` Period string `json:"period" binding:"required"` CouponCode string `json:"coupon_code"` } if err := c.ShouldBindJSON(&req); err != nil { Fail(c, http.StatusBadRequest, "invalid request body") return } var pendingCount int64 database.DB.Model(&model.Order{}).Where("user_id = ? AND status IN ?", user.ID, []int{0, 1}).Count(&pendingCount) if pendingCount > 0 { Fail(c, http.StatusBadRequest, "you have an unpaid or pending order, please try again later or cancel it") return } var plan model.Plan if err := database.DB.First(&plan, req.PlanID).Error; err != nil { Fail(c, http.StatusBadRequest, "subscription plan does not exist") return } totalAmount, ok := resolvePlanPrice(&plan, req.Period) if !ok { Fail(c, http.StatusBadRequest, "the period is not available for this subscription") return } orderType := 1 if user.PlanID != nil { orderType = 2 if *user.PlanID != req.PlanID { orderType = 3 } } order := model.Order{ UserID: user.ID, PlanID: &req.PlanID, Period: req.Period, TradeNo: generateTradeNo(), TotalAmount: totalAmount, Type: orderType, Status: 0, InviteUserID: user.InviteUserID, CreatedAt: time.Now().Unix(), UpdatedAt: time.Now().Unix(), } if couponCode := strings.TrimSpace(req.CouponCode); couponCode != "" { coupon, discount, err := validateCoupon(couponCode, req.PlanID, user.ID, req.Period) if err != nil { Fail(c, http.StatusBadRequest, err.Error()) return } order.CouponID = &coupon.ID order.DiscountAmount = &discount order.TotalAmount -= discount if order.TotalAmount < 0 { order.TotalAmount = 0 } } if err := database.DB.Create(&order).Error; err != nil { Fail(c, http.StatusInternalServerError, "failed to create order") return } Success(c, order.TradeNo) } func UserOrderCheckout(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } var req struct { TradeNo string `json:"trade_no" binding:"required"` Method int `json:"method"` } if err := c.ShouldBindJSON(&req); err != nil { Fail(c, http.StatusBadRequest, "invalid request body") return } var order model.Order if err := database.DB.Where("trade_no = ? AND user_id = ? AND status = ?", req.TradeNo, user.ID, 0).First(&order).Error; err != nil { Fail(c, http.StatusBadRequest, "order does not exist or has been paid") return } if order.TotalAmount <= 0 { now := time.Now().Unix() if err := database.DB.Model(&model.Order{}).Where("id = ?", order.ID). Updates(map[string]any{"status": 3, "paid_at": now, "updated_at": now}).Error; err != nil { Fail(c, http.StatusBadRequest, "payment failed") return } c.JSON(http.StatusOK, gin.H{"type": -1, "data": true}) return } var payment model.Payment if err := database.DB.Where("id = ? AND enable = ?", req.Method, 1).First(&payment).Error; err != nil { Fail(c, http.StatusBadRequest, "payment method is not available") return } var handlingAmount int64 if payment.HandlingFeePercent != nil { handlingAmount += (order.TotalAmount * *payment.HandlingFeePercent) / 100 } if payment.HandlingFeeFixed != nil { handlingAmount += *payment.HandlingFeeFixed } updates := map[string]any{ "payment_id": payment.ID, "updated_at": time.Now().Unix(), "handling_amount": handlingAmount, } if err := database.DB.Model(&model.Order{}).Where("id = ?", order.ID).Updates(updates).Error; err != nil { Fail(c, http.StatusBadRequest, "request failed, please try again later") return } c.JSON(http.StatusOK, gin.H{ "type": 0, "data": gin.H{ "trade_no": order.TradeNo, "payment_id": payment.ID, "payment": payment.Payment, "total_amount": order.TotalAmount + handlingAmount, "message": "payment method recorded; external gateway callback is not required for local validation flows", "handling_fee": handlingAmount, "requires_poll": true, }, }) } func UserCouponCheck(c *gin.Context) { user, ok := currentUser(c) if !ok { Fail(c, http.StatusUnauthorized, "user not found") return } var req struct { Code string `json:"code"` PlanID int `json:"plan_id"` Period string `json:"period"` } if err := c.ShouldBindJSON(&req); err != nil || strings.TrimSpace(req.Code) == "" { Fail(c, http.StatusUnprocessableEntity, "coupon cannot be empty") return } coupon, _, err := validateCoupon(req.Code, req.PlanID, user.ID, req.Period) if err != nil { Fail(c, http.StatusBadRequest, err.Error()) return } Success(c, gin.H{ "id": coupon.ID, "name": coupon.Name, "code": coupon.Code, "type": coupon.Type, "value": coupon.Value, "limit_plan_ids": parseStringSlice(coupon.LimitPlanIDs), "limit_period": parseStringSlice(coupon.LimitPeriod), "started_at": coupon.StartedAt, "ended_at": coupon.EndedAt, "show": coupon.Show, }) } func UserGiftCardCheck(c *gin.Context) { Fail(c, http.StatusBadRequest, "gift card integration is not enabled in the current Go backend") } func UserGiftCardRedeem(c *gin.Context) { Fail(c, http.StatusBadRequest, "gift card integration is not enabled in the current Go backend") } func UserGiftCardHistory(c *gin.Context) { page := parsePositiveInt(c.DefaultQuery("page", "1"), 1) perPage := parsePositiveInt(c.DefaultQuery("per_page", "15"), 15) Success(c, gin.H{ "data": []any{}, "pagination": gin.H{ "current_page": page, "last_page": 1, "per_page": perPage, "total": 0, }, }) } func UserGiftCardDetail(c *gin.Context) { Fail(c, http.StatusNotFound, "record does not exist") } func UserGiftCardTypes(c *gin.Context) { Success(c, gin.H{ "types": map[int]string{ 1: "general", 2: "plan", 3: "mystery", }, }) } func UserTelegramBotInfo(c *gin.Context) { link := service.MustGetString("telegram_discuss_link", "") username := extractTelegramUsername(link) Success(c, gin.H{ "username": username, "link": link, "enabled": service.MustGetBool("telegram_bot_enable", false), }) } func UserGetStripePublicKey(c *gin.Context) { var req struct { ID int `json:"id"` } _ = c.ShouldBindJSON(&req) if req.ID > 0 { var payment model.Payment if err := database.DB.Where("id = ? AND payment = ?", req.ID, "StripeCredit").First(&payment).Error; err == nil { if value := parseConfigString(payment.Config, "stripe_pk_live"); value != "" { Success(c, value) return } } } if pk := service.MustGetString("stripe_pk_live", ""); pk != "" { Success(c, pk) return } Fail(c, http.StatusBadRequest, "payment is not found") } func AdminConfigSave(c *gin.Context) { var payload map[string]any if err := c.ShouldBindJSON(&payload); err != nil { Fail(c, http.StatusBadRequest, "invalid request body") return } now := time.Now() for key, value := range payload { if nested, ok := value.(map[string]any); ok { for nestedKey, nestedValue := range nested { if err := upsertSetting(nestedKey, nestedValue, now); err != nil { Fail(c, http.StatusInternalServerError, "failed to save config") return } } continue } if err := upsertSetting(key, value, now); err != nil { Fail(c, http.StatusInternalServerError, "failed to save config") return } } Success(c, true) } func quickLoginURL(c *gin.Context, userID int, redirectValues ...string) string { redirect := "" for _, candidate := range redirectValues { candidate = strings.TrimSpace(candidate) if candidate != "" { redirect = candidate break } } verify := service.StoreQuickLoginToken(userID, 15*time.Minute) query := url.Values{} query.Set("verify", verify) if redirect != "" { query.Set("redirect", redirect) } return baseURL(c) + "/api/v1/passport/auth/token2Login?" + query.Encode() } func baseURL(c *gin.Context) string { if appURL := service.GetAppURL(); appURL != "" { return strings.TrimRight(appURL, "/") } scheme := "http" if c.Request.TLS != nil { scheme = "https" } return scheme + "://" + c.Request.Host } func generateTradeNo() string { buf := make([]byte, 8) if _, err := rand.Read(buf); err != nil { return strconv.FormatInt(time.Now().UnixNano(), 10) } return fmt.Sprintf("%d%s", time.Now().Unix(), strings.ToUpper(hex.EncodeToString(buf))) } func randomAlphaNum(length int) string { if length <= 0 { return "" } const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" buf := make([]byte, length) random := make([]byte, length) if _, err := rand.Read(random); err != nil { return generateTradeNo()[:length] } for i := range buf { buf[i] = alphabet[int(random[i])%len(alphabet)] } return string(buf) } func resolvePlanPrice(plan *model.Plan, period string) (int64, bool) { if plan == nil || plan.Prices == nil || strings.TrimSpace(*plan.Prices) == "" { return 0, false } var raw map[string]any if err := json.Unmarshal([]byte(*plan.Prices), &raw); err != nil { return 0, false } value, ok := raw[period] if !ok { return 0, false } switch typed := value.(type) { case float64: return int64(typed), true case int: return int64(typed), true case int64: return typed, true case string: parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64) if err != nil { return 0, false } return parsed, true default: return 0, false } } func validateCoupon(code string, planID, userID int, period string) (*model.Coupon, int64, error) { var coupon model.Coupon if err := database.DB.Where("code = ?", strings.TrimSpace(code)).First(&coupon).Error; err != nil { return nil, 0, errors.New("invalid coupon") } if !coupon.Show { return nil, 0, errors.New("invalid coupon") } now := time.Now().Unix() if coupon.LimitUse != nil && *coupon.LimitUse <= 0 { return nil, 0, errors.New("this coupon is no longer available") } if coupon.StartedAt > 0 && now < coupon.StartedAt { return nil, 0, errors.New("this coupon has not yet started") } if coupon.EndedAt > 0 && now > coupon.EndedAt { return nil, 0, errors.New("this coupon has expired") } if planID > 0 && len(parseIntSlice(coupon.LimitPlanIDs)) > 0 && !containsInt(parseIntSlice(coupon.LimitPlanIDs), planID) { return nil, 0, errors.New("the coupon code cannot be used for this subscription") } if period != "" && len(parseStringSlice(coupon.LimitPeriod)) > 0 && !containsString(parseStringSlice(coupon.LimitPeriod), period) { return nil, 0, errors.New("the coupon code cannot be used for this period") } if coupon.LimitUseWithUser != nil && userID > 0 { var usedCount int64 database.DB.Model(&model.Order{}). Where("coupon_id = ? AND user_id = ? AND status NOT IN ?", coupon.ID, userID, []int{0, 2}). Count(&usedCount) if usedCount >= int64(*coupon.LimitUseWithUser) { return nil, 0, fmt.Errorf("the coupon can only be used %d times per person", *coupon.LimitUseWithUser) } } var discount int64 switch coupon.Type { case 1: discount = coupon.Value case 2: var plan model.Plan if err := database.DB.First(&plan, planID).Error; err != nil { return nil, 0, errors.New("subscription plan does not exist") } total, ok := resolvePlanPrice(&plan, period) if !ok { return nil, 0, errors.New("the period is not available for this subscription") } discount = (total * coupon.Value) / 100 default: discount = 0 } return &coupon, discount, nil } func normalizeOrders(orders []model.Order) []gin.H { items := make([]gin.H, 0, len(orders)) for _, order := range orders { items = append(items, normalizeOrder(order)) } return items } func normalizeOrder(order model.Order) gin.H { data := gin.H{ "id": order.ID, "user_id": order.UserID, "plan_id": order.PlanID, "payment_id": order.PaymentID, "period": order.Period, "trade_no": order.TradeNo, "total_amount": order.TotalAmount, "handling_amount": order.HandlingAmount, "balance_amount": order.BalanceAmount, "refund_amount": order.RefundAmount, "surplus_amount": order.SurplusAmount, "type": order.Type, "status": order.Status, "surplus_order_ids": order.SurplusOrderIDs, "coupon_id": order.CouponID, "created_at": order.CreatedAt, "updated_at": order.UpdatedAt, "commission_status": order.CommissionStatus, "invite_user_id": order.InviteUserID, "actual_commission_balance": order.ActualCommissionBalance, "commission_rate": order.CommissionRate, "commission_auto_check": order.CommissionAutoCheck, "commission_balance": order.CommissionBalance, "discount_amount": order.DiscountAmount, "paid_at": order.PaidAt, "callback_no": order.CallbackNo, "plan": order.Plan, "payment": order.Payment, } return data } func parseIntSlice(raw *string) []int { if raw == nil || strings.TrimSpace(*raw) == "" { return nil } var values []int if err := json.Unmarshal([]byte(*raw), &values); err == nil { return values } parts := strings.Split(*raw, ",") values = make([]int, 0, len(parts)) for _, part := range parts { value, err := strconv.Atoi(strings.TrimSpace(part)) if err == nil { values = append(values, value) } } return values } func parseStringSlice(raw *string) []string { if raw == nil || strings.TrimSpace(*raw) == "" { return nil } var values []string if err := json.Unmarshal([]byte(*raw), &values); err == nil { return values } parts := strings.Split(*raw, ",") values = make([]string, 0, len(parts)) for _, part := range parts { part = strings.TrimSpace(part) if part != "" { values = append(values, part) } } return values } func containsInt(values []int, target int) bool { for _, value := range values { if value == target { return true } } return false } func containsString(values []string, target string) bool { for _, value := range values { if value == target { return true } } return false } func extractTelegramUsername(link string) string { link = strings.TrimSpace(link) link = strings.TrimPrefix(link, "https://t.me/") link = strings.TrimPrefix(link, "http://t.me/") link = strings.TrimPrefix(link, "t.me/") link = strings.TrimPrefix(link, "@") link = strings.Trim(link, "/") return link } func parseConfigString(raw *string, key string) string { if raw == nil || strings.TrimSpace(*raw) == "" { return "" } var payload map[string]any if err := json.Unmarshal([]byte(*raw), &payload); err != nil { return "" } value, ok := payload[key] if !ok { return "" } result, _ := value.(string) return result } func upsertSetting(name string, value any, now time.Time) error { if strings.TrimSpace(name) == "" { return nil } var setting model.Setting err := database.DB.Where("name = ?", name).First(&setting).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } stringValue, err := stringifySettingValue(value) if err != nil { return err } if errors.Is(err, gorm.ErrRecordNotFound) || setting.ID == 0 { setting = model.Setting{ Name: name, Value: stringValue, CreatedAt: &now, UpdatedAt: &now, } return database.DB.Create(&setting).Error } return database.DB.Model(&model.Setting{}).Where("id = ?", setting.ID). Updates(map[string]any{"value": stringValue, "updated_at": now}).Error } func stringifySettingValue(value any) (string, error) { switch typed := value.(type) { case nil: return "", nil case string: return typed, nil case bool: if typed { return "1", nil } return "0", nil case float64: if typed == float64(int64(typed)) { return strconv.FormatInt(int64(typed), 10), nil } return strconv.FormatFloat(typed, 'f', -1, 64), nil case int: return strconv.Itoa(typed), nil case int64: return strconv.FormatInt(typed, 10), nil case []any, map[string]any: bytes, err := json.Marshal(typed) if err != nil { return "", err } return string(bytes), nil default: return fmt.Sprint(typed), nil } }