package handlers import ( "crypto/md5" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "sort" "strconv" "strings" "sale/internal/models" "sale/internal/utils" "github.com/gin-gonic/gin" ) type PaymentHandler struct{} func NewPaymentHandler() *PaymentHandler { return &PaymentHandler{} } type BepUsdtCreateRequest struct { Address string `json:"address"` TradeType string `json:"trade_type"` OrderID string `json:"order_id"` Amount float64 `json:"amount"` Signature string `json:"signature"` NotifyURL string `json:"notify_url"` RedirectURL string `json:"redirect_url"` Timeout int `json:"timeout"` Rate string `json:"rate"` } type BepUsdtCreateResponse struct { StatusCode int `json:"status_code"` Message string `json:"message"` TradeID string `json:"trade_id"` OrderID string `json:"order_id"` Amount string `json:"amount"` ActualAmount string `json:"actual_amount"` Token string `json:"token"` Expiration int `json:"expiration_time"` PaymentURL string `json:"payment_url"` } 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] var config map[string]string json.Unmarshal([]byte(channel.Config), &config) apiURL := config["bepusdt_api_url"] apiToken := config["bepusdt_api_token"] tradeType := config["bepusdt_trade_type"] if tradeType == "" { tradeType = "usdt.trc20" } timeout, _ := strconv.Atoi(config["bepusdt_timeout"]) if timeout < 60 { timeout = 600 } rate := config["bepusdt_rate"] 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, "trade_type": tradeType, } if timeout > 0 { params["timeout"] = strconv.Itoa(timeout) } if rate != "" { params["rate"] = rate } signature := signBepUsdt(params, apiToken) reqBody := BepUsdtCreateRequest{ TradeType: tradeType, OrderID: strconv.Itoa(int(order.ID)), Amount: order.TotalAmount, Signature: signature, NotifyURL: notifyURL, RedirectURL: redirectURL, Timeout: timeout, Rate: rate, } body, _ := json.Marshal(reqBody) resp, err := http.Post(apiURL+"/api/v1/order/create-transaction", "application/json", strings.NewReader(string(body))) 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) 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 := result["data"].(map[string]interface{}) tradeID, _ := data["trade_id"].(string) actualAmount, _ := data["actual_amount"].(string) token, _ := data["token"].(string) expiration, _ := data["expiration_time"].(float64) 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": int(expiration), "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] var config map[string]string json.Unmarshal([]byte(channel.Config), &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] != "" { 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[:]) }