增加显式C2C充值校验渠道
This commit is contained in:
parent
7609509f7f
commit
3210db25c4
@ -850,6 +850,8 @@ func normalizeRechargeProvider(providerCode string, chain string) (string, strin
|
||||
return "usdt", "TRON", nil
|
||||
case "usdt_bep20", "bep20", "bsc", "bnb":
|
||||
return "usdt", "BSC", nil
|
||||
case "usdt_c2c", "c2c":
|
||||
return "usdt", "C2C", nil
|
||||
default:
|
||||
return "", "", errors.New("充值方式不正确")
|
||||
}
|
||||
@ -865,6 +867,8 @@ func normalizeRechargeChain(chain string) string {
|
||||
return "TRON"
|
||||
case "bsc", "bnb", "bep20", "usdt_bep20":
|
||||
return "BSC"
|
||||
case "c2c", "usdt_c2c":
|
||||
return "C2C"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@ -879,6 +883,8 @@ func receiptProviderCode(order model.CoinSellerRechargeOrder) string {
|
||||
return "usdt_trc20"
|
||||
case "BSC":
|
||||
return "usdt_bep20"
|
||||
case "C2C":
|
||||
return "usdt_c2c"
|
||||
default:
|
||||
return "usdt"
|
||||
}
|
||||
@ -903,6 +909,8 @@ func receiptChain(order model.CoinSellerRechargeOrder) string {
|
||||
return "trc20"
|
||||
case "BSC":
|
||||
return "bep20"
|
||||
case "C2C":
|
||||
return "c2c"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ func (c *Client) FindUSDTTransfer(ctx context.Context, req ports.BinanceTransfer
|
||||
req.Coin = strings.ToUpper(strings.TrimSpace(req.Coin))
|
||||
req.Network = strings.ToUpper(strings.TrimSpace(req.Network))
|
||||
req.ToAddress = strings.TrimSpace(req.ToAddress)
|
||||
if req.ExternalID == "" || req.Coin == "" || req.Network == "" || req.ToAddress == "" || req.AmountMinor <= 0 {
|
||||
if req.ExternalID == "" || req.Coin == "" || req.Network == "" || req.AmountMinor <= 0 {
|
||||
return ports.BinanceTransferRecord{}, xerr.New(xerr.InvalidArgument, "binance transfer verification request is incomplete")
|
||||
}
|
||||
serverTimeMS, err := c.serverTime(ctx)
|
||||
@ -260,7 +260,8 @@ func matchDepositRecord(req ports.BinanceTransferLookupRequest, records []deposi
|
||||
if candidate.Status != 1 {
|
||||
return ports.BinanceTransferRecord{}, xerr.New(xerr.Conflict, "binance deposit status is not successful")
|
||||
}
|
||||
if !strings.EqualFold(strings.TrimSpace(candidate.Address), strings.TrimSpace(req.ToAddress)) {
|
||||
// Deposit History 属于具体链上入账,必须有预先配置的收款地址;允许空地址仅服务于随后独立校验的 Pay C2C 事实源。
|
||||
if strings.TrimSpace(req.ToAddress) == "" || !strings.EqualFold(strings.TrimSpace(candidate.Address), strings.TrimSpace(req.ToAddress)) {
|
||||
return ports.BinanceTransferRecord{}, xerr.New(xerr.Conflict, "binance deposit address mismatch")
|
||||
}
|
||||
amountMinor, err := parseUSDTAmountMinor(candidate.Amount)
|
||||
|
||||
@ -68,6 +68,8 @@ const (
|
||||
PaymentProviderUSDTTRC20 = "usdt_trc20"
|
||||
// PaymentProviderUSDTBEP20 是用户提交 BSC BEP20 链上交易哈希的 USDT 充值渠道。
|
||||
PaymentProviderUSDTBEP20 = "usdt_bep20"
|
||||
// PaymentProviderUSDTC2C 是 Binance Pay C2C 收款订单;它不绑定公链,也不要求链上收款地址。
|
||||
PaymentProviderUSDTC2C = "usdt_c2c"
|
||||
// ThirdPartyPaymentStatusActive 表示渠道或支付方式可用于 H5 下单。
|
||||
ThirdPartyPaymentStatusActive = "active"
|
||||
// ThirdPartyPaymentStatusDisabled 表示渠道或支付方式已被后台关闭。
|
||||
|
||||
@ -416,6 +416,8 @@ func NormalizePaymentProvider(provider string) string {
|
||||
return PaymentProviderUSDTTRC20
|
||||
case PaymentProviderUSDTBEP20:
|
||||
return PaymentProviderUSDTBEP20
|
||||
case PaymentProviderUSDTC2C:
|
||||
return PaymentProviderUSDTC2C
|
||||
case PaymentProviderGooglePlay:
|
||||
return PaymentProviderGooglePlay
|
||||
default:
|
||||
|
||||
@ -33,6 +33,9 @@ func (s *Service) VerifyCoinSellerRechargeReceipt(ctx context.Context, command l
|
||||
return s.verifyUSDTRechargeReceipt(ctx, command, "trc20", s.externalRecharge.USDTTRC20Enabled, s.externalRecharge.usdtTRC20AddressForApp(command.AppCode), s.tronUSDT)
|
||||
case ledger.PaymentProviderUSDTBEP20:
|
||||
return s.verifyUSDTRechargeReceipt(ctx, command, "bep20", s.externalRecharge.USDTBEP20Enabled, s.externalRecharge.usdtBEP20AddressForApp(command.AppCode), s.bscUSDT)
|
||||
case ledger.PaymentProviderUSDTC2C:
|
||||
// C2C 是 Binance 账户内收款事实,不属于 TRON/BSC;空地址和关闭的链上开关不影响只读查单。
|
||||
return s.verifyUSDTRechargeReceipt(ctx, command, "c2c", false, "", nil)
|
||||
default:
|
||||
return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.InvalidArgument, "receipt provider is invalid")
|
||||
}
|
||||
@ -333,20 +336,23 @@ type receiptUSDTClient interface {
|
||||
}
|
||||
|
||||
func (s *Service) verifyUSDTRechargeReceipt(ctx context.Context, command ledger.VerifyCoinSellerRechargeReceiptCommand, chain string, enabled bool, receiveAddress string, client receiptUSDTClient) (ledger.CoinSellerRechargeReceiptVerification, error) {
|
||||
if !enabled || strings.TrimSpace(receiveAddress) == "" {
|
||||
return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "usdt "+chain+" receipt verification is not configured")
|
||||
}
|
||||
snapshot := s.receiptSnapshot(command)
|
||||
snapshot.Chain = chain
|
||||
snapshot.CurrencyCode = ledger.PaidCurrencyUSDT
|
||||
snapshot.ProviderAmountMinor = command.USDMinorAmount
|
||||
snapshot.ProviderOrderID = command.ExternalOrderNo
|
||||
snapshot.ReceiveAddress = strings.TrimSpace(receiveAddress)
|
||||
if !isLikelyChainTransactionHash(command.ExternalOrderNo) {
|
||||
// Binance Pay C2C 订单号不属于某条公链;后台选择 TRC20 或 BEP20 只表示运营记录的支付币种。
|
||||
// 因此所有非链上哈希都必须先走 Binance 账户入账事实,不能把 BEP20 的 C2C 订单号误送给 BSC 链上客户端。
|
||||
if chain == "c2c" {
|
||||
// 查单来源由运营显式选择;C2C 只读取 Binance 账户事实,绝不从 TRON/BSC 隐式降级或猜测。
|
||||
return s.verifyBinanceTransferReceipt(ctx, command, snapshot, binanceNetworkForReceiptChain(chain))
|
||||
}
|
||||
if !enabled || snapshot.ReceiveAddress == "" {
|
||||
return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "usdt "+chain+" receipt verification is not configured")
|
||||
}
|
||||
if !isLikelyChainTransactionHash(command.ExternalOrderNo) {
|
||||
// TRON/BSC 只接受标准交易哈希;非链上编号必须由运营明确改选 C2C,避免跨事实源误校验。
|
||||
return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.InvalidArgument, "usdt "+chain+" receipt requires transaction hash")
|
||||
}
|
||||
if client == nil {
|
||||
return ledger.CoinSellerRechargeReceiptVerification{}, xerr.New(xerr.Unavailable, "usdt "+chain+" client is not configured")
|
||||
}
|
||||
@ -387,7 +393,7 @@ func (s *Service) verifyBinanceTransferReceipt(ctx context.Context, command ledg
|
||||
snapshot.ProviderOrderID = record.TxID
|
||||
snapshot.ProviderAmountMinor = record.AmountMinor
|
||||
// Pay C2C 没有链上收款地址;只有 Deposit History 命中时才用 Binance 返回地址覆盖配置快照。
|
||||
if strings.TrimSpace(record.Address) != "" {
|
||||
if snapshot.Chain != "c2c" && strings.TrimSpace(record.Address) != "" {
|
||||
snapshot.ReceiveAddress = record.Address
|
||||
}
|
||||
snapshot.RawJSON = strings.TrimSpace(record.RawJSON)
|
||||
@ -400,6 +406,8 @@ func binanceNetworkForReceiptChain(chain string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(chain)) {
|
||||
case "bep20":
|
||||
return "BSC"
|
||||
case "c2c":
|
||||
return "C2C"
|
||||
default:
|
||||
return "TRX"
|
||||
}
|
||||
|
||||
@ -529,7 +529,25 @@ func TestVerifyCoinSellerRechargeReceiptUSDTTRC20HashUsesTronGrid(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyCoinSellerRechargeReceiptUSDTTRC20OffchainUsesBinance(t *testing.T) {
|
||||
func TestVerifyCoinSellerRechargeReceiptUSDTTRC20RejectsC2COrderNumber(t *testing.T) {
|
||||
svc := walletservice.New(nil)
|
||||
svc.SetTronUSDTClient(&fakeTronUSDTClient{})
|
||||
svc.SetExternalRechargeConfig(walletservice.ExternalRechargeConfig{
|
||||
USDTTRC20Enabled: true,
|
||||
USDTTRC20Address: "TPlatformReceiveAddress",
|
||||
})
|
||||
_, err := svc.VerifyCoinSellerRechargeReceipt(context.Background(), ledger.VerifyCoinSellerRechargeReceiptCommand{
|
||||
AppCode: "lalu",
|
||||
ProviderCode: ledger.PaymentProviderUSDTTRC20,
|
||||
ExternalOrderNo: "440537037787594752",
|
||||
USDMinorAmount: 20000,
|
||||
})
|
||||
if !xerr.IsCode(err, xerr.InvalidArgument) || !strings.Contains(err.Error(), "requires transaction hash") {
|
||||
t.Fatalf("TRC20 must reject C2C order number without fallback: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyCoinSellerRechargeReceiptUSDTC2CUsesBinance(t *testing.T) {
|
||||
tron := &fakeTronUSDTClient{err: xerr.New(xerr.Internal, "tron should not be called")}
|
||||
binance := &fakeBinanceClient{record: walletservice.BinanceTransferRecord{
|
||||
TxID: "Off-chain transfer 388611987194",
|
||||
@ -553,7 +571,7 @@ func TestVerifyCoinSellerRechargeReceiptUSDTTRC20OffchainUsesBinance(t *testing.
|
||||
})
|
||||
receipt, err := svc.VerifyCoinSellerRechargeReceipt(context.Background(), ledger.VerifyCoinSellerRechargeReceiptCommand{
|
||||
AppCode: "aslan",
|
||||
ProviderCode: ledger.PaymentProviderUSDTTRC20,
|
||||
ProviderCode: ledger.PaymentProviderUSDTC2C,
|
||||
ExternalOrderNo: "388611987194",
|
||||
USDMinorAmount: 50000,
|
||||
OrderDateMS: 1783344000000,
|
||||
@ -561,10 +579,10 @@ func TestVerifyCoinSellerRechargeReceiptUSDTTRC20OffchainUsesBinance(t *testing.
|
||||
if err != nil {
|
||||
t.Fatalf("VerifyCoinSellerRechargeReceipt binance offchain failed: %v", err)
|
||||
}
|
||||
if !receipt.Verified || receipt.Status != "confirmed" || receipt.ProviderOrderID != "Off-chain transfer 388611987194" || receipt.ProviderAmountMinor != 50000 || receipt.ReceiveAddress != "TNwqCkF8cnM4XiwjnbbKTfdZvFDqVoa1TN" {
|
||||
if !receipt.Verified || receipt.Status != "confirmed" || receipt.ProviderOrderID != "Off-chain transfer 388611987194" || receipt.ProviderAmountMinor != 50000 || receipt.ReceiveAddress != "" {
|
||||
t.Fatalf("binance offchain receipt snapshot mismatch: %+v", receipt)
|
||||
}
|
||||
if binance.calls != 1 || binance.req.AppCode != "aslan" || binance.req.ExternalID != "388611987194" || binance.req.Network != "TRX" || binance.req.ToAddress != "TNwqCkF8cnM4XiwjnbbKTfdZvFDqVoa1TN" || binance.req.AmountMinor != 50000 || binance.req.OrderTimeMS != 1783344000000 {
|
||||
if binance.calls != 1 || binance.req.AppCode != "aslan" || binance.req.ExternalID != "388611987194" || binance.req.Network != "C2C" || binance.req.ToAddress != "" || binance.req.AmountMinor != 50000 || binance.req.OrderTimeMS != 1783344000000 {
|
||||
t.Fatalf("binance offchain request mismatch: calls=%d req=%+v", binance.calls, binance.req)
|
||||
}
|
||||
if tron.lastTxHash != "" {
|
||||
@ -572,7 +590,7 @@ func TestVerifyCoinSellerRechargeReceiptUSDTTRC20OffchainUsesBinance(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyCoinSellerRechargeReceiptUSDTTRC20PayKeepsConfiguredAddress(t *testing.T) {
|
||||
func TestVerifyCoinSellerRechargeReceiptUSDTC2CPayKeepsNoChainAddress(t *testing.T) {
|
||||
binance := &fakeBinanceClient{record: walletservice.BinanceTransferRecord{
|
||||
Source: "pay_history",
|
||||
ID: "433527303245463552",
|
||||
@ -593,7 +611,7 @@ func TestVerifyCoinSellerRechargeReceiptUSDTTRC20PayKeepsConfiguredAddress(t *te
|
||||
})
|
||||
receipt, err := svc.VerifyCoinSellerRechargeReceipt(context.Background(), ledger.VerifyCoinSellerRechargeReceiptCommand{
|
||||
AppCode: "yumi",
|
||||
ProviderCode: ledger.PaymentProviderUSDTTRC20,
|
||||
ProviderCode: ledger.PaymentProviderUSDTC2C,
|
||||
ExternalOrderNo: "433527303245463552",
|
||||
USDMinorAmount: 10000,
|
||||
OrderDateMS: 1779713682799,
|
||||
@ -604,15 +622,15 @@ func TestVerifyCoinSellerRechargeReceiptUSDTTRC20PayKeepsConfiguredAddress(t *te
|
||||
if !receipt.Verified || receipt.Status != "confirmed" || receipt.ProviderOrderID != "P_A223BU5M4Y171116" || receipt.ProviderAmountMinor != 10000 {
|
||||
t.Fatalf("Pay C2C receipt snapshot mismatch: %+v", receipt)
|
||||
}
|
||||
if receipt.ReceiveAddress != "TYumiConfiguredChainAddress" {
|
||||
t.Fatalf("Pay C2C must not erase configured chain address: %+v", receipt)
|
||||
if receipt.ReceiveAddress != "" {
|
||||
t.Fatalf("Pay C2C must not inherit an unrelated chain address: %+v", receipt)
|
||||
}
|
||||
if binance.req.OrderTimeMS != 1779713682799 {
|
||||
t.Fatalf("coin seller order time must bound Pay History query: %+v", binance.req)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyCoinSellerRechargeReceiptUSDTBEP20PayUsesBinance(t *testing.T) {
|
||||
func TestVerifyCoinSellerRechargeReceiptUSDTC2CPayUsesBinance(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",
|
||||
@ -635,7 +653,7 @@ func TestVerifyCoinSellerRechargeReceiptUSDTBEP20PayUsesBinance(t *testing.T) {
|
||||
})
|
||||
receipt, err := svc.VerifyCoinSellerRechargeReceipt(context.Background(), ledger.VerifyCoinSellerRechargeReceiptCommand{
|
||||
AppCode: "yumi",
|
||||
ProviderCode: ledger.PaymentProviderUSDTBEP20,
|
||||
ProviderCode: ledger.PaymentProviderUSDTC2C,
|
||||
ExternalOrderNo: "440537037787594752",
|
||||
USDMinorAmount: 20000,
|
||||
OrderDateMS: 1784017140000,
|
||||
@ -646,18 +664,49 @@ func TestVerifyCoinSellerRechargeReceiptUSDTBEP20PayUsesBinance(t *testing.T) {
|
||||
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 receipt.ReceiveAddress != "" {
|
||||
t.Fatalf("C2C must not persist an unrelated 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 binance.calls != 1 || binance.req.Network != "C2C" || binance.req.ExternalID != "440537037787594752" || binance.req.AmountMinor != 20000 {
|
||||
t.Fatalf("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 TestVerifyCoinSellerRechargeReceiptUSDTC2CDoesNotRequireChainConfig(t *testing.T) {
|
||||
binance := &fakeBinanceClient{record: walletservice.BinanceTransferRecord{
|
||||
Source: "pay_history",
|
||||
TxID: "P_C2C_LALU_RECEIPT",
|
||||
Coin: ledger.PaidCurrencyUSDT,
|
||||
Status: 1,
|
||||
Amount: "200.00000000",
|
||||
AmountMinor: 20000,
|
||||
}}
|
||||
svc := walletservice.New(nil)
|
||||
svc.SetBinanceClient(binance)
|
||||
// Lalu 只校验 Binance C2C 收款事实时不需要开启 BSC RPC,也不需要伪造一个链上收款地址。
|
||||
svc.SetExternalRechargeConfig(walletservice.ExternalRechargeConfig{})
|
||||
receipt, err := svc.VerifyCoinSellerRechargeReceipt(context.Background(), ledger.VerifyCoinSellerRechargeReceiptCommand{
|
||||
AppCode: "lalu",
|
||||
ProviderCode: ledger.PaymentProviderUSDTC2C,
|
||||
ExternalOrderNo: "440537037787594752",
|
||||
USDMinorAmount: 20000,
|
||||
OrderDateMS: 1784017620000,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BEP20 Pay C2C must not require chain config: %v", err)
|
||||
}
|
||||
if !receipt.Verified || receipt.ProviderOrderID != "P_C2C_LALU_RECEIPT" {
|
||||
t.Fatalf("BEP20 Pay C2C receipt mismatch without chain config: %+v", receipt)
|
||||
}
|
||||
if binance.calls != 1 || binance.req.ToAddress != "" || binance.req.Network != "C2C" {
|
||||
t.Fatalf("BEP20 Pay C2C lookup mismatch without chain config: calls=%d req=%+v", binance.calls, binance.req)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyCoinSellerRechargeReceiptUSDTC2CMissingBinanceAccount(t *testing.T) {
|
||||
binance := &fakeBinanceClient{err: xerr.New(xerr.Unavailable, "binance account is not configured")}
|
||||
svc := walletservice.New(nil)
|
||||
svc.SetBinanceClient(binance)
|
||||
@ -667,7 +716,7 @@ func TestVerifyCoinSellerRechargeReceiptUSDTTRC20OffchainMissingBinanceAccount(t
|
||||
})
|
||||
receipt, err := svc.VerifyCoinSellerRechargeReceipt(context.Background(), ledger.VerifyCoinSellerRechargeReceiptCommand{
|
||||
AppCode: "yumi",
|
||||
ProviderCode: ledger.PaymentProviderUSDTTRC20,
|
||||
ProviderCode: ledger.PaymentProviderUSDTC2C,
|
||||
ExternalOrderNo: "388611987194",
|
||||
USDMinorAmount: 50000,
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user