Initial commit: 网络验证平台

This commit is contained in:
Admin
2026-04-27 17:22:56 +08:00
commit afe67d704e
780 changed files with 88960 additions and 0 deletions
+211
View File
@@ -0,0 +1,211 @@
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
}
+105
View File
@@ -0,0 +1,105 @@
package payment
import (
"encoding/json"
"fmt"
"strconv"
"verification-platform-backend/internal/model"
)
type PaymentResult struct {
OrderID string
TradeID string
PaymentURL string
ActualAmount string
Token string
ExpirationTime int
}
type PaymentService interface {
CreateOrder(orderID string, amount float64, notifyURL, redirectURL, name string) (*PaymentResult, error)
VerifyCallback(data interface{}) bool
}
func GetPaymentService(channel model.PaymentChannel, callbackBaseURL string) (PaymentService, error) {
switch channel.Type {
case "bepusdt":
config, err := parseBEpusdtConfig(channel.Config)
if err != nil {
return nil, fmt.Errorf("parse bepusdt config: %w", err)
}
return NewBEpusdtAdapter(config, callbackBaseURL), nil
default:
return nil, fmt.Errorf("unsupported payment type: %s", channel.Type)
}
}
func parseBEpusdtConfig(configStr string) (BEpusdtConfig, error) {
var config BEpusdtConfig
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
return config, fmt.Errorf("invalid config format: %w", err)
}
if config.ApiURL == "" {
return config, fmt.Errorf("api_url is required")
}
if config.ApiToken == "" {
return config, fmt.Errorf("api_token is required")
}
if config.TradeType == "" {
config.TradeType = "usdt.trc20"
}
return config, nil
}
func interfaceToString(v interface{}) string {
switch val := v.(type) {
case string:
return val
case float64:
return strconv.FormatFloat(val, 'f', -1, 64)
case int:
return strconv.Itoa(val)
default:
return fmt.Sprintf("%v", val)
}
}
type BEpusdtAdapter struct {
client *BEpusdtClient
callbackBaseURL string
}
func NewBEpusdtAdapter(config BEpusdtConfig, callbackBaseURL string) *BEpusdtAdapter {
return &BEpusdtAdapter{
client: NewBEpusdtClient(config),
callbackBaseURL: callbackBaseURL,
}
}
func (a *BEpusdtAdapter) CreateOrder(orderID string, amount float64, notifyURL, redirectURL, name string) (*PaymentResult, error) {
resp, err := a.client.CreateTransaction(orderID, amount, notifyURL, redirectURL, name)
if err != nil {
return nil, err
}
return &PaymentResult{
OrderID: resp.Data.OrderID,
TradeID: resp.Data.TradeID,
PaymentURL: resp.Data.PaymentURL,
ActualAmount: interfaceToString(resp.Data.ActualAmount),
Token: resp.Data.Token,
ExpirationTime: resp.Data.ExpirationTime,
}, nil
}
func (a *BEpusdtAdapter) VerifyCallback(data interface{}) bool {
switch v := data.(type) {
case CallbackData:
return a.client.VerifyCallback(v)
default:
return false
}
}