diff --git a/services/wallet-service/internal/service/wallet/service_test.go b/services/wallet-service/internal/service/wallet/service_test.go index 5a6c2e9b..340e23db 100644 --- a/services/wallet-service/internal/service/wallet/service_test.go +++ b/services/wallet-service/internal/service/wallet/service_test.go @@ -382,6 +382,55 @@ func TestDebitGiftUsesRegionalReturnCoinRatioWithoutChangingHostDiamonds(t *test } } +func TestDebitGiftAllowsZeroReturnCoinRatio(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := walletservice.New(repository) + cases := []struct { + name string + giftID string + giftType string + regionID int64 + senderID int64 + targetID int64 + wantHeat int64 + }{ + {name: "lucky", giftID: "zero-return-lucky-gift", giftType: resourcedomain.GiftTypeLucky, regionID: 7702, senderID: 14601, targetID: 14602, wantHeat: 10}, + {name: "super-lucky", giftID: "zero-return-super-lucky-gift", giftType: resourcedomain.GiftTypeSuperLucky, regionID: 7703, senderID: 14603, targetID: 14604, wantHeat: 1}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + repository.SetBalance(tc.senderID, 500) + repository.SetGiftPrice(tc.giftID, "v1", 100, 100, 100) + repository.SetGiftType(tc.giftID, tc.giftType) + // 0% 返币是后台可配置的合法业务结果;扣费和房间贡献仍要按礼物真实价格结算。 + repository.SetGiftReturnCoinRatio(tc.regionID, tc.giftType, "0.00") + + receipt, err := svc.DebitGift(context.Background(), ledger.DebitGiftCommand{ + CommandID: "cmd-zero-return-" + tc.name, + RoomID: "room-zero-return-" + tc.name, + SenderUserID: tc.senderID, + SenderRegionID: 1001, + RegionID: tc.regionID, + TargetUserID: tc.targetID, + GiftID: tc.giftID, + GiftCount: 1, + PriceVersion: "v1", + }) + if err != nil { + t.Fatalf("DebitGift with zero return ratio failed: %v", err) + } + if receipt.CoinSpent != 100 || receipt.HeatValue != tc.wantHeat || receipt.BalanceAfter != 400 { + t.Fatalf("zero return ratio settlement mismatch: %+v want heat %d", receipt, tc.wantHeat) + } + assertBalance(t, svc, tc.senderID, ledger.AssetCoin, 400) + assertBalance(t, svc, tc.targetID, ledger.AssetCoin, 0) + if got := repository.CountRows("wallet_outbox", "transaction_id = ? AND event_type = ?", receipt.TransactionID, "WalletGiftIncomeCredited"); got != 0 { + t.Fatalf("zero return ratio must not publish gift income event, got %d", got) + } + }) + } +} + func TestBatchDebitGiftAppliesGlobalDefaultGiftDiamondRatioToEachTargetHeatValue(t *testing.T) { repository := mysqltest.NewRepository(t) repository.SetBalance(15001, 1000) diff --git a/services/wallet-service/internal/storage/mysql/gift_pricing.go b/services/wallet-service/internal/storage/mysql/gift_pricing.go index 5afff69e..97581a3c 100644 --- a/services/wallet-service/internal/storage/mysql/gift_pricing.go +++ b/services/wallet-service/internal/storage/mysql/gift_pricing.go @@ -174,6 +174,10 @@ func giftDiamondAmount(chargeAmount int64, ratioPPM int64) (int64, error) { if chargeAmount < 0 || ratioPPM < 0 { return 0, xerr.New(xerr.InvalidArgument, "gift diamond amount is invalid") } + if chargeAmount == 0 || ratioPPM == 0 { + // 0 金额或 0% 比例都是合法业务结果;这里只跳过乘法溢出工具的正数乘数约束,不放宽负数和数量校验。 + return 0, nil + } product, err := checkedMul(chargeAmount, ratioPPM) if err != nil { return 0, err