2026-07-07 18:05:02 +08:00

212 lines
7.0 KiB
Go

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)
}
}