diff --git a/cmd/api/server.go b/cmd/api/server.go index 055774f..69f7ddd 100644 --- a/cmd/api/server.go +++ b/cmd/api/server.go @@ -266,6 +266,7 @@ func initializeRoutes(engine *gin.Engine) { // Orders engine.GET("/v2/orders", middleware.RequireAuth, handlers.CreateHandler(handlers.GetUserOrders)) + engine.GET("/v2/orders/:order_id/:transaction_id", middleware.RequireAuth, handlers.CreateHandler(handlers.GetUserOrder)) engine.GET("/v2/orders/donations/trial", middleware.RequireAuth, handlers.CreateHandler(handlers.GetUserFreeTrialDonatorOrder)) engine.GET("/v2/orders/donations/prices", handlers.CreateHandler(handlers.GetDonatorPrices)) engine.POST("/v2/orders/checkout", middleware.RequireAuth, handlers.CreateHandler(handlers.CreateOrderCheckoutSession)) diff --git a/db/orders.go b/db/orders.go index e479c9d..db58b04 100644 --- a/db/orders.go +++ b/db/orders.go @@ -271,6 +271,25 @@ func GetUserOrders(userId int) ([]*Order, error) { return orders, nil } +// GetUserOrdersByIds retrieves a user's orders by their order and transaction ids. +// Multiple order rows can share these ids when a transaction contains multiple items. +func GetUserOrdersByIds(userId int, orderId int, transactionId string) ([]*Order, error) { + var orders = make([]*Order, 0) + + result := SQL. + Preload("Receiver"). + Preload("Item"). + Preload("Subscription"). + Where("orders.user_id = ? AND orders.order_id = ? AND orders.transaction_id = ?", userId, orderId, transactionId). + Find(&orders) + + if result.Error != nil { + return nil, result.Error + } + + return orders, nil +} + // GetSteamOrdersByIds Retrieves orders by their steam order id & transaction id. // Multiple orders in the database can have them if a user has multiple items in their cart. func GetSteamOrdersByIds(steamOrderId string, transactionId string) ([]*Order, error) { diff --git a/handlers/orders.go b/handlers/orders.go index c87afdf..066b518 100644 --- a/handlers/orders.go +++ b/handlers/orders.go @@ -12,6 +12,7 @@ import ( "net" "net/http" "slices" + "strconv" ) type DonatorPricing struct { @@ -26,6 +27,12 @@ type DonatorPaymentMethod struct { Months12 float32 `json:"months_12"` } +type userOrderResponse struct { + *db.Order + OrderId int `json:"order_id"` + TransactionId string `json:"transaction_id"` +} + type checkoutPaymentMethod int const ( @@ -85,6 +92,52 @@ func GetUserOrders(c *gin.Context) *APIError { return nil } +// GetUserOrder retrieves the authenticated user's orders by order and transaction ids. +// Endpoint: GET /v2/orders/:order_id/:transaction_id +func GetUserOrder(c *gin.Context) *APIError { + user := getAuthedUser(c) + + if user == nil { + return nil + } + + orderId, err := strconv.Atoi(c.Param("order_id")) + + if err != nil { + return APIErrorBadRequest("You must provide a valid order_id.") + } + + transactionId := c.Param("transaction_id") + + if transactionId == "" { + return APIErrorBadRequest("You must provide a valid transaction_id.") + } + + orders, err := db.GetUserOrdersByIds(user.Id, orderId, transactionId) + + if err != nil { + return APIErrorServerError("Error retrieving order from db", err) + } + + if len(orders) == 0 { + return APIErrorNotFound("Order") + } + + orderResponses := make([]*userOrderResponse, 0, len(orders)) + for _, order := range orders { + orderResponses = append(orderResponses, &userOrderResponse{ + Order: order, + OrderId: order.OrderId, + TransactionId: order.TransactionId, + }) + } + + c.JSON(http.StatusOK, gin.H{ + "orders": orderResponses, + }) + return nil +} + // GetUserFreeTrialDonatorOrder Retrieves a user's free trial donator order if they've had one before func GetUserFreeTrialDonatorOrder(c *gin.Context) *APIError { user := getAuthedUser(c) diff --git a/handlers/orders_test.go b/handlers/orders_test.go index 91afe63..018ca8e 100644 --- a/handlers/orders_test.go +++ b/handlers/orders_test.go @@ -1,6 +1,44 @@ package handlers -import "testing" +import ( + "encoding/json" + "github.com/Quaver/api2/db" + "testing" +) + +func TestUserOrderResponseIncludesPaymentIdentifiers(t *testing.T) { + order := &db.Order{ + OrderId: 123, + TransactionId: "transaction-456", + IPAddress: "192.168.1.1", + } + + data, err := json.Marshal(userOrderResponse{ + Order: order, + OrderId: order.OrderId, + TransactionId: order.TransactionId, + }) + if err != nil { + t.Fatal(err) + } + + var response map[string]any + if err := json.Unmarshal(data, &response); err != nil { + t.Fatal(err) + } + + if response["order_id"] != float64(123) { + t.Fatalf("expected order_id to be included, got %#v", response["order_id"]) + } + + if response["transaction_id"] != "transaction-456" { + t.Fatalf("expected transaction_id to be included, got %#v", response["transaction_id"]) + } + + if _, exists := response["ip_address"]; exists { + t.Fatal("expected ip_address to remain private") + } +} func TestGetOrderIpIpv4(t *testing.T) { ip := getOrderIp("192.168.1.1")