From b1a9d818aaaf10261979fd75feb6ff9e4bd56f46 Mon Sep 17 00:00:00 2001 From: zhx Date: Fri, 5 Jun 2026 13:51:14 +0800 Subject: [PATCH] fix: settle gift contribution by coin spent --- server/admin/internal/modules/resource/dto.go | 2 -- .../internal/modules/resource/request.go | 6 ++-- .../internal/service/wallet/service_test.go | 35 ++++++++++--------- .../internal/storage/mysql/repository.go | 20 ++++------- .../storage/mysql/resource_repository.go | 10 ++++-- 5 files changed, 34 insertions(+), 39 deletions(-) diff --git a/server/admin/internal/modules/resource/dto.go b/server/admin/internal/modules/resource/dto.go index 0e8161cb..f232ebd6 100644 --- a/server/admin/internal/modules/resource/dto.go +++ b/server/admin/internal/modules/resource/dto.go @@ -75,7 +75,6 @@ type giftDTO struct { ChargeAssetType string `json:"chargeAssetType"` CoinPrice int64 `json:"coinPrice"` GiftPointAmount int64 `json:"giftPointAmount"` - HeatValue int64 `json:"heatValue"` EffectiveFromMS int64 `json:"effectiveFromMs"` EffectiveToMS int64 `json:"effectiveToMs"` EffectTypes []string `json:"effectTypes"` @@ -300,7 +299,6 @@ func giftFromProto(gift *walletv1.GiftConfig) giftDTO { ChargeAssetType: gift.GetChargeAssetType(), CoinPrice: gift.GetCoinPrice(), GiftPointAmount: gift.GetGiftPointAmount(), - HeatValue: gift.GetHeatValue(), EffectiveFromMS: gift.GetEffectiveFromMs(), EffectiveToMS: gift.GetEffectiveToMs(), EffectTypes: gift.GetEffectTypes(), diff --git a/server/admin/internal/modules/resource/request.go b/server/admin/internal/modules/resource/request.go index 81426397..ec2c2008 100644 --- a/server/admin/internal/modules/resource/request.go +++ b/server/admin/internal/modules/resource/request.go @@ -135,7 +135,7 @@ type giftRequest struct { ChargeAssetType string `json:"chargeAssetType"` CoinPrice int64 `json:"coinPrice"` GiftPointAmount int64 `json:"giftPointAmount"` - HeatValue int64 `json:"heatValue"` + HeatValue int64 `json:"heatValue"` // 兼容旧后台请求;新后台不再展示或提交热度。 EffectiveAtMS int64 `json:"effectiveAtMs"` EffectiveFromMS int64 `json:"effectiveFromMs"` EffectiveToMS int64 `json:"effectiveToMs"` @@ -371,7 +371,7 @@ func (r giftRequest) createProto(c *gin.Context) *walletv1.CreateGiftConfigReque ChargeAssetType: strings.TrimSpace(r.ChargeAssetType), CoinPrice: r.CoinPrice, GiftPointAmount: r.GiftPointAmount, - HeatValue: r.HeatValue, + HeatValue: r.CoinPrice, EffectiveAtMs: r.EffectiveAtMS, EffectiveFromMs: r.EffectiveFromMS, EffectiveToMs: r.EffectiveToMS, @@ -396,7 +396,7 @@ func (r giftRequest) updateProto(c *gin.Context, giftID string) *walletv1.Update ChargeAssetType: strings.TrimSpace(r.ChargeAssetType), CoinPrice: r.CoinPrice, GiftPointAmount: r.GiftPointAmount, - HeatValue: r.HeatValue, + HeatValue: r.CoinPrice, EffectiveAtMs: r.EffectiveAtMS, EffectiveFromMs: r.EffectiveFromMS, EffectiveToMs: r.EffectiveToMS, diff --git a/services/wallet-service/internal/service/wallet/service_test.go b/services/wallet-service/internal/service/wallet/service_test.go index 6f2813cf..4bd94a25 100644 --- a/services/wallet-service/internal/service/wallet/service_test.go +++ b/services/wallet-service/internal/service/wallet/service_test.go @@ -54,7 +54,7 @@ func TestDebitGiftUsesServerPriceAndCreditsGiftPoint(t *testing.T) { t.Fatalf("DebitGift failed: %v", err) } - if receipt.CoinSpent != 14 || receipt.GiftPointAdded != 6 || receipt.HeatValue != 22 || receipt.BalanceAfter != 86 || receipt.PriceVersion != "v9" { + if receipt.CoinSpent != 14 || receipt.GiftPointAdded != 6 || receipt.HeatValue != 14 || receipt.BalanceAfter != 86 || receipt.PriceVersion != "v9" { t.Fatalf("server price settlement mismatch: %+v", receipt) } if receipt.TransactionID == "" || receipt.BillingReceiptID == "" { @@ -113,7 +113,7 @@ func TestBatchDebitGiftSettlesAllTargetsAtomically(t *testing.T) { t.Fatalf("BatchDebitGift failed: %v", err) } - if receipt.Aggregate.CoinSpent != 28 || receipt.Aggregate.GiftPointAdded != 12 || receipt.Aggregate.HeatValue != 44 || receipt.Aggregate.BalanceAfter != 72 || len(receipt.Targets) != 2 { + if receipt.Aggregate.CoinSpent != 28 || receipt.Aggregate.GiftPointAdded != 12 || receipt.Aggregate.HeatValue != 28 || receipt.Aggregate.BalanceAfter != 72 || len(receipt.Targets) != 2 { t.Fatalf("batch aggregate mismatch: %+v", receipt) } for _, userID := range []int64{10002, 10003} { @@ -272,21 +272,22 @@ func TestDebitGiftAppliesGlobalDefaultGiftDiamondRatioToHeatValueByGiftType(t *t repository.SetGiftDiamondRatio(8801, resourcedomain.GiftTypeSuperLucky, "50.00") cases := []struct { - giftID string - giftType string - command string - senderID int64 - heatUnit int64 - wantHeat int64 + giftID string + giftType string + command string + senderID int64 + coinPrice int64 + heatUnit int64 + wantHeat int64 }{ - {giftID: "normal-room-ratio-gift", giftType: resourcedomain.GiftTypeNormal, command: "cmd-normal-room-ratio", senderID: 13001, heatUnit: 100, wantHeat: 100}, - {giftID: "lucky-room-ratio-gift", giftType: resourcedomain.GiftTypeLucky, command: "cmd-lucky-room-ratio", senderID: 13002, heatUnit: 100, wantHeat: 10}, - {giftID: "super-lucky-room-ratio-gift", giftType: resourcedomain.GiftTypeSuperLucky, command: "cmd-super-lucky-room-ratio", senderID: 13003, heatUnit: 99, wantHeat: 0}, + {giftID: "normal-room-ratio-gift", giftType: resourcedomain.GiftTypeNormal, command: "cmd-normal-room-ratio", senderID: 13001, coinPrice: 100, heatUnit: 999, wantHeat: 100}, + {giftID: "lucky-room-ratio-gift", giftType: resourcedomain.GiftTypeLucky, command: "cmd-lucky-room-ratio", senderID: 13002, coinPrice: 100, heatUnit: 999, wantHeat: 10}, + {giftID: "super-lucky-room-ratio-gift", giftType: resourcedomain.GiftTypeSuperLucky, command: "cmd-super-lucky-room-ratio", senderID: 13003, coinPrice: 200, heatUnit: 500, wantHeat: 2}, } for _, tc := range cases { - repository.SetBalance(tc.senderID, 100) - repository.SetGiftPrice(tc.giftID, "v1", tc.heatUnit, 100, tc.heatUnit) + repository.SetBalance(tc.senderID, 500) + repository.SetGiftPrice(tc.giftID, "v1", tc.coinPrice, 100, tc.heatUnit) repository.SetGiftType(tc.giftID, tc.giftType) receipt, err := svc.DebitGift(context.Background(), ledger.DebitGiftCommand{ CommandID: tc.command, @@ -302,7 +303,7 @@ func TestDebitGiftAppliesGlobalDefaultGiftDiamondRatioToHeatValueByGiftType(t *t if err != nil { t.Fatalf("DebitGift %s failed: %v", tc.giftType, err) } - if receipt.HeatValue != tc.wantHeat || receipt.GiftPointAdded != 100 || receipt.CoinSpent != tc.heatUnit { + if receipt.HeatValue != tc.wantHeat || receipt.GiftPointAdded != 100 || receipt.CoinSpent != tc.coinPrice { t.Fatalf("%s room contribution ratio mismatch: %+v want heat %d", tc.giftType, receipt, tc.wantHeat) } } @@ -311,7 +312,7 @@ func TestDebitGiftAppliesGlobalDefaultGiftDiamondRatioToHeatValueByGiftType(t *t func TestBatchDebitGiftAppliesGlobalDefaultGiftDiamondRatioToEachTargetHeatValue(t *testing.T) { repository := mysqltest.NewRepository(t) repository.SetBalance(15001, 1000) - repository.SetGiftPrice("batch-room-ratio-gift", "v1", 100, 100, 100) + repository.SetGiftPrice("batch-room-ratio-gift", "v1", 100, 100, 999) repository.SetGiftType("batch-room-ratio-gift", resourcedomain.GiftTypeLucky) repository.SetGiftDiamondRatio(9901, resourcedomain.GiftTypeLucky, "25.00") svc := walletservice.New(repository) @@ -1085,7 +1086,7 @@ func TestGetUserGiftWallAggregatesSettledGifts(t *testing.T) { if err != nil { t.Fatalf("GetUserGiftWall failed: %v", err) } - if wall.GiftKindCount != 2 || wall.GiftTotalCount != 6 || wall.TotalValue != 135 || wall.TotalGiftPoint != 65 || wall.TotalHeatValue != 255 { + if wall.GiftKindCount != 2 || wall.GiftTotalCount != 6 || wall.TotalValue != 135 || wall.TotalGiftPoint != 65 || wall.TotalHeatValue != 135 { t.Fatalf("gift wall summary mismatch: %+v", wall) } rose, ok := giftWallItemByID(wall.Items, "rose") @@ -2150,7 +2151,7 @@ func TestGiftConfigMustSelectGiftResource(t *testing.T) { if err != nil { t.Fatalf("DebitGift with resource-backed gift failed: %v", err) } - if receipt.CoinSpent != 10 || receipt.GiftPointAdded != 4 || receipt.HeatValue != 18 { + if receipt.CoinSpent != 10 || receipt.GiftPointAdded != 4 || receipt.HeatValue != 10 { t.Fatalf("resource-backed gift settlement mismatch: %+v", receipt) } } diff --git a/services/wallet-service/internal/storage/mysql/repository.go b/services/wallet-service/internal/storage/mysql/repository.go index 60f69e6d..95a24148 100644 --- a/services/wallet-service/internal/storage/mysql/repository.go +++ b/services/wallet-service/internal/storage/mysql/repository.go @@ -222,16 +222,12 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm if err != nil { return ledger.Receipt{}, err } - baseHeatValue, err := checkedMul(price.HeatValue, int64(command.GiftCount)) - if err != nil { - return ledger.Receipt{}, err - } roomContributionRatio, roomContributionRatioRegionID, err := r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode) if err != nil { return ledger.Receipt{}, err } - // 房间贡献统一按全局默认礼物比例折算;room-service 只消费结算后的 heat_value,不再自己查钱包配置。 - heatValue, err := giftDiamondAmount(baseHeatValue, roomContributionRatio.PPM) + // 房间贡献只以真实扣费 COIN 为基数;旧 gift price heat_value 不再参与结算,避免后台热度配置覆盖实际消费。 + heatValue, err := giftDiamondAmount(chargeAmount, roomContributionRatio.PPM) if err != nil { return ledger.Receipt{}, err } @@ -301,7 +297,7 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm RoomContributionRatioRegionID: roomContributionRatioRegionID, CoinPrice: price.CoinPrice, GiftPointAmount: price.GiftPointAmount, - HeatUnitValue: price.HeatValue, + HeatUnitValue: price.CoinPrice, } if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeGiftDebit, requestHash, fmt.Sprintf("%s:%s", command.GiftID, price.PriceVersion), metadata, nowMs); err != nil { return ledger.Receipt{}, err @@ -410,16 +406,12 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb if err != nil { return ledger.BatchGiftReceipt{}, err } - baseHeatValue, err := checkedMul(price.HeatValue, int64(command.GiftCount)) - if err != nil { - return ledger.BatchGiftReceipt{}, err - } roomContributionRatio, roomContributionRatioRegionID, err := r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode) if err != nil { return ledger.BatchGiftReceipt{}, err } - // 批量多目标仍然是每个目标各送一份同款礼物;每份房间贡献先按全局默认礼物比例折算,再由聚合回执累加。 - heatValue, err := giftDiamondAmount(baseHeatValue, roomContributionRatio.PPM) + // 批量多目标仍然是每个目标各送一份同款礼物;每份房间贡献先按真实扣费折算,再由聚合回执累加。 + heatValue, err := giftDiamondAmount(chargeAmount, roomContributionRatio.PPM) if err != nil { return ledger.BatchGiftReceipt{}, err } @@ -549,7 +541,7 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb RoomContributionRatioRegionID: roomContributionRatioRegionID, CoinPrice: price.CoinPrice, GiftPointAmount: price.GiftPointAmount, - HeatUnitValue: price.HeatValue, + HeatUnitValue: price.CoinPrice, } if err := r.insertTransaction(ctx, tx, transactionID, target.CommandID, bizTypeGiftDebit, debitRequestHash(debitGiftCommandFromBatchTarget(command, target)), fmt.Sprintf("%s:%s", command.GiftID, price.PriceVersion), metadata, nowMs); err != nil { return ledger.BatchGiftReceipt{}, err diff --git a/services/wallet-service/internal/storage/mysql/resource_repository.go b/services/wallet-service/internal/storage/mysql/resource_repository.go index 213181a7..f23a2355 100644 --- a/services/wallet-service/internal/storage/mysql/resource_repository.go +++ b/services/wallet-service/internal/storage/mysql/resource_repository.go @@ -628,7 +628,7 @@ func (r *Repository) ListGiftConfigs(ctx context.Context, query resourcedomain.L item.ChargeAssetType = price.ChargeAssetType item.CoinPrice = price.CoinPrice item.GiftPointAmount = price.GiftPointAmount - item.HeatValue = price.HeatValue + item.HeatValue = price.CoinPrice } items = append(items, item) } @@ -1952,6 +1952,7 @@ func (r *Repository) listResourceGrantItemsWithQuery(ctx context.Context, querie } func (r *Repository) upsertGiftPriceTx(ctx context.Context, tx *sql.Tx, command resourcedomain.GiftConfigCommand, nowMs int64) error { + legacyHeatValue := command.CoinPrice _, err := tx.ExecContext(ctx, ` INSERT INTO wallet_gift_prices ( app_code, gift_id, price_version, status, charge_asset_type, coin_price, gift_point_amount, @@ -1966,7 +1967,7 @@ func (r *Repository) upsertGiftPriceTx(ctx context.Context, tx *sql.Tx, command effective_at_ms = VALUES(effective_at_ms), updated_at_ms = VALUES(updated_at_ms)`, appcode.FromContext(ctx), command.GiftID, command.PriceVersion, command.ChargeAssetType, command.CoinPrice, - command.GiftPointAmount, command.HeatValue, command.EffectiveAtMS, nowMs, nowMs, + command.GiftPointAmount, legacyHeatValue, command.EffectiveAtMS, nowMs, nowMs, ) return err } @@ -2006,7 +2007,8 @@ func (r *Repository) getGiftConfig(ctx context.Context, giftID string) (resource gift.ChargeAssetType = price.ChargeAssetType gift.CoinPrice = price.CoinPrice gift.GiftPointAmount = price.GiftPointAmount - gift.HeatValue = price.HeatValue + // heat_value 是旧配置列,后台不再读写;对兼容调用方只回真实价格,房间贡献也按真实扣费结算。 + gift.HeatValue = price.CoinPrice } gift.RegionIDs, err = r.listGiftConfigRegionIDs(ctx, r.db, gift.GiftID) if err != nil { @@ -3269,6 +3271,8 @@ func applyResourcePricingToGiftCommand(command resourcedomain.GiftConfigCommand, command.CoinPrice = 0 command.GiftPointAmount = 0 } + // 后台已取消热度配置;旧列只保留兼容写入,数值始终跟真实 COIN 价格一致。 + command.HeatValue = command.CoinPrice return command }