添加订单押金暂扣与归还

This commit is contained in:
yml2213
2026-07-05 19:35:57 +08:00
parent 9ef0843eb0
commit 090a90a268
17 changed files with 587 additions and 36 deletions
+48
View File
@@ -29,6 +29,12 @@ type RentalOrder struct {
RefundStatus string `gorm:"size:32;not null;default:'none';index" json:"refund_status"`
RefundAmountCent int64 `gorm:"not null;default:0" json:"refund_amount_cent"`
RefundedAt *time.Time `json:"refunded_at"`
DepositHoldStatus string `gorm:"size:16;not null;default:'none';index" json:"deposit_hold_status"`
DepositHoldAmountCent int64 `gorm:"not null;default:0" json:"deposit_hold_amount_cent"`
DepositHoldReason string `gorm:"size:255;not null;default:''" json:"deposit_hold_reason"`
DepositHeldBy *uint64 `json:"deposit_held_by"`
DepositHeldAt *time.Time `json:"deposit_held_at"`
DepositHoldReleasedAt *time.Time `json:"deposit_hold_released_at"`
OwnerSettledAt *time.Time `json:"owner_settled_at"`
SettledAt *time.Time `json:"settled_at"`
CreatedAt time.Time `json:"created_at"`
@@ -38,3 +44,45 @@ type RentalOrder struct {
func (RentalOrder) TableName() string {
return "rental_orders"
}
// 押金暂扣状态:客服可对进行中订单的押金退款进行暂扣,订单照常结算,
// 但本应原路退给租客的押金部分挂起不退,后续由客服手动归还。
const (
DepositHoldStatusNone = "none" // 未暂扣
DepositHoldStatusHeld = "held" // 已暂扣(押金退款被拦截/挂起)
DepositHoldStatusReleased = "released" // 已归还
)
// IsDepositHeld 判断订单当前是否处于押金暂扣状态。
func (o *RentalOrder) IsDepositHeld() bool {
return o.DepositHoldStatus == DepositHoldStatusHeld
}
// ApplyDepositHold 在任一结算退款路径中拦截押金退款。
// totalRefundCent 是本次本应原路退还给租客的总金额(含租金退款和押金退款),
// depositPortionCent 是其中属于押金退款的部分。
// 若订单已暂扣,则把押金部分(不超过本次退款总额)从退款中扣除,
// 并把尚未记录过的部分累加到暂扣金额(累计不超过实付押金),返回扣除后实际发起原路退款的金额;
// 未暂扣时原样返回 totalRefundCent。
// 注意:本函数只做“少退、挂起”,绝不多退,因此在任何路径下都不会造成租客损失或平台多付。
func (o *RentalOrder) ApplyDepositHold(totalRefundCent, depositPortionCent int64) int64 {
if !o.IsDepositHeld() {
return totalRefundCent
}
withheld := depositPortionCent
if withheld > totalRefundCent {
withheld = totalRefundCent
}
if withheld <= 0 {
return totalRefundCent
}
remainingDepositHoldCent := o.DepositAmountCent - o.DepositHoldAmountCent
if remainingDepositHoldCent > 0 {
newHold := withheld
if newHold > remainingDepositHoldCent {
newHold = remainingDepositHoldCent
}
o.DepositHoldAmountCent += newHold
}
return totalRefundCent - withheld
}
+49
View File
@@ -0,0 +1,49 @@
package model
import "testing"
func TestRentalOrderApplyDepositHold(t *testing.T) {
order := RentalOrder{
DepositAmountCent: 1000,
DepositHoldStatus: DepositHoldStatusHeld,
}
actualRefundCent := order.ApplyDepositHold(1500, 1000)
if actualRefundCent != 500 {
t.Fatalf("actual refund = %d, want 500", actualRefundCent)
}
if order.DepositHoldAmountCent != 1000 {
t.Fatalf("hold amount = %d, want 1000", order.DepositHoldAmountCent)
}
}
func TestRentalOrderApplyDepositHoldKeepsWithholdingAfterAmountRecorded(t *testing.T) {
order := RentalOrder{
DepositAmountCent: 1000,
DepositHoldStatus: DepositHoldStatusHeld,
DepositHoldAmountCent: 1000,
}
actualRefundCent := order.ApplyDepositHold(1500, 1000)
if actualRefundCent != 500 {
t.Fatalf("actual refund = %d, want 500", actualRefundCent)
}
if order.DepositHoldAmountCent != 1000 {
t.Fatalf("hold amount = %d, want 1000", order.DepositHoldAmountCent)
}
}
func TestRentalOrderApplyDepositHoldSkipsWhenNotHeld(t *testing.T) {
order := RentalOrder{
DepositAmountCent: 1000,
DepositHoldStatus: DepositHoldStatusNone,
}
actualRefundCent := order.ApplyDepositHold(1500, 1000)
if actualRefundCent != 1500 {
t.Fatalf("actual refund = %d, want 1500", actualRefundCent)
}
if order.DepositHoldAmountCent != 0 {
t.Fatalf("hold amount = %d, want 0", order.DepositHoldAmountCent)
}
}