Files
2026-04-27 17:22:56 +08:00

212 lines
5.6 KiB
Go

package payment
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
type BEpusdtConfig struct {
ApiURL string
ApiToken string
TradeType string
Fiat string
Timeout int
Rate string
}
type BEpusdtClient struct {
config BEpusdtConfig
client *http.Client
}
type CreateTransactionRequest struct {
OrderID string `json:"order_id"`
Amount float64 `json:"amount"`
NotifyURL string `json:"notify_url"`
RedirectURL string `json:"redirect_url"`
Signature string `json:"signature"`
TradeType string `json:"trade_type,omitempty"`
Fiat string `json:"fiat,omitempty"`
Name string `json:"name,omitempty"`
Timeout int `json:"timeout,omitempty"`
Rate string `json:"rate,omitempty"`
Address string `json:"address,omitempty"`
}
type CreateTransactionResponse struct {
StatusCode int `json:"status_code"`
Message string `json:"message"`
Data struct {
Fiat string `json:"fiat"`
TradeID string `json:"trade_id"`
OrderID string `json:"order_id"`
Amount interface{} `json:"amount"`
ActualAmount interface{} `json:"actual_amount"`
Status int `json:"status"`
Token string `json:"token"`
ExpirationTime int `json:"expiration_time"`
PaymentURL string `json:"payment_url"`
} `json:"data"`
RequestID string `json:"request_id"`
}
type CallbackData struct {
TradeID string `json:"trade_id"`
OrderID string `json:"order_id"`
Amount float64 `json:"amount"`
ActualAmount float64 `json:"actual_amount"`
Token string `json:"token"`
BlockTransactionID string `json:"block_transaction_id"`
Signature string `json:"signature"`
Status int `json:"status"`
}
func NewBEpusdtClient(config BEpusdtConfig) *BEpusdtClient {
if config.Fiat == "" {
config.Fiat = "CNY"
}
if config.Timeout == 0 {
config.Timeout = 600
}
return &BEpusdtClient{
config: config,
client: &http.Client{
Timeout: 30 * time.Second,
},
}
}
func (c *BEpusdtClient) GenerateSignature(params map[string]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]))
}
signStr := strings.Join(parts, "&") + c.config.ApiToken
hash := md5.New()
hash.Write([]byte(signStr))
return hex.EncodeToString(hash.Sum(nil))
}
func (c *BEpusdtClient) CreateTransaction(orderID string, amount float64, notifyURL, redirectURL, name string) (*CreateTransactionResponse, error) {
params := map[string]string{
"order_id": orderID,
"amount": fmt.Sprintf("%.2f", amount),
"notify_url": notifyURL,
"redirect_url": redirectURL,
}
if c.config.TradeType != "" {
params["trade_type"] = c.config.TradeType
}
if c.config.Fiat != "" {
params["fiat"] = c.config.Fiat
}
if name != "" {
params["name"] = name
}
if c.config.Timeout > 0 {
params["timeout"] = fmt.Sprintf("%d", c.config.Timeout)
}
if c.config.Rate != "" {
params["rate"] = c.config.Rate
}
params["signature"] = c.GenerateSignature(params)
jsonData, err := json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
apiURL := strings.TrimRight(c.config.ApiURL, "/")
reqURL := apiURL + "/api/v1/order/create-transaction"
req, err := http.NewRequest("POST", reqURL, strings.NewReader(string(jsonData)))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
var result CreateTransactionResponse
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
if result.StatusCode != 200 {
return nil, fmt.Errorf("API error: %s (code: %d)", result.Message, result.StatusCode)
}
return &result, nil
}
func (c *BEpusdtClient) VerifyCallback(data CallbackData) bool {
params := map[string]string{
"trade_id": data.TradeID,
"order_id": data.OrderID,
"amount": fmt.Sprintf("%.2f", data.Amount),
"actual_amount": fmt.Sprintf("%.2f", data.ActualAmount),
"token": data.Token,
"block_transaction_id": data.BlockTransactionID,
"status": fmt.Sprintf("%d", data.Status),
}
expectedSign := c.GenerateSignature(params)
return strings.EqualFold(expectedSign, data.Signature)
}
func ParseCallbackFromQuery(query url.Values) CallbackData {
var data CallbackData
data.TradeID = query.Get("trade_id")
data.OrderID = query.Get("order_id")
data.Token = query.Get("token")
data.BlockTransactionID = query.Get("block_transaction_id")
data.Signature = query.Get("signature")
if amount := query.Get("amount"); amount != "" {
fmt.Sscanf(amount, "%f", &data.Amount)
}
if actualAmount := query.Get("actual_amount"); actualAmount != "" {
fmt.Sscanf(actualAmount, "%f", &data.ActualAmount)
}
if status := query.Get("status"); status != "" {
fmt.Sscanf(status, "%d", &data.Status)
}
return data
}
func ParseCallbackFromJSON(body []byte) (CallbackData, error) {
var data CallbackData
err := json.Unmarshal(body, &data)
return data, err
}