金额使用整数, 不再扣押金, 价格每个人看到自己的-1

This commit is contained in:
yml2213
2026-05-27 00:22:26 +08:00
parent e270ce8bfc
commit 89f4dabfb3
27 changed files with 351 additions and 119 deletions
+12 -6
View File
@@ -3,6 +3,7 @@ package dispute
import (
"encoding/json"
"errors"
"math"
"time"
"hfb_sys/backend/internal/model"
@@ -302,10 +303,10 @@ type arbitrationSettlement struct {
}
func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (arbitrationSettlement, error) {
total := order.RentAmount + order.DepositAmount
ownerRentAmount := order.OwnerRentAmount
total := roundMoney(order.RentAmount + order.DepositAmount)
ownerRentAmount := roundMoney(order.OwnerRentAmount)
if ownerRentAmount <= 0 || ownerRentAmount > order.RentAmount {
ownerRentAmount = order.RentAmount
ownerRentAmount = roundMoney(order.RentAmount)
}
settlement := arbitrationSettlement{}
orderID := order.ID
@@ -359,6 +360,7 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (
case "full_refund":
addRenterRefund(total, "仲裁全额退款")
case "partial_refund":
req.Amount = roundMoney(req.Amount)
if req.Amount <= 0 || req.Amount > total {
return settlement, ErrInvalidDispute
}
@@ -368,7 +370,7 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (
addOwnerIncome(ownerRentAmount, "仲裁确认订单金额结算给号主")
addRenterRefund(order.DepositAmount, "仲裁释放押金给租客")
case "deduct_deposit", "compensate_owner":
deductAmount := req.Amount
deductAmount := roundMoney(req.Amount)
if deductAmount <= 0 {
deductAmount = order.DepositAmount
}
@@ -390,9 +392,13 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (
func minMoney(a float64, b float64) float64 {
if a < b {
return a
return roundMoney(a)
}
return b
return roundMoney(b)
}
func roundMoney(value float64) float64 {
return math.Round(value)
}
func (r *Repository) baseQuery() *gorm.DB {
+21 -3
View File
@@ -61,11 +61,12 @@ func (r *Repository) Create(ownerID uint64, req CreateRequest, reviewRequired bo
return err
}
price := normalizedListingPrice(req)
depositAmount := roundMoney(req.DepositAmount)
listing := model.RentalListing{
AccountID: account.ID,
OwnerID: ownerID,
Price: price,
DepositAmount: req.DepositAmount,
DepositAmount: depositAmount,
Status: listingStatus,
ReviewStatus: reviewStatus,
PublishedAt: publishedAt,
@@ -109,7 +110,7 @@ func (r *Repository) Update(ownerID uint64, listingID uint64, req UpdateRequest,
price := normalizedListingPrice(req)
listing.Price = price
listing.DepositAmount = req.DepositAmount
listing.DepositAmount = roundMoney(req.DepositAmount)
listingStatus, reviewStatus, publishedAt := initialPublishState(reviewRequired)
listing.Status = listingStatus
listing.ReviewStatus = reviewStatus
@@ -488,7 +489,15 @@ func rowsToDTO(rows []listingRow) []ListingDTO {
}
func normalizedListingPrice(req CreateRequest) float64 {
return req.Price
if req.AssetSummary != nil {
if breakdown, ok := req.AssetSummary["price_breakdown"].(map[string]any); ok {
buyerPrice := readSummaryNumber(breakdown["buyer_total_price"])
if buyerPrice > 0 {
return roundMoney(buyerPrice)
}
}
}
return roundMoney(req.Price)
}
func publicListings(items []ListingDTO) []ListingDTO {
@@ -524,6 +533,15 @@ func applySellerListingPrice(item *ListingDTO) {
if sellerPrice > 0 {
item.Price = sellerPrice
}
sellerRatio := readSummaryNumber(breakdown["seller_ratio"])
if sellerRatio > 0 {
item.AssetSummary["publish_ratio"] = sellerRatio
}
delete(breakdown, "buyer_coin_base_price")
delete(breakdown, "buyer_total_price")
delete(breakdown, "buyer_ratio")
delete(breakdown, "platform_markup_amount")
delete(breakdown, "platform_rule_type")
}
func (row listingRow) toDTO() ListingDTO {
+1 -1
View File
@@ -313,7 +313,7 @@ func readUnitPrice(priceText string) float64 {
}
func roundMoney(value float64) float64 {
return math.Round(value*100) / 100
return math.Round(value)
}
func readFireLevel(summary map[string]any) (int, bool) {
@@ -11,8 +11,8 @@ func TestConsumableValueOnlyCountsChargedResources(t *testing.T) {
},
})
if value != 1.5 {
t.Fatalf("expected 1.5, got %.2f", value)
if value != 2 {
t.Fatalf("expected 2, got %.2f", value)
}
}
@@ -21,7 +21,7 @@ func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) {
Title: "测试账号",
ServerRegion: "烽火地带",
Price: 100,
DepositAmount: 1.5,
DepositAmount: 2,
HafCoinAmount: 1000000,
ScreenshotURLS: []string{"https://example.com/a.png"},
AssetSummary: map[string]any{
@@ -35,7 +35,7 @@ func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) {
t.Fatalf("expected ErrDepositTooLow, got %v", err)
}
req.DepositAmount = 2.01
req.DepositAmount = 3
if err := validateRequest(req, publishRules{}); err != nil {
t.Fatalf("expected valid request, got %v", err)
}
+12 -8
View File
@@ -20,10 +20,12 @@ type OrderDTO struct {
LoginPlatform string `json:"login_platform"`
RentedAt *time.Time `json:"rented_at"`
EstimatedDurationHours int `json:"estimated_duration_hours"`
RentAmount float64 `json:"rent_amount"`
OwnerRentAmount float64 `json:"owner_rent_amount"`
PriceRole string `json:"price_role,omitempty"`
DisplayAmount float64 `json:"display_amount"`
RentAmount *float64 `json:"rent_amount,omitempty"`
OwnerRentAmount *float64 `json:"owner_rent_amount,omitempty"`
DepositAmount float64 `json:"deposit_amount"`
PlatformFee float64 `json:"platform_fee"`
PlatformFee *float64 `json:"platform_fee,omitempty"`
AccountSnapshot datatypes.JSON `json:"account_snapshot"`
Status string `json:"status"`
HandoffStatus string `json:"handoff_status"`
@@ -88,16 +90,18 @@ type CheckoutDTO struct {
OrderID uint64 `json:"order_id"`
InitiatedBy uint64 `json:"initiated_by"`
Status string `json:"status"`
RentAmount float64 `json:"rent_amount"`
OwnerRentAmount float64 `json:"owner_rent_amount"`
PlatformFee float64 `json:"platform_fee"`
PriceRole string `json:"price_role,omitempty"`
DisplayAmount float64 `json:"display_amount"`
RentAmount *float64 `json:"rent_amount,omitempty"`
OwnerRentAmount *float64 `json:"owner_rent_amount,omitempty"`
PlatformFee *float64 `json:"platform_fee,omitempty"`
DepositAmount float64 `json:"deposit_amount"`
ConsumableAmount float64 `json:"consumable_amount"`
CoinConsumedM float64 `json:"coin_consumed_m"`
OtherAmount float64 `json:"other_amount"`
DepositDeductAmount float64 `json:"deposit_deduct_amount"`
RenterRefundAmount float64 `json:"renter_refund_amount"`
OwnerIncomeAmount float64 `json:"owner_income_amount"`
RenterRefundAmount *float64 `json:"renter_refund_amount,omitempty"`
OwnerIncomeAmount *float64 `json:"owner_income_amount,omitempty"`
Content string `json:"content"`
EvidenceURLS []string `json:"evidence_urls"`
OwnerAdjustmentReason string `json:"owner_adjustment_reason"`
+179 -23
View File
@@ -624,7 +624,12 @@ func (r *Repository) CounterCheckout(userID uint64, orderID uint64, req CounterC
if err != nil {
return nil, err
}
dto := toCheckoutDTO(*checkout)
dto := toCheckoutDTOForUser(*checkout, userID, model.RentalOrder{
OwnerID: userID,
RentAmount: checkout.RentAmount,
OwnerRentAmount: checkout.OwnerRentAmount,
DepositAmount: checkout.DepositAmount,
})
return &dto, nil
}
@@ -665,7 +670,7 @@ func (r *Repository) ListForUser(userID uint64) ([]OrderDTO, error) {
}
items := make([]OrderDTO, 0, len(rows))
for _, row := range rows {
items = append(items, row.toDTO())
items = append(items, row.toDTOForUser(userID))
}
return items, nil
}
@@ -681,7 +686,7 @@ func (r *Repository) ListAdmin() ([]OrderDTO, error) {
}
items := make([]OrderDTO, 0, len(rows))
for _, row := range rows {
items = append(items, row.toDTO())
items = append(items, row.toAdminDTO())
}
return items, nil
}
@@ -691,8 +696,8 @@ func (r *Repository) FindAdmin(orderID uint64) (*OrderDTO, error) {
if err := r.adminQuery().Where("o.id = ?", orderID).First(&row).Error; err != nil {
return nil, err
}
dto := row.toDTO()
dto.Checkout = r.latestCheckoutDTO(orderID)
dto := row.toAdminDTO()
dto.Checkout = r.latestCheckoutAdminDTO(orderID)
return &dto, nil
}
@@ -882,8 +887,8 @@ func (r *Repository) FindForUser(userID uint64, orderID uint64) (*OrderDTO, erro
First(&row).Error; err != nil {
return nil, err
}
dto := row.toDTO()
dto.Checkout = r.latestCheckoutDTO(orderID)
dto := row.toDTOForUser(userID)
dto.Checkout = r.latestCheckoutDTOForUser(orderID, userID, row.RentalOrder)
return &dto, nil
}
@@ -1018,7 +1023,10 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c
if consumableAmount < 0 || coinConsumedM < 0 || otherAmount < 0 || explicitDeduct < 0 {
return model.OrderCheckout{}, ErrInvalidCheckoutAmount
}
deductAmount := consumableAmount + otherAmount
consumableAmount = roundMoney(consumableAmount)
otherAmount = roundMoney(otherAmount)
explicitDeduct = roundMoney(explicitDeduct)
deductAmount := otherAmount
if useExplicitDeduct {
deductAmount = explicitDeduct
}
@@ -1038,9 +1046,9 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c
OwnerRentAmount: order.OwnerRentAmount,
PlatformFee: order.PlatformFee,
DepositAmount: order.DepositAmount,
ConsumableAmount: roundMoney(consumableAmount),
CoinConsumedM: roundMoney(coinConsumedM),
OtherAmount: roundMoney(otherAmount),
ConsumableAmount: consumableAmount,
CoinConsumedM: roundQuantity(coinConsumedM),
OtherAmount: otherAmount,
DepositDeductAmount: roundMoney(deductAmount),
RenterRefundAmount: roundMoney(renterRefund),
OwnerIncomeAmount: roundMoney(order.OwnerRentAmount + deductAmount),
@@ -1069,6 +1077,10 @@ func decodeStringList(raw datatypes.JSON) []string {
}
func roundMoney(value float64) float64 {
return math.Round(value)
}
func roundQuantity(value float64) float64 {
return math.Round(value*100) / 100
}
@@ -1080,12 +1092,21 @@ func hasOpenCheckout(tx *gorm.DB, orderID uint64) (bool, error) {
return count > 0, err
}
func (r *Repository) latestCheckoutDTO(orderID uint64) *CheckoutDTO {
func (r *Repository) latestCheckoutAdminDTO(orderID uint64) *CheckoutDTO {
var checkout model.OrderCheckout
if err := r.db.Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil {
return nil
}
dto := toCheckoutDTO(checkout)
dto := toCheckoutAdminDTO(checkout)
return &dto
}
func (r *Repository) latestCheckoutDTOForUser(orderID uint64, userID uint64, order model.RentalOrder) *CheckoutDTO {
var checkout model.OrderCheckout
if err := r.db.Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil {
return nil
}
dto := toCheckoutDTOForUser(checkout, userID, order)
return &dto
}
@@ -1120,9 +1141,12 @@ type orderRow struct {
RenterPhone string
}
func (row orderRow) toDTO() OrderDTO {
func (row orderRow) toAdminDTO() OrderDTO {
rentedAt := row.RentedAt
durationHours := orderDurationHours(row.RentalOrder)
rentAmount := row.RentAmount
ownerRentAmount := row.OwnerRentAmount
platformFee := row.PlatformFee
return OrderDTO{
ID: row.ID,
OrderNo: row.OrderNo,
@@ -1137,10 +1161,12 @@ func (row orderRow) toDTO() OrderDTO {
LoginPlatform: row.LoginPlatform,
RentedAt: rentedAt,
EstimatedDurationHours: durationHours,
RentAmount: row.RentAmount,
OwnerRentAmount: row.OwnerRentAmount,
PriceRole: "admin",
DisplayAmount: row.RentAmount,
RentAmount: &rentAmount,
OwnerRentAmount: &ownerRentAmount,
DepositAmount: row.DepositAmount,
PlatformFee: row.PlatformFee,
PlatformFee: &platformFee,
AccountSnapshot: row.AccountSnapshot,
Status: row.Status,
HandoffStatus: row.HandoffStatus,
@@ -1150,6 +1176,12 @@ func (row orderRow) toDTO() OrderDTO {
}
}
func (row orderRow) toDTOForUser(userID uint64) OrderDTO {
dto := row.toAdminDTO()
applyOrderPriceView(&dto, row.RentalOrder, userID)
return dto
}
func toHandoffDTO(record model.HandoffRecord) HandoffRecordDTO {
return HandoffRecordDTO{
ID: record.ID,
@@ -1164,22 +1196,29 @@ func toHandoffDTO(record model.HandoffRecord) HandoffRecordDTO {
}
}
func toCheckoutDTO(checkout model.OrderCheckout) CheckoutDTO {
func toCheckoutAdminDTO(checkout model.OrderCheckout) CheckoutDTO {
rentAmount := checkout.RentAmount
ownerRentAmount := checkout.OwnerRentAmount
platformFee := checkout.PlatformFee
renterRefundAmount := checkout.RenterRefundAmount
ownerIncomeAmount := checkout.OwnerIncomeAmount
return CheckoutDTO{
ID: checkout.ID,
OrderID: checkout.OrderID,
InitiatedBy: checkout.InitiatedBy,
Status: checkout.Status,
RentAmount: checkout.RentAmount,
OwnerRentAmount: checkout.OwnerRentAmount,
PlatformFee: checkout.PlatformFee,
PriceRole: "admin",
DisplayAmount: checkout.RentAmount,
RentAmount: &rentAmount,
OwnerRentAmount: &ownerRentAmount,
PlatformFee: &platformFee,
DepositAmount: checkout.DepositAmount,
ConsumableAmount: checkout.ConsumableAmount,
CoinConsumedM: checkout.CoinConsumedM,
OtherAmount: checkout.OtherAmount,
DepositDeductAmount: checkout.DepositDeductAmount,
RenterRefundAmount: checkout.RenterRefundAmount,
OwnerIncomeAmount: checkout.OwnerIncomeAmount,
RenterRefundAmount: &renterRefundAmount,
OwnerIncomeAmount: &ownerIncomeAmount,
Content: checkout.Content,
EvidenceURLS: decodeStringList(checkout.EvidenceURLS),
OwnerAdjustmentReason: checkout.OwnerAdjustmentReason,
@@ -1191,6 +1230,123 @@ func toCheckoutDTO(checkout model.OrderCheckout) CheckoutDTO {
}
}
func toCheckoutDTOForUser(checkout model.OrderCheckout, userID uint64, order model.RentalOrder) CheckoutDTO {
dto := toCheckoutAdminDTO(checkout)
applyCheckoutPriceView(&dto, order, userID)
return dto
}
func applyOrderPriceView(dto *OrderDTO, order model.RentalOrder, userID uint64) {
if dto == nil {
return
}
dto.PlatformFee = nil
switch {
case userID == order.OwnerID:
ownerAmount := order.OwnerRentAmount
if ownerAmount <= 0 {
ownerAmount = order.RentAmount
}
dto.PriceRole = "owner"
dto.DisplayAmount = ownerAmount
dto.RentAmount = nil
dto.OwnerRentAmount = &ownerAmount
sanitizeOrderSnapshot(&dto.AccountSnapshot, "owner")
case userID == order.RenterID:
rentAmount := order.RentAmount
dto.PriceRole = "renter"
dto.DisplayAmount = rentAmount
dto.RentAmount = &rentAmount
dto.OwnerRentAmount = nil
sanitizeOrderSnapshot(&dto.AccountSnapshot, "renter")
default:
dto.PriceRole = ""
dto.DisplayAmount = 0
dto.RentAmount = nil
dto.OwnerRentAmount = nil
sanitizeOrderSnapshot(&dto.AccountSnapshot, "")
}
}
func applyCheckoutPriceView(dto *CheckoutDTO, order model.RentalOrder, userID uint64) {
if dto == nil {
return
}
dto.PlatformFee = nil
dto.RenterRefundAmount = nil
dto.OwnerIncomeAmount = nil
switch {
case userID == order.OwnerID:
ownerAmount := order.OwnerRentAmount
if ownerAmount <= 0 {
ownerAmount = order.RentAmount
}
ownerIncomeAmount := dto.DepositDeductAmount + ownerAmount
dto.PriceRole = "owner"
dto.DisplayAmount = ownerAmount
dto.RentAmount = nil
dto.OwnerRentAmount = &ownerAmount
dto.OwnerIncomeAmount = &ownerIncomeAmount
case userID == order.RenterID:
rentAmount := order.RentAmount
renterRefundAmount := order.DepositAmount - dto.DepositDeductAmount
if renterRefundAmount < 0 {
renterRefundAmount = 0
}
dto.PriceRole = "renter"
dto.DisplayAmount = rentAmount
dto.RentAmount = &rentAmount
dto.OwnerRentAmount = nil
dto.RenterRefundAmount = &renterRefundAmount
default:
dto.PriceRole = ""
dto.DisplayAmount = 0
dto.RentAmount = nil
dto.OwnerRentAmount = nil
}
}
func sanitizeOrderSnapshot(snapshot *datatypes.JSON, role string) {
if snapshot == nil || len(*snapshot) == 0 {
return
}
var payload map[string]any
if err := json.Unmarshal(*snapshot, &payload); err != nil {
return
}
rawSummary, ok := payload["asset_summary"]
if !ok {
return
}
var summary map[string]any
switch typed := rawSummary.(type) {
case map[string]any:
summary = typed
case string:
if err := json.Unmarshal([]byte(typed), &summary); err != nil {
return
}
default:
raw, err := json.Marshal(typed)
if err != nil || json.Unmarshal(raw, &summary) != nil {
return
}
}
breakdown, _ := summary["price_breakdown"].(map[string]any)
if role == "owner" && breakdown != nil {
if sellerRatio := readJSONNumber(breakdown["seller_ratio"]); sellerRatio > 0 {
summary["publish_ratio"] = sellerRatio
}
}
delete(summary, "price_breakdown")
payload["asset_summary"] = summary
raw, err := json.Marshal(payload)
if err != nil {
return
}
*snapshot = datatypes.JSON(raw)
}
func makeAccountSnapshot(account model.GameAccount) (datatypes.JSON, error) {
payload := map[string]any{
"account_id": account.ID,
@@ -202,7 +202,7 @@ func applyEntry(account *model.WalletAccount, entry Entry) (float64, error) {
}
func roundWalletMoney(value float64) float64 {
return math.Round(value*100) / 100
return math.Round(value)
}
func toAccountDTO(account model.WalletAccount) *AccountDTO {