fix: 禁止后台单独取消子订单,避免父订单支付后子订单被复活或余额未退

This commit is contained in:
dujiao-next committed 2026-10-11 12:39:20 +08:00
1 parent 495a0ce0f0
commit ff9d9a1f61
3 files changed
+30

No files matched your search

@@ -39,6 +39,7 @@ func TestOrderServiceTestsAreSplitByResponsibility(t *testing.T) {
"TestCalcParentStatus", "TestCalcParentStatusAllRefunded", "TestCalcParentStatusPartiallyRefunded",
"TestExpectedRefundStatus", "TestResolvedParentStatusPrefersOwnRefund",
"TestIsTransitionAllowedRefunded", "TestUpdateOrderStatusParentToPartiallyRefundedSyncsChildren",
"TestUpdateOrderStatusRejectsCancelingChildOrder",
"TestUpdateOrderStatusRejectsCancelingPaidParent",
"TestUpdateOrderStatusRejectsManualPaidTransition",
"TestCanCompleteParentOrder", "TestCanCompleteParentOrderRejectInvalidStatus",
@@ -281,6 +281,11 @@ func (s *OrderService) UpdateOrderStatus(orderID uint, targetStatus string) (*or
if !IsTransitionAllowed(order.Status, target) {
return nil, ErrOrderStatusInvalid
}
// 子订单不能单独取消:父订单的钱包扣款与优惠券不会随之回滚,
// 且父订单后续支付会把已取消的子订单重新置为已支付。取消须针对父订单。
if target == constants.OrderStatusCanceled && order.ParentID != nil {
return nil, ErrOrderStatusInvalid
}
now := time.Now()
updates := map[string]interface{}{
@@ -370,3 +370,27 @@ func TestUpdateOrderStatusRejectsCancelingPaidParent(t *testing.T) {
t.Fatalf("paid parent status mutated to %s", stored.Status)
}
}
func TestUpdateOrderStatusRejectsCancelingChildOrder(t *testing.T) {
dsn := fmt.Sprintf("file:order_service_reject_cancel_child_%d?mode=memory&cache=shared", time.Now().UnixNano())
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
if err != nil {
t.Fatalf("open sqlite failed: %v", err)
}
if err := db.AutoMigrate(&orderdomain.Order{}, &orderdomain.OrderItem{}, &fulfillmentdomain.Fulfillment{}); err != nil {
t.Fatalf("auto migrate failed: %v", err)
}
now := time.Now()
parent := &orderdomain.Order{OrderNo: "CHILD-CANCEL-PARENT", Status: constants.OrderStatusPendingPayment, Currency: "CNY", CreatedAt: now, UpdatedAt: now}
if err := db.Create(parent).Error; err != nil {
t.Fatalf("create parent failed: %v", err)
}
child := &orderdomain.Order{OrderNo: "CHILD-CANCEL-PARENT-A", ParentID: &parent.ID, Status: constants.OrderStatusPendingPayment, Currency: "CNY", CreatedAt: now, UpdatedAt: now}
if err := db.Create(child).Error; err != nil {
t.Fatalf("create child failed: %v", err)
}
svc := NewOrderService(OrderServiceOptions{OrderStore: ordergormstore.New(db, "test-guest-credential-secret-with-32-bytes")})
if _, err := svc.UpdateOrderStatus(child.ID, constants.OrderStatusCanceled); err != ErrOrderStatusInvalid {
t.Fatalf("cancel child error = %v, want %v", err, ErrOrderStatusInvalid)
}
}