修复BSC币商C2C凭证校验

This commit is contained in:
zhx 2026-07-14 16:24:16 +08:00
parent aac2b4dc76
commit 7609509f7f
2 changed files with 65 additions and 6 deletions

View File

@ -342,8 +342,10 @@ func (s *Service) verifyUSDTRechargeReceipt(ctx context.Context, command ledger.
snapshot.ProviderAmountMinor = command.USDMinorAmount snapshot.ProviderAmountMinor = command.USDMinorAmount
snapshot.ProviderOrderID = command.ExternalOrderNo snapshot.ProviderOrderID = command.ExternalOrderNo
snapshot.ReceiveAddress = strings.TrimSpace(receiveAddress) snapshot.ReceiveAddress = strings.TrimSpace(receiveAddress)
if chain == "trc20" && !isLikelyChainTransactionHash(command.ExternalOrderNo) { if !isLikelyChainTransactionHash(command.ExternalOrderNo) {
return s.verifyBinanceTRXDepositReceipt(ctx, command, snapshot) // Binance Pay C2C 订单号不属于某条公链;后台选择 TRC20 或 BEP20 只表示运营记录的支付币种。
// 因此所有非链上哈希都必须先走 Binance 账户入账事实,不能把 BEP20 的 C2C 订单号误送给 BSC 链上客户端。
return s.verifyBinanceTransferReceipt(ctx, command, snapshot, binanceNetworkForReceiptChain(chain))
} }
if client == nil { if client == nil {
return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "usdt "+chain+" client is not configured") return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "usdt "+chain+" client is not configured")
@ -360,7 +362,7 @@ func (s *Service) verifyUSDTRechargeReceipt(ctx context.Context, command ledger.
return snapshot, nil return snapshot, nil
} }
func (s *Service) verifyBinanceTRXDepositReceipt(ctx context.Context, command ledger.VerifyCoinSellerRechargeReceiptCommand, snapshot ledger.CoinSellerRechargeReceiptVerification) (ledger.CoinSellerRechargeReceiptVerification, error) { func (s *Service) verifyBinanceTransferReceipt(ctx context.Context, command ledger.VerifyCoinSellerRechargeReceiptCommand, snapshot ledger.CoinSellerRechargeReceiptVerification, network string) (ledger.CoinSellerRechargeReceiptVerification, error) {
if s.binance == nil { if s.binance == nil {
return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "binance client is not configured") return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "binance client is not configured")
} }
@ -370,7 +372,7 @@ func (s *Service) verifyBinanceTRXDepositReceipt(ctx context.Context, command le
AppCode: command.AppCode, AppCode: command.AppCode,
ExternalID: command.ExternalOrderNo, ExternalID: command.ExternalOrderNo,
Coin: ledger.PaidCurrencyUSDT, Coin: ledger.PaidCurrencyUSDT,
Network: "TRX", Network: network,
ToAddress: snapshot.ReceiveAddress, ToAddress: snapshot.ReceiveAddress,
AmountMinor: command.USDMinorAmount, AmountMinor: command.USDMinorAmount,
OrderTimeMS: command.OrderDateMS, OrderTimeMS: command.OrderDateMS,
@ -392,6 +394,17 @@ func (s *Service) verifyBinanceTRXDepositReceipt(ctx context.Context, command le
return snapshot, nil return snapshot, nil
} }
func binanceNetworkForReceiptChain(chain string) string {
// Binance Deposit History 使用交易所网络代码而不是后台 provider 名称Pay C2C 不校验网络,
// 但同一个入口也支持 off-chain Deposit History因此这里必须保留准确映射。
switch strings.ToLower(strings.TrimSpace(chain)) {
case "bep20":
return "BSC"
default:
return "TRX"
}
}
func (s *Service) receiptSnapshot(command ledger.VerifyCoinSellerRechargeReceiptCommand) ledger.CoinSellerRechargeReceiptVerification { func (s *Service) receiptSnapshot(command ledger.VerifyCoinSellerRechargeReceiptCommand) ledger.CoinSellerRechargeReceiptVerification {
return ledger.CoinSellerRechargeReceiptVerification{ return ledger.CoinSellerRechargeReceiptVerification{
ProviderCode: command.ProviderCode, ProviderCode: command.ProviderCode,

View File

@ -467,10 +467,11 @@ func TestVerifyCoinSellerRechargeReceiptUSDTBEP20UsesReadOnlyClient(t *testing.T
USDTBEP20Enabled: true, USDTBEP20Enabled: true,
USDTBEP20Address: "0xPlatformBEP20ReceiveAddress", USDTBEP20Address: "0xPlatformBEP20ReceiveAddress",
}) })
txHash := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
receipt, err := svc.VerifyCoinSellerRechargeReceipt(context.Background(), ledger.VerifyCoinSellerRechargeReceiptCommand{ receipt, err := svc.VerifyCoinSellerRechargeReceipt(context.Background(), ledger.VerifyCoinSellerRechargeReceiptCommand{
AppCode: "lalu", AppCode: "lalu",
ProviderCode: ledger.PaymentProviderUSDTBEP20, ProviderCode: ledger.PaymentProviderUSDTBEP20,
ExternalOrderNo: "0xreceiptbep20", ExternalOrderNo: txHash,
USDMinorAmount: 150, USDMinorAmount: 150,
}) })
if err != nil { if err != nil {
@ -479,7 +480,7 @@ func TestVerifyCoinSellerRechargeReceiptUSDTBEP20UsesReadOnlyClient(t *testing.T
if !receipt.Verified || receipt.Status != "confirmed" || receipt.Chain != "bep20" || receipt.ProviderAmountMinor != 150 || receipt.ReceiveAddress != "0xPlatformBEP20ReceiveAddress" { if !receipt.Verified || receipt.Status != "confirmed" || receipt.Chain != "bep20" || receipt.ProviderAmountMinor != 150 || receipt.ReceiveAddress != "0xPlatformBEP20ReceiveAddress" {
t.Fatalf("bep20 receipt snapshot mismatch: %+v", receipt) t.Fatalf("bep20 receipt snapshot mismatch: %+v", receipt)
} }
if bsc.lastTxHash != "0xreceiptbep20" || bsc.lastToAddress != "0xPlatformBEP20ReceiveAddress" || bsc.lastAmountMinor != 150 { if bsc.lastTxHash != txHash || bsc.lastToAddress != "0xPlatformBEP20ReceiveAddress" || bsc.lastAmountMinor != 150 {
t.Fatalf("bep20 client request mismatch: %+v", bsc) t.Fatalf("bep20 client request mismatch: %+v", bsc)
} }
} }
@ -611,6 +612,51 @@ func TestVerifyCoinSellerRechargeReceiptUSDTTRC20PayKeepsConfiguredAddress(t *te
} }
} }
func TestVerifyCoinSellerRechargeReceiptUSDTBEP20PayUsesBinance(t *testing.T) {
bsc := &fakeTronUSDTClient{err: xerr.New(xerr.Internal, "bsc chain client should not be called")}
binance := &fakeBinanceClient{record: walletservice.BinanceTransferRecord{
Source: "pay_history",
ID: "440537037787594752",
TxID: "P_C2C_BEP20_RECEIPT",
Coin: ledger.PaidCurrencyUSDT,
Status: 1,
Amount: "200.00000000",
AmountMinor: 20000,
RawJSON: `{"orderType":"C2C","orderId":"440537037787594752","amount":"200.00000000","currency":"USDT"}`,
}}
svc := walletservice.New(nil)
svc.SetBSCUSDTClient(bsc)
svc.SetBinanceClient(binance)
svc.SetExternalRechargeConfig(walletservice.ExternalRechargeConfig{
USDTBEP20Enabled: true,
USDTBEP20Addresses: map[string]string{
"yumi": "0xYumiConfiguredBSCAddress",
},
})
receipt, err := svc.VerifyCoinSellerRechargeReceipt(context.Background(), ledger.VerifyCoinSellerRechargeReceiptCommand{
AppCode: "yumi",
ProviderCode: ledger.PaymentProviderUSDTBEP20,
ExternalOrderNo: "440537037787594752",
USDMinorAmount: 20000,
OrderDateMS: 1784017140000,
})
if err != nil {
t.Fatalf("VerifyCoinSellerRechargeReceipt BEP20 Pay C2C failed: %v", err)
}
if !receipt.Verified || receipt.Status != "confirmed" || receipt.ProviderOrderID != "P_C2C_BEP20_RECEIPT" || receipt.ProviderAmountMinor != 20000 {
t.Fatalf("BEP20 Pay C2C receipt snapshot mismatch: %+v", receipt)
}
if receipt.ReceiveAddress != "0xYumiConfiguredBSCAddress" {
t.Fatalf("BEP20 Pay C2C must keep configured chain address: %+v", receipt)
}
if binance.calls != 1 || binance.req.Network != "BSC" || binance.req.ExternalID != "440537037787594752" || binance.req.AmountMinor != 20000 {
t.Fatalf("BEP20 Pay C2C must use Binance transfer lookup: calls=%d req=%+v", binance.calls, binance.req)
}
if bsc.lastTxHash != "" {
t.Fatalf("BEP20 Pay C2C order id must not call BSC chain client: %+v", bsc)
}
}
func TestVerifyCoinSellerRechargeReceiptUSDTTRC20OffchainMissingBinanceAccount(t *testing.T) { func TestVerifyCoinSellerRechargeReceiptUSDTTRC20OffchainMissingBinanceAccount(t *testing.T) {
binance := &fakeBinanceClient{err: xerr.New(xerr.Unavailable, "binance account is not configured")} binance := &fakeBinanceClient{err: xerr.New(xerr.Unavailable, "binance account is not configured")}
svc := walletservice.New(nil) svc := walletservice.New(nil)