mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 02:06:16 +00:00
feat(order): use crypto-random url-safe trade IDs and update docs examples
This commit is contained in:
@@ -26,7 +26,7 @@ const (
|
||||
)
|
||||
|
||||
type Orders struct {
|
||||
TradeId string `gorm:"column:trade_id;uniqueIndex:orders_trade_id_uindex" json:"trade_id" example:"T2026041612345678"`
|
||||
TradeId string `gorm:"column:trade_id;uniqueIndex:orders_trade_id_uindex" json:"trade_id" example:"3nQ9pL2xV7sK1mR8cT4yB_aZ"`
|
||||
OrderId string `gorm:"column:order_id;uniqueIndex:orders_order_id_uindex" json:"order_id" example:"ORD20260416001"`
|
||||
ParentTradeId string `gorm:"column:parent_trade_id;index:idx_orders_parent_trade_id;default:''" json:"parent_trade_id"`
|
||||
BlockTransactionId string `gorm:"index:orders_block_transaction_id_index;column:block_transaction_id" json:"block_transaction_id" example:"0xabc123..."`
|
||||
|
||||
@@ -23,7 +23,7 @@ const (
|
||||
// The main orders table remains the source of truth for merchant-facing state,
|
||||
// while this table keeps provider-specific identifiers and callback payloads.
|
||||
type ProviderOrder struct {
|
||||
TradeId string `gorm:"column:trade_id;size:32;not null;uniqueIndex:provider_orders_trade_id_provider_uindex,priority:1;index:provider_orders_trade_id_index" json:"trade_id" example:"T2026041612345678"`
|
||||
TradeId string `gorm:"column:trade_id;size:32;not null;uniqueIndex:provider_orders_trade_id_provider_uindex,priority:1;index:provider_orders_trade_id_index" json:"trade_id" example:"3nQ9pL2xV7sK1mR8cT4yB_aZ"`
|
||||
// Provider values should match the bound Orders.PayProvider value.
|
||||
Provider string `gorm:"column:provider;size:32;not null;uniqueIndex:provider_orders_trade_id_provider_uindex,priority:2;index:provider_orders_provider_status_index,priority:1" json:"provider" example:"okpay"`
|
||||
// ProviderOrderID is the downstream provider's own order identifier for the
|
||||
|
||||
@@ -52,7 +52,7 @@ func (r ManualPaymentRequest) Translates() map[string]string {
|
||||
|
||||
// SwitchNetworkRequest 切换支付网络
|
||||
type SwitchNetworkRequest struct {
|
||||
TradeId string `json:"trade_id" validate:"required" example:"T2026041612345678"`
|
||||
TradeId string `json:"trade_id" validate:"required" example:"3nQ9pL2xV7sK1mR8cT4yB_aZ"`
|
||||
Token string `json:"token" validate:"required" example:"USDT"`
|
||||
Network string `json:"network" validate:"required" example:"okpay,tron,solana,ethereum"`
|
||||
}
|
||||
|
||||
@@ -2,21 +2,21 @@ package response
|
||||
|
||||
// CreateTransactionResponse 创建订单成功返回
|
||||
type CreateTransactionResponse struct {
|
||||
TradeId string `json:"trade_id" example:"T2026041612345678"` // epusdt订单号
|
||||
OrderId string `json:"order_id" example:"ORD20260416001"` // 客户交易id
|
||||
Amount float64 `json:"amount" example:"100.0000"` // 订单金额,按 system.amount_precision 保留小数
|
||||
Currency string `json:"currency" example:"CNY"` // 订单货币类型 CNY USD......
|
||||
ActualAmount float64 `json:"actual_amount" example:"14.2857"` // 订单实际需要支付的金额,按 system.amount_precision 保留小数
|
||||
ReceiveAddress string `json:"receive_address" example:"TTestTronAddress001"` // 收款钱包地址
|
||||
Token string `json:"token" example:"USDT"` // 所属币种 TRX USDT......
|
||||
ExpirationTime int64 `json:"expiration_time" example:"1713264600"` // 过期时间 时间戳
|
||||
PaymentUrl string `json:"payment_url" example:"https://pay.example.com/checkout/T2026041612345678"` // 收银台地址
|
||||
TradeId string `json:"trade_id" example:"3nQ9pL2xV7sK1mR8cT4yB_aZ"` // epusdt订单号
|
||||
OrderId string `json:"order_id" example:"ORD20260416001"` // 客户交易id
|
||||
Amount float64 `json:"amount" example:"100.0000"` // 订单金额,按 system.amount_precision 保留小数
|
||||
Currency string `json:"currency" example:"CNY"` // 订单货币类型 CNY USD......
|
||||
ActualAmount float64 `json:"actual_amount" example:"14.2857"` // 订单实际需要支付的金额,按 system.amount_precision 保留小数
|
||||
ReceiveAddress string `json:"receive_address" example:"TTestTronAddress001"` // 收款钱包地址
|
||||
Token string `json:"token" example:"USDT"` // 所属币种 TRX USDT......
|
||||
ExpirationTime int64 `json:"expiration_time" example:"1713264600"` // 过期时间 时间戳
|
||||
PaymentUrl string `json:"payment_url" example:"https://pay.example.com/checkout/3nQ9pL2xV7sK1mR8cT4yB_aZ"` // 收银台地址
|
||||
}
|
||||
|
||||
// OrderNotifyResponse 订单异步回调结构体
|
||||
type OrderNotifyResponse struct {
|
||||
Pid string `json:"pid" example:"1000"` // 签名使用的商户 PID,商户据此查本地 secret 验签
|
||||
TradeId string `json:"trade_id" example:"T2026041612345678"` // epusdt订单号
|
||||
TradeId string `json:"trade_id" example:"3nQ9pL2xV7sK1mR8cT4yB_aZ"` // epusdt订单号
|
||||
OrderId string `json:"order_id" example:"ORD20260416001"` // 客户交易id
|
||||
Amount float64 `json:"amount" example:"100.0000"` // 订单金额,按 system.amount_precision 保留小数
|
||||
ActualAmount float64 `json:"actual_amount" example:"14.2857"` // 订单实际需要支付的金额,按 system.amount_precision 保留小数
|
||||
@@ -30,13 +30,13 @@ type OrderNotifyResponse struct {
|
||||
|
||||
// OrderNotifyResponseEpay epay订单异步回调结构体
|
||||
type OrderNotifyResponseEpay struct {
|
||||
PID int `json:"pid" example:"1001"` // 商户ID
|
||||
TradeNo string `json:"trade_no" example:"T2026041612345678"` // 平台订单号
|
||||
OutTradeNo string `json:"out_trade_no" example:"ORD20260416001"` // 商户订单号
|
||||
Type string `json:"type" example:"usdt"` // 订单类型
|
||||
Name string `json:"name" example:"VIP月卡"` // 商品名称
|
||||
Money string `json:"money" example:"100.0000"` // 订单金额,保留4位小数
|
||||
Sign string `json:"sign" example:"a1b2c3d4..."` // 签名
|
||||
SignType string `json:"sign_type" example:"MD5"` // 签名类型
|
||||
TradeStatus string `json:"trade_status" example:"TRADE_SUCCESS"` // 订单状态
|
||||
PID int `json:"pid" example:"1001"` // 商户ID
|
||||
TradeNo string `json:"trade_no" example:"3nQ9pL2xV7sK1mR8cT4yB_aZ"` // 平台订单号
|
||||
OutTradeNo string `json:"out_trade_no" example:"ORD20260416001"` // 商户订单号
|
||||
Type string `json:"type" example:"usdt"` // 订单类型
|
||||
Name string `json:"name" example:"VIP月卡"` // 商品名称
|
||||
Money string `json:"money" example:"100.0000"` // 订单金额,保留4位小数
|
||||
Sign string `json:"sign" example:"a1b2c3d4..."` // 签名
|
||||
SignType string `json:"sign_type" example:"MD5"` // 签名类型
|
||||
TradeStatus string `json:"trade_status" example:"TRADE_SUCCESS"` // 订单状态
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package response
|
||||
|
||||
type CheckoutCounterResponse struct {
|
||||
TradeId string `json:"trade_id" example:"T2026041612345678"` // epusdt订单号
|
||||
TradeId string `json:"trade_id" example:"3nQ9pL2xV7sK1mR8cT4yB_aZ"` // epusdt订单号
|
||||
Amount float64 `json:"amount" example:"100.0000"` // 订单金额,按 system.amount_precision 保留小数 法币金额
|
||||
ActualAmount float64 `json:"actual_amount" example:"14.2857"` // 订单实际需要支付的金额,按 system.amount_precision 保留小数 加密货币金额
|
||||
Token string `json:"token" example:"USDT"` // 所属币种 TRX USDT......
|
||||
@@ -10,19 +10,19 @@ type CheckoutCounterResponse struct {
|
||||
Network string `json:"network" example:"tron"` // 网络 TRON ETH ...
|
||||
ExpirationTime int64 `json:"expiration_time" example:"1713264600"` // 过期时间 时间戳
|
||||
RedirectUrl string `json:"redirect_url" example:"https://example.com/success"`
|
||||
PaymentUrl string `json:"payment_url" example:"https://pay.example.com/checkout/T2026041612345678"` // 支付链接;链上订单通常为本地收银台,OkPay 子订单为第三方 payLink
|
||||
CreatedAt int64 `json:"created_at" example:"1713264000"` // 订单创建时间 时间戳
|
||||
PaymentUrl string `json:"payment_url" example:"https://pay.example.com/checkout/3nQ9pL2xV7sK1mR8cT4yB_aZ"` // 支付链接;链上订单通常为本地收银台,OkPay 子订单为第三方 payLink
|
||||
CreatedAt int64 `json:"created_at" example:"1713264000"` // 订单创建时间 时间戳
|
||||
IsSelected bool `json:"is_selected" example:"false"`
|
||||
}
|
||||
|
||||
type CheckStatusResponse struct {
|
||||
TradeId string `json:"trade_id" example:"T2026041612345678"` // epusdt订单号
|
||||
TradeId string `json:"trade_id" example:"3nQ9pL2xV7sK1mR8cT4yB_aZ"` // epusdt订单号
|
||||
// 订单状态 1=等待支付 2=支付成功 3=已过期
|
||||
Status int `json:"status" enums:"1,2,3" example:"1"`
|
||||
}
|
||||
|
||||
type ManualPaymentResponse struct {
|
||||
TradeId string `json:"trade_id" example:"T2026041612345678"` // epusdt订单号
|
||||
TradeId string `json:"trade_id" example:"3nQ9pL2xV7sK1mR8cT4yB_aZ"` // epusdt订单号
|
||||
Status int `json:"status" enums:"1,2,3" example:"2"` // 订单状态 1=等待支付 2=支付成功 3=已过期
|
||||
BlockTransactionId string `json:"block_transaction_id" example:"0xabc123def456..."` // 已验证并入账的交易哈希
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
cryptorand "crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/config"
|
||||
"github.com/GMWalletApp/epusdt/model/dao"
|
||||
@@ -322,9 +322,11 @@ func ReserveAvailableWalletAndAmount(tradeID string, network string, token strin
|
||||
|
||||
// GenerateCode creates a unique trade id.
|
||||
func GenerateCode() string {
|
||||
date := time.Now().Format("20060102")
|
||||
r := rand.Intn(1000)
|
||||
return fmt.Sprintf("%s%d%03d", date, time.Now().UnixNano()/1e6, r)
|
||||
buf := make([]byte, 18)
|
||||
if _, err := cryptorand.Read(buf); err != nil {
|
||||
panic(fmt.Sprintf("generate trade id: crypto/rand failed: %v", err))
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(buf)
|
||||
}
|
||||
|
||||
// GetOrderInfoByTradeId returns a validated order.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -36,6 +37,31 @@ func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return f(req)
|
||||
}
|
||||
|
||||
func TestGenerateCodeFormat(t *testing.T) {
|
||||
tradeID := GenerateCode()
|
||||
|
||||
if len(tradeID) != 24 {
|
||||
t.Fatalf("trade id length = %d, want 24", len(tradeID))
|
||||
}
|
||||
if strings.Contains(tradeID, "=") {
|
||||
t.Fatalf("trade id = %q, contains padding '='", tradeID)
|
||||
}
|
||||
for _, ch := range tradeID {
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '_' {
|
||||
continue
|
||||
}
|
||||
t.Fatalf("trade id = %q, contains non URL-safe character %q", tradeID, ch)
|
||||
}
|
||||
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(tradeID)
|
||||
if err != nil {
|
||||
t.Fatalf("decode trade id %q: %v", tradeID, err)
|
||||
}
|
||||
if len(decoded) != 18 {
|
||||
t.Fatalf("decoded trade id length = %d, want 18", len(decoded))
|
||||
}
|
||||
}
|
||||
|
||||
func installMockHTTPClient(t *testing.T, handler roundTripFunc) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user