diff --git a/api/api.go b/api/api.go
index d4ea1737b52b2fd47c883670651d2961b323dfbb..3801c1314fd490de40fa973c148417282e289455 100644
--- a/api/api.go
+++ b/api/api.go
@@ -57,6 +57,7 @@ func Init(dbPath string, signKey string, mail *Mail, r *mux.Router) error {
 	r.HandleFunc("/order", a.auth(a.ListOrders)).Methods("GET")
 	r.HandleFunc("/order", a.authNum(a.AddOrder)).Methods("POST")
 	r.HandleFunc("/order/{id:[0-9]+}", a.authNum(a.GetOrder)).Methods("GET")
+	r.HandleFunc("/order/{id:[0-9]+}", a.authNumRole(a.DeleteOrder)).Methods("DELETE")
 	r.HandleFunc("/order/active", a.auth(a.ListActiveOrders)).Methods("GET")
 	r.HandleFunc("/order/{id:[0-9]+}/purchase", a.authNum(a.AddOrderPurchase)).Methods("POST")
 	return nil
diff --git a/api/db/order.go b/api/db/order.go
index 3a4cc7e3b00eb4b8b33d4df75170cf7781d4e485..e00e4b8cb70deb99fe8dd4d099399f1b0033dd79 100644
--- a/api/db/order.go
+++ b/api/db/order.go
@@ -48,8 +48,11 @@ func (d *DB) GetOrder(memberNum int, id int) (order Order, transaction Transacti
 		Preload("Transactions.OrderPurchase").
 		Preload("Transactions.Member").
 		First(&order, id).Error
-	if errors.Is(err, gorm.ErrRecordNotFound) {
-		err = ErrorNotFound
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			err = ErrorNotFound
+			return
+		}
 		return
 	}
 	err = d.db.Where("member = ? AND type = 'order' AND order_id = ?", memberNum, id).
@@ -62,6 +65,48 @@ func (d *DB) AddOrder(order *Order) error {
 	return d.db.Create(&order).Error
 }
 
+func (d *DB) DeleteOrder(memberNum int, id int) error {
+	var order Order
+	err := d.db.Preload(clause.Associations).
+		Preload("Transactions.OrderPurchase").
+		Preload("Transactions.Member").
+		First(&order, id).Error
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return ErrorNotFound
+		}
+		return err
+	}
+	if memberNum != 0 && order.MemberNum != memberNum {
+		return ErrorInvalidRequest
+	}
+
+	return d.db.Transaction(func(tx *gorm.DB) error {
+		if order.TransactionID != nil {
+			var transaction Transaction
+			err = tx.First(&transaction, order.TransactionID).Error
+			if err != nil {
+				return err
+			}
+			order.Transactions = append(order.Transactions, transaction)
+		}
+
+		for _, transaction := range order.Transactions {
+			err := updateMemberBalance(tx, transaction.MemberNum, -transaction.Total)
+			if err != nil {
+				return err
+			}
+
+			err = tx.Select("OrderPurchase").Delete(&transaction).Error
+			if err != nil {
+				return err
+			}
+		}
+
+		return tx.Delete(&order).Error
+	})
+}
+
 func (d *DB) AddOrderPurchase(memberNum int, orderID int, purchase []OrderPurchase) (transaction Transaction, err error) {
 	order, transaction, err := d.GetOrder(memberNum, orderID)
 	if err != nil {
diff --git a/api/db/transaction.go b/api/db/transaction.go
index f51fad706f25cfb9c7a14a51b8804ce5015244da..151c3df37c175178cfa9ec2381f70cf97b62f673 100644
--- a/api/db/transaction.go
+++ b/api/db/transaction.go
@@ -20,8 +20,8 @@ type Transaction struct {
 
 	Purchase      []Purchase      `json:"purchase,omitempty"`
 	Topup         *Topup          `json:"topup,omitempty"`
-	OrderPurchase []OrderPurchase `json:"order_purchase,omitempty" gorm:"foreignKey:TransactionID"`
-	Order         *Order          `json:"order,omitempty"`
+	OrderPurchase []OrderPurchase `json:"order_purchase,omitempty" gorm:"foreignKey:TransactionID;constraint:OnDelete:CASCADE"`
+	Order         *Order          `json:"order,omitempty" gorm:"constraint:OnDelete:CASCADE"`
 	OrderID       *uint           `json:"-"`
 	Refund        *Order          `json:"refund,omitempty" gorm:"foreignKey:TransactionID"`
 }
diff --git a/api/order.go b/api/order.go
index 36fea279a48b60b36eaa077f770646a04750b95d..1945e24693315b5d5c6a45529da42b933f221c73 100644
--- a/api/order.go
+++ b/api/order.go
@@ -86,6 +86,29 @@ func (a *api) GetOrder(num int, w http.ResponseWriter, req *http.Request) {
 	}
 }
 
+func (a *api) DeleteOrder(num int, role string, w http.ResponseWriter, req *http.Request) {
+	vars := mux.Vars(req)
+	id, _ := strconv.Atoi(vars["id"])
+	if role == "admin" {
+		num = 0
+	}
+	err := a.db.DeleteOrder(num, id)
+	if err != nil {
+		if errors.Is(err, db.ErrorNotFound) {
+			w.WriteHeader(http.StatusNotFound)
+			return
+		}
+		if errors.Is(err, db.ErrorInvalidRequest) {
+			w.WriteHeader(http.StatusUnauthorized)
+			return
+		}
+		log.Printf("Can't get order %s: %v", vars["id"], err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	w.WriteHeader(http.StatusOK)
+}
+
 func (a *api) AddOrder(num int, w http.ResponseWriter, req *http.Request) {
 	var order db.Order
 	err := json.NewDecoder(req.Body).Decode(&order)
diff --git a/api/order_test.go b/api/order_test.go
index f78827f2d5ae47f91dd99bb34edae4bbfcf8de27..f8ceb39652764d88ad13da67c568e64f860a61d9 100644
--- a/api/order_test.go
+++ b/api/order_test.go
@@ -66,6 +66,85 @@ func TestOrderActive(t *testing.T) {
 	}
 }
 
+func TestOrderDelete(t *testing.T) {
+	tapi := newTestAPI(t)
+	defer tapi.close()
+	tapi.addTestMember()
+
+	order := testOrder
+	order.Deadline = time.Now().Add(-24 * time.Hour)
+	resp := tapi.do("POST", "/order", order, nil)
+	if resp.StatusCode != http.StatusCreated {
+		t.Fatal("Can't create order:", resp.Status)
+	}
+
+	var orders []db.Order
+	resp = tapi.do("GET", "/order", nil, &orders)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get orders:", resp.Status)
+	}
+
+	purchase := []db.OrderPurchase{
+		{
+			ProductCode: testProduct.Code,
+			Amount:      3,
+		},
+	}
+	resp = tapi.doAdmin("POST", fmt.Sprintf("/order/%d/purchase", orders[0].ID), purchase, nil)
+	if resp.StatusCode != http.StatusCreated {
+		t.Fatal("Can't create order purchase:", resp.Status)
+	}
+
+	dbPath := path.Join(tapi.testPath, "test.db")
+	database, err := db.Init(dbPath)
+	if err != nil {
+		t.Fatal("Can't initialize the db:", err)
+	}
+	orders = database.DeactivateOrders()
+	if len(orders) != 1 {
+		t.Error("Deactivated wrong orders:", orders)
+	}
+
+	resp = tapi.do("DELETE", fmt.Sprintf("/order/%d", orders[0].ID), nil, nil)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't delete order:", resp.Status)
+	}
+
+	resp = tapi.do("GET", "/order", nil, &orders)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get orders:", resp.Status)
+	}
+	if len(orders) != 0 {
+		t.Error("The order didn't get removed", orders)
+	}
+
+	var transactions []db.Transaction
+	resp = tapi.doAdmin("GET", "/transaction", nil, &transactions)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get orders:", resp.Status)
+	}
+	if len(transactions) != 0 {
+		t.Error("The transactions didn't get removed", transactions)
+	}
+
+	var member db.Member
+	resp = tapi.doAdmin("GET", fmt.Sprintf("/member/%d", testMember.Num), nil, &member)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get orders:", resp.Status)
+	}
+	if member.Balance != testMember.Balance {
+		t.Error("Wrong balance on test member:", member.Balance)
+	}
+
+	resp = tapi.doAdmin("GET", fmt.Sprintf("/member/%d", testMemberAdmin.Num), nil, &member)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get orders:", resp.Status)
+	}
+	if member.Balance != testMemberAdmin.Balance {
+		t.Error("Wrong balance on admin member:", member.Balance)
+	}
+}
+
 func TestOrderPurchase(t *testing.T) {
 	tapi := newTestAPI(t)
 	defer tapi.close()