退款纠纷分配
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hfb_sys/backend/internal/listingstatus"
|
"hfb_sys/backend/internal/listingstatus"
|
||||||
@@ -246,7 +247,11 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest, r
|
|||||||
addRenterRefund(req.AmountCent, "仲裁部分退款")
|
addRenterRefund(req.AmountCent, "仲裁部分退款")
|
||||||
// 部分退款为合并金额,无法精确拆分租金/押金,按押金优先归类以便暂扣。
|
// 部分退款为合并金额,无法精确拆分租金/押金,按押金优先归类以便暂扣。
|
||||||
settlement.RenterDepositRefundCent = money.MinCent(req.AmountCent, depositAmountCent)
|
settlement.RenterDepositRefundCent = money.MinCent(req.AmountCent, depositAmountCent)
|
||||||
addOwnerIncome(money.MinCent(totalCent-req.AmountCent, ownerRentAmountCent+depositAmountCent), "仲裁剩余金额结算给号主")
|
// 号主仅拿「未退租金中的号主份额 + 未退押金」,平台加价按未退租金比例预留,避免整笔剩余进号主。
|
||||||
|
addOwnerIncome(
|
||||||
|
partialRefundOwnerIncomeCent(rentAmountCent, ownerRentAmountCent, depositAmountCent, req.AmountCent),
|
||||||
|
"仲裁剩余金额结算给号主",
|
||||||
|
)
|
||||||
case "release_deposit":
|
case "release_deposit":
|
||||||
addOwnerIncome(ownerRentAmountCent, "仲裁确认订单金额结算给号主")
|
addOwnerIncome(ownerRentAmountCent, "仲裁确认订单金额结算给号主")
|
||||||
addRenterRefund(depositAmountCent, "仲裁释放押金给租客")
|
addRenterRefund(depositAmountCent, "仲裁释放押金给租客")
|
||||||
@@ -273,6 +278,66 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest, r
|
|||||||
return settlement, nil
|
return settlement, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// partialRefundOwnerIncomeCent 计算部分退款后号主应得。
|
||||||
|
// 退款优先冲押金,其余冲租金;未退租金按预收结构拆分:
|
||||||
|
// 平台费 = 租客租金 − 号主租金,并按 未退租金/预收租金 比例预留,剩余才归号主。
|
||||||
|
// 这样可避免「剩余金额整笔给号主」把平台加价分给用户。
|
||||||
|
func partialRefundOwnerIncomeCent(rentAmountCent, ownerRentAmountCent, depositAmountCent, renterRefundCent int64) int64 {
|
||||||
|
if renterRefundCent < 0 {
|
||||||
|
renterRefundCent = 0
|
||||||
|
}
|
||||||
|
totalCent := rentAmountCent + depositAmountCent
|
||||||
|
if renterRefundCent > totalCent {
|
||||||
|
renterRefundCent = totalCent
|
||||||
|
}
|
||||||
|
if ownerRentAmountCent < 0 {
|
||||||
|
ownerRentAmountCent = 0
|
||||||
|
}
|
||||||
|
if ownerRentAmountCent > rentAmountCent {
|
||||||
|
ownerRentAmountCent = rentAmountCent
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退款优先视为押金退还,其余视为租金退还(与 RenterDepositRefundCent 归类一致)
|
||||||
|
depositRefunded := money.MinCent(renterRefundCent, depositAmountCent)
|
||||||
|
rentRefunded := renterRefundCent - depositRefunded
|
||||||
|
rentLeft := rentAmountCent - rentRefunded
|
||||||
|
if rentLeft < 0 {
|
||||||
|
rentLeft = 0
|
||||||
|
}
|
||||||
|
depositLeft := depositAmountCent - depositRefunded
|
||||||
|
|
||||||
|
platformFeeCent := rentAmountCent - ownerRentAmountCent
|
||||||
|
if platformFeeCent < 0 {
|
||||||
|
platformFeeCent = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按未退租金占比预留平台费
|
||||||
|
platformKeepCent := int64(0)
|
||||||
|
if rentAmountCent > 0 && rentLeft > 0 && platformFeeCent > 0 {
|
||||||
|
platformKeepCent = int64(math.Round(float64(platformFeeCent) * float64(rentLeft) / float64(rentAmountCent)))
|
||||||
|
platformKeepCent = money.MinCent(platformKeepCent, rentLeft)
|
||||||
|
platformKeepCent = money.MinCent(platformKeepCent, platformFeeCent)
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerFromRent := rentLeft - platformKeepCent
|
||||||
|
if ownerFromRent > ownerRentAmountCent {
|
||||||
|
ownerFromRent = ownerRentAmountCent
|
||||||
|
}
|
||||||
|
if ownerFromRent < 0 {
|
||||||
|
ownerFromRent = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerIncome := ownerFromRent + depositLeft
|
||||||
|
remaining := totalCent - renterRefundCent
|
||||||
|
if ownerIncome > remaining {
|
||||||
|
ownerIncome = remaining
|
||||||
|
}
|
||||||
|
if ownerIncome < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return ownerIncome
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Repository) prepareRefund(order *model.RentalOrder, amountCent int64, bizType string, remark string) (*refundAction, error) {
|
func (r *Repository) prepareRefund(order *model.RentalOrder, amountCent int64, bizType string, remark string) (*refundAction, error) {
|
||||||
if amountCent <= 0 {
|
if amountCent <= 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -261,3 +261,78 @@ func TestBuildArbitrationHandoffContent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 复现线上单:预收 94.7(号主 66.7 + 平台 28),部分退 50 时,号主不应吃掉平台加价。
|
||||||
|
func TestPartialRefundReservesPlatformFee(t *testing.T) {
|
||||||
|
order := model.RentalOrder{
|
||||||
|
ID: 30,
|
||||||
|
OrderNo: "RO-ARBIT-PARTIAL-001",
|
||||||
|
RenterID: 9233,
|
||||||
|
OwnerID: 7463,
|
||||||
|
RentAmountCent: 9470,
|
||||||
|
OwnerRentAmountCent: 6670,
|
||||||
|
DepositAmountCent: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
settlement, err := buildArbitrationSettlement(order, ArbitrateRequest{
|
||||||
|
Result: "partial_refund",
|
||||||
|
AmountCent: 5000,
|
||||||
|
Remark: "没打完 号主登录不上",
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("buildArbitrationSettlement() error = %v", err)
|
||||||
|
}
|
||||||
|
if settlement.RenterRefundAmountCent != 5000 {
|
||||||
|
t.Fatalf("RenterRefundAmountCent = %d, want 5000", settlement.RenterRefundAmountCent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未退租金 44.7:平台按比例预留 round(28 * 44.7/94.7)=13.22 → 号主 31.48
|
||||||
|
wantOwner := partialRefundOwnerIncomeCent(9470, 6670, 0, 5000)
|
||||||
|
if settlement.OwnerIncomeAmountCent != wantOwner {
|
||||||
|
t.Fatalf("OwnerIncomeAmountCent = %d, want %d", settlement.OwnerIncomeAmountCent, wantOwner)
|
||||||
|
}
|
||||||
|
if settlement.OwnerIncomeAmountCent >= 4470 {
|
||||||
|
t.Fatalf("号主收入 %d 不应吞掉整笔剩余 4470(含平台费)", settlement.OwnerIncomeAmountCent)
|
||||||
|
}
|
||||||
|
platformKeep := 9470 - settlement.RenterRefundAmountCent - settlement.OwnerIncomeAmountCent
|
||||||
|
if platformKeep <= 0 {
|
||||||
|
t.Fatalf("平台留存 = %d, 部分退款后应保留平台加价份额", platformKeep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartialRefundFullRentRetainedGivesOwnerFullOwnerRent(t *testing.T) {
|
||||||
|
// 只退押金:号主拿满预收号主租金,平台留满预收平台费
|
||||||
|
order := model.RentalOrder{
|
||||||
|
RentAmountCent: 9470,
|
||||||
|
OwnerRentAmountCent: 6670,
|
||||||
|
DepositAmountCent: 20000,
|
||||||
|
}
|
||||||
|
settlement, err := buildArbitrationSettlement(order, ArbitrateRequest{
|
||||||
|
Result: "partial_refund",
|
||||||
|
AmountCent: 20000,
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("buildArbitrationSettlement() error = %v", err)
|
||||||
|
}
|
||||||
|
if settlement.OwnerIncomeAmountCent != 6670 {
|
||||||
|
t.Fatalf("OwnerIncomeAmountCent = %d, want 6670", settlement.OwnerIncomeAmountCent)
|
||||||
|
}
|
||||||
|
if settlement.RenterRefundAmountCent != 20000 {
|
||||||
|
t.Fatalf("RenterRefundAmountCent = %d, want 20000", settlement.RenterRefundAmountCent)
|
||||||
|
}
|
||||||
|
platformKeep := 9470 + 20000 - settlement.RenterRefundAmountCent - settlement.OwnerIncomeAmountCent
|
||||||
|
if platformKeep != 2800 {
|
||||||
|
t.Fatalf("platformKeep = %d, want 2800", platformKeep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartialRefundOwnerIncomeCentUnit(t *testing.T) {
|
||||||
|
// 无平台费时,未退租金全部给号主
|
||||||
|
if got := partialRefundOwnerIncomeCent(10000, 10000, 0, 3000); got != 7000 {
|
||||||
|
t.Fatalf("no platform fee: got %d, want 7000", got)
|
||||||
|
}
|
||||||
|
// 全额退:号主 0
|
||||||
|
if got := partialRefundOwnerIncomeCent(9470, 6670, 0, 9470); got != 0 {
|
||||||
|
t.Fatalf("full refund: got %d, want 0", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user