diff --git a/api/api.go b/api/api.go
index 20f25c954e033bbb89f52315dffb890d31c5584e..6d87d7ec94c9ae7e41906bf991551b739ac6fd97 100644
--- a/api/api.go
+++ b/api/api.go
@@ -19,7 +19,8 @@ func initDB(dbPath string) (*gorm.DB, error) {
 		return nil, err
 	}
 
-	db.AutoMigrate(&Member{}, &Product{}, &Purchase{}, &Topup{}, &Transaction{})
+	db.AutoMigrate(&Member{}, &Product{}, &Purchase{}, &Topup{}, &Transaction{},
+		&OrderPurchase{}, &OrderProduct{}, &Order{})
 	return db, err
 }
 
@@ -30,6 +31,7 @@ func Init(dbPath string, signKey string, r *mux.Router) error {
 	}
 
 	a := api{db, []byte(signKey)}
+	go a.refundOrders()
 
 	token, err := a.newToken(0, "admin", false)
 	log.Print(token)
@@ -57,5 +59,12 @@ func Init(dbPath string, signKey string, r *mux.Router) error {
 
 	r.HandleFunc("/purchase", a.authNum(a.AddPurchase)).Methods("POST")
 	r.HandleFunc("/topup", a.authAdminNum(a.AddTopup)).Methods("POST")
+
+	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.auth(a.GetOrder)).Methods("GET")
+	r.HandleFunc("/order/active", a.auth(a.ListActiveOrders)).Methods("GET")
+	r.HandleFunc("/order/purchase", a.authNum(a.AddOrderPurchase)).Methods("POST")
+	// TODO: r.HandleFunc("/order/purchase", a.authNum(a.UpdateOrderPurchase)).Methods("PUT")
 	return nil
 }
diff --git a/api/order.go b/api/order.go
new file mode 100644
index 0000000000000000000000000000000000000000..a9c25b65d93ffa1007ebce5fb0f4c7e0f3c619f5
--- /dev/null
+++ b/api/order.go
@@ -0,0 +1,256 @@
+package api
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+	"time"
+
+	"github.com/gorilla/mux"
+	"gorm.io/gorm"
+	"gorm.io/gorm/clause"
+)
+
+type Order struct {
+	gorm.Model
+	Name        string    `json:"name"`
+	Description string    `json:"description"`
+	MemberNum   int       `json:"-" gorm:"column:member"`
+	Member      *Member   `json:"member,omitempty" gorm:"foreignKey:MemberNum;references:Num"`
+	Deadline    time.Time `json:"deadline"`
+	Active      bool      `json:"active" gorm:"index"`
+
+	Products      []OrderProduct `json:"products"`
+	TransactionID *uint          `json:"transaction" gorm:"column:transaction"`
+}
+
+type OrderProduct struct {
+	gorm.Model
+	Name    string `json:"name"`
+	Price   int    `json:"price"`
+	OrderID uint   `json:"-"`
+
+	Purchases []OrderPurchase `json:"purchases"`
+}
+
+type OrderPurchase struct {
+	gorm.Model
+	OrderProductID uint  `json:"product_id"`
+	OrderProduct   Order `json:"product"`
+	TransactionID  uint  `json:"-"`
+	Ammount        int   `json:"ammount"`
+}
+
+func (a *api) refundOrders() {
+	const refundSleeptime = 10 * time.Minute
+	for {
+		time.Sleep(refundSleeptime)
+		a.deactivateOrders()
+	}
+}
+
+func (a *api) deactivateOrders() {
+	var orders []Order
+	now := time.Now()
+	t := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
+	err := a.db.Where("active = ? AND deadline < ?", true, t).
+		Preload("Products.Purchases").
+		Find(&orders).Error
+	if err != nil {
+		log.Println("Error refunding orders:", err)
+		return
+	}
+
+	for _, order := range orders {
+		log.Println("Refund order", order.Name)
+
+		total := 0
+		for _, product := range order.Products {
+			for _, purchase := range product.Purchases {
+				total += product.Price * purchase.Ammount
+			}
+		}
+
+		transaction := Transaction{
+			MemberNum: order.MemberNum,
+			Date:      time.Now(),
+			Type:      "refund",
+			Total:     total,
+		}
+		err = a.db.Transaction(func(tx *gorm.DB) error {
+			_, err := createTransaction(tx, &transaction)
+			if err != nil {
+				return err
+			}
+			return tx.Model(&Order{}).
+				Where("id = ?", order.ID).
+				Updates(map[string]interface{}{
+					"active":      false,
+					"transaction": transaction.ID}).
+				Error
+		})
+		if err != nil {
+			log.Printf("Can't create refund: %v\n%v", err, order)
+			continue
+		}
+	}
+}
+
+func (a *api) ListOrders(w http.ResponseWriter, req *http.Request) {
+	a.listOrders(false, w, req)
+}
+
+func (a *api) ListActiveOrders(w http.ResponseWriter, req *http.Request) {
+	a.listOrders(true, w, req)
+}
+
+func (a *api) listOrders(active bool, w http.ResponseWriter, req *http.Request) {
+	var orders []Order
+	query := a.db.Preload("Products.Purchases").
+		Preload(clause.Associations)
+	if active {
+		query = query.Where("active = ?", true)
+	}
+	err := query.Order("deadline desc").
+		Find(&orders).Error
+	if err != nil {
+		log.Printf("Can't list orders: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusOK)
+	err = json.NewEncoder(w).Encode(orders)
+	if err != nil {
+		log.Printf("Can't encode orders: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+	}
+}
+
+func (a *api) GetOrder(w http.ResponseWriter, req *http.Request) {
+	vars := mux.Vars(req)
+	var order Order
+	err := a.db.Preload("Products.Purchases").
+		Preload(clause.Associations).
+		First(&order, vars["id"]).Error
+	if err != nil {
+		if err.Error() == "record not found" {
+			w.WriteHeader(http.StatusNotFound)
+			return
+		}
+		log.Printf("Can't get order %s: %v", vars["id"], err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusOK)
+	err = json.NewEncoder(w).Encode(order)
+	if err != nil {
+		log.Printf("Can't encode order: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+}
+
+func (a *api) AddOrder(num int, w http.ResponseWriter, req *http.Request) {
+	var order Order
+	err := json.NewDecoder(req.Body).Decode(&order)
+	if err != nil {
+		log.Printf("Can't parse order: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	order.MemberNum = num
+	order.Active = true
+	err = a.db.Create(&order).Error
+	if err != nil {
+		log.Printf("Can't create order: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusCreated)
+	err = json.NewEncoder(w).Encode(order)
+	if err != nil {
+		log.Printf("Can't encode order: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+}
+
+func (a *api) AddOrderPurchase(num int, w http.ResponseWriter, req *http.Request) {
+	var purchase []OrderPurchase
+	err := json.NewDecoder(req.Body).Decode(&purchase)
+	if err != nil {
+		log.Printf("Can't parse order: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	if len(purchase) == 0 {
+		log.Printf("Empty order purchase")
+		w.WriteHeader(http.StatusBadRequest)
+		return
+	}
+
+	var order Order
+	err = a.db.Where("id = (?)",
+		a.db.Table("order_products").
+			Where("id = ?", purchase[0].OrderProductID).
+			Select("order_id")).
+		Preload("Products").
+		First(&order).Error
+	if err != nil {
+		log.Printf("Can't get order from product %d: %v", purchase[0].OrderProductID, err)
+		w.WriteHeader(http.StatusNotAcceptable)
+		return
+	}
+	if !order.Active {
+		log.Printf("Order is not active %d: %v", order.ID, purchase)
+		w.WriteHeader(http.StatusBadRequest)
+		return
+	}
+
+	total := 0
+	for _, p := range purchase {
+		found := false
+		for _, product := range order.Products {
+			if product.ID == p.OrderProductID {
+				total += product.Price * p.Ammount
+				found = true
+				break
+			}
+		}
+
+		if !found {
+			log.Printf("Order purchase product %d not in order: %v", p.OrderProductID, purchase)
+			w.WriteHeader(http.StatusNotAcceptable)
+			return
+		}
+	}
+
+	transaction := Transaction{
+		MemberNum: num,
+		Total:     -total,
+		Type:      "order",
+		Date:      time.Now(),
+		Order:     purchase,
+	}
+	httpStatus, err := createTransaction(a.db, &transaction)
+	if err != nil {
+		log.Println(err)
+		w.WriteHeader(httpStatus)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusCreated)
+	err = json.NewEncoder(w).Encode(transaction)
+	if err != nil {
+		log.Printf("Can't encode order transaction: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+}
diff --git a/api/order_test.go b/api/order_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..29887a55b2ad95446d8127638c13995a406652bc
--- /dev/null
+++ b/api/order_test.go
@@ -0,0 +1,233 @@
+package api
+
+import (
+	"net/http"
+	"path"
+	"testing"
+	"time"
+)
+
+var testOrder = Order{
+	Name:        "huevos",
+	Description: "huevos frescos",
+	Deadline:    time.Now().Add(24 * time.Hour),
+	Products: []OrderProduct{
+		{
+			Name:  "huevos",
+			Price: 234,
+		},
+	},
+}
+
+func TestOrderAddList(t *testing.T) {
+	tapi := newTestAPI(t)
+	defer tapi.close()
+	tapi.addTestMember()
+	tapi.addTestOrder()
+
+	var orders []Order
+	resp := tapi.do("GET", "/order", nil, &orders)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get orders:", resp.Status)
+	}
+
+	if len(orders) != 1 {
+		t.Fatal("Wrong number of orders", len(orders), orders)
+	}
+	if orders[0].Name != testOrder.Name {
+		t.Error("Wrong name:", orders[0].Name)
+	}
+	if len(orders[0].Products) != 1 {
+		t.Fatal("Wrong number of products", len(orders[0].Products), orders[0].Products)
+	}
+	if orders[0].Products[0].Price != testOrder.Products[0].Price {
+		t.Error("Wrong product price:", orders[0].Products[0].Price)
+	}
+}
+
+func TestOrderActive(t *testing.T) {
+	tapi := newTestAPI(t)
+	defer tapi.close()
+	tapi.addTestMember()
+	tapi.addTestOrder()
+
+	var orders []Order
+	resp := tapi.do("GET", "/order/active", nil, &orders)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get orders:", resp.Status)
+	}
+
+	if len(orders) != 1 {
+		t.Fatal("Wrong number of orders", len(orders), orders)
+	}
+	if orders[0].Name != testOrder.Name {
+		t.Error("Wrong name:", orders[0].Name)
+	}
+}
+
+func TestOrderPurchase(t *testing.T) {
+	tapi := newTestAPI(t)
+	defer tapi.close()
+	tapi.addTestMember()
+	tapi.addTestOrder()
+
+	var orders []Order
+	resp := tapi.do("GET", "/order/active", nil, &orders)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get orders:", resp.Status)
+	}
+
+	purchase := []OrderPurchase{
+		{
+			OrderProductID: orders[0].Products[0].ID,
+			Ammount:        3,
+		},
+	}
+	resp = tapi.do("POST", "/order/purchase", purchase, nil)
+	if resp.StatusCode != http.StatusCreated {
+		t.Fatal("Can't create order:", resp.Status)
+	}
+
+	var transactions []Transaction
+	resp = tapi.do("GET", "/transaction/mine", nil, &transactions)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get transactions:", resp.Status)
+	}
+	if len(transactions) != 1 {
+		t.Fatal("Wrong number of transactions", len(orders), orders)
+	}
+	total := 3 * testOrder.Products[0].Price
+	if transactions[0].Total != -total {
+		t.Fatal("Wrong total", transactions[0].Total)
+	}
+
+	var member Member
+	resp = tapi.do("GET", "/member/me", nil, &member)
+	if resp.StatusCode != http.StatusOK {
+		t.Error("Can't find the member:", resp.Status)
+	}
+	if member.Balance != testMember.Balance-total {
+		t.Error("Wrong product balance:", member.Balance)
+	}
+}
+
+func TestOrderNoDeactivation(t *testing.T) {
+	tapi := newTestAPI(t)
+	defer tapi.close()
+	tapi.addTestMember()
+
+	order := testOrder
+	now := time.Now()
+	order.Deadline = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
+	resp := tapi.do("POST", "/order", order, nil)
+	if resp.StatusCode != http.StatusCreated {
+		t.Fatal("Can't create order:", resp.Status)
+	}
+
+	var orders []Order
+	resp = tapi.do("GET", "/order/active", nil, &orders)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get transactions:", resp.Status)
+	}
+	if len(orders) != 1 {
+		t.Fatal("Didn't find my new order")
+	}
+
+	dbPath := path.Join(tapi.testPath, "test.db")
+	db, err := initDB(dbPath)
+	if err != nil {
+		t.Fatal("Can't initialize the db:", err)
+	}
+	a := api{db: db}
+	a.deactivateOrders()
+
+	resp = tapi.do("GET", "/order/active", nil, &orders)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get transactions:", resp.Status)
+	}
+	if len(orders) != 1 {
+		t.Fatal("Didn't find my new order after deactivation")
+	}
+}
+
+func TestOrderDeactivation(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 []Order
+	resp = tapi.do("GET", "/order/active", nil, &orders)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get transactions:", resp.Status)
+	}
+	if len(orders) != 1 {
+		t.Fatal("Didn't find my new order")
+	}
+
+	purchase := []OrderPurchase{
+		{
+			OrderProductID: orders[0].Products[0].ID,
+			Ammount:        3,
+		},
+	}
+	resp = tapi.doAdmin("POST", "/order/purchase", purchase, nil)
+	if resp.StatusCode != http.StatusCreated {
+		t.Fatal("Can't create order:", resp.Status)
+	}
+
+	dbPath := path.Join(tapi.testPath, "test.db")
+	db, err := initDB(dbPath)
+	if err != nil {
+		t.Fatal("Can't initialize the db:", err)
+	}
+	a := api{db: db}
+	a.deactivateOrders()
+
+	resp = tapi.do("GET", "/order/active", nil, &orders)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get transactions:", resp.Status)
+	}
+	if len(orders) != 0 {
+		t.Fatal("I found some orders")
+	}
+
+	total := 3 * testOrder.Products[0].Price
+
+	var transactions []Transaction
+	resp = tapi.do("GET", "/transaction/mine", nil, &transactions)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't get transactions:", resp.Status)
+	}
+	if len(transactions) != 1 {
+		t.Fatal("Wrong number of transactions", len(orders), orders)
+	}
+	if transactions[0].Type != "refund" {
+		t.Fatal("Should be a refund", transactions[0].Type)
+	}
+	if transactions[0].Total != total {
+		t.Fatal("Wrong total:", transactions[0].Total)
+	}
+
+	var member Member
+	resp = tapi.do("GET", "/member/me", nil, &member)
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("Can't member:", resp.Status)
+	}
+	if member.Balance != testMember.Balance+total {
+		t.Fatal("Wrong member balance:", member.Balance, testMember.Balance, total)
+	}
+}
+
+func (tapi *testAPI) addTestOrder() {
+	resp := tapi.do("POST", "/order", testOrder, nil)
+	if resp.StatusCode != http.StatusCreated {
+		tapi.t.Fatal("Can't create order:", resp.Status)
+	}
+}
diff --git a/api/purchase.go b/api/purchase.go
index 0eec3edda652410495598a7cd79a6d09d75a688a..aa4010c8376493fc8383e8147408884681cc0092 100644
--- a/api/purchase.go
+++ b/api/purchase.go
@@ -12,7 +12,7 @@ import (
 
 type Purchase struct {
 	gorm.Model    `json:"-"`
-	TransactionID int     `json:"-" gorm:"column:transaction"`
+	TransactionID uint    `json:"-" gorm:"column:transaction"`
 	ProductCode   int     `json:"code" gorm:"column:product"`
 	Product       Product `json:"product" gorm:"foreignKey:ProductCode;references:Code"`
 	Price         int     `json:"price"`
diff --git a/api/topup.go b/api/topup.go
index 4ee42a62e65a20bb533b570e897f32c32fcbe364..e4d9d44bb5c6c97d2635bf25879fea74dcee7719 100644
--- a/api/topup.go
+++ b/api/topup.go
@@ -11,7 +11,7 @@ import (
 
 type Topup struct {
 	gorm.Model    `json:"-"`
-	TransactionID int    `json:"-" gorm:"column:transaction"`
+	TransactionID uint   `json:"-" gorm:"column:transaction"`
 	MemberNum     int    `json:"member" gorm:"column:member"`
 	Member        Member `json:"-" gorm:"foreignKey:MemberNum;references:Num"`
 	Comment       string `json:"comment"`
@@ -33,7 +33,7 @@ func (a *api) AddTopup(adminNum int, w http.ResponseWriter, req *http.Request) {
 	transaction := Transaction{
 		MemberNum: topup.Member,
 		Date:      time.Now(),
-		Topup: Topup{
+		Topup: &Topup{
 			MemberNum: adminNum,
 			Comment:   topup.Comment,
 		},
diff --git a/api/transaction.go b/api/transaction.go
index 0ec5c635e7707fd758ace7342e53f83b8a2eabd1..2c58a8594335e767daf4ae3bddb072799b64a3ff 100644
--- a/api/transaction.go
+++ b/api/transaction.go
@@ -16,13 +16,15 @@ import (
 type Transaction struct {
 	gorm.Model
 	MemberNum int       `json:"-" gorm:"column:member"`
-	Member    Member    `json:"member" gorm:"foreignKey:MemberNum;references:Num"`
+	Member    *Member   `json:"member,omitempty" gorm:"foreignKey:MemberNum;references:Num"`
 	Date      time.Time `json:"date"`
 	Total     int       `json:"total"`
 	Type      string    `json:"type"`
 
-	Purchase []Purchase `json:"purchase"`
-	Topup    Topup      `json:"topup"`
+	Purchase []Purchase      `json:"purchase"`
+	Topup    *Topup          `json:"topup"`
+	Order    []OrderPurchase `json:"order"`
+	Refund   *Order          `json:"refund"`
 }
 
 func (a *api) ListTransactions(w http.ResponseWriter, req *http.Request) {