修复退款审核前账号提前释放

This commit is contained in:
yml2213
2026-08-20 16:50:32 +08:00
parent cc974f954f
commit 266abb4aaa
5 changed files with 130 additions and 12 deletions
@@ -391,10 +391,13 @@ func (r *Repository) buildRefundStatusDTO(order *model.RentalOrder) *RefundStatu
func (r *Repository) AdminApproveRefund(ctx context.Context, orderID uint64) error {
var refund *refundAction
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var order model.RentalOrder
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
assets, err := r.lockOrderAssets(tx, orderID)
if err != nil {
return err
}
order := assets.Order
listing := assets.Listing
account := assets.Account
if order.RefundStatus != refundStatusPendingReview {
return errors.New("订单不处于待审核退款状态")
}
@@ -407,14 +410,23 @@ func (r *Repository) AdminApproveRefund(ctx context.Context, orderID uint64) err
// 全部押金被暂扣且无其他可退金额,直接标记为已处理,等待后续手动退押金。
order.RefundStatus = "none"
order.RefundAmountCent = 0
return tx.Save(&order).Error
} else {
action, err := r.prepareRefund(order, refundAmountCent, refundBizCancel, "取消订单原路退款(客服审核通过)")
if err != nil {
return err
}
refund = action
}
action, err := r.prepareRefund(&order, refundAmountCent, refundBizCancel, "取消订单原路退款(客服审核通过)")
if err != nil {
if err := releaseAssetsForRentalIfIdle(tx, order, listing, account); err != nil {
return err
}
refund = action
return tx.Save(&order).Error
if err := tx.Save(order).Error; err != nil {
return err
}
if err := tx.Save(listing).Error; err != nil {
return err
}
return tx.Save(account).Error
})
if err != nil {
return err
+5 -2
View File
@@ -368,8 +368,11 @@ func (r *Repository) Cancel(ctx context.Context, userID uint64, orderID uint64)
return err
}
}
if err := releaseAssetsForRentalIfIdle(tx, &order, listing, account); err != nil {
return err
// 已付款订单取消后需等待客服审核退款,审核通过前继续占用账号。
if beforeStatus == orderStatusPendingPayment {
if err := releaseAssetsForRentalIfIdle(tx, &order, listing, account); err != nil {
return err
}
}
if err := closePendingOrderPayments(tx, order.ID, "order_cancel"); err != nil {
return err
@@ -1,6 +1,7 @@
package order
import (
"context"
"testing"
"time"
@@ -721,6 +722,105 @@ func TestCancelClosesPendingPaymentOrder(t *testing.T) {
}
}
func TestCancelPendingHandoffKeepsAssetsUntilRefundApproved(t *testing.T) {
db := setupOrderTestDB(t)
repo := NewRepository(db, Dependencies{
RefundStarter: RefundStarterFunc(func(context.Context, uint64, int64, string, string) (string, error) {
return "refunding", nil
}),
})
owner := model.User{Phone: "13800001002"}
renter := model.User{Phone: "13900001002"}
if err := db.Create(&owner).Error; err != nil {
t.Fatalf("create owner failed: %v", err)
}
if err := db.Create(&renter).Error; err != nil {
t.Fatalf("create renter failed: %v", err)
}
account := model.GameAccount{
OwnerID: owner.ID,
Status: accountStatusRented,
ServerRegion: "国服",
LoginPlatform: "steam",
Title: "待退款审核账号",
}
if err := db.Create(&account).Error; err != nil {
t.Fatalf("create account failed: %v", err)
}
listing := model.RentalListing{
ListingNo: "LST-REFUND-REVIEW-001",
OwnerID: owner.ID,
AccountID: account.ID,
Status: listingStatusRented,
ReviewStatus: listingReviewStatusApproved,
InTransaction: true,
PriceCent: 1000,
}
if err := db.Create(&listing).Error; err != nil {
t.Fatalf("create listing failed: %v", err)
}
order := model.RentalOrder{
OrderNo: "ORD-REFUND-REVIEW-001",
ListingID: listing.ID,
AccountID: account.ID,
OwnerID: owner.ID,
RenterID: renter.ID,
RentAmountCent: 1000,
Status: orderStatusPendingHandoff,
HandoffStatus: handoffStatusPendingOwner,
}
if err := db.Create(&order).Error; err != nil {
t.Fatalf("create order failed: %v", err)
}
if err := repo.Cancel(t.Context(), renter.ID, order.ID); err != nil {
t.Fatalf("Cancel() error = %v", err)
}
assertAssetsRented := func() {
var savedListing model.RentalListing
if err := db.First(&savedListing, listing.ID).Error; err != nil {
t.Fatalf("find listing failed: %v", err)
}
if savedListing.Status != listingStatusRented || !savedListing.InTransaction {
t.Fatalf("listing = %s/in_transaction:%t, want rented/true", savedListing.Status, savedListing.InTransaction)
}
var savedAccount model.GameAccount
if err := db.First(&savedAccount, account.ID).Error; err != nil {
t.Fatalf("find account failed: %v", err)
}
if savedAccount.Status != accountStatusRented {
t.Fatalf("account status = %q, want %q", savedAccount.Status, accountStatusRented)
}
}
assertAssetsRented()
var pendingReviewOrder model.RentalOrder
if err := db.First(&pendingReviewOrder, order.ID).Error; err != nil {
t.Fatalf("find cancelled order failed: %v", err)
}
if pendingReviewOrder.RefundStatus != refundStatusPendingReview {
t.Fatalf("refund status = %q, want %q", pendingReviewOrder.RefundStatus, refundStatusPendingReview)
}
if err := repo.AdminApproveRefund(t.Context(), order.ID); err != nil {
t.Fatalf("AdminApproveRefund() error = %v", err)
}
var releasedListing model.RentalListing
if err := db.First(&releasedListing, listing.ID).Error; err != nil {
t.Fatalf("find released listing failed: %v", err)
}
if releasedListing.Status != listingStatusPublished || releasedListing.InTransaction {
t.Fatalf("listing = %s/in_transaction:%t, want published/false", releasedListing.Status, releasedListing.InTransaction)
}
var releasedAccount model.GameAccount
if err := db.First(&releasedAccount, account.ID).Error; err != nil {
t.Fatalf("find released account failed: %v", err)
}
if releasedAccount.Status != accountStatusPublished {
t.Fatalf("account status = %q, want %q", releasedAccount.Status, accountStatusPublished)
}
}
func TestCancelDoesNotReleaseAssetsWhenSiblingOrderActive(t *testing.T) {
db := setupOrderTestDB(t)
repo := NewRepository(db)