支持C2C Pay的USDC入账校验
This commit is contained in:
parent
bb5efebabd
commit
449110aa95
@ -89,13 +89,15 @@ func (c *Client) FindUSDTTransfer(ctx context.Context, req ports.BinanceTransfer
|
||||
return ports.BinanceTransferRecord{}, err
|
||||
}
|
||||
if req.Network == "C2C" {
|
||||
// C2C/P2P 与 Binance Pay 是两套独立账单;显式 C2C 只查法币交易历史,禁止再回退到 Pay 或链上入金。
|
||||
startTimeMS, endTimeMS := payHistoryRange(req.OrderTimeMS, serverTimeMS)
|
||||
records, err := c.c2cHistory(ctx, account, startTimeMS, endTimeMS, serverTimeMS)
|
||||
// 后台的 C2C 对应 Binance Pay 账单中的 orderType=C2C,而不是法币 P2P Trade History。
|
||||
// 显式 C2C 只查 Pay,且允许收款币种为 USDT 或 USDC;不能再回退到链上或 P2P 历史。
|
||||
// 运营录单时间可能是补单操作时间而非 Binance 成交时间;显式订单号查验使用有界 90 天窗口,避免错误窄窗漏单。
|
||||
startTimeMS, endTimeMS := maxInt64(0, serverTimeMS-int64(defaultPayLookback/time.Millisecond)), serverTimeMS
|
||||
records, err := c.payHistory(ctx, account, startTimeMS, endTimeMS, serverTimeMS)
|
||||
if err != nil {
|
||||
return ports.BinanceTransferRecord{}, err
|
||||
}
|
||||
return matchC2CRecord(req, records)
|
||||
return matchExplicitC2CPayRecord(req, records)
|
||||
}
|
||||
// Binance 的 deposit history 查询窗口有限;后台凭证校验只做近期有界查询,避免一次手工校验变成无界扫描。
|
||||
records, err := c.depositHistory(ctx, account, req.Coin, serverTimeMS-int64(defaultDepositLookback/time.Millisecond), serverTimeMS)
|
||||
@ -118,6 +120,32 @@ func (c *Client) FindUSDTTransfer(ctx context.Context, req ports.BinanceTransfer
|
||||
return matchPayRecord(req, payRecords)
|
||||
}
|
||||
|
||||
func matchExplicitC2CPayRecord(req ports.BinanceTransferLookupRequest, records []payRecord) (ports.BinanceTransferRecord, error) {
|
||||
for _, record := range records {
|
||||
if !payExternalIDMatches(record, req.ExternalID) {
|
||||
continue
|
||||
}
|
||||
if record.OrderType != "C2C" {
|
||||
return ports.BinanceTransferRecord{}, xerr.New(xerr.Conflict, "binance pay order type is not c2c")
|
||||
}
|
||||
if record.Currency != "USDT" && record.Currency != "USDC" {
|
||||
return ports.BinanceTransferRecord{}, xerr.New(xerr.Conflict, "binance pay c2c currency is not supported")
|
||||
}
|
||||
if strings.HasPrefix(strings.TrimSpace(record.Amount), "-") {
|
||||
return ports.BinanceTransferRecord{}, xerr.New(xerr.Conflict, "binance pay transfer is not income")
|
||||
}
|
||||
amountMinor, err := parseUSDTAmountMinor(record.Amount)
|
||||
if err != nil {
|
||||
return ports.BinanceTransferRecord{}, err
|
||||
}
|
||||
if amountMinor <= 0 || amountMinor != req.AmountMinor {
|
||||
return ports.BinanceTransferRecord{}, xerr.New(xerr.Conflict, "binance pay amount mismatch")
|
||||
}
|
||||
return ports.BinanceTransferRecord{Source: "pay_history", ID: record.OrderID, TxID: firstNonEmpty(record.TransactionID, record.OrderID), Coin: record.Currency, Status: 1, Amount: record.Amount, AmountMinor: amountMinor, InsertTimeMS: record.TransactionTimeMS, CompleteTimeMS: record.TransactionTimeMS, WalletType: record.WalletType, RawJSON: strings.TrimSpace(record.RawJSON)}, nil
|
||||
}
|
||||
return ports.BinanceTransferRecord{}, xerr.New(xerr.NotFound, "binance pay c2c transfer not found")
|
||||
}
|
||||
|
||||
// c2cHistory 查询当前 API Key 账户作为买方完成的 C2C 订单;BUY 表示账户收到 asset,SELL 则是付出 asset。
|
||||
func (c *Client) c2cHistory(ctx context.Context, account account, startTimeMS int64, endTimeMS int64, serverTimeMS int64) ([]c2cRecord, error) {
|
||||
query := url.Values{}
|
||||
|
||||
@ -209,14 +209,14 @@ func TestFindUSDTTransferFallsBackToIncomingPayC2C(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindUSDTTransferC2CUsesFiatTradeHistoryOnly(t *testing.T) {
|
||||
func TestFindUSDTTransferC2CUsesPayAndAcceptsUSDC(t *testing.T) {
|
||||
const orderID = "440537037787594752"
|
||||
var sawC2C bool
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/v3/time":
|
||||
_, _ = w.Write([]byte(`{"serverTime":1784018000000}`))
|
||||
case "/sapi/v1/c2c/orderMatch/listUserOrderHistory":
|
||||
case "/sapi/v1/pay/transactions":
|
||||
sawC2C = true
|
||||
values := r.URL.Query()
|
||||
signature := values.Get("signature")
|
||||
@ -224,10 +224,10 @@ func TestFindUSDTTransferC2CUsesFiatTradeHistoryOnly(t *testing.T) {
|
||||
if signature != signQueryString(values.Encode(), "secret-lalu") {
|
||||
t.Fatalf("C2C signature mismatch: %s", values.Encode())
|
||||
}
|
||||
if values.Get("tradeType") != "BUY" || values.Get("page") != "1" || values.Get("rows") != "100" {
|
||||
if values.Get("limit") != "100" {
|
||||
t.Fatalf("C2C query mismatch: %s", values.Encode())
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"code":"000000","success":true,"data":[{"orderNumber":"440537037787594752","tradeType":"BUY","asset":"USDT","amount":"200.00000000","orderStatus":"COMPLETED","createTime":1784017560000}]}`))
|
||||
_, _ = w.Write([]byte(`{"code":"000000","success":true,"data":[{"orderType":"C2C","orderId":"440537037787594752","transactionId":"P_A22V8RE4HQS71114","transactionTime":1782977844437,"amount":"200","currency":"USDC","walletType":1}]}`))
|
||||
default:
|
||||
t.Fatalf("C2C lookup must not call another history endpoint: %s", r.URL.Path)
|
||||
}
|
||||
@ -238,7 +238,7 @@ func TestFindUSDTTransferC2CUsesFiatTradeHistoryOnly(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("C2C lookup failed: %v", err)
|
||||
}
|
||||
if !sawC2C || record.Source != "c2c_history" || record.ID != orderID || record.AmountMinor != 20000 {
|
||||
if !sawC2C || record.Source != "pay_history" || record.ID != orderID || record.Coin != "USDC" || record.AmountMinor != 20000 {
|
||||
t.Fatalf("C2C record mismatch: %+v", record)
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,6 +392,10 @@ func (s *Service) verifyBinanceTransferReceipt(ctx context.Context, command ledg
|
||||
snapshot.Status = "confirmed"
|
||||
snapshot.ProviderOrderID = record.TxID
|
||||
snapshot.ProviderAmountMinor = record.AmountMinor
|
||||
if snapshot.Chain == "c2c" && strings.TrimSpace(record.Coin) != "" {
|
||||
// C2C Pay 实际收款可能是 USDT 或 USDC;凭证必须保存 Binance 返回币种,不能继续伪装成表单默认 USDT。
|
||||
snapshot.CurrencyCode = strings.ToUpper(strings.TrimSpace(record.Coin))
|
||||
}
|
||||
// Pay C2C 没有链上收款地址;只有 Deposit History 命中时才用 Binance 返回地址覆盖配置快照。
|
||||
if snapshot.Chain != "c2c" && strings.TrimSpace(record.Address) != "" {
|
||||
snapshot.ReceiveAddress = record.Address
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user