修复取消订单误释放商品
This commit is contained in:
@@ -447,6 +447,13 @@ func (r *Repository) AdminRejectRefund(ctx context.Context, orderID uint64, acti
|
||||
if order.Status != orderStatusCancelled {
|
||||
return errors.New("仅已取消的订单可以恢复")
|
||||
}
|
||||
active, err := hasActiveSiblingOrder(tx, order)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if active {
|
||||
return ErrListingUnavailable
|
||||
}
|
||||
order.Status = orderStatusPendingHandoff
|
||||
if order.HandoffStatus == handoffStatusCancelled || order.HandoffStatus == handoffStatusRenterConfirmTimeout {
|
||||
order.HandoffStatus = handoffStatusPendingOwner
|
||||
|
||||
@@ -61,6 +61,50 @@ func releaseAssetsForRental(tx *gorm.DB, listing *model.RentalListing, account *
|
||||
return listingstatus.AppendTransition(tx, listing, from, listingstatus.SourceOrder, listingstatus.ActorSystem, 0, "订单取消恢复上架")
|
||||
}
|
||||
|
||||
func terminalOrderStatuses() []string {
|
||||
return []string{orderStatusCompleted, orderStatusCancelled, orderStatusClosed}
|
||||
}
|
||||
|
||||
func hasActiveSiblingOrder(tx *gorm.DB, order *model.RentalOrder) (bool, error) {
|
||||
if order == nil {
|
||||
return false, nil
|
||||
}
|
||||
var count int64
|
||||
err := tx.Model(&model.RentalOrder{}).
|
||||
Where("id <> ? AND (listing_id = ? OR account_id = ?) AND status NOT IN ?",
|
||||
order.ID,
|
||||
order.ListingID,
|
||||
order.AccountID,
|
||||
terminalOrderStatuses(),
|
||||
).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
func hasActiveOrderForAssets(tx *gorm.DB, listingID uint64, accountID uint64, excludeOrderID uint64) (bool, error) {
|
||||
var count int64
|
||||
err := tx.Model(&model.RentalOrder{}).
|
||||
Where("id <> ? AND (listing_id = ? OR account_id = ?) AND status NOT IN ?",
|
||||
excludeOrderID,
|
||||
listingID,
|
||||
accountID,
|
||||
terminalOrderStatuses(),
|
||||
).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
func releaseAssetsForRentalIfIdle(tx *gorm.DB, order *model.RentalOrder, listing *model.RentalListing, account *model.GameAccount) error {
|
||||
active, err := hasActiveSiblingOrder(tx, order)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if active {
|
||||
return nil
|
||||
}
|
||||
return releaseAssetsForRental(tx, listing, account)
|
||||
}
|
||||
|
||||
func archiveAssets(tx *gorm.DB, listing *model.RentalListing, account *model.GameAccount) error {
|
||||
from := listing.Status
|
||||
listing.Status = listingStatusOffline
|
||||
|
||||
@@ -33,6 +33,13 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, listing.AccountID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
active, err := hasActiveOrderForAssets(tx, listing.ID, account.ID, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if active {
|
||||
return ErrListingUnavailable
|
||||
}
|
||||
snapshot, err := makeAccountSnapshot(account, listing)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -219,6 +226,13 @@ func (r *Repository) ConfirmPaidFromChannelTx(tx *gorm.DB, orderID uint64) (uint
|
||||
if listing.Status != listingStatusPublished || listing.ReviewStatus != listingReviewStatusApproved || !listing.InTransaction {
|
||||
return 0, ErrListingUnavailable
|
||||
}
|
||||
active, err := hasActiveSiblingOrder(tx, order)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if active {
|
||||
return 0, ErrListingUnavailable
|
||||
}
|
||||
|
||||
// 租客已通过外部渠道付款,这里不写租客钱包流水。
|
||||
orderID = order.ID
|
||||
@@ -354,7 +368,7 @@ func (r *Repository) Cancel(ctx context.Context, userID uint64, orderID uint64)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := releaseAssetsForRental(tx, listing, account); err != nil {
|
||||
if err := releaseAssetsForRentalIfIdle(tx, &order, listing, account); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := closePendingOrderPayments(tx, order.ID, "order_cancel"); err != nil {
|
||||
|
||||
@@ -140,6 +140,63 @@ func TestRepositoryCreateOrderRejectsOwnListing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryCreateOrderRejectsActiveSiblingOrder(t *testing.T) {
|
||||
db := setupOrderTestDB(t)
|
||||
repo := NewRepository(db)
|
||||
|
||||
owner := model.User{Phone: "13800000011"}
|
||||
renter := model.User{Phone: "13800000012"}
|
||||
activeRenter := model.User{Phone: "13800000013"}
|
||||
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)
|
||||
}
|
||||
if err := db.Create(&activeRenter).Error; err != nil {
|
||||
t.Fatalf("create active renter failed: %v", err)
|
||||
}
|
||||
|
||||
account := model.GameAccount{
|
||||
OwnerID: owner.ID,
|
||||
ServerRegion: "国服",
|
||||
LoginPlatform: "steam",
|
||||
Title: "测试账号",
|
||||
Status: accountStatusPublished,
|
||||
}
|
||||
if err := db.Create(&account).Error; err != nil {
|
||||
t.Fatalf("create account failed: %v", err)
|
||||
}
|
||||
listing := model.RentalListing{
|
||||
AccountID: account.ID,
|
||||
OwnerID: owner.ID,
|
||||
PriceCent: 1000,
|
||||
Status: listingStatusPublished,
|
||||
ReviewStatus: listingReviewStatusApproved,
|
||||
InTransaction: false,
|
||||
}
|
||||
if err := db.Create(&listing).Error; err != nil {
|
||||
t.Fatalf("create listing failed: %v", err)
|
||||
}
|
||||
activeOrder := model.RentalOrder{
|
||||
OrderNo: "ORD-ACTIVE-SIBLING-001",
|
||||
ListingID: listing.ID,
|
||||
AccountID: account.ID,
|
||||
OwnerID: owner.ID,
|
||||
RenterID: activeRenter.ID,
|
||||
Status: orderStatusRenting,
|
||||
HandoffStatus: handoffStatusReceived,
|
||||
}
|
||||
if err := db.Create(&activeOrder).Error; err != nil {
|
||||
t.Fatalf("create active order failed: %v", err)
|
||||
}
|
||||
|
||||
_, err := repo.Create(t.Context(), renter.ID, CreateRequest{ListingID: listing.ID})
|
||||
if err != ErrListingUnavailable {
|
||||
t.Fatalf("Create() error = %v, want ErrListingUnavailable", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryCreateAppliesRenterGrowthDiscount(t *testing.T) {
|
||||
db := setupOrderTestDB(t)
|
||||
repo := NewRepository(db)
|
||||
@@ -664,6 +721,100 @@ func TestCancelClosesPendingPaymentOrder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelDoesNotReleaseAssetsWhenSiblingOrderActive(t *testing.T) {
|
||||
db := setupOrderTestDB(t)
|
||||
repo := NewRepository(db)
|
||||
|
||||
owner := model.User{Phone: "13800002001"}
|
||||
activeRenter := model.User{Phone: "13800002002"}
|
||||
cancelRenter := model.User{Phone: "13800002003"}
|
||||
if err := db.Create(&owner).Error; err != nil {
|
||||
t.Fatalf("create owner failed: %v", err)
|
||||
}
|
||||
if err := db.Create(&activeRenter).Error; err != nil {
|
||||
t.Fatalf("create active renter failed: %v", err)
|
||||
}
|
||||
if err := db.Create(&cancelRenter).Error; err != nil {
|
||||
t.Fatalf("create cancel renter failed: %v", err)
|
||||
}
|
||||
|
||||
account := model.GameAccount{
|
||||
OwnerID: owner.ID,
|
||||
ServerRegion: "国服",
|
||||
LoginPlatform: "steam",
|
||||
Title: "测试账号",
|
||||
Status: accountStatusRented,
|
||||
}
|
||||
if err := db.Create(&account).Error; err != nil {
|
||||
t.Fatalf("create account failed: %v", err)
|
||||
}
|
||||
listing := model.RentalListing{
|
||||
AccountID: account.ID,
|
||||
OwnerID: owner.ID,
|
||||
PriceCent: 1000,
|
||||
Status: listingStatusRented,
|
||||
ReviewStatus: listingReviewStatusApproved,
|
||||
InTransaction: true,
|
||||
}
|
||||
if err := db.Create(&listing).Error; err != nil {
|
||||
t.Fatalf("create listing failed: %v", err)
|
||||
}
|
||||
activeOrder := model.RentalOrder{
|
||||
OrderNo: "ORD-ACTIVE-SIBLING-002",
|
||||
ListingID: listing.ID,
|
||||
AccountID: account.ID,
|
||||
OwnerID: owner.ID,
|
||||
RenterID: activeRenter.ID,
|
||||
Status: orderStatusRenting,
|
||||
HandoffStatus: handoffStatusReceived,
|
||||
}
|
||||
if err := db.Create(&activeOrder).Error; err != nil {
|
||||
t.Fatalf("create active order failed: %v", err)
|
||||
}
|
||||
cancelOrder := model.RentalOrder{
|
||||
OrderNo: "ORD-CANCEL-SIBLING-001",
|
||||
ListingID: listing.ID,
|
||||
AccountID: account.ID,
|
||||
OwnerID: owner.ID,
|
||||
RenterID: cancelRenter.ID,
|
||||
RentAmountCent: 1000,
|
||||
Status: orderStatusPendingHandoff,
|
||||
HandoffStatus: handoffStatusPendingOwner,
|
||||
}
|
||||
if err := db.Create(&cancelOrder).Error; err != nil {
|
||||
t.Fatalf("create cancel order failed: %v", err)
|
||||
}
|
||||
|
||||
if err := repo.Cancel(t.Context(), cancelRenter.ID, cancelOrder.ID); err != nil {
|
||||
t.Fatalf("Cancel() error = %v", err)
|
||||
}
|
||||
|
||||
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 {
|
||||
t.Fatalf("listing status = %q, want %q", savedListing.Status, listingStatusRented)
|
||||
}
|
||||
if !savedListing.InTransaction {
|
||||
t.Fatal("listing InTransaction = false, want true")
|
||||
}
|
||||
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)
|
||||
}
|
||||
var savedCancelOrder model.RentalOrder
|
||||
if err := db.First(&savedCancelOrder, cancelOrder.ID).Error; err != nil {
|
||||
t.Fatalf("find cancel order failed: %v", err)
|
||||
}
|
||||
if savedCancelOrder.Status != orderStatusCancelled {
|
||||
t.Fatalf("cancel order status = %q, want %q", savedCancelOrder.Status, orderStatusCancelled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminResetCheckoutConfirmTimeout(t *testing.T) {
|
||||
db := setupOrderTestDB(t)
|
||||
repo := NewRepository(db)
|
||||
|
||||
Reference in New Issue
Block a user