package handlers import ( "crypto/md5" "encoding/hex" "encoding/json" "fmt" "io" "log" "net/http" "sort" "strconv" "strings" "time" "sale/internal/models" "sale/internal/utils" "github.com/gin-gonic/gin" ) type PaymentHandler struct{} func NewPaymentHandler() *PaymentHandler { return &PaymentHandler{} } func (h *PaymentHandler) CreatePayment(c *gin.Context) { userID := c.GetUint("user_id") orderID, _ := strconv.Atoi(c.Param("id")) var order models.Order if err := utils.DB.Where("id = ? AND user_id = ?", orderID, userID).First(&order).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) return } if order.Status != models.OrderStatusPendingPayment { c.JSON(http.StatusBadRequest, gin.H{"error": "Order is not pending payment"}) return } var channels []models.PaymentChannel utils.DB.Where("is_enabled = ? AND type = ?", true, "bepusdt").Order("sort_order ASC").Find(&channels) if len(channels) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "No available payment channel"}) return } channel := channels[0] config := parseChannelConfig(channel.Config) apiURL := strings.TrimSuffix(config["bepusdt_api_url"], "/") apiToken := config["bepusdt_api_token"] tradeType := config["bepusdt_trade_type"] if tradeType == "" { tradeType = "usdt.trc20" } scheme := "http" if c.Request.TLS != nil || c.GetHeader("X-Forwarded-Proto") == "https" { scheme = "https" } host := c.Request.Host baseURL := fmt.Sprintf("%s://%s", scheme, host) notifyURL := fmt.Sprintf("%s/api/payment/bepusdt/notify", baseURL) redirectURL := fmt.Sprintf("%s/orders/%d", baseURL, order.ID) params := map[string]string{ "order_id": strconv.Itoa(int(order.ID)), "amount": fmt.Sprintf("%.2f", order.TotalAmount), "notify_url": notifyURL, "redirect_url": redirectURL, } if tradeType != "" { params["trade_type"] = tradeType } signature := signBepUsdt(params, apiToken) params["signature"] = signature log.Printf("[BepUsdt] signature params: %v", params) log.Printf("[BepUsdt] signature: %s, token: %s", signature, apiToken) body, _ := json.Marshal(params) log.Printf("[BepUsdt] request body: %s", string(body)) req, err := http.NewRequest("POST", apiURL+"/api/v1/order/create-transaction", strings.NewReader(string(body))) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"}) return } req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to connect payment gateway"}) return } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) log.Printf("[BepUsdt] response: %s", string(respBody)) var result map[string]interface{} json.Unmarshal(respBody, &result) if statusCode, ok := result["status_code"].(float64); !ok || int(statusCode) != 200 { errMsg := "Payment creation failed" if msg, ok := result["message"].(string); ok { errMsg = msg } c.JSON(http.StatusInternalServerError, gin.H{"error": errMsg}) return } data, ok := result["data"].(map[string]interface{}) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid response from payment gateway"}) return } tradeID, _ := data["trade_id"].(string) token, _ := data["token"].(string) var actualAmount float64 if v, ok := data["actual_amount"].(float64); ok { actualAmount = v } var expiration int64 if v, ok := data["expiration_time"].(float64); ok { expiration = int64(v) } if expiration > 1e12 { expiration = expiration / 1000 } var remainingSeconds int64 if expiration > 0 { remainingSeconds = expiration - time.Now().Unix() if remainingSeconds < 0 { remainingSeconds = 0 } } utils.DB.Model(&order).Updates(map[string]interface{}{ "payment_method": "bepusdt", }) paymentInfo := map[string]interface{}{ "trade_id": tradeID, "order_id": order.ID, "amount": data["amount"], "actual_amount": actualAmount, "token": token, "expiration_time": remainingSeconds, "trade_type": tradeType, "channel_id": channel.ID, } c.JSON(http.StatusOK, gin.H{"data": paymentInfo}) } func (h *PaymentHandler) BepUsdtNotify(c *gin.Context) { body, _ := io.ReadAll(c.Request.Body) var params map[string]interface{} json.Unmarshal(body, ¶ms) orderIDStr, _ := params["order_id"].(string) status, _ := params["status"].(float64) signature, _ := params["signature"].(string) orderID, err := strconv.Atoi(orderIDStr) if err != nil { c.String(http.StatusOK, "ok") return } var order models.Order if err := utils.DB.First(&order, orderID).Error; err != nil { c.String(http.StatusOK, "ok") return } var channels []models.PaymentChannel utils.DB.Where("type = ?", "bepusdt").Find(&channels) if len(channels) == 0 { c.String(http.StatusOK, "ok") return } channel := channels[0] config := parseChannelConfig(channel.Config) apiToken := config["bepusdt_api_token"] stringParams := make(map[string]string) for k, v := range params { if k != "signature" { stringParams[k] = fmt.Sprintf("%v", v) } } expectedSign := signBepUsdt(stringParams, apiToken) if signature != expectedSign { c.String(http.StatusOK, "ok") return } if int(status) == 2 { utils.DB.Model(&order).Update("status", models.OrderStatusPendingConfirm) } else if int(status) == 3 { utils.DB.Model(&order).Update("status", models.OrderStatusCancelled) } c.String(http.StatusOK, "ok") } func (h *PaymentHandler) CheckPaymentStatus(c *gin.Context) { userID := c.GetUint("user_id") orderID, _ := strconv.Atoi(c.Param("id")) var order models.Order if err := utils.DB.Where("id = ? AND user_id = ?", orderID, userID).First(&order).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) return } c.JSON(http.StatusOK, gin.H{"data": gin.H{ "status": order.Status, }}) } func signBepUsdt(params map[string]string, token string) string { keys := make([]string, 0, len(params)) for k := range params { if params[k] != "" && k != "signature" && k != "sign_type" { keys = append(keys, k) } } sort.Strings(keys) var parts []string for _, k := range keys { parts = append(parts, fmt.Sprintf("%s=%s", k, params[k])) } str := strings.Join(parts, "&") + token hash := md5.Sum([]byte(str)) return hex.EncodeToString(hash[:]) } func parseChannelConfig(configStr string) map[string]string { result := make(map[string]string) if configStr == "" { return result } var raw map[string]interface{} if err := json.Unmarshal([]byte(configStr), &raw); err != nil { var rawStr map[string]string if err2 := json.Unmarshal([]byte(configStr), &rawStr); err2 == nil { return rawStr } log.Printf("[BepUsdt] failed to parse channel config: %v", err) return result } for k, v := range raw { if v == nil { continue } switch val := v.(type) { case string: result[k] = val case float64: if val == float64(int64(val)) { result[k] = strconv.FormatInt(int64(val), 10) } else { result[k] = strconv.FormatFloat(val, 'f', -1, 64) } case bool: result[k] = strconv.FormatBool(val) default: result[k] = fmt.Sprintf("%v", val) } } return result }