212 lines
5.6 KiB
Go
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
|
|
}
|