diff --git a/backend/internal/modules/order/pricing.go b/backend/internal/modules/order/pricing.go index d1f637c..ce4ca04 100644 --- a/backend/internal/modules/order/pricing.go +++ b/backend/internal/modules/order/pricing.go @@ -161,19 +161,19 @@ func estimateOrderDurationHours(snapshot datatypes.JSON) int { func buildOrderPricing(listing model.RentalListing, account model.GameAccount) orderPricing { rentAmountCent := listing.PriceCent - ownerRentAmountCent := int64(math.Round(readSnapshotPrice(account.AssetSummary, "seller_total_price") * 100)) + ownerRentAmountCent := readSnapshotCent(account.AssetSummary, "seller_total_price") if ownerRentAmountCent <= 0 || ownerRentAmountCent > rentAmountCent { ownerRentAmountCent = rentAmountCent } - platformFeeCent := int64(math.Round(readSnapshotPrice(account.AssetSummary, "platform_markup_amount") * 100)) + platformFeeCent := readSnapshotCent(account.AssetSummary, "platform_markup_amount") if platformFeeCent <= 0 || ownerRentAmountCent+platformFeeCent != rentAmountCent { platformFeeCent = rentAmountCent - ownerRentAmountCent } 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)) + pureCoinAmountCent := readSnapshotCent(account.AssetSummary, "buyer_coin_base_price") + extraItemSnapshotCent := readSnapshotCent(account.AssetSummary, "consumable_price") if pureCoinAmountCent <= 0 { if extraItemSnapshotCent > 0 && extraItemSnapshotCent <= rentAmountCent { pureCoinAmountCent = rentAmountCent - extraItemSnapshotCent @@ -185,7 +185,7 @@ func buildOrderPricing(listing model.RentalListing, account model.GameAccount) o pureCoinAmountCent = rentAmountCent } extraItemAmountCent := maxCent(rentAmountCent-pureCoinAmountCent, 0) - ownerPureCoinAmountCent := int64(math.Round(readSnapshotPrice(account.AssetSummary, "seller_coin_base_price") * 100)) + ownerPureCoinAmountCent := readSnapshotCent(account.AssetSummary, "seller_coin_base_price") if ownerPureCoinAmountCent <= 0 || ownerPureCoinAmountCent > ownerRentAmountCent { ownerPureCoinAmountCent = maxCent(ownerRentAmountCent-minCent(extraItemAmountCent, ownerRentAmountCent), 0) } @@ -207,18 +207,72 @@ func readSnapshotPrice(raw datatypes.JSON, key string) float64 { return readOrderSnapshotPrice(raw, key) } -func readOrderSnapshotPrice(raw datatypes.JSON, key string) float64 { - if len(raw) == 0 { +// readSnapshotCent 优先读取 price_breakdown 的整数分字段,缺失时兼容旧元字段。 +func readSnapshotCent(raw datatypes.JSON, key string) int64 { + return readOrderSnapshotCent(raw, key) +} + +func readOrderSnapshotCent(raw datatypes.JSON, key string) int64 { + breakdown, ok := readOrderSnapshotBreakdown(raw) + if !ok { return 0 } + if cent := readJSONInt64(breakdown[key+"_cent"]); cent > 0 { + return cent + } + legacy := readJSONNumber(breakdown[key]) + if legacy <= 0 { + return 0 + } + return int64(math.Round(legacy * 100)) +} + +func readOrderSnapshotBreakdown(raw datatypes.JSON) (map[string]any, bool) { + if len(raw) == 0 { + return nil, false + } var snapshot map[string]any if err := json.Unmarshal(raw, &snapshot); err != nil { - return 0 + return nil, false } if assetSummary, ok := snapshot["asset_summary"].(map[string]any); ok { snapshot = assetSummary } breakdown, ok := snapshot["price_breakdown"].(map[string]any) + return breakdown, ok +} + +func readJSONInt64(value any) int64 { + switch typed := value.(type) { + case float64: + return int64(typed) + case float32: + return int64(typed) + case int: + return int64(typed) + case int64: + return typed + case int32: + return int64(typed) + case json.Number: + number, err := typed.Int64() + if err != nil { + return 0 + } + return number + case string: + number, err := strconv.ParseInt(typed, 10, 64) + if err != nil { + return 0 + } + return number + default: + return 0 + } +} + +func readOrderSnapshotPrice(raw datatypes.JSON, key string) float64 { + breakdown, ok := readOrderSnapshotBreakdown(raw) if !ok { return 0 } @@ -313,7 +367,7 @@ func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent i orderDepositAmountCent := order.DepositAmountCent buyerCoinBasePriceCent := orderPureCoinOriginalAmountCent(order) - sellerCoinBasePriceCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "seller_coin_base_price") * 100)) + sellerCoinBasePriceCent := readOrderSnapshotCent(order.AccountSnapshot, "seller_coin_base_price") if sellerCoinBasePriceCent <= 0 || sellerCoinBasePriceCent > orderOwnerRentAmountCent { ownerExtraItemCent := minCent(orderExtraItemOriginalAmountCent(order), orderOwnerRentAmountCent) sellerCoinBasePriceCent = maxCent(orderOwnerRentAmountCent-ownerExtraItemCent, 0) @@ -427,11 +481,11 @@ func orderPureCoinOriginalAmountCent(order model.RentalOrder) int64 { if order.ExtraItemOriginalAmountCent > 0 { return maxCent(originalRentCent-minCent(order.ExtraItemOriginalAmountCent, originalRentCent), 0) } - pureCoinCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "buyer_coin_base_price") * 100)) + pureCoinCent := readOrderSnapshotCent(order.AccountSnapshot, "buyer_coin_base_price") if pureCoinCent > 0 && pureCoinCent <= originalRentCent { return pureCoinCent } - extraItemCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "consumable_price") * 100)) + extraItemCent := readOrderSnapshotCent(order.AccountSnapshot, "consumable_price") if extraItemCent > 0 && extraItemCent <= originalRentCent { return originalRentCent - extraItemCent } diff --git a/backend/internal/modules/order/pricing_cent_test.go b/backend/internal/modules/order/pricing_cent_test.go new file mode 100644 index 0000000..f4d008b --- /dev/null +++ b/backend/internal/modules/order/pricing_cent_test.go @@ -0,0 +1,68 @@ +package order + +import ( + "testing" + + "hfb_sys/backend/internal/model" + + "gorm.io/datatypes" +) + +func TestReadOrderSnapshotCentPrefersCentField(t *testing.T) { + raw := datatypes.JSON([]byte(`{ + "asset_summary": { + "price_breakdown": { + "seller_total_price": 353.00, + "seller_total_price_cent": 35301 + } + } + }`)) + if got := readSnapshotCent(raw, "seller_total_price"); got != 35301 { + t.Fatalf("优先读取分字段失败: got %d, want 35301", got) + } +} + +func TestReadOrderSnapshotCentFallsBackToLegacyYuan(t *testing.T) { + raw := datatypes.JSON([]byte(`{ + "asset_summary": { + "price_breakdown": { + "consumable_price": 120.00 + } + } + }`)) + if got := readSnapshotCent(raw, "consumable_price"); got != 12000 { + t.Fatalf("旧元字段回退失败: got %d, want 12000", got) + } +} + +func TestBuildOrderPricingUsesCentBreakdown(t *testing.T) { + account := model.GameAccount{ + AssetSummary: datatypes.JSON([]byte(`{ + "asset_summary": { + "price_breakdown": { + "seller_total_price_cent": 35301, + "platform_markup_amount_cent": 2999, + "buyer_coin_base_price_cent": 26315, + "consumable_price_cent": 12000, + "seller_coin_base_price_cent": 23315 + } + } + }`)), + } + pricing := buildOrderPricing(model.RentalListing{PriceCent: 38300}, account) + if pricing.OwnerRentAmountCent != 35301 { + t.Fatalf("OwnerRentAmountCent = %d, want 35301", pricing.OwnerRentAmountCent) + } + if pricing.PlatformFeeCent != 2999 { + t.Fatalf("PlatformFeeCent = %d, want 2999", pricing.PlatformFeeCent) + } + if pricing.PureCoinAmountCent != 26315 { + t.Fatalf("PureCoinAmountCent = %d, want 26315", pricing.PureCoinAmountCent) + } + if pricing.ExtraItemAmountCent != 11985 { + t.Fatalf("ExtraItemAmountCent = %d, want 11985", pricing.ExtraItemAmountCent) + } + if pricing.OwnerPureCoinAmountCent != 23315 { + t.Fatalf("OwnerPureCoinAmountCent = %d, want 23315", pricing.OwnerPureCoinAmountCent) + } +}