diff --git a/services/wallet-service/internal/service/wallet/external_recharge_receipt.go b/services/wallet-service/internal/service/wallet/external_recharge_receipt.go index 7819594a..198eaff4 100644 --- a/services/wallet-service/internal/service/wallet/external_recharge_receipt.go +++ b/services/wallet-service/internal/service/wallet/external_recharge_receipt.go @@ -342,8 +342,10 @@ func (s *Service) verifyUSDTRechargeReceipt(ctx context.Context, command ledger. snapshot.ProviderAmountMinor = command.USDMinorAmount snapshot.ProviderOrderID = command.ExternalOrderNo snapshot.ReceiveAddress = strings.TrimSpace(receiveAddress) - if chain == "trc20" && !isLikelyChainTransactionHash(command.ExternalOrderNo) { - return s.verifyBinanceTRXDepositReceipt(ctx, command, snapshot) + if !isLikelyChainTransactionHash(command.ExternalOrderNo) { + // Binance Pay C2C 订单号不属于某条公链;后台选择 TRC20 或 BEP20 只表示运营记录的支付币种。 + // 因此所有非链上哈希都必须先走 Binance 账户入账事实,不能把 BEP20 的 C2C 订单号误送给 BSC 链上客户端。 + return s.verifyBinanceTransferReceipt(ctx, command, snapshot, binanceNetworkForReceiptChain(chain)) } if client == nil { 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 } -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 { 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, ExternalID: command.ExternalOrderNo, Coin: ledger.PaidCurrencyUSDT, - Network: "TRX", + Network: network, ToAddress: snapshot.ReceiveAddress, AmountMinor: command.USDMinorAmount, OrderTimeMS: command.OrderDateMS, @@ -392,6 +394,17 @@ func (s *Service) verifyBinanceTRXDepositReceipt(ctx context.Context, command le 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 { return ledger.CoinSellerRechargeReceiptVerification{ ProviderCode: command.ProviderCode, diff --git a/services/wallet-service/internal/service/wallet/external_recharge_receipt_test.go b/services/wallet-service/internal/service/wallet/external_recharge_receipt_test.go index 1bb4c601..27836330 100644 --- a/services/wallet-service/internal/service/wallet/external_recharge_receipt_test.go +++ b/services/wallet-service/internal/service/wallet/external_recharge_receipt_test.go @@ -467,10 +467,11 @@ func TestVerifyCoinSellerRechargeReceiptUSDTBEP20UsesReadOnlyClient(t *testing.T USDTBEP20Enabled: true, USDTBEP20Address: "0xPlatformBEP20ReceiveAddress", }) + txHash := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" receipt, err := svc.VerifyCoinSellerRechargeReceipt(context.Background(), ledger.VerifyCoinSellerRechargeReceiptCommand{ AppCode: "lalu", ProviderCode: ledger.PaymentProviderUSDTBEP20, - ExternalOrderNo: "0xreceiptbep20", + ExternalOrderNo: txHash, USDMinorAmount: 150, }) 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" { 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) } } @@ -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) { binance := &fakeBinanceClient{err: xerr.New(xerr.Unavailable, "binance account is not configured")} svc := walletservice.New(nil)