package financeorder import ( "context" "strings" "testing" "hyapp-admin-server/internal/model" "hyapp-admin-server/internal/modules/shared" walletv1 "hyapp.local/api/proto/wallet/v1" ) func TestNormalizeCreateOrderRequiresExplicitUSDTChain(t *testing.T) { _, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{ AppCode: "lalu", TargetUserID: "10001", USDAmount: 12.34, CoinAmount: 1200, ProviderCode: "usdt", ExternalOrderNo: "0xabc", }) if err == nil || !strings.Contains(err.Error(), "必须选择链") { t.Fatalf("expected explicit usdt chain error, got %v", err) } input, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{ AppCode: "lalu", TargetUserID: "10001", USDAmount: 12.34, CoinAmount: 1200, ProviderCode: "usdt", Chain: "bsc", ExternalOrderNo: "0xabc", }) if err != nil { t.Fatalf("normalize usdt order failed: %v", err) } if input.ProviderCode != "usdt" || input.Chain != "BSC" || input.USDMinorAmount != 1234 { t.Fatalf("normalized input mismatch: %+v", input) } } func TestNormalizeCreateOrderDoesNotRequireProviderHints(t *testing.T) { for _, providerCode := range []string{"mifapay", "v5pay"} { input, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{ AppCode: "lalu", TargetUserID: "10001", USDAmount: 12.34, CoinAmount: 1200, ProviderCode: providerCode, ExternalOrderNo: providerCode + "-order", }) if err != nil { t.Fatalf("normalize %s without provider hints failed: %v", providerCode, err) } if input.ProviderCurrencyCode != "" || input.ProviderCountryCode != "" || input.OrderDateMS != 0 { t.Fatalf("provider hints should remain optional for %s: %+v", providerCode, input) } } } func TestNormalizeCreateOrderAllowsZeroCoinAmount(t *testing.T) { input, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{ AppCode: "lalu", TargetUserID: "10001", USDAmount: 12.34, CoinAmount: 0, ProviderCode: "mifapay", ExternalOrderNo: "MIFA-001", Remark: " 补单,不发金币 ", }) if err != nil { t.Fatalf("zero coin makeup order should be accepted: %v", err) } if input.CoinAmount != 0 || input.USDMinorAmount != 1234 || input.Remark != "补单,不发金币" { t.Fatalf("normalized zero coin order mismatch: %+v", input) } } func TestExecuteGrantSkipsWalletAndLegacyForZeroCoinMakeupOrder(t *testing.T) { service := NewService(nil, nil, nil) result, err := service.executeGrant(context.Background(), shared.Actor{UserID: 7}, model.CoinSellerRechargeOrder{ ID: 42, AppCode: "aslan", TargetUserID: 10001, CoinAmount: 0, }, "req-1") if err != nil { t.Fatalf("zero coin makeup order should not require wallet or legacy writer: %v", err) } if result.CommandID != "admin-coin-seller-recharge:42" || result.TransactionID != "" || result.AmountDelta != 0 { t.Fatalf("zero coin grant result mismatch: %+v", result) } } func TestVerifyReceiptMatchesOrderAmounts(t *testing.T) { order := model.CoinSellerRechargeOrder{ ExternalOrderNo: "merchant-1", USDMinorAmount: 1234, ProviderCurrencyCode: "USD", ProviderAmountMinor: 0, ProviderPayloadJSON: "", TargetDisplayUserID: "10001", TargetCountryID: 1, TargetRegionID: 2, ProviderCountryCode: "SA", ProviderPayType: "CARD", ProviderOrderID: "merchant-1", ProviderStatus: "paid", WalletTransactionID: "", WalletAssetType: "", WalletAmountDelta: 0, WalletBalanceAfter: 0, OperatorName: "ops", VerifiedByName: "", GrantedByName: "", ReceiveAddress: "", FailureReason: "", WalletCommandID: "", TargetUserID: 10001, ProviderCode: "mifapay", Chain: "", USDAmount: "12.34", CoinAmount: 1200, Status: model.CoinSellerRechargeOrderStatusCreated, VerifyStatus: model.CoinSellerRechargeVerifyStatusPending, GrantStatus: model.CoinSellerRechargeGrantStatusPending, OperatorUserID: 7, CreatedAtMS: 1, UpdatedAtMS: 1, } if err := verifyReceiptMatchesOrder(order, &walletv1.VerifyCoinSellerRechargeReceiptResponse{ ExternalOrderNo: "merchant-1", CurrencyCode: "USD", ProviderAmountMinor: 1234, }); err != nil { t.Fatalf("expected USD amount to match: %v", err) } if err := verifyReceiptMatchesOrder(order, &walletv1.VerifyCoinSellerRechargeReceiptResponse{ ExternalOrderNo: "merchant-1", CurrencyCode: "USD", ProviderAmountMinor: 1200, }); err == nil { t.Fatalf("expected USD amount mismatch") } } func TestVerifyReceiptMatchesNonUSDProviderAmount(t *testing.T) { order := model.CoinSellerRechargeOrder{ ExternalOrderNo: "v5-1", USDMinorAmount: 1000, ProviderCurrencyCode: "IDR", ProviderAmountMinor: 15000000, ProviderCode: "v5pay", TargetDisplayUserID: "10001", TargetUserID: 10001, USDAmount: "10.00", CoinAmount: 1000, Status: model.CoinSellerRechargeOrderStatusCreated, VerifyStatus: model.CoinSellerRechargeVerifyStatusPending, GrantStatus: model.CoinSellerRechargeGrantStatusPending, OperatorUserID: 7, } if err := verifyReceiptMatchesOrder(order, &walletv1.VerifyCoinSellerRechargeReceiptResponse{ ExternalOrderNo: "v5-1", CurrencyCode: "IDR", ProviderAmountMinor: 15000000, }); err != nil { t.Fatalf("expected provider amount to match: %v", err) } order.ProviderAmountMinor = 0 if err := verifyReceiptMatchesOrder(order, &walletv1.VerifyCoinSellerRechargeReceiptResponse{ ExternalOrderNo: "v5-1", CurrencyCode: "IDR", ProviderAmountMinor: 15000000, }); err != nil { t.Fatalf("non-USD provider amount should be an audit snapshot when operator did not fill local minor amount: %v", err) } } func TestVerifyReceiptMatchesInputRequiresUSDExactMatch(t *testing.T) { input := normalizedCreateOrderInput{ ExternalOrderNo: "usdt-tx", USDMinorAmount: 1234, ProviderCode: "usdt", Chain: "TRON", } if err := verifyReceiptMatchesInput(input, coinSellerRechargeReceiptVerificationDTO{ ExternalOrderNo: "usdt-tx", CurrencyCode: "USDT", ProviderAmountMinor: 1234, }); err != nil { t.Fatalf("expected usdt amount to match: %v", err) } if err := verifyReceiptMatchesInput(input, coinSellerRechargeReceiptVerificationDTO{ ExternalOrderNo: "usdt-tx", CurrencyCode: "USDT", ProviderAmountMinor: 1200, }); err == nil { t.Fatalf("expected usdt amount mismatch") } } func TestReceiptProviderCodeForUSDTChains(t *testing.T) { if got := receiptProviderCode(model.CoinSellerRechargeOrder{ProviderCode: "usdt", Chain: "TRON"}); got != "usdt_trc20" { t.Fatalf("tron provider code mismatch: %s", got) } if got := receiptProviderCode(model.CoinSellerRechargeOrder{ProviderCode: "usdt", Chain: "BSC"}); got != "usdt_bep20" { t.Fatalf("bsc provider code mismatch: %s", got) } }