package order import ( "encoding/json" "math" "strconv" "hfb_sys/backend/internal/model" "hfb_sys/backend/pkg/money" "gorm.io/datatypes" ) type orderPricing struct { RentAmountCent int64 OwnerRentAmountCent int64 PlatformFeeCent int64 } type checkoutSettlement struct { OwnerRentIncomeCent int64 DepositCompensationCent int64 OwnerIncomeCent int64 RentRefundCent int64 DepositRefundCent int64 RenterRefundCent int64 PlatformFeeCent int64 ActualRentAmountCent int64 } func orderDurationHours(order model.RentalOrder) int { if order.EstimatedDurationHours > 0 { return order.EstimatedDurationHours } return internalOrderHours } func buildOrderPricing(listing model.RentalListing, account model.GameAccount) orderPricing { rentAmountCent := listing.PriceCent ownerRentAmountCent := int64(math.Round(readSnapshotPrice(account.AssetSummary, "seller_total_price") * 100)) if ownerRentAmountCent <= 0 || ownerRentAmountCent > rentAmountCent { ownerRentAmountCent = rentAmountCent } platformFeeCent := int64(math.Round(readSnapshotPrice(account.AssetSummary, "platform_markup_amount") * 100)) if platformFeeCent <= 0 || ownerRentAmountCent+platformFeeCent != rentAmountCent { platformFeeCent = rentAmountCent - ownerRentAmountCent } if platformFeeCent < 0 { platformFeeCent = 0 } return orderPricing{ RentAmountCent: rentAmountCent, OwnerRentAmountCent: ownerRentAmountCent, PlatformFeeCent: platformFeeCent, } } func readSnapshotPrice(raw datatypes.JSON, key string) float64 { return readOrderSnapshotPrice(raw, key) } func readOrderSnapshotPrice(raw datatypes.JSON, key string) float64 { if len(raw) == 0 { return 0 } var snapshot map[string]any if err := json.Unmarshal(raw, &snapshot); err != nil { return 0 } if assetSummary, ok := snapshot["asset_summary"].(map[string]any); ok { snapshot = assetSummary } breakdown, ok := snapshot["price_breakdown"].(map[string]any) if !ok { return 0 } return readJSONNumber(breakdown[key]) } func readJSONNumber(value any) float64 { switch typed := value.(type) { case float64: return typed case float32: return float64(typed) case int: return float64(typed) case int64: return float64(typed) case json.Number: number, err := typed.Float64() if err != nil { return 0 } return number case string: number, err := strconv.ParseFloat(typed, 64) if err != nil { return 0 } return number default: return 0 } } func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, content string, evidenceURLS []string, consumableAmountCent int64, coinConsumedM float64, otherAmountCent int64, explicitDeductCent int64, useExplicitDeduct bool) (model.OrderCheckout, error) { if consumableAmountCent < 0 || coinConsumedM < 0 || otherAmountCent < 0 || explicitDeductCent < 0 { return model.OrderCheckout{}, ErrInvalidCheckoutAmount } deductAmountCent := otherAmountCent if useExplicitDeduct { deductAmountCent = explicitDeductCent } if deductAmountCent > order.DepositAmountCent { return model.OrderCheckout{}, ErrInvalidCheckoutAmount } settlement := calculateCheckoutSettlement(order, consumableAmountCent, roundQuantity(coinConsumedM), deductAmountCent) evidence, err := marshalStringList(evidenceURLS) if err != nil { return model.OrderCheckout{}, err } return model.OrderCheckout{ OrderID: order.ID, InitiatedBy: initiatedBy, Status: status, RentAmountCent: settlement.ActualRentAmountCent, OwnerRentAmountCent: settlement.OwnerRentIncomeCent, PlatformFeeCent: settlement.PlatformFeeCent, DepositAmountCent: order.DepositAmountCent, ConsumableAmountCent: consumableAmountCent, CoinConsumedM: roundQuantity(coinConsumedM), OtherAmountCent: otherAmountCent, DepositDeductAmountCent: settlement.DepositCompensationCent, RenterRefundAmountCent: settlement.RenterRefundCent, OwnerIncomeAmountCent: settlement.OwnerIncomeCent, Content: content, EvidenceURLS: evidence, }, nil } func buildCheckoutSettlement(order model.RentalOrder, checkout *model.OrderCheckout) checkoutSettlement { return calculateCheckoutSettlement(order, checkout.ConsumableAmountCent, checkout.CoinConsumedM, checkout.DepositDeductAmountCent) } func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent int64, coinConsumedM float64, depositDeductAmountCent int64) checkoutSettlement { orderRentAmountCent := order.RentAmountCent 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 } 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) } prepaidOwnerConsumablePriceCent := maxCent(orderOwnerRentAmountCent-sellerCoinBasePriceCent, 0) totalCoinM := readOrderSnapshotCoinM(order.AccountSnapshot) coinUseRatio := 1.0 if totalCoinM > 0 { coinUseRatio = minRatio(maxRatio(roundQuantity(coinConsumedM)/totalCoinM, 0), 1) } usedBuyerCoinPriceCent := int64(math.Round(float64(buyerCoinBasePriceCent) * coinUseRatio)) usedOwnerCoinPriceCent := int64(math.Round(float64(sellerCoinBasePriceCent) * coinUseRatio)) usedBuyerConsumablePriceCent := minCent(consumableAmountCent, prepaidConsumablePriceCent) consumableUseRatio := 1.0 if prepaidConsumablePriceCent > 0 { consumableUseRatio = minRatio(maxRatio(float64(usedBuyerConsumablePriceCent)/float64(prepaidConsumablePriceCent), 0), 1) } usedOwnerConsumablePriceCent := int64(math.Round(float64(prepaidOwnerConsumablePriceCent) * consumableUseRatio)) actualRentAmountCent := minCent(usedBuyerCoinPriceCent+usedBuyerConsumablePriceCent, orderRentAmountCent) ownerRentIncomeCent := minCent(usedOwnerCoinPriceCent+usedOwnerConsumablePriceCent, orderOwnerRentAmountCent) depositCompensationCent := minCent(depositDeductAmountCent, orderDepositAmountCent) rentRefundCent := maxCent(orderRentAmountCent-actualRentAmountCent, 0) depositRefundCent := maxCent(orderDepositAmountCent-depositCompensationCent, 0) return checkoutSettlement{ OwnerRentIncomeCent: ownerRentIncomeCent, DepositCompensationCent: depositCompensationCent, OwnerIncomeCent: ownerRentIncomeCent + depositCompensationCent, RentRefundCent: rentRefundCent, DepositRefundCent: depositRefundCent, RenterRefundCent: rentRefundCent + depositRefundCent, PlatformFeeCent: maxCent(actualRentAmountCent-ownerRentIncomeCent, 0), ActualRentAmountCent: actualRentAmountCent, } } func readOrderSnapshotCoinM(raw datatypes.JSON) float64 { if len(raw) == 0 { return 0 } var snapshot map[string]any if err := json.Unmarshal(raw, &snapshot); err != nil { return 0 } return roundQuantity(readJSONNumber(snapshot["haf_coin_amount"]) / 1000000) } // roundMoney 使用统一的角精度(0.1元) func roundMoney(value float64) float64 { return money.Round(value) } func roundQuantity(value float64) float64 { return math.Round(value*100) / 100 } // minMoney 返回较小金额(角精度) func minMoney(a float64, b float64) float64 { return money.Min(a, b) } // maxMoney 返回较大金额(角精度) func maxMoney(a float64, b float64) float64 { return money.Max(a, b) } func minCent(a int64, b int64) int64 { if a < b { return a } return b } func maxCent(a int64, b int64) int64 { if a > b { return a } return b } func minRatio(a float64, b float64) float64 { if a < b { return a } return b } func maxRatio(a float64, b float64) float64 { if a > b { return a } return b }