feat(distribution): 收口账号同步与支付配置并优化结算
This commit is contained in:
@@ -279,6 +279,52 @@ func readOrderSnapshotPrice(raw datatypes.JSON, key string) float64 {
|
||||
return readJSONNumber(breakdown[key])
|
||||
}
|
||||
|
||||
// readOrderSnapshotBPS 优先读取整数基点字段;旧快照只有 float 比例时换算为 bps。
|
||||
func readOrderSnapshotBPS(raw datatypes.JSON, key string) int64 {
|
||||
breakdown, ok := readOrderSnapshotBreakdown(raw)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
if bps := readJSONNumber(breakdown[key+"_bps"]); bps > 0 {
|
||||
return int64(math.Round(bps))
|
||||
}
|
||||
legacy := readJSONNumber(breakdown[key])
|
||||
if legacy <= 0 {
|
||||
return 0
|
||||
}
|
||||
return int64(math.Round(legacy * 10000))
|
||||
}
|
||||
|
||||
// quantityUnits 把 M 数量转成百分之一 M 的整数单位,避免比例计算依赖浮点。
|
||||
func quantityUnits(value float64) int64 {
|
||||
if value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) {
|
||||
return 0
|
||||
}
|
||||
return int64(math.Round(value * 100))
|
||||
}
|
||||
|
||||
// roundedDiv 对非负整数做四舍五入除法。
|
||||
func roundedDiv(numerator, denominator int64) int64 {
|
||||
if numerator <= 0 || denominator <= 0 {
|
||||
return 0
|
||||
}
|
||||
return (numerator + denominator/2) / denominator
|
||||
}
|
||||
|
||||
// proportionalCent 按 part/total 折算金额;total<=0 时保持旧行为取全额。
|
||||
func proportionalCent(baseCent, partUnits, totalUnits int64) int64 {
|
||||
if baseCent <= 0 {
|
||||
return 0
|
||||
}
|
||||
if totalUnits <= 0 {
|
||||
return baseCent
|
||||
}
|
||||
if partUnits <= 0 {
|
||||
return 0
|
||||
}
|
||||
return roundedDiv(baseCent*partUnits, totalUnits)
|
||||
}
|
||||
|
||||
func readJSONNumber(value any) float64 {
|
||||
switch typed := value.(type) {
|
||||
case float64:
|
||||
@@ -377,28 +423,28 @@ func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent i
|
||||
|
||||
consumedM := roundQuantity(coinConsumedM)
|
||||
totalCoinM := readOrderSnapshotCoinM(order.AccountSnapshot)
|
||||
buyerRatio := readOrderSnapshotPrice(order.AccountSnapshot, "buyer_ratio")
|
||||
sellerRatio := readOrderSnapshotPrice(order.AccountSnapshot, "seller_ratio")
|
||||
buyerRatioBPS := readOrderSnapshotBPS(order.AccountSnapshot, "buyer_ratio")
|
||||
sellerRatioBPS := readOrderSnapshotBPS(order.AccountSnapshot, "seller_ratio")
|
||||
consumedUnits := quantityUnits(consumedM)
|
||||
totalCoinUnits := quantityUnits(totalCoinM)
|
||||
|
||||
// 币费:优先按各自比例 × 实际消耗;无比例时回退到预收币基价 × (消耗/发布量),允许 >1(打超)
|
||||
// 无发布量快照时保持旧行为:按全额使用(ratio=1)
|
||||
coinUseRatio := coinConsumptionRatio(consumedM, totalCoinM)
|
||||
// 币费:比例优先按 bps 整数换算;缺失 bps 时把旧比例换算成 bps。
|
||||
// 无比例时回退到预收币基价按消耗比例折算,仍使用整数除法。
|
||||
var usedBuyerCoinPriceCent, usedOwnerCoinPriceCent int64
|
||||
if buyerRatio > 0 && (totalCoinM > 0 || consumedM > 0) {
|
||||
// 价(元) = 消耗M×100 / ratio;分 = 元×100
|
||||
usedBuyerCoinPriceCent = int64(math.Round(consumedM * 10000 / buyerRatio))
|
||||
if buyerRatioBPS > 0 && (totalCoinUnits > 0 || consumedUnits > 0) {
|
||||
usedBuyerCoinPriceCent = roundedDiv(consumedUnits*1_000_000, buyerRatioBPS)
|
||||
} else {
|
||||
usedBuyerCoinPriceCent = int64(math.Round(float64(buyerCoinBasePriceCent) * coinUseRatio))
|
||||
usedBuyerCoinPriceCent = proportionalCent(buyerCoinBasePriceCent, consumedUnits, totalCoinUnits)
|
||||
}
|
||||
if sellerRatio > 0 && (totalCoinM > 0 || consumedM > 0) {
|
||||
usedOwnerCoinPriceCent = int64(math.Round(consumedM * 10000 / sellerRatio))
|
||||
if sellerRatioBPS > 0 && (totalCoinUnits > 0 || consumedUnits > 0) {
|
||||
usedOwnerCoinPriceCent = roundedDiv(consumedUnits*1_000_000, sellerRatioBPS)
|
||||
} else {
|
||||
usedOwnerCoinPriceCent = int64(math.Round(float64(sellerCoinBasePriceCent) * coinUseRatio))
|
||||
usedOwnerCoinPriceCent = proportionalCent(sellerCoinBasePriceCent, consumedUnits, totalCoinUnits)
|
||||
}
|
||||
coinOvershoot := totalCoinM > 0 && consumedM > totalCoinM
|
||||
coinOvershoot := totalCoinUnits > 0 && consumedUnits > totalCoinUnits
|
||||
if !coinOvershoot {
|
||||
// 未打超时,比例反推只能用于折算未用完部分,不能因四舍五入超过订单快照。
|
||||
if totalCoinM > 0 && consumedM >= totalCoinM {
|
||||
// 未打超时,比例反推只能用于折算未用完部分,不能因取整超过订单快照。
|
||||
if totalCoinUnits > 0 && consumedUnits >= totalCoinUnits {
|
||||
usedBuyerCoinPriceCent = buyerCoinBasePriceCent
|
||||
usedOwnerCoinPriceCent = sellerCoinBasePriceCent
|
||||
} else {
|
||||
@@ -412,8 +458,10 @@ func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent i
|
||||
var usedOwnerConsumablePriceCent int64
|
||||
if prepaidConsumablePriceCent > 0 {
|
||||
if usedBuyerConsumablePriceCent <= prepaidConsumablePriceCent {
|
||||
consumableUseRatio := maxRatio(float64(usedBuyerConsumablePriceCent)/float64(prepaidConsumablePriceCent), 0)
|
||||
usedOwnerConsumablePriceCent = int64(math.Round(float64(prepaidOwnerConsumablePriceCent) * consumableUseRatio))
|
||||
usedOwnerConsumablePriceCent = roundedDiv(
|
||||
prepaidOwnerConsumablePriceCent*usedBuyerConsumablePriceCent,
|
||||
prepaidConsumablePriceCent,
|
||||
)
|
||||
usedOwnerConsumablePriceCent = minCent(usedOwnerConsumablePriceCent, prepaidOwnerConsumablePriceCent)
|
||||
} else {
|
||||
// 预收范围内按号主预收基准,超出预收部分作为后置损耗全额划归号主(平台不抽成)
|
||||
|
||||
@@ -66,3 +66,37 @@ func TestBuildOrderPricingUsesCentBreakdown(t *testing.T) {
|
||||
t.Fatalf("OwnerPureCoinAmountCent = %d, want 23315", pricing.OwnerPureCoinAmountCent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateCheckoutSettlementPrefersRatioBPS(t *testing.T) {
|
||||
// bps 必须优先于旧 float 比例:buyer 1:40、seller 1:45,消耗 180M。
|
||||
order := model.RentalOrder{
|
||||
RentAmountCent: 25000,
|
||||
OwnerRentAmountCent: 22222,
|
||||
DepositAmountCent: 50000,
|
||||
AccountSnapshot: datatypes.JSON([]byte(`{
|
||||
"haf_coin_amount": 100000000,
|
||||
"asset_summary": {
|
||||
"price_breakdown": {
|
||||
"buyer_ratio": 1,
|
||||
"seller_ratio": 1,
|
||||
"buyer_ratio_bps": 400000,
|
||||
"seller_ratio_bps": 450000,
|
||||
"buyer_coin_base_price": 250,
|
||||
"seller_coin_base_price": 222.22,
|
||||
"consumable_price": 0
|
||||
}
|
||||
}
|
||||
}`)),
|
||||
}
|
||||
|
||||
settlement := calculateCheckoutSettlement(order, 0, 180, 0)
|
||||
if settlement.ActualRentAmountCent != 45000 {
|
||||
t.Fatalf("ActualRentAmountCent = %d, want 45000", settlement.ActualRentAmountCent)
|
||||
}
|
||||
if settlement.OwnerRentIncomeCent != 40000 {
|
||||
t.Fatalf("OwnerRentIncomeCent = %d, want 40000", settlement.OwnerRentIncomeCent)
|
||||
}
|
||||
if settlement.PlatformFeeCent != 5000 {
|
||||
t.Fatalf("PlatformFeeCent = %d, want 5000", settlement.PlatformFeeCent)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user