按实际纯币金额计算租客优惠与积分

This commit is contained in:
yml2213
2026-07-27 10:45:41 +08:00
parent 99e8eb28af
commit 8af7333969
30 changed files with 983 additions and 154 deletions
@@ -255,6 +255,9 @@ func applyCounterCheckoutUpdate(checkout *model.OrderCheckout, next model.OrderC
checkout.OwnerRentAmountCent = next.OwnerRentAmountCent
checkout.PlatformFeeCent = next.PlatformFeeCent
checkout.DepositAmountCent = next.DepositAmountCent
checkout.PureCoinAmountCent = next.PureCoinAmountCent
checkout.PureCoinDiscountCent = next.PureCoinDiscountCent
checkout.PureCoinPayableCent = next.PureCoinPayableCent
checkout.ConsumableAmountCent = next.ConsumableAmountCent
checkout.CoinConsumedM = next.CoinConsumedM
checkout.OtherAmountCent = next.OtherAmountCent
@@ -40,10 +40,13 @@ func (r *Repository) finalizeCheckout(tx *gorm.DB, order *model.RentalOrder, che
return nil, err
}
applyCheckoutSettlement(checkout, settlement)
order.ActualCoinConsumedM = settlement.CoinConsumedM
order.ActualPureCoinAmountCent = settlement.PureCoinAmountCent
order.ActualPureCoinDiscountCent = settlement.PureCoinDiscountCent
if err := appendCheckoutCompletedNotifications(tx, order, renterContent); err != nil {
return nil, err
}
if err := rentergrowth.AwardOrderCompleted(tx, order, settlement.ActualRentAmountCent, rentergrowth.SourceOrderCompleted); err != nil {
if err := rentergrowth.AwardOrderCompleted(tx, order, settlement.PureCoinAmountCent, rentergrowth.SourceOrderCompleted); err != nil {
return nil, err
}
if err := saveFinalizedCheckout(tx, order, checkout, listing, account); err != nil {
@@ -110,6 +113,9 @@ func applyCheckoutSettlement(checkout *model.OrderCheckout, settlement checkoutS
checkout.RentAmountCent = settlement.ActualRentAmountCent
checkout.OwnerRentAmountCent = settlement.OwnerRentIncomeCent
checkout.PlatformFeeCent = settlement.PlatformFeeCent
checkout.PureCoinAmountCent = settlement.PureCoinAmountCent
checkout.PureCoinDiscountCent = settlement.PureCoinDiscountCent
checkout.PureCoinPayableCent = settlement.PureCoinPayableCent
checkout.RenterRefundAmountCent = settlement.RenterRefundCent
checkout.OwnerIncomeAmountCent = settlement.OwnerIncomeCent
checkout.ShortfallCent = settlement.ShortfallCent
+10
View File
@@ -37,9 +37,16 @@ type OrderDTO struct {
PlatformFeeCent *int64 `json:"platform_fee_cent,omitempty"`
RentOriginalAmountCent int64 `json:"rent_original_amount_cent,omitempty"`
RentDiscountAmountCent int64 `json:"rent_discount_amount_cent,omitempty"`
PureCoinOriginalAmountCent int64 `json:"pure_coin_original_amount_cent,omitempty"`
ExtraItemOriginalAmountCent int64 `json:"extra_item_original_amount_cent,omitempty"`
ActualCoinConsumedM float64 `json:"actual_coin_consumed_m,omitempty"`
ActualPureCoinAmountCent int64 `json:"actual_pure_coin_amount_cent,omitempty"`
ActualPureCoinDiscountCent int64 `json:"actual_pure_coin_discount_cent,omitempty"`
RenterGrowthLevel string `json:"renter_growth_level,omitempty"`
RenterGrowthLevelName string `json:"renter_growth_level_name,omitempty"`
RenterDiscountBps int `json:"renter_discount_bps,omitempty"`
GrowthPointsBasisCent int64 `json:"growth_points_basis_cent,omitempty"`
GrowthPointsPerYuan int64 `json:"growth_points_per_yuan,omitempty"`
GrowthPointsAwarded int64 `json:"growth_points_awarded,omitempty"`
GrowthPointsAwardedAt *time.Time `json:"growth_points_awarded_at,omitempty"`
AccountSnapshot datatypes.JSON `json:"account_snapshot"`
@@ -191,6 +198,9 @@ type CheckoutDTO struct {
OwnerRentAmountCent *int64 `json:"owner_rent_amount_cent,omitempty"`
PlatformFeeCent *int64 `json:"platform_fee_cent,omitempty"`
DepositAmountCent int64 `json:"deposit_amount_cent"`
PureCoinAmountCent int64 `json:"pure_coin_amount_cent"`
PureCoinDiscountCent int64 `json:"pure_coin_discount_cent"`
PureCoinPayableCent int64 `json:"pure_coin_payable_cent"`
ConsumableAmountCent int64 `json:"consumable_amount_cent"`
CoinConsumedM float64 `json:"coin_consumed_m"`
OtherAmountCent int64 `json:"other_amount_cent"`
+31 -29
View File
@@ -48,7 +48,7 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
if err != nil {
return err
}
rentDiscountAmountCent := rentergrowth.CalculateDiscountCent(rentOriginalAmountCent, pricing.PlatformFeeCent, growthSnapshot.DiscountBps)
rentDiscountAmountCent := rentergrowth.CalculateDiscountCent(pricing.PureCoinAmountCent, pricing.PureCoinPlatformFeeCent, growthSnapshot.DiscountBps)
if rentDiscountAmountCent > 0 {
pricing.PlatformFeeCent -= rentDiscountAmountCent
pricing.RentAmountCent -= rentDiscountAmountCent
@@ -59,34 +59,36 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
return err
}
order := model.RentalOrder{
OrderNo: orderNo,
ListingID: listing.ID,
AccountID: listing.AccountID,
OwnerID: listing.OwnerID,
RenterID: renterID,
EstimatedDurationHours: rentHours,
RentAmountCent: pricing.RentAmountCent,
OwnerRentAmountCent: pricing.OwnerRentAmountCent,
DepositAmountCent: depositWaiver.PaidCent,
DepositOriginalAmountCent: depositOriginalAmountCent,
DepositWaivedAmountCent: depositWaiver.WaivedCent,
DepositFreeLevelQuotaCent: growthSnapshot.LevelDepositFreeQuotaCent,
DepositFreeManualQuotaCent: growthSnapshot.ManualDepositFreeQuotaCent,
DepositFreeUsedBeforeCent: depositWaiver.UsedBeforeCent,
PlatformFeeCent: pricing.PlatformFeeCent,
RentOriginalAmountCent: rentOriginalAmountCent,
RentDiscountAmountCent: rentDiscountAmountCent,
RenterGrowthLevel: growthSnapshot.LevelCode,
RenterGrowthLevelName: growthSnapshot.LevelName,
RenterDiscountBps: growthSnapshot.DiscountBps,
AccountSnapshot: snapshot,
Status: orderStatusPendingPayment,
HandoffStatus: handoffStatusNone,
HandoffMode: listingHandoffMode(listing),
SettlementMode: listingSettlementMode(listing),
ManagedAdminID: listing.ManagedAdminID,
SettlementStatus: settlementStatusUnsettled,
OfflineSettlementStatus: offlineSettlementStatusNone,
OrderNo: orderNo,
ListingID: listing.ID,
AccountID: listing.AccountID,
OwnerID: listing.OwnerID,
RenterID: renterID,
EstimatedDurationHours: rentHours,
RentAmountCent: pricing.RentAmountCent,
OwnerRentAmountCent: pricing.OwnerRentAmountCent,
DepositAmountCent: depositWaiver.PaidCent,
DepositOriginalAmountCent: depositOriginalAmountCent,
DepositWaivedAmountCent: depositWaiver.WaivedCent,
DepositFreeLevelQuotaCent: growthSnapshot.LevelDepositFreeQuotaCent,
DepositFreeManualQuotaCent: growthSnapshot.ManualDepositFreeQuotaCent,
DepositFreeUsedBeforeCent: depositWaiver.UsedBeforeCent,
PlatformFeeCent: pricing.PlatformFeeCent,
RentOriginalAmountCent: rentOriginalAmountCent,
RentDiscountAmountCent: rentDiscountAmountCent,
PureCoinOriginalAmountCent: pricing.PureCoinAmountCent,
ExtraItemOriginalAmountCent: pricing.ExtraItemAmountCent,
RenterGrowthLevel: growthSnapshot.LevelCode,
RenterGrowthLevelName: growthSnapshot.LevelName,
RenterDiscountBps: growthSnapshot.DiscountBps,
AccountSnapshot: snapshot,
Status: orderStatusPendingPayment,
HandoffStatus: handoffStatusNone,
HandoffMode: listingHandoffMode(listing),
SettlementMode: listingSettlementMode(listing),
ManagedAdminID: listing.ManagedAdminID,
SettlementStatus: settlementStatusUnsettled,
OfflineSettlementStatus: offlineSettlementStatusNone,
}
if err := tx.Create(&order).Error; err != nil {
return err
@@ -87,9 +87,16 @@ func (row orderRow) toAdminDTO() OrderDTO {
PlatformFeeCent: &platformFeeCent,
RentOriginalAmountCent: effectiveRentOriginalAmountCent(row.RentalOrder),
RentDiscountAmountCent: row.RentDiscountAmountCent,
PureCoinOriginalAmountCent: orderPureCoinOriginalAmountCent(row.RentalOrder),
ExtraItemOriginalAmountCent: orderExtraItemOriginalAmountCent(row.RentalOrder),
ActualCoinConsumedM: row.ActualCoinConsumedM,
ActualPureCoinAmountCent: row.ActualPureCoinAmountCent,
ActualPureCoinDiscountCent: row.ActualPureCoinDiscountCent,
RenterGrowthLevel: effectiveRenterGrowthLevel(row.RentalOrder),
RenterGrowthLevelName: effectiveRenterGrowthLevelName(row.RentalOrder),
RenterDiscountBps: effectiveRenterDiscountBps(row.RentalOrder),
GrowthPointsBasisCent: row.GrowthPointsBasisCent,
GrowthPointsPerYuan: row.GrowthPointsPerYuan,
GrowthPointsAwarded: row.GrowthPointsAwarded,
GrowthPointsAwardedAt: row.GrowthPointsAwardedAt,
AccountSnapshot: row.AccountSnapshot,
@@ -225,6 +232,9 @@ func toCheckoutAdminDTO(checkout model.OrderCheckout) CheckoutDTO {
OwnerRentAmountCent: &ownerRentAmountCent,
PlatformFeeCent: &platformFeeCent,
DepositAmountCent: checkout.DepositAmountCent,
PureCoinAmountCent: checkout.PureCoinAmountCent,
PureCoinDiscountCent: checkout.PureCoinDiscountCent,
PureCoinPayableCent: checkout.PureCoinPayableCent,
ConsumableAmountCent: checkout.ConsumableAmountCent,
CoinConsumedM: checkout.CoinConsumedM,
OtherAmountCent: checkout.OtherAmountCent,
@@ -315,11 +325,18 @@ func applyOrderPriceView(dto *OrderDTO, order model.RentalOrder, userID uint64)
dto.OwnerRentAmountCent = &ownerAmountCent
dto.RentOriginalAmountCent = 0
dto.RentDiscountAmountCent = 0
dto.PureCoinOriginalAmountCent = 0
dto.ExtraItemOriginalAmountCent = 0
dto.ActualCoinConsumedM = 0
dto.ActualPureCoinAmountCent = 0
dto.ActualPureCoinDiscountCent = 0
dto.RenterGrowthLevel = ""
dto.RenterGrowthLevelName = ""
dto.RenterDiscountBps = 0
dto.GrowthPointsAwarded = 0
dto.GrowthPointsAwardedAt = nil
dto.GrowthPointsBasisCent = 0
dto.GrowthPointsPerYuan = 0
sanitizeOrderSnapshot(&dto.AccountSnapshot, "owner")
case userID == order.RenterID:
rentAmountCent := order.RentAmountCent
@@ -340,11 +357,18 @@ func applyOrderPriceView(dto *OrderDTO, order model.RentalOrder, userID uint64)
dto.OwnerRentAmountCent = nil
dto.RentOriginalAmountCent = 0
dto.RentDiscountAmountCent = 0
dto.PureCoinOriginalAmountCent = 0
dto.ExtraItemOriginalAmountCent = 0
dto.ActualCoinConsumedM = 0
dto.ActualPureCoinAmountCent = 0
dto.ActualPureCoinDiscountCent = 0
dto.RenterGrowthLevel = ""
dto.RenterGrowthLevelName = ""
dto.RenterDiscountBps = 0
dto.GrowthPointsAwarded = 0
dto.GrowthPointsAwardedAt = nil
dto.GrowthPointsBasisCent = 0
dto.GrowthPointsPerYuan = 0
sanitizeOrderSnapshot(&dto.AccountSnapshot, "")
}
}
@@ -379,6 +403,9 @@ func applyCheckoutPriceView(dto *CheckoutDTO, order model.RentalOrder, userID ui
dto.RentAmountCent = nil
dto.OwnerRentAmountCent = &ownerAmountCent
dto.OwnerIncomeAmountCent = &ownerIncomeAmountCent
dto.PureCoinAmountCent = 0
dto.PureCoinDiscountCent = 0
dto.PureCoinPayableCent = 0
case userID == order.RenterID:
dto.PriceRole = "renter"
dto.DisplayAmountCent = rentAmountCent
@@ -390,6 +417,9 @@ func applyCheckoutPriceView(dto *CheckoutDTO, order model.RentalOrder, userID ui
dto.DisplayAmountCent = 0
dto.RentAmountCent = nil
dto.OwnerRentAmountCent = nil
dto.PureCoinAmountCent = 0
dto.PureCoinDiscountCent = 0
dto.PureCoinPayableCent = 0
}
}
+104 -16
View File
@@ -7,15 +7,21 @@ import (
"time"
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/rentergrowth"
"hfb_sys/backend/pkg/money"
"gorm.io/datatypes"
)
type orderPricing struct {
RentAmountCent int64
OwnerRentAmountCent int64
PlatformFeeCent int64
RentAmountCent int64
OwnerRentAmountCent int64
PlatformFeeCent int64
PureCoinAmountCent int64
OwnerPureCoinAmountCent int64
ExtraItemAmountCent int64
OwnerExtraItemAmountCent int64
PureCoinPlatformFeeCent int64
}
type checkoutSettlement struct {
@@ -27,10 +33,34 @@ type checkoutSettlement struct {
RenterRefundCent int64
PlatformFeeCent int64
ActualRentAmountCent int64
PureCoinAmountCent int64
PureCoinDiscountCent int64
PureCoinPayableCent int64
ExtraItemAmountCent int64
CoinConsumedM float64
OvershootAmountCent int64
ShortfallCent int64
}
// ActualPureCoinAmounts 是跨正常结账与仲裁共用的最终纯币计价结果。
type ActualPureCoinAmounts struct {
AmountCent int64
DiscountCent int64
PayableCent int64
ConsumedM float64
}
// CalculateActualPureCoinAmounts 按订单价格快照和实际消耗 M 计算纯币原价及等级优惠。
func CalculateActualPureCoinAmounts(order model.RentalOrder, coinConsumedM float64) ActualPureCoinAmounts {
settlement := calculateCheckoutSettlement(order, 0, coinConsumedM, 0)
return ActualPureCoinAmounts{
AmountCent: settlement.PureCoinAmountCent,
DiscountCent: settlement.PureCoinDiscountCent,
PayableCent: settlement.PureCoinPayableCent,
ConsumedM: settlement.CoinConsumedM,
}
}
func orderDurationHours(order model.RentalOrder) int {
if order.EstimatedDurationHours > 0 {
return order.EstimatedDurationHours
@@ -85,10 +115,34 @@ func buildOrderPricing(listing model.RentalListing, account model.GameAccount) o
if platformFeeCent < 0 {
platformFeeCent = 0
}
pureCoinAmountCent := int64(math.Round(readSnapshotPrice(account.AssetSummary, "buyer_coin_base_price") * 100))
extraItemSnapshotCent := int64(math.Round(readSnapshotPrice(account.AssetSummary, "consumable_price") * 100))
if pureCoinAmountCent <= 0 {
if extraItemSnapshotCent > 0 && extraItemSnapshotCent <= rentAmountCent {
pureCoinAmountCent = rentAmountCent - extraItemSnapshotCent
} else {
pureCoinAmountCent = rentAmountCent
}
}
if pureCoinAmountCent < 0 || pureCoinAmountCent > rentAmountCent {
pureCoinAmountCent = rentAmountCent
}
extraItemAmountCent := maxCent(rentAmountCent-pureCoinAmountCent, 0)
ownerPureCoinAmountCent := int64(math.Round(readSnapshotPrice(account.AssetSummary, "seller_coin_base_price") * 100))
if ownerPureCoinAmountCent <= 0 || ownerPureCoinAmountCent > ownerRentAmountCent {
ownerPureCoinAmountCent = maxCent(ownerRentAmountCent-minCent(extraItemAmountCent, ownerRentAmountCent), 0)
}
ownerExtraItemAmountCent := maxCent(ownerRentAmountCent-ownerPureCoinAmountCent, 0)
pureCoinPlatformFeeCent := maxCent(pureCoinAmountCent-ownerPureCoinAmountCent, 0)
return orderPricing{
RentAmountCent: rentAmountCent,
OwnerRentAmountCent: ownerRentAmountCent,
PlatformFeeCent: platformFeeCent,
RentAmountCent: rentAmountCent,
OwnerRentAmountCent: ownerRentAmountCent,
PlatformFeeCent: platformFeeCent,
PureCoinAmountCent: pureCoinAmountCent,
OwnerPureCoinAmountCent: ownerPureCoinAmountCent,
ExtraItemAmountCent: extraItemAmountCent,
OwnerExtraItemAmountCent: ownerExtraItemAmountCent,
PureCoinPlatformFeeCent: pureCoinPlatformFeeCent,
}
}
@@ -162,6 +216,9 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c
OwnerRentAmountCent: settlement.OwnerRentIncomeCent,
PlatformFeeCent: settlement.PlatformFeeCent,
DepositAmountCent: order.DepositAmountCent,
PureCoinAmountCent: settlement.PureCoinAmountCent,
PureCoinDiscountCent: settlement.PureCoinDiscountCent,
PureCoinPayableCent: settlement.PureCoinPayableCent,
ConsumableAmountCent: consumableAmountCent,
CoinConsumedM: roundQuantity(coinConsumedM),
OtherAmountCent: otherAmountCent,
@@ -185,18 +242,13 @@ func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent i
orderOwnerRentAmountCent := order.OwnerRentAmountCent
orderDepositAmountCent := order.DepositAmountCent
buyerCoinBasePriceCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "buyer_coin_base_price") * 100))
if buyerCoinBasePriceCent <= 0 || buyerCoinBasePriceCent > orderRentAmountCent {
buyerCoinBasePriceCent = orderRentAmountCent
}
buyerCoinBasePriceCent := orderPureCoinOriginalAmountCent(order)
sellerCoinBasePriceCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "seller_coin_base_price") * 100))
if sellerCoinBasePriceCent <= 0 || sellerCoinBasePriceCent > orderOwnerRentAmountCent {
sellerCoinBasePriceCent = orderOwnerRentAmountCent
}
prepaidConsumablePriceCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "consumable_price") * 100))
if prepaidConsumablePriceCent <= 0 || prepaidConsumablePriceCent > orderRentAmountCent-buyerCoinBasePriceCent {
prepaidConsumablePriceCent = maxCent(orderRentAmountCent-buyerCoinBasePriceCent, 0)
ownerExtraItemCent := minCent(orderExtraItemOriginalAmountCent(order), orderOwnerRentAmountCent)
sellerCoinBasePriceCent = maxCent(orderOwnerRentAmountCent-ownerExtraItemCent, 0)
}
prepaidConsumablePriceCent := orderExtraItemOriginalAmountCent(order)
prepaidOwnerConsumablePriceCent := maxCent(orderOwnerRentAmountCent-sellerCoinBasePriceCent, 0)
consumedM := roundQuantity(coinConsumedM)
@@ -243,7 +295,10 @@ func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent i
usedOwnerConsumablePriceCent = minCent(usedOwnerConsumablePriceCent, prepaidOwnerConsumablePriceCent)
}
actualBuyerRentCent := usedBuyerCoinPriceCent + usedBuyerConsumablePriceCent
pureCoinPlatformFeeCent := maxCent(usedBuyerCoinPriceCent-usedOwnerCoinPriceCent, 0)
pureCoinDiscountCent := rentergrowth.CalculateDiscountCent(usedBuyerCoinPriceCent, pureCoinPlatformFeeCent, effectiveRenterDiscountBps(order))
pureCoinPayableCent := maxCent(usedBuyerCoinPriceCent-pureCoinDiscountCent, 0)
actualBuyerRentCent := pureCoinPayableCent + usedBuyerConsumablePriceCent
actualOwnerRentCent := usedOwnerCoinPriceCent + usedOwnerConsumablePriceCent
if actualOwnerRentCent > actualBuyerRentCent {
actualOwnerRentCent = actualBuyerRentCent
@@ -278,11 +333,44 @@ func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent i
RenterRefundCent: rentRefundCent + depositRefundCent,
PlatformFeeCent: platformFeeCent,
ActualRentAmountCent: actualRentAmountCent,
PureCoinAmountCent: usedBuyerCoinPriceCent,
PureCoinDiscountCent: pureCoinDiscountCent,
PureCoinPayableCent: pureCoinPayableCent,
ExtraItemAmountCent: usedBuyerConsumablePriceCent,
CoinConsumedM: consumedM,
OvershootAmountCent: overshootCent,
ShortfallCent: shortfallCent,
}
}
func orderPureCoinOriginalAmountCent(order model.RentalOrder) int64 {
originalRentCent := effectiveRentOriginalAmountCent(order)
if order.PureCoinOriginalAmountCent > 0 {
return minCent(order.PureCoinOriginalAmountCent, originalRentCent)
}
if order.ExtraItemOriginalAmountCent > 0 {
return maxCent(originalRentCent-minCent(order.ExtraItemOriginalAmountCent, originalRentCent), 0)
}
pureCoinCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "buyer_coin_base_price") * 100))
if pureCoinCent > 0 && pureCoinCent <= originalRentCent {
return pureCoinCent
}
extraItemCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "consumable_price") * 100))
if extraItemCent > 0 && extraItemCent <= originalRentCent {
return originalRentCent - extraItemCent
}
return originalRentCent
}
func orderExtraItemOriginalAmountCent(order model.RentalOrder) int64 {
originalRentCent := effectiveRentOriginalAmountCent(order)
if order.ExtraItemOriginalAmountCent > 0 {
return minCent(order.ExtraItemOriginalAmountCent, originalRentCent)
}
pureCoinCent := orderPureCoinOriginalAmountCent(order)
return maxCent(originalRentCent-pureCoinCent, 0)
}
func readOrderSnapshotCoinM(raw datatypes.JSON) float64 {
if len(raw) == 0 {
return 0
@@ -162,7 +162,15 @@ func TestRepositoryCreateAppliesRenterGrowthDiscount(t *testing.T) {
ServerRegion: "国服",
LoginPlatform: "steam",
Title: "测试账号",
AssetSummary: datatypes.JSON([]byte(`{"price_breakdown":{"seller_total_price":90,"platform_markup_amount":10}}`)),
AssetSummary: datatypes.JSON([]byte(`{
"price_breakdown": {
"buyer_coin_base_price": 263,
"seller_coin_base_price": 233,
"consumable_price": 120,
"seller_total_price": 353,
"platform_markup_amount": 30
}
}`)),
}
if err := db.Create(&account).Error; err != nil {
t.Fatalf("create account failed: %v", err)
@@ -170,7 +178,7 @@ func TestRepositoryCreateAppliesRenterGrowthDiscount(t *testing.T) {
listing := model.RentalListing{
AccountID: account.ID,
OwnerID: owner.ID,
PriceCent: 10000,
PriceCent: 38300,
DepositAmountCent: 100000,
Status: listingStatusPublished,
ReviewStatus: listingReviewStatusApproved,
@@ -183,11 +191,14 @@ func TestRepositoryCreateAppliesRenterGrowthDiscount(t *testing.T) {
if err != nil {
t.Fatalf("Create() error = %v", err)
}
if dto.RentAmountCent == nil || *dto.RentAmountCent != 9900 {
t.Fatalf("rent amount = %v, want 9900", dto.RentAmountCent)
if dto.RentAmountCent == nil || *dto.RentAmountCent != 38037 {
t.Fatalf("rent amount = %v, want 38037", dto.RentAmountCent)
}
if dto.RentOriginalAmountCent != 10000 || dto.RentDiscountAmountCent != 100 {
t.Fatalf("discount snapshot = original %d discount %d, want 10000/100", dto.RentOriginalAmountCent, dto.RentDiscountAmountCent)
if dto.RentOriginalAmountCent != 38300 || dto.RentDiscountAmountCent != 263 {
t.Fatalf("discount snapshot = original %d discount %d, want 38300/263", dto.RentOriginalAmountCent, dto.RentDiscountAmountCent)
}
if dto.PureCoinOriginalAmountCent != 26300 || dto.ExtraItemOriginalAmountCent != 12000 {
t.Fatalf("pure coin/extra snapshot = %d/%d, want 26300/12000", dto.PureCoinOriginalAmountCent, dto.ExtraItemOriginalAmountCent)
}
if dto.RenterGrowthLevel != "platinum" || dto.RenterGrowthLevelName != "铂金" || dto.RenterDiscountBps != 9900 {
t.Fatalf("growth snapshot = %s/%s/%d, want platinum/铂金/9900", dto.RenterGrowthLevel, dto.RenterGrowthLevelName, dto.RenterDiscountBps)
@@ -196,8 +207,8 @@ func TestRepositoryCreateAppliesRenterGrowthDiscount(t *testing.T) {
if err := db.First(&saved, dto.ID).Error; err != nil {
t.Fatalf("find saved order failed: %v", err)
}
if saved.OwnerRentAmountCent != 9000 || saved.PlatformFeeCent != 900 {
t.Fatalf("pricing = owner %d platform %d, want 9000/900", saved.OwnerRentAmountCent, saved.PlatformFeeCent)
if saved.OwnerRentAmountCent != 35300 || saved.PlatformFeeCent != 2737 {
t.Fatalf("pricing = owner %d platform %d, want 35300/2737", saved.OwnerRentAmountCent, saved.PlatformFeeCent)
}
if saved.DepositAmountCent != 30000 || saved.DepositWaivedAmountCent != 70000 {
t.Fatalf("deposit = paid %d waived %d, want 30000/70000", saved.DepositAmountCent, saved.DepositWaivedAmountCent)
@@ -5,11 +5,116 @@ import (
"time"
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/rentergrowth"
"hfb_sys/backend/internal/timeutil"
"gorm.io/datatypes"
)
func pureCoinDiscountOrder() model.RentalOrder {
return model.RentalOrder{
RentAmountCent: 38037,
OwnerRentAmountCent: 35300,
DepositAmountCent: 15000,
RentOriginalAmountCent: 38300,
RentDiscountAmountCent: 263,
PureCoinOriginalAmountCent: 26300,
ExtraItemOriginalAmountCent: 12000,
RenterDiscountBps: 9900,
AccountSnapshot: datatypes.JSON([]byte(`{
"haf_coin_amount": 100000000,
"asset_summary": {
"price_breakdown": {
"buyer_coin_base_price": 263,
"seller_coin_base_price": 233,
"consumable_price": 120
}
}
}`)),
}
}
func TestCalculateCheckoutSettlementUsesOnlyActualPureCoinForDiscount(t *testing.T) {
tests := []struct {
name string
consumableAmountCent int64
coinConsumedM float64
wantPureCoinCent int64
wantDiscountCent int64
wantActualRentCent int64
wantOvershootCent int64
}{
{
name: "全量纯币和额外物品",
consumableAmountCent: 12000,
coinConsumedM: 100,
wantPureCoinCent: 26300,
wantDiscountCent: 263,
wantActualRentCent: 38037,
wantOvershootCent: 0,
},
{
name: "部分纯币和全量额外物品",
consumableAmountCent: 12000,
coinConsumedM: 50,
wantPureCoinCent: 13150,
wantDiscountCent: 132,
wantActualRentCent: 25018,
wantOvershootCent: 0,
},
{
name: "只使用额外物品",
consumableAmountCent: 12000,
coinConsumedM: 0,
wantPureCoinCent: 0,
wantDiscountCent: 0,
wantActualRentCent: 12000,
wantOvershootCent: 0,
},
{
name: "纯币打超仍参与优惠",
consumableAmountCent: 12000,
coinConsumedM: 110,
wantPureCoinCent: 28930,
wantDiscountCent: 290,
wantActualRentCent: 40640,
wantOvershootCent: 2603,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
settlement := calculateCheckoutSettlement(pureCoinDiscountOrder(), test.consumableAmountCent, test.coinConsumedM, 0)
if settlement.PureCoinAmountCent != test.wantPureCoinCent || settlement.PureCoinDiscountCent != test.wantDiscountCent {
t.Fatalf("纯币原价/优惠 = %d/%d, want %d/%d", settlement.PureCoinAmountCent, settlement.PureCoinDiscountCent, test.wantPureCoinCent, test.wantDiscountCent)
}
if settlement.ActualRentAmountCent != test.wantActualRentCent || settlement.OvershootAmountCent != test.wantOvershootCent {
t.Fatalf("实际租金/打超 = %d/%d, want %d/%d", settlement.ActualRentAmountCent, settlement.OvershootAmountCent, test.wantActualRentCent, test.wantOvershootCent)
}
})
}
}
func TestBuildOrderPricingDoesNotDiscountAllExtraItemOrder(t *testing.T) {
pricing := buildOrderPricing(
model.RentalListing{PriceCent: 12000},
model.GameAccount{AssetSummary: datatypes.JSON([]byte(`{
"price_breakdown": {
"buyer_coin_base_price": 0,
"seller_coin_base_price": 0,
"consumable_price": 120,
"seller_total_price": 120
}
}`))},
)
if pricing.PureCoinAmountCent != 0 || pricing.ExtraItemAmountCent != 12000 || pricing.PureCoinPlatformFeeCent != 0 {
t.Fatalf("全额外物品订单纯币/额外物品/纯币差价 = %d/%d/%d, want 0/12000/0", pricing.PureCoinAmountCent, pricing.ExtraItemAmountCent, pricing.PureCoinPlatformFeeCent)
}
if discount := rentergrowth.CalculateDiscountCent(pricing.PureCoinAmountCent, pricing.PureCoinPlatformFeeCent, 9900); discount != 0 {
t.Fatalf("全额外物品订单优惠 = %d, want 0", discount)
}
}
func TestCalculateCheckoutSettlementRefundsUnusedRent(t *testing.T) {
order := model.RentalOrder{
RentAmountCent: 38300,