feat(integrations): align shopify parity

This commit is contained in:
2026-06-06 17:23:15 +08:00
parent a6f97c8bbc
commit a9d69de440
8 changed files with 319 additions and 39 deletions
@@ -1,7 +1,9 @@
package v1
import (
"errors"
"net/http"
"strings"
"github.com/gin-gonic/gin"
@@ -30,10 +32,10 @@ func (h *ShopifyIntegrationHandler) Delete(c *gin.Context) {
}
if svcErr := h.svc.Delete(c.Request.Context(), accountID); svcErr != nil {
handleServiceError(c, svcErr)
handleShopifyServiceError(c, svcErr)
return
}
response.OK(c, gin.H{"message": "Shopify integration deleted"})
c.Status(http.StatusOK)
}
// Auth returns the Shopify OAuth authorize URL.
@@ -46,17 +48,21 @@ func (h *ShopifyIntegrationHandler) Auth(c *gin.Context) {
}
var req service.CreateShopifyAuthRequest
if err := c.ShouldBindJSON(&req); err != nil {
if err := c.ShouldBind(&req); err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error())
return
}
if strings.TrimSpace(req.ShopDomain) == "" {
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "Shop domain is required"})
return
}
redirect, svcErr := h.svc.BuildAuthRedirect(c.Request.Context(), accountID, req)
if svcErr != nil {
handleServiceError(c, svcErr)
handleShopifyServiceError(c, svcErr)
return
}
response.OK(c, redirect)
c.JSON(http.StatusOK, redirect)
}
// GetOrders retrieves Shopify orders for an account.
@@ -68,16 +74,22 @@ func (h *ShopifyIntegrationHandler) GetOrders(c *gin.Context) {
return
}
orders, svcErr := h.svc.GetOrders(c.Request.Context(), accountID)
if svcErr != nil {
handleServiceError(c, svcErr)
contactID, err := parseOptionalUintQueryParam(c, "contact_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid contact_id")
return
}
response.OK(c, orders)
orders, svcErr := h.svc.GetOrders(c.Request.Context(), accountID, contactID)
if svcErr != nil {
handleShopifyServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, gin.H{"orders": orders})
}
// RegisterShopifyIntegrationRoutes registers Shopify integration routes.
func RegisterShopifyIntegrationRoutes(g *gin.RouterGroup, h *ShopifyIntegrationHandler) {
g.DELETE("/shopify", h.Delete)
shopify := g.Group("/shopify")
{
shopify.DELETE("/", h.Delete)
@@ -85,3 +97,12 @@ func RegisterShopifyIntegrationRoutes(g *gin.RouterGroup, h *ShopifyIntegrationH
shopify.GET("/orders", h.GetOrders)
}
}
func handleShopifyServiceError(c *gin.Context, err error) {
var providerErr *service.ShopifyProviderError
if errors.As(err, &providerErr) {
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": providerErr.Message})
return
}
handleServiceError(c, err)
}
@@ -74,6 +74,21 @@ func TestShopifyIntegration_Auth_InvalidJSON(t *testing.T) {
assert.False(t, resp["success"].(bool))
}
func TestShopifyIntegration_Auth_MissingShopDomain(t *testing.T) {
r := setupShopifyIntegrationRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/integrations/shopify/auth", bytes.NewReader([]byte(`{}`)))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnprocessableEntity, w.Code)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, "Shop domain is required", resp["error"])
}
func TestShopifyIntegration_GetOrders_BadAccountID(t *testing.T) {
r := setupShopifyIntegrationRouter()