chore(init): 初始化解忧商行项目并完成 Phase 0 基座
This commit is contained in:
@@ -0,0 +1,534 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strconv"
|
||||
"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
|
||||
PureCoinAmountCent int64
|
||||
OwnerPureCoinAmountCent int64
|
||||
ExtraItemAmountCent int64
|
||||
OwnerExtraItemAmountCent int64
|
||||
PureCoinPlatformFeeCent int64
|
||||
}
|
||||
|
||||
type checkoutSettlement struct {
|
||||
OwnerRentIncomeCent int64
|
||||
DepositCompensationCent int64
|
||||
OwnerIncomeCent int64
|
||||
RentRefundCent int64
|
||||
DepositRefundCent int64
|
||||
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
|
||||
}
|
||||
|
||||
// ActualCheckoutSettlement 是正常结账与仲裁共用的实际消耗结算结果。
|
||||
type ActualCheckoutSettlement struct {
|
||||
OwnerRentIncomeCent int64
|
||||
DepositCompensationCent int64
|
||||
OwnerIncomeCent int64
|
||||
RentRefundCent int64
|
||||
DepositRefundCent int64
|
||||
RenterRefundCent int64
|
||||
PlatformFeeCent int64
|
||||
ActualRentAmountCent int64
|
||||
PureCoinAmountCent int64
|
||||
PureCoinDiscountCent int64
|
||||
PureCoinPayableCent int64
|
||||
ExtraItemAmountCent int64
|
||||
CoinConsumedM float64
|
||||
OvershootAmountCent int64
|
||||
ShortfallCent int64
|
||||
}
|
||||
|
||||
// CalculateActualPureCoinAmounts 按订单价格快照和实际消耗 M 计算纯币原价及等级优惠。
|
||||
func CalculateActualPureCoinAmounts(order model.RentalOrder, coinConsumedM float64) ActualPureCoinAmounts {
|
||||
settlement := CalculateActualCheckoutSettlement(order, 0, coinConsumedM, 0)
|
||||
return ActualPureCoinAmounts{
|
||||
AmountCent: settlement.PureCoinAmountCent,
|
||||
DiscountCent: settlement.PureCoinDiscountCent,
|
||||
PayableCent: settlement.PureCoinPayableCent,
|
||||
ConsumedM: settlement.CoinConsumedM,
|
||||
}
|
||||
}
|
||||
|
||||
// CalculateActualCheckoutSettlement 按实际纯币和额外物品消耗计算完整结算。
|
||||
func CalculateActualCheckoutSettlement(order model.RentalOrder, consumableAmountCent int64, coinConsumedM float64, depositDeductAmountCent int64) ActualCheckoutSettlement {
|
||||
return toActualCheckoutSettlement(calculateCheckoutSettlement(order, consumableAmountCent, coinConsumedM, depositDeductAmountCent))
|
||||
}
|
||||
|
||||
func toActualCheckoutSettlement(settlement checkoutSettlement) ActualCheckoutSettlement {
|
||||
return ActualCheckoutSettlement{
|
||||
OwnerRentIncomeCent: settlement.OwnerRentIncomeCent,
|
||||
DepositCompensationCent: settlement.DepositCompensationCent,
|
||||
OwnerIncomeCent: settlement.OwnerIncomeCent,
|
||||
RentRefundCent: settlement.RentRefundCent,
|
||||
DepositRefundCent: settlement.DepositRefundCent,
|
||||
RenterRefundCent: settlement.RenterRefundCent,
|
||||
PlatformFeeCent: settlement.PlatformFeeCent,
|
||||
ActualRentAmountCent: settlement.ActualRentAmountCent,
|
||||
PureCoinAmountCent: settlement.PureCoinAmountCent,
|
||||
PureCoinDiscountCent: settlement.PureCoinDiscountCent,
|
||||
PureCoinPayableCent: settlement.PureCoinPayableCent,
|
||||
ExtraItemAmountCent: settlement.ExtraItemAmountCent,
|
||||
CoinConsumedM: settlement.CoinConsumedM,
|
||||
OvershootAmountCent: settlement.OvershootAmountCent,
|
||||
ShortfallCent: settlement.ShortfallCent,
|
||||
}
|
||||
}
|
||||
|
||||
func orderDurationHours(order model.RentalOrder) int {
|
||||
if order.EstimatedDurationHours > 0 {
|
||||
return order.EstimatedDurationHours
|
||||
}
|
||||
return internalOrderHours
|
||||
}
|
||||
|
||||
func orderEstimatedEndAt(order model.RentalOrder) *time.Time {
|
||||
if order.RentedAt == nil {
|
||||
return nil
|
||||
}
|
||||
durationHours := orderDurationHours(order)
|
||||
if durationHours <= 0 {
|
||||
return nil
|
||||
}
|
||||
endAt := order.RentedAt.Add(time.Duration(durationHours) * time.Hour)
|
||||
return &endAt
|
||||
}
|
||||
|
||||
// orderListingTerms 用下单快照里的哈夫币和日损耗估算租期,和首页「可租 / 消耗」同一口径。
|
||||
func orderListingTerms(snapshot datatypes.JSON) (days int, dailyLossM float64) {
|
||||
dailyLossM = readOrderSnapshotAssetNumber(snapshot, "daily_loss_m")
|
||||
coinM := readOrderSnapshotCoinM(snapshot)
|
||||
if dailyLossM <= 0 || coinM <= 0 {
|
||||
return 0, 0
|
||||
}
|
||||
days = int(math.Ceil(coinM / dailyLossM))
|
||||
if days < 1 {
|
||||
days = 1
|
||||
}
|
||||
return days, roundQuantity(dailyLossM)
|
||||
}
|
||||
|
||||
func estimateOrderDurationHours(snapshot datatypes.JSON) int {
|
||||
hafCoinM := readOrderSnapshotCoinM(snapshot)
|
||||
if hafCoinM <= 0 {
|
||||
return internalOrderHours
|
||||
}
|
||||
dailyLossM := readOrderSnapshotAssetNumber(snapshot, "daily_loss_m")
|
||||
if dailyLossM <= 0 {
|
||||
return internalOrderHours
|
||||
}
|
||||
days := hafCoinM / dailyLossM
|
||||
if days <= 0 {
|
||||
return internalOrderHours
|
||||
}
|
||||
// 不足整天按整天向上取整,例如 1.3 / 1.7 天均按 2 天计
|
||||
daysCeil := int(math.Ceil(days))
|
||||
if daysCeil < 1 {
|
||||
daysCeil = 1
|
||||
}
|
||||
hours := daysCeil * 24
|
||||
return hours
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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,
|
||||
PureCoinAmountCent: pureCoinAmountCent,
|
||||
OwnerPureCoinAmountCent: ownerPureCoinAmountCent,
|
||||
ExtraItemAmountCent: extraItemAmountCent,
|
||||
OwnerExtraItemAmountCent: ownerExtraItemAmountCent,
|
||||
PureCoinPlatformFeeCent: pureCoinPlatformFeeCent,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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,
|
||||
PureCoinAmountCent: settlement.PureCoinAmountCent,
|
||||
PureCoinDiscountCent: settlement.PureCoinDiscountCent,
|
||||
PureCoinPayableCent: settlement.PureCoinPayableCent,
|
||||
ConsumableAmountCent: consumableAmountCent,
|
||||
CoinConsumedM: roundQuantity(coinConsumedM),
|
||||
OtherAmountCent: otherAmountCent,
|
||||
// 存用户申报的押金赔付(损坏等),打超由结算自动计入 shortfall/overshoot
|
||||
DepositDeductAmountCent: deductAmountCent,
|
||||
RenterRefundAmountCent: settlement.RenterRefundCent,
|
||||
OwnerIncomeAmountCent: settlement.OwnerIncomeCent,
|
||||
ShortfallCent: settlement.ShortfallCent,
|
||||
OvershootAmountCent: settlement.OvershootAmountCent,
|
||||
Content: content,
|
||||
EvidenceURLS: evidence,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildCheckoutSettlement(order model.RentalOrder, checkout *model.OrderCheckout) checkoutSettlement {
|
||||
return calculateCheckoutSettlement(order, checkout.ConsumableAmountCent, checkout.CoinConsumedM, effectiveCheckoutDepositDeductCent(*checkout))
|
||||
}
|
||||
|
||||
// effectiveCheckoutDepositDeductCent keeps legacy checkouts that only populated
|
||||
// other_amount_cent from losing their deposit compensation at finalization.
|
||||
func effectiveCheckoutDepositDeductCent(checkout model.OrderCheckout) int64 {
|
||||
if checkout.DepositDeductAmountCent > 0 {
|
||||
return checkout.DepositDeductAmountCent
|
||||
}
|
||||
return maxCent(checkout.OtherAmountCent, 0)
|
||||
}
|
||||
|
||||
func orderConsumptionPrepaidCent(order model.RentalOrder) int64 {
|
||||
return order.RentAmountCent
|
||||
}
|
||||
|
||||
func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent int64, coinConsumedM float64, depositDeductAmountCent int64) checkoutSettlement {
|
||||
orderRentAmountCent := orderConsumptionPrepaidCent(order)
|
||||
orderOwnerRentAmountCent := order.OwnerRentAmountCent
|
||||
orderDepositAmountCent := order.DepositAmountCent
|
||||
|
||||
buyerCoinBasePriceCent := orderPureCoinOriginalAmountCent(order)
|
||||
sellerCoinBasePriceCent := int64(math.Round(readOrderSnapshotPrice(order.AccountSnapshot, "seller_coin_base_price") * 100))
|
||||
if sellerCoinBasePriceCent <= 0 || sellerCoinBasePriceCent > orderOwnerRentAmountCent {
|
||||
ownerExtraItemCent := minCent(orderExtraItemOriginalAmountCent(order), orderOwnerRentAmountCent)
|
||||
sellerCoinBasePriceCent = maxCent(orderOwnerRentAmountCent-ownerExtraItemCent, 0)
|
||||
}
|
||||
prepaidConsumablePriceCent := orderExtraItemOriginalAmountCent(order)
|
||||
prepaidOwnerConsumablePriceCent := maxCent(orderOwnerRentAmountCent-sellerCoinBasePriceCent, 0)
|
||||
|
||||
consumedM := roundQuantity(coinConsumedM)
|
||||
totalCoinM := readOrderSnapshotCoinM(order.AccountSnapshot)
|
||||
buyerRatio := readOrderSnapshotPrice(order.AccountSnapshot, "buyer_ratio")
|
||||
sellerRatio := readOrderSnapshotPrice(order.AccountSnapshot, "seller_ratio")
|
||||
|
||||
// 币费:优先按各自比例 × 实际消耗;无比例时回退到预收币基价 × (消耗/发布量),允许 >1(打超)
|
||||
// 无发布量快照时保持旧行为:按全额使用(ratio=1)
|
||||
coinUseRatio := coinConsumptionRatio(consumedM, totalCoinM)
|
||||
var usedBuyerCoinPriceCent, usedOwnerCoinPriceCent int64
|
||||
if buyerRatio > 0 && (totalCoinM > 0 || consumedM > 0) {
|
||||
// 价(元) = 消耗M×100 / ratio;分 = 元×100
|
||||
usedBuyerCoinPriceCent = int64(math.Round(consumedM * 10000 / buyerRatio))
|
||||
} else {
|
||||
usedBuyerCoinPriceCent = int64(math.Round(float64(buyerCoinBasePriceCent) * coinUseRatio))
|
||||
}
|
||||
if sellerRatio > 0 && (totalCoinM > 0 || consumedM > 0) {
|
||||
usedOwnerCoinPriceCent = int64(math.Round(consumedM * 10000 / sellerRatio))
|
||||
} else {
|
||||
usedOwnerCoinPriceCent = int64(math.Round(float64(sellerCoinBasePriceCent) * coinUseRatio))
|
||||
}
|
||||
coinOvershoot := totalCoinM > 0 && consumedM > totalCoinM
|
||||
if !coinOvershoot {
|
||||
// 未打超时,比例反推只能用于折算未用完部分,不能因四舍五入超过订单快照。
|
||||
if totalCoinM > 0 && consumedM >= totalCoinM {
|
||||
usedBuyerCoinPriceCent = buyerCoinBasePriceCent
|
||||
usedOwnerCoinPriceCent = sellerCoinBasePriceCent
|
||||
} else {
|
||||
usedBuyerCoinPriceCent = minCent(usedBuyerCoinPriceCent, buyerCoinBasePriceCent)
|
||||
usedOwnerCoinPriceCent = minCent(usedOwnerCoinPriceCent, sellerCoinBasePriceCent)
|
||||
}
|
||||
}
|
||||
|
||||
// 消耗品按申报金额计价,允许超过预收;号主侧按预收消耗品占比外推,超额及无预收损耗全额归号主
|
||||
usedBuyerConsumablePriceCent := maxCent(consumableAmountCent, 0)
|
||||
var usedOwnerConsumablePriceCent int64
|
||||
if prepaidConsumablePriceCent > 0 {
|
||||
if usedBuyerConsumablePriceCent <= prepaidConsumablePriceCent {
|
||||
consumableUseRatio := maxRatio(float64(usedBuyerConsumablePriceCent)/float64(prepaidConsumablePriceCent), 0)
|
||||
usedOwnerConsumablePriceCent = int64(math.Round(float64(prepaidOwnerConsumablePriceCent) * consumableUseRatio))
|
||||
usedOwnerConsumablePriceCent = minCent(usedOwnerConsumablePriceCent, prepaidOwnerConsumablePriceCent)
|
||||
} else {
|
||||
// 预收范围内按号主预收基准,超出预收部分作为后置损耗全额划归号主(平台不抽成)
|
||||
excessCent := usedBuyerConsumablePriceCent - prepaidConsumablePriceCent
|
||||
usedOwnerConsumablePriceCent = prepaidOwnerConsumablePriceCent + excessCent
|
||||
}
|
||||
} else {
|
||||
// 原订单无预收消耗品,后置申报的消耗/损耗金额 100% 归号主,平台不抽成
|
||||
usedOwnerConsumablePriceCent = 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
|
||||
}
|
||||
|
||||
// 打超:实际租客侧费用超出预收租金的部分,从押金扣
|
||||
overshootCent := maxCent(actualBuyerRentCent-orderRentAmountCent, 0)
|
||||
damageRequestCent := maxCent(depositDeductAmountCent, 0)
|
||||
// 押金先覆盖损坏赔付,再覆盖打超;不足记 shortfall,禁止自动完结
|
||||
damageAppliedCent := minCent(damageRequestCent, orderDepositAmountCent)
|
||||
depositLeftAfterDamage := maxCent(orderDepositAmountCent-damageAppliedCent, 0)
|
||||
overshootCoveredCent := minCent(overshootCent, depositLeftAfterDamage)
|
||||
shortfallCent := maxCent(damageRequestCent-damageAppliedCent, 0) + maxCent(overshootCent-overshootCoveredCent, 0)
|
||||
|
||||
rentFromPrepaidCent := minCent(actualBuyerRentCent, orderRentAmountCent)
|
||||
rentRefundCent := maxCent(orderRentAmountCent-rentFromPrepaidCent, 0)
|
||||
depositUsedCent := damageAppliedCent + overshootCoveredCent
|
||||
depositRefundCent := maxCent(orderDepositAmountCent-depositUsedCent, 0)
|
||||
|
||||
// 可完结时租客实际承担的租金向金额;有 shortfall 时仍展示完整应付便于协商
|
||||
actualRentAmountCent := actualBuyerRentCent
|
||||
ownerRentIncomeCent := actualOwnerRentCent
|
||||
platformFeeCent := maxCent(actualRentAmountCent-ownerRentIncomeCent, 0)
|
||||
depositCompensationCent := damageAppliedCent
|
||||
|
||||
return checkoutSettlement{
|
||||
OwnerRentIncomeCent: ownerRentIncomeCent,
|
||||
DepositCompensationCent: depositCompensationCent,
|
||||
OwnerIncomeCent: ownerRentIncomeCent + depositCompensationCent,
|
||||
RentRefundCent: rentRefundCent,
|
||||
DepositRefundCent: depositRefundCent,
|
||||
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
|
||||
}
|
||||
var snapshot map[string]any
|
||||
if err := json.Unmarshal(raw, &snapshot); err != nil {
|
||||
return 0
|
||||
}
|
||||
return roundQuantity(readJSONNumber(snapshot["haf_coin_amount"]) / 1000000)
|
||||
}
|
||||
|
||||
// coinConsumptionRatio 实际消耗相对发布量的比例,允许 >1 表示打超。
|
||||
// publishedM<=0 时视为无快照,回退为全额使用(兼容旧单测/无币价场景)。
|
||||
func coinConsumptionRatio(consumedM, publishedM float64) float64 {
|
||||
if publishedM > 0 {
|
||||
return maxRatio(roundQuantity(consumedM)/publishedM, 0)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func readOrderSnapshotAssetNumber(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
|
||||
}
|
||||
assetSummary, ok := snapshot["asset_summary"].(map[string]any)
|
||||
if !ok {
|
||||
return readJSONNumber(snapshot[key])
|
||||
}
|
||||
if value, ok := assetSummary[key]; ok {
|
||||
return readJSONNumber(value)
|
||||
}
|
||||
return readJSONNumber(snapshot[key])
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user