├── .github └── workflows │ ├── coverage.yml │ ├── dispatch.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── adapters ├── http │ ├── http.go │ └── http_test.go └── interface.go ├── container ├── constants.go └── container.go ├── examples └── auth │ └── main.go ├── go.mod ├── go.sum ├── mocks └── httpadapter.go └── services ├── authentication ├── authentication.go ├── authentication_test.go └── mock.go ├── catalog ├── catalog.go ├── catalog_test.go ├── category.go ├── category_test.go ├── errors.go ├── interface.go ├── item.go ├── item_test.go ├── products.go ├── products_test.go └── types.go ├── events ├── events.go ├── events_test.go └── types.go ├── merchant ├── merchant.go ├── merchant_test.go └── types.go └── orders ├── errors.go ├── orders.go ├── orders_test.go └── types.go /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: codecov 2 | on: [push, pull_request] 3 | jobs: 4 | codecov: 5 | name: codecov 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Install Go 9 | uses: actions/setup-go@v2 10 | with: 11 | go-version: 1.17.x 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | - name: Calc coverage 15 | run: go test -v -covermode=count -coverprofile=coverage.out ./... 16 | - name: Convert coverage.out to coverage.lcov 17 | uses: jandelgado/gcov2lcov-action@v1.0.6 18 | - name: Coveralls 19 | uses: coverallsapp/github-action@v1.1.2 20 | with: 21 | github-token: ${{ secrets.github_token }} 22 | path-to-lcov: coverage.lcov -------------------------------------------------------------------------------- /.github/workflows/dispatch.yml: -------------------------------------------------------------------------------- 1 | name: dispatch run 2 | 3 | on: 4 | repository_dispatch: 5 | types: [trigger-test] 6 | 7 | jobs: 8 | test: 9 | strategy: 10 | matrix: 11 | go-version: [1.17.x] 12 | os: [macos-latest] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - name: print event env 16 | env: 17 | EVENT_PAYLOAD: ${{ toJSON(github.event.client_payload) }} 18 | run: echo $EVENT_PAYLOAD 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: [1.15.x, 1.16.x, 1.17.x] 8 | os: [ubuntu-latest, macos-latest, windows-latest] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - name: Install Go 12 | uses: actions/setup-go@v2 13 | with: 14 | go-version: ${{ matrix.go-version }} 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | - name: Test 18 | run: go test -v ./... -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Arthur Silva 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # golang-ifood-sdk 2 | A golang Ifood sdk 3 | 4 | ![Actions on main](https://github.com/arxdsilva/golang-ifood-sdk/actions/workflows/test.yml/badge.svg?branch=main) 5 | [![Coverage Status](https://coveralls.io/repos/github/arxdsilva/golang-ifood-sdk/badge.svg?branch=main)](https://coveralls.io/github/arxdsilva/golang-ifood-sdk?branch=main) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/arxdsilva/golang-ifood-sdk)](https://goreportcard.com/report/github.com/arxdsilva/golang-ifood-sdk) 7 | [![LICENSE](https://img.shields.io/badge/license-MIT-orange.svg)](LICENSE) 8 | [![GoDoc](https://godoc.org/github.com/arxdsilva/golang-ifood-sdk?status.svg)](https://godoc.org/github.com/arxdsilva/golang-ifood-sdk) 9 | 10 | ## Usage V2 11 | 12 | ```go 13 | 14 | package main 15 | 16 | import ( 17 | sdk "github.com/arxdsilva/golang-ifood-sdk/container" 18 | ) 19 | 20 | func main() { 21 | var clientID, clientSecret, user, password string 22 | clientID = os.GetEnv("CLIENT_ID") 23 | clientSecret = os.GetEnv("CLIENT_SECRET") 24 | // START new SDK instance 25 | container := sdk.Create(clientID, clientSecret, 0, true) 26 | // Get user code to connect this supplier to the restaurant 27 | uc, err := container.AuthService.V2GetUserCode() 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | fmt.Println("user_code_url_complete:", uc.VerificationURLComplete) 32 | v2Creds, err := container.AuthService.V2Authenticate("client_credentials", uc.Usercode, uc.AuthorizationCodeVerifier) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | events, err := container.EventsService.V2Poll() 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | err = container.EventsService.V2Acknowledge(events) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | var newOrdersDetails []orders.OrderDetails 45 | for _, event := range events { 46 | // avoid non new orders 47 | if event.Code != "PLACED" { 48 | continue 49 | } 50 | details, err := container.OrdersService.V2GetDetails(event.ID) 51 | if err != nil { 52 | fmt.Println("err: ", err) 53 | continue 54 | } 55 | newOrdersDetails = append(newOrdersDetails, details) 56 | } 57 | for _, order := range newOrdersDetails { 58 | // change order status 59 | err = container.OrdersService.V2SetConfirmStatus(order.ID) 60 | if err != nil { 61 | fmt.Println("err: ", err) 62 | continue 63 | } 64 | // change other statuses 65 | } 66 | fmt.Printf("new orders: %+v\n", newOrdersDetails) 67 | } 68 | ``` 69 | 70 | 71 | ## Usage V1 72 | 73 | ```go 74 | 75 | package main 76 | 77 | import ( 78 | sdk "github.com/arxdsilva/golang-ifood-sdk/container" 79 | ) 80 | 81 | func main() { 82 | var clientID, clientSecret, user, password string 83 | clientID = os.GetEnv("CLIENT_ID") 84 | clientSecret = os.GetEnv("CLIENT_SECRET") 85 | // START new SDK instance 86 | container := sdk.New(0, time.Minute, false) 87 | container.GetHttpAdapter() 88 | // Alocate services 89 | container.GetAuthenticationService(clientID, clientSecret) 90 | container.GetMerchantService() 91 | container.GetCatalogService() 92 | container.GetEventsService() 93 | container.GetOrdersService() 94 | user = os.GetEnv("USER") 95 | password = os.GetEnv("PASSWORD") 96 | creds, err := container.AuthService.Authenticate(user,password) 97 | if err != nil { 98 | log.Fatal(err) 99 | } 100 | merchants, err := container.MerchantService.ListAll() 101 | if err != nil { 102 | log.Fatal(err) 103 | } 104 | events, err := container.EventsService.Poll() 105 | if err != nil { 106 | log.Fatal(err) 107 | } 108 | var newOrdersDetails []orders.OrderDetails 109 | for _, event := range events { 110 | err = container.EventsService.Acknowledge(event) 111 | if err != nil { 112 | fmt.Println("err: ", err) 113 | continue 114 | } 115 | // avoid non new orders 116 | if event.Code != "PLACED" { 117 | continue 118 | } 119 | details, err := container.OrdersService.GetDetails(event.ID) 120 | if err != nil { 121 | fmt.Println("err: ", err) 122 | continue 123 | } 124 | newOrdersDetails = append(newOrdersDetails, details) 125 | } 126 | for _, order := range newOrdersDetails { 127 | // change order status 128 | err = container.OrdersService.SetIntegrateStatus(order.ID) 129 | if err != nil { 130 | fmt.Println("err: ", err) 131 | continue 132 | } 133 | // change other statuses 134 | } 135 | fmt.Printf("new orders: %+v\n", newOrdersDetails) 136 | } 137 | ``` 138 | -------------------------------------------------------------------------------- /adapters/http/http.go: -------------------------------------------------------------------------------- 1 | package httpadapter 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io" 8 | "io/ioutil" 9 | "mime/multipart" 10 | "net/http" 11 | "net/textproto" 12 | 13 | "github.com/kpango/glg" 14 | ) 15 | 16 | // HTTPClient implementation 17 | type HTTPClient interface { 18 | Do(req *http.Request) (*http.Response, error) 19 | } 20 | 21 | type httpAdapter struct { 22 | client HTTPClient 23 | baseUrl string 24 | } 25 | 26 | var ( 27 | // ErrorNilData no data 28 | ErrorNilData = errors.New("no data to parse ") 29 | // ErrorNilAuth no auth service 30 | ErrorNilAuth = errors.New("no auth to parse ") 31 | ) 32 | 33 | // New returns an httpAdapter 34 | func New(client HTTPClient, baseUrl string) *httpAdapter { 35 | return &httpAdapter{client, baseUrl} 36 | } 37 | 38 | // DoRequest is the httpAdapter requester 39 | func (h *httpAdapter) DoRequest(method, path string, reader io.Reader, headers map[string]string) (response []byte, status int, err error) { 40 | glg.Debugf("[DoRequest]: method:%v, url:%v\n", method, h.baseUrl+path) 41 | request, err := http.NewRequest(method, h.baseUrl+path, reader) 42 | if err != nil { 43 | return nil, 0, err 44 | } 45 | for k, v := range headers { 46 | request.Header.Add(k, v) 47 | } 48 | resp, err := h.client.Do(request) 49 | if err != nil { 50 | return nil, 0, err 51 | } 52 | defer closeBodyReader(resp.Body) 53 | result, err := ioutil.ReadAll(resp.Body) 54 | glg.Debug("[DoRequest]: resp ", string(result)) 55 | return result, resp.StatusCode, err 56 | } 57 | 58 | // NewJsonReader returns a reader from a given data 59 | func NewJsonReader(data interface{}) (io.Reader, error) { 60 | if data == nil { 61 | return nil, ErrorNilData 62 | } 63 | jsonData, err := json.Marshal(data) 64 | if err != nil { 65 | glg.Warnf("Error on makeReader marshaling json data: %e", err) 66 | return nil, errors.New("error on marshal data: " + err.Error()) 67 | } 68 | return bytes.NewReader(jsonData), nil 69 | } 70 | 71 | // NewMultipartReader returns a multipart reader from a given data 72 | func NewMultipartReader(data interface{}) (reader io.Reader, boundary string, err error) { 73 | if data == nil { 74 | err = ErrorNilData 75 | return 76 | } 77 | jsonData, err := json.Marshal(data) 78 | if err != nil { 79 | err = errors.New("error on marshal data: " + err.Error()) 80 | glg.Warnf("Error on makeReader marshaling json data: %e", err) 81 | return 82 | } 83 | body := &bytes.Buffer{} 84 | writer, err := getWriter(body, jsonData) 85 | if err != nil { 86 | err = errors.New("error on create part data: " + err.Error()) 87 | glg.Warnf("Error on writing metadata headers: %v", err) 88 | return 89 | } 90 | return bytes.NewReader(body.Bytes()), writer.Boundary(), nil 91 | } 92 | 93 | func getWriter(body *bytes.Buffer, data []byte) (*multipart.Writer, error) { 94 | writer := multipart.NewWriter(body) 95 | metadataHeader := textproto.MIMEHeader{} 96 | metadataHeader.Set("Content-Type", "application/json") 97 | metadataHeader.Set("Content-ID", "metadata") 98 | part, err := writer.CreatePart(metadataHeader) 99 | if err != nil { 100 | err = errors.New("error on create part data: " + err.Error()) 101 | glg.Warnf("Error on writing metadata headers: %v", err) 102 | return nil, err 103 | } 104 | _, err = part.Write(data) 105 | if err != nil { 106 | err = errors.New("error on create part data: " + err.Error()) 107 | glg.Warnf("Error on writing data: %v", err) 108 | return nil, err 109 | } 110 | if err := writer.Close(); err != nil { 111 | err = errors.New("error on create part data: " + err.Error()) 112 | glg.Fatalf("Error closing multipart writer: %v", err) 113 | return nil, err 114 | } 115 | return writer, nil 116 | } 117 | 118 | func closeBodyReader(reader io.ReadCloser) { 119 | if err := reader.Close(); err != nil { 120 | glg.Warnf("Error on closeBodyReader %e", err) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /adapters/http/http_test.go: -------------------------------------------------------------------------------- 1 | package httpadapter 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io/ioutil" 7 | "net/http" 8 | "testing" 9 | 10 | "github.com/arxdsilva/golang-ifood-sdk/mocks" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var clientMock = new(mocks.HttpClientMock) 15 | 16 | func TestHttpAdapter_Do_Success(t *testing.T) { 17 | request, _ := http.NewRequest(http.MethodPost, "test/", nil) 18 | json := "{message: success}" 19 | body := ioutil.NopCloser(bytes.NewReader([]byte(json))) 20 | expectedResp := &http.Response{ 21 | StatusCode: http.StatusOK, 22 | Body: body, 23 | } 24 | clientMock.On("Do", request).Once().Return(expectedResp, nil) 25 | adapter := New(clientMock, "test") 26 | resp, status, err := adapter.DoRequest(http.MethodPost, "/", nil, nil) 27 | assert.Nil(t, err) 28 | assert.NotNil(t, resp) 29 | assert.Equal(t, http.StatusOK, status) 30 | } 31 | 32 | func TestHttpAdapter_Do_Error(t *testing.T) { 33 | request, _ := http.NewRequest(http.MethodPost, "test/", nil) 34 | clientMock.On("Do", request).Once().Return(nil, errors.New("error")) 35 | adapter := New(clientMock, "test") 36 | resp, status, err := adapter.DoRequest(http.MethodPost, "/", nil, nil) 37 | assert.Nil(t, resp) 38 | assert.NotNil(t, err) 39 | assert.Equal(t, 0, status) 40 | } 41 | 42 | func TestNewJsonReader_NilErr(t *testing.T) { 43 | _, err := NewJsonReader(nil) 44 | assert.Equal(t, ErrorNilData, err) 45 | } 46 | 47 | func TestNewJsonReader_OK(t *testing.T) { 48 | someObject := struct { 49 | Name string `json:"name"` 50 | }{Name: "Newman"} 51 | reader, err := NewJsonReader(someObject) 52 | assert.Nil(t, err) 53 | assert.NotNil(t, reader) 54 | } 55 | 56 | func TestNewMultipartReader_NilErr(t *testing.T) { 57 | _, _, err := NewMultipartReader(nil) 58 | assert.Equal(t, ErrorNilData, err) 59 | } 60 | 61 | func TestNewMultipartReader_OK(t *testing.T) { 62 | someObject := struct { 63 | Name string `json:"name"` 64 | }{Name: "Newman"} 65 | reader, boudary, err := NewMultipartReader(someObject) 66 | assert.Nil(t, err) 67 | assert.NotNil(t, reader) 68 | assert.NotEmpty(t, boudary) 69 | } 70 | -------------------------------------------------------------------------------- /adapters/interface.go: -------------------------------------------------------------------------------- 1 | package adapters 2 | 3 | import "io" 4 | 5 | // Http represents the API querier abstraction 6 | type Http interface { 7 | DoRequest(method, path string, reader io.Reader, headers map[string]string) ([]byte, int, error) 8 | } 9 | -------------------------------------------------------------------------------- /container/constants.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | const ( 4 | // EnvProduction is the production env 5 | EnvProduction = iota 6 | // EnvDevelopment is the dev env 7 | EnvDevelopment 8 | // EnvSandBox is the local/sandbox env 9 | EnvSandBox 10 | 11 | urlProduction = "https://pos-api.ifood.com.br" 12 | v2urlProduction = "https://merchant-api.ifood.com.br" 13 | urlSandbox = "https://pos-api.ifood.com.br" 14 | ) 15 | -------------------------------------------------------------------------------- /container/container.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/arxdsilva/golang-ifood-sdk/adapters" 10 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 11 | "github.com/arxdsilva/golang-ifood-sdk/mocks" 12 | "github.com/arxdsilva/golang-ifood-sdk/services/authentication" 13 | "github.com/arxdsilva/golang-ifood-sdk/services/catalog" 14 | "github.com/arxdsilva/golang-ifood-sdk/services/events" 15 | "github.com/arxdsilva/golang-ifood-sdk/services/merchant" 16 | "github.com/arxdsilva/golang-ifood-sdk/services/orders" 17 | "github.com/kpango/glg" 18 | ) 19 | 20 | // Container is the SDK abstractions holder to facilitate the API manipulation 21 | type Container struct { 22 | env int 23 | v2 bool 24 | timeout time.Duration 25 | httpadapter adapters.Http 26 | AuthService authentication.Service 27 | MerchantService merchant.Service 28 | CatalogService catalog.Service 29 | EventsService events.Service 30 | OrdersService orders.Service 31 | } 32 | 33 | // New returns a new container 34 | func New(env int, timeout time.Duration, v2 bool) *Container { 35 | return &Container{env: env, timeout: timeout, v2: v2} 36 | } 37 | 38 | func Create(clientId, clientSecret string, env int, v2 bool) (c *Container) { 39 | c = New(env, time.Minute, v2) 40 | c.GetHttpAdapter() 41 | c.GetAuthenticationService(clientId, clientSecret) 42 | c.GetMerchantService() 43 | c.GetCatalogService() 44 | c.GetEventsService() 45 | c.GetOrdersService() 46 | return 47 | } 48 | 49 | // CreateFromEnvs creates a new instance of the container struct 50 | // from envs 51 | // "IFOOD_CLIENT_ID" 52 | // "IFOOD_CLIENT_SECRET" 53 | // "IFOOD_ENV" (default to Production) 54 | // always uses the api v2 55 | func CreateFromEnvs() (c *Container) { 56 | clientID := os.Getenv("IFOOD_CLIENT_ID") 57 | clientSecret := os.Getenv("IFOOD_CLIENT_SECRET") 58 | env, _ := strconv.Atoi(os.Getenv("IFOOD_ENV")) 59 | return Create(clientID, clientSecret, env, true) 60 | } 61 | 62 | // GetHttpAdapter returns new HTTP adapter according to the env 63 | func (c *Container) GetHttpAdapter() adapters.Http { 64 | if c.httpadapter != nil { 65 | return c.httpadapter 66 | } 67 | client := &http.Client{ 68 | Timeout: c.timeout, 69 | } 70 | switch c.env { 71 | case EnvDevelopment: 72 | c.httpadapter = httpadapter.New(new(mocks.HttpClientMock), "") 73 | case EnvProduction: 74 | if c.v2 { 75 | c.httpadapter = httpadapter.New(client, v2urlProduction) 76 | return c.httpadapter 77 | } 78 | c.httpadapter = httpadapter.New(client, urlProduction) 79 | case EnvSandBox: 80 | if c.v2 { 81 | c.httpadapter = httpadapter.New(client, v2urlProduction) 82 | return c.httpadapter 83 | } 84 | c.httpadapter = httpadapter.New(client, urlSandbox) 85 | } 86 | return c.httpadapter 87 | } 88 | 89 | // Do a start method to instantiate all services instead of each separated 90 | 91 | // GetAuthenticationService instantiates an auth service, also adds it to the container 92 | func (c *Container) GetAuthenticationService(clientId, clientSecret string) authentication.Service { 93 | if c.httpadapter == nil { 94 | glg.Warn("[GetAuthenticationService]: http adapter is nil, please set it with Container.GetHttpAdapter") 95 | return nil 96 | } 97 | if c.AuthService == nil { 98 | c.AuthService = authentication.New(c.GetHttpAdapter(), clientId, clientSecret, c.v2) 99 | } 100 | return c.AuthService 101 | } 102 | 103 | // GetMerchantService instantiates an merchant service, also adds it to the container 104 | func (c *Container) GetMerchantService() merchant.Service { 105 | if c.httpadapter == nil { 106 | glg.Warn("[GetMerchantService]: http adapter is nil, please set it with Container.GetHttpAdapter") 107 | return nil 108 | } 109 | if c.AuthService == nil { 110 | glg.Warn("[GetMerchantService]: please set the authentication service") 111 | return nil 112 | } 113 | if c.MerchantService == nil { 114 | c.MerchantService = merchant.New(c.GetHttpAdapter(), c.AuthService) 115 | } 116 | return c.MerchantService 117 | } 118 | 119 | // GetCatalogService instantiates an catalog service, also adds it to the container 120 | func (c *Container) GetCatalogService() catalog.Service { 121 | if c.httpadapter == nil { 122 | glg.Warn("[GetCatalogService]: http adapter is nil, please set it with Container.GetHttpAdapter") 123 | return nil 124 | } 125 | if c.AuthService == nil { 126 | glg.Warn("[GetCatalogService]: please set the authentication service") 127 | return nil 128 | } 129 | if c.CatalogService == nil { 130 | c.CatalogService = catalog.New(c.GetHttpAdapter(), c.AuthService) 131 | } 132 | return c.CatalogService 133 | } 134 | 135 | // GetEventsService instantiates an events service, also adds it to the container 136 | func (c *Container) GetEventsService() events.Service { 137 | if c.httpadapter == nil { 138 | glg.Warn("[GetEventsService]: http adapter is nil, please set it with Container.GetHttpAdapter") 139 | return nil 140 | } 141 | if c.AuthService == nil { 142 | glg.Warn("[GetEventsService]: please set the authentication service") 143 | return nil 144 | } 145 | if c.EventsService == nil { 146 | c.EventsService = events.New(c.GetHttpAdapter(), c.AuthService, true) 147 | } 148 | return c.EventsService 149 | } 150 | 151 | // GetOrdersService instantiates an orders service, also adds it to the container 152 | func (c *Container) GetOrdersService() orders.Service { 153 | if c.httpadapter == nil { 154 | glg.Warn("[GetOrdersService]: http adapter is nil, please set it with Container.GetHttpAdapter") 155 | return nil 156 | } 157 | if c.AuthService == nil { 158 | glg.Warn("[GetOrdersService]: please set the authentication service") 159 | return nil 160 | } 161 | if c.OrdersService == nil { 162 | c.OrdersService = orders.New(c.GetHttpAdapter(), c.AuthService) 163 | } 164 | return c.OrdersService 165 | } 166 | -------------------------------------------------------------------------------- /examples/auth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | sdk "github.com/arxdsilva/golang-ifood-sdk/container" 9 | ) 10 | 11 | func main() { 12 | var clientID, clientSecret string 13 | clientID = os.Getenv("CLIENT_ID") 14 | clientSecret = os.Getenv("CLIENT_SECRET") 15 | // START new SDK instance 16 | c := sdk.Create(clientID, clientSecret, 0, true) 17 | // Get user code to connect this supplier to the restaurant 18 | uc, err := c.AuthService.V2GetUserCode() 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | fmt.Printf("v2 user: %+v\n", uc) 23 | v2Creds, err := c.AuthService.V2Authenticate( 24 | "client_credentials", 25 | uc.Usercode, uc.AuthorizationCodeVerifier, "") 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | fmt.Printf("v2 creds: %+v\n", v2Creds) 30 | } 31 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/arxdsilva/golang-ifood-sdk 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/gofrs/uuid v4.2.0+incompatible 7 | github.com/kpango/glg v1.6.10 8 | github.com/stretchr/testify v1.7.1 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/goccy/go-json v0.9.4 // indirect 14 | github.com/kpango/fastime v1.1.4 // indirect 15 | github.com/kr/pretty v0.1.0 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | github.com/stretchr/objx v0.1.0 // indirect 18 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 19 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/goccy/go-json v0.9.4 h1:L8MLKG2mvVXiQu07qB6hmfqeSYQdOnqPot2GhsIwIaI= 6 | github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 7 | github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= 8 | github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 9 | github.com/kpango/fastime v1.1.4 h1:pus9JgJBg/8Jie3ozayA4yNIV67BUPhbq0wMZY3CtYo= 10 | github.com/kpango/fastime v1.1.4/go.mod h1:tTNDbIo5qL6D7g5vh2YbkyUbOVP2kD/we3rSjN22PMY= 11 | github.com/kpango/glg v1.6.10 h1:ykvO7lKmfYp9d3jBvIO7UFV5mqp2FYPezC0rAyN1IqA= 12 | github.com/kpango/glg v1.6.10/go.mod h1:Er7L/mFi0MY+Nse2a85sc3kU80cR3Vi3iBKax56r5ng= 13 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 14 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 15 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 16 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 17 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 18 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 19 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 20 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 21 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 22 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 23 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 24 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 25 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 26 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 27 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 28 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 29 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 30 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 31 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 32 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 33 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 34 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 35 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 36 | go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= 37 | go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= 38 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 39 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 40 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 41 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 42 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 43 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 44 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 45 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 46 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 47 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 48 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 49 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 53 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= 54 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 55 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 56 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 57 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 58 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 59 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 60 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 61 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 62 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 63 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 64 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 65 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 66 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 67 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 68 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 69 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 70 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 71 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 72 | -------------------------------------------------------------------------------- /mocks/httpadapter.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/stretchr/testify/mock" 7 | ) 8 | 9 | // HttpClientMock mocks a http client 10 | type HttpClientMock struct { 11 | mock.Mock 12 | } 13 | 14 | // Do mocks the Do method 15 | func (h *HttpClientMock) Do(req *http.Request) (*http.Response, error) { 16 | args := h.Called(req) 17 | if res, ok := args.Get(0).(*http.Response); ok { 18 | return res, nil 19 | } 20 | return nil, args.Error(1) 21 | } 22 | -------------------------------------------------------------------------------- /services/authentication/authentication.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "mime/multipart" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | "time" 13 | 14 | "github.com/arxdsilva/golang-ifood-sdk/adapters" 15 | "github.com/kpango/glg" 16 | ) 17 | 18 | const ( 19 | authRoot = "/authentication/v1.0" 20 | authEndpoint = "/oauth/token" 21 | userCodeEndpoint = "/oauth/userCode" 22 | valueGrantType = "password" 23 | ) 24 | 25 | // ErrUnauthorized API no auth error 26 | var ErrUnauthorized = errors.New("Unauthorized") 27 | var ErrGrantType = errors.New("Grant type is invalid, should be 'client_credentials', 'authorization_code' or 'refresh_token'") 28 | var ErrNoRefreshToken = errors.New("Grant type 'refresh_token', should have a refresh token provided") 29 | var ErrNoAuthCodeOrVerifier = errors.New("Grant type 'authorization_code', should have both 'authorizationCode' and 'authorizationCodeVerifier' provided") 30 | 31 | type ( 32 | // Service describes the auth service abstraction 33 | Service interface { 34 | V2GetUserCode() (*UserCode, error) 35 | Authenticate(username, password string) (*Credentials, error) 36 | V2Authenticate(authType, authCode, authCodeVerifier, refreshToken string) (c *V2Credentials, err error) 37 | Validate() error 38 | GetToken() string 39 | } 40 | 41 | // Credentials describes the API credential type 42 | Credentials struct { 43 | AccessToken string `json:"access_token"` 44 | TokenType string `json:"token_type"` 45 | Scope string `json:"scope"` 46 | ExpiresIn int `json:"expires_in"` 47 | } 48 | 49 | // V2Credentials describes the API credential type 50 | V2Credentials struct { 51 | AccessToken string `json:"accessToken"` 52 | RefreshToken string `json:"refreshToken"` 53 | Type string `json:"type"` 54 | ExpiresIn int `json:"expires_in"` 55 | } 56 | 57 | authError struct { 58 | Error struct { 59 | Code string `json:"code"` 60 | Message string `json:"error"` 61 | } `json:"error"` 62 | } 63 | 64 | UserCode struct { 65 | Usercode string `json:"userCode"` 66 | AuthorizationCodeVerifier string `json:"authorizationCodeVerifier"` 67 | VerificationURL string `json:"verificationUrl"` 68 | VerificationURLComplete string `json:"verificationUrlComplete"` 69 | ExpiresIn int `json:"expiresIn"` 70 | } 71 | 72 | authService struct { 73 | adapter adapters.Http 74 | clientId, clientSecret string 75 | username, password string 76 | currentExpiration time.Time 77 | Token string 78 | refreshToken string 79 | v2 bool 80 | } 81 | ) 82 | 83 | // New returns an auth service implementation 84 | func New(adapter adapters.Http, clientId, clientSecret string, v2 bool) Service { 85 | return &authService{adapter: adapter, clientId: clientId, clientSecret: clientSecret, v2: v2} 86 | } 87 | 88 | func (a *authService) V2GetUserCode() (uc *UserCode, err error) { 89 | params := url.Values{} 90 | params.Add("clientId", a.clientId) 91 | body := strings.NewReader(params.Encode()) 92 | headers := make(map[string]string) 93 | headers["Content-Type"] = "application/x-www-form-urlencoded" 94 | resp, status, err := a.adapter.DoRequest(http.MethodPost, authRoot+userCodeEndpoint, body, headers) 95 | if err != nil { 96 | glg.Error("[SDK] (V2GetUserCode::DoRequest) error: ", err.Error()) 97 | return 98 | } 99 | if status != http.StatusOK { 100 | glg.Warn("[SDK] (V2GetUserCode::status.check): status code ", status) 101 | err = ErrUnauthorized 102 | return 103 | } 104 | if err = json.Unmarshal(resp, &uc); err != nil { 105 | glg.Error("[SDK] (V2GetUserCode::Unmarshal) error: ", err) 106 | return 107 | } 108 | glg.Info("[SDK] V2GetUserCode success") 109 | return 110 | } 111 | 112 | // V2Authenticate queries the iFood API for a credential 113 | func (a *authService) V2Authenticate(authType, authCode, authCodeVerifier, refreshToken string) (c *V2Credentials, err error) { 114 | if err = verifyV2Inputs(authType, authCode, authCodeVerifier, refreshToken); err != nil { 115 | glg.Error("[SDK] (V2Authenticate::verifyV2Inputs) error: ", err.Error()) 116 | return 117 | } 118 | data := url.Values{} 119 | data.Set("client_id", a.clientId) 120 | data.Set("client_secret", a.clientSecret) 121 | data.Set("grant_type", valueGrantType) 122 | data.Set("authorizationCode", authCode) 123 | data.Set("authorizationCodeVerifier", authCodeVerifier) 124 | data.Set("refreshToken", refreshToken) 125 | headers := make(map[string]string) 126 | headers["Content-Type"] = "application/x-www-form-urlencoded" 127 | body := strings.NewReader(data.Encode()) 128 | resp, status, err := a.adapter.DoRequest(http.MethodPost, authRoot+authEndpoint, body, headers) 129 | if err != nil { 130 | glg.Error("[SDK] (V2Authenticate::DoRequest) error: ", err.Error()) 131 | return 132 | } 133 | if status != http.StatusOK { 134 | glg.Warn("[SDK] V2Authenticate: status code ", status) 135 | authErr := authError{} 136 | json.Unmarshal(resp, &authErr) 137 | warn := fmt.Sprintf("[SDK] (V2GetUserCode::status.code): code '%s' message '%s'", 138 | authErr.Error.Code, authErr.Error.Message) 139 | glg.Warn(warn) 140 | err = ErrUnauthorized 141 | return 142 | } 143 | if err = json.Unmarshal(resp, &c); err != nil { 144 | glg.Error("[SDK] (V2Authenticate::Unmarshal) error: ", err) 145 | return 146 | } 147 | glg.Info("[SDK] (V2Authenticate) success") 148 | a.currentExpiration = time.Now().Add(time.Hour * 6) 149 | a.Token = c.AccessToken 150 | a.refreshToken = c.RefreshToken 151 | return 152 | } 153 | 154 | // Authenticate queries the iFood API for a credential 155 | func (a *authService) Authenticate(username, password string) (c *Credentials, err error) { 156 | payload := &bytes.Buffer{} 157 | writer := multipart.NewWriter(payload) 158 | writer.WriteField("client_id", a.clientId) 159 | writer.WriteField("client_secret", a.clientSecret) 160 | writer.WriteField("grant_type", valueGrantType) 161 | writer.WriteField("username", username) 162 | writer.WriteField("password", password) 163 | if err = writer.Close(); err != nil { 164 | glg.Error("[SDK] (Auth::writer.Close) error: ", err.Error()) 165 | return 166 | } 167 | reader := bytes.NewReader(payload.Bytes()) 168 | headers := make(map[string]string) 169 | headers["Content-Type"] = writer.FormDataContentType() 170 | headers["Accept"] = "*/*" 171 | resp, status, err := a.adapter.DoRequest(http.MethodPost, authEndpoint, reader, headers) 172 | if err != nil { 173 | glg.Error("[SDK] (Auth::DoRequest) error: ", err.Error()) 174 | return 175 | } 176 | if status != http.StatusOK { 177 | glg.Warn("[SDK] (Auth::status.code): status code ", status) 178 | err = ErrUnauthorized 179 | return 180 | } 181 | if err = json.Unmarshal(resp, &c); err != nil { 182 | glg.Error("[SDK] (Auth::Unmarshal) error: ", err) 183 | return 184 | } 185 | glg.Info("[SDK] (Authenticate) success") 186 | a.currentExpiration = time.Now().Add(time.Hour) 187 | a.username = username 188 | a.password = password 189 | a.Token = c.AccessToken 190 | return 191 | } 192 | 193 | // Validate validates or renews a token auth 194 | func (a *authService) Validate() (err error) { 195 | if !time.Now().After(a.currentExpiration) { 196 | glg.Debug("[SDK] (auth::Validate) not time") 197 | return 198 | } 199 | glg.Info("[SDK] (auth::Validate) Renewing Auth") 200 | if a.v2 { 201 | _, err = a.V2Authenticate("refresh_token", "", "", a.refreshToken) 202 | return 203 | } 204 | _, err = a.Authenticate(a.username, a.password) 205 | return 206 | } 207 | 208 | // GetToken returns the last valid token 209 | func (a *authService) GetToken() (token string) { 210 | return a.Token 211 | } 212 | 213 | func verifyV2Inputs(authType, authCode, authCodeVerifier, refreshToken string) (err error) { 214 | if (authType != "client_credentials") && (authType != "authorization_code") && (authType != "refresh_token") { 215 | return ErrGrantType 216 | } 217 | switch authType { 218 | case "authorization_code": 219 | if authCode == "" || authCodeVerifier == "" { 220 | return ErrNoAuthCodeOrVerifier 221 | } 222 | case "refresh_token": 223 | if refreshToken == "" { 224 | return ErrNoRefreshToken 225 | } 226 | } 227 | return 228 | } 229 | -------------------------------------------------------------------------------- /services/authentication/authentication_test.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 10 | "github.com/arxdsilva/golang-ifood-sdk/mocks" 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestNew(t *testing.T) { 16 | mock := new(mocks.HttpClientMock) 17 | adapter := httpadapter.New(mock, "") 18 | as := New(adapter, "client", "secret", false) 19 | assert.NotNil(t, as) 20 | } 21 | 22 | func TestAuth_OK(t *testing.T) { 23 | ts := httptest.NewServer( 24 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 25 | assert.Equal(t, "/oauth/token", r.URL.Path) 26 | assert.Contains(t, r.Header["Content-Type"][0], "multipart") 27 | fmt.Fprintf(w, `{"access_token":"token","expires_in":3600}`) 28 | w.WriteHeader(http.StatusOK) 29 | }), 30 | ) 31 | defer ts.Close() 32 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 33 | as := New(adapter, "client", "secret", false) 34 | assert.NotNil(t, as) 35 | c, err := as.Authenticate("user", "pass") 36 | assert.Nil(t, err) 37 | assert.Equal(t, "token", c.AccessToken) 38 | } 39 | 40 | func TestAuth_NotOK(t *testing.T) { 41 | ts := httptest.NewServer( 42 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 43 | assert.Equal(t, "/oauth/token", r.URL.Path) 44 | assert.Contains(t, r.Header["Content-Type"][0], "multipart") 45 | w.WriteHeader(http.StatusBadRequest) 46 | }), 47 | ) 48 | defer ts.Close() 49 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 50 | as := New(adapter, "client", "secret", false) 51 | assert.NotNil(t, as) 52 | c, err := as.Authenticate("user", "pass") 53 | assert.Nil(t, c) 54 | assert.NotNil(t, err) 55 | assert.Equal(t, err, ErrUnauthorized) 56 | } 57 | 58 | func TestAuth_BadResp(t *testing.T) { 59 | ts := httptest.NewServer( 60 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 61 | assert.Equal(t, "/oauth/token", r.URL.Path) 62 | assert.Contains(t, r.Header["Content-Type"][0], "multipart") 63 | fmt.Fprintf(w, `{`) 64 | }), 65 | ) 66 | defer ts.Close() 67 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 68 | as := New(adapter, "client", "secret", false) 69 | assert.NotNil(t, as) 70 | c, err := as.Authenticate("user", "pass") 71 | assert.Nil(t, c) 72 | assert.NotNil(t, err) 73 | assert.Contains(t, err.Error(), "JSON") 74 | } 75 | 76 | func Test_verifyV2Inputs_ErrGrantType(t *testing.T) { 77 | authType := "" 78 | authCode := "" 79 | authCodeVerifier := "" 80 | refreshToken := "" 81 | err := verifyV2Inputs(authType, authCode, authCodeVerifier, refreshToken) 82 | assert.NotNil(t, err) 83 | assert.Equal(t, ErrGrantType, err) 84 | } 85 | 86 | func Test_verifyV2Inputs_ErrNoAuthCodeOrVerifier(t *testing.T) { 87 | authType := "authorization_code" 88 | authCode := "" 89 | authCodeVerifier := "" 90 | refreshToken := "" 91 | err := verifyV2Inputs(authType, authCode, authCodeVerifier, refreshToken) 92 | assert.NotNil(t, err) 93 | assert.Equal(t, ErrNoAuthCodeOrVerifier, err) 94 | } 95 | 96 | func Test_verifyV2Inputs_ErrNoRefreshToken(t *testing.T) { 97 | authType := "refresh_token" 98 | authCode := "testCode" 99 | authCodeVerifier := "testToken" 100 | refreshToken := "" 101 | err := verifyV2Inputs(authType, authCode, authCodeVerifier, refreshToken) 102 | assert.NotNil(t, err) 103 | assert.Equal(t, ErrNoRefreshToken, err) 104 | } 105 | 106 | func Test_verifyV2Inputs_OK_authorization_code(t *testing.T) { 107 | authType := "authorization_code" 108 | authCode := "testCode" 109 | authCodeVerifier := "testToken" 110 | refreshToken := "" 111 | err := verifyV2Inputs(authType, authCode, authCodeVerifier, refreshToken) 112 | assert.Nil(t, err) 113 | } 114 | 115 | func Test_verifyV2Inputs_OK_refresh_token(t *testing.T) { 116 | authType := "refresh_token" 117 | authCode := "" 118 | authCodeVerifier := "" 119 | refreshToken := "TOKEN" 120 | err := verifyV2Inputs(authType, authCode, authCodeVerifier, refreshToken) 121 | assert.Nil(t, err) 122 | } 123 | 124 | func TestV2Auth_OK(t *testing.T) { 125 | ts := httptest.NewServer( 126 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 127 | require.Equal(t, "/authentication/v1.0/oauth/token", r.URL.Path) 128 | require.Contains(t, r.Header["Content-Type"][0], "application/x-www-form-urlencoded") 129 | fmt.Fprintf(w, `{"accessToken":"token","expiresIn":3600}`) 130 | w.WriteHeader(http.StatusOK) 131 | }), 132 | ) 133 | defer ts.Close() 134 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 135 | as := New(adapter, "client", "secret", false) 136 | assert.NotNil(t, as) 137 | c, err := as.V2Authenticate("authorization_code", "testCode", "verifier", "refresh") 138 | assert.Nil(t, err) 139 | assert.Equal(t, "token", c.AccessToken) 140 | } 141 | 142 | func TestV2Auth_RespNotOK(t *testing.T) { 143 | ts := httptest.NewServer( 144 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 145 | require.Equal(t, http.MethodPost, r.Method) 146 | require.Equal(t, "/authentication/v1.0/oauth/token", r.URL.Path) 147 | require.Contains(t, r.Header["Content-Type"][0], "application/x-www-form-urlencoded") 148 | w.WriteHeader(http.StatusBadRequest) 149 | fmt.Fprintf(w, `{"error": {"code": "BadRequest","message": "Invalid grant type"}}`) 150 | }), 151 | ) 152 | defer ts.Close() 153 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 154 | as := New(adapter, "client", "secret", false) 155 | assert.NotNil(t, as) 156 | c, err := as.V2Authenticate("authorization_code", "testCode", "verifier", "refresh") 157 | assert.Nil(t, c) 158 | assert.NotNil(t, err) 159 | assert.Equal(t, ErrUnauthorized, err) 160 | } 161 | 162 | func Test_V2GetUserCode_OK(t *testing.T) { 163 | resp := `{ 164 | "userCode": "HJLX-LPSQ", 165 | "authorizationCodeVerifier": "g58pczze01xqxo38iqozohexeviqrm86tfhcqf5qxz9453oknyk6dfb3a0tlsnt98zw4y40i9izeokdkwgzgtogsu2zx7wn4t2f", 166 | "verificationUrl": "https://portal.ifood.com.br/apps/code", 167 | "verificationUrlComplete": "https://portal.ifood.com.br/apps/code?c=HJLX-LPSQ", 168 | "expiresIn": 600 169 | }` 170 | ts := httptest.NewServer( 171 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 172 | require.Equal(t, http.MethodPost, r.Method) 173 | require.Equal(t, "/authentication/v1.0"+userCodeEndpoint, r.URL.Path) 174 | require.Equal(t, r.Header["Content-Type"][0], "application/x-www-form-urlencoded") 175 | w.WriteHeader(http.StatusOK) 176 | fmt.Fprintf(w, resp) 177 | }), 178 | ) 179 | defer ts.Close() 180 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 181 | as := New(adapter, "client", "secret", false) 182 | assert.NotNil(t, as) 183 | uc, err := as.V2GetUserCode() 184 | assert.Nil(t, err) 185 | assert.Equal(t, "HJLX-LPSQ", uc.Usercode) 186 | assert.Equal(t, "g58pczze01xqxo38iqozohexeviqrm86tfhcqf5qxz9453oknyk6dfb3a0tlsnt98zw4y40i9izeokdkwgzgtogsu2zx7wn4t2f", uc.AuthorizationCodeVerifier) 187 | assert.Equal(t, "https://portal.ifood.com.br/apps/code", uc.VerificationURL) 188 | assert.Equal(t, "https://portal.ifood.com.br/apps/code?c=HJLX-LPSQ", uc.VerificationURLComplete) 189 | } 190 | 191 | func Test_V2GetUserCode_ErrUnauthorized(t *testing.T) { 192 | ts := httptest.NewServer( 193 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 194 | require.Equal(t, http.MethodPost, r.Method) 195 | require.Equal(t, "/authentication/v1.0"+userCodeEndpoint, r.URL.Path) 196 | require.Equal(t, r.Header["Content-Type"][0], "application/x-www-form-urlencoded") 197 | w.WriteHeader(http.StatusBadRequest) 198 | fmt.Fprintf(w, `{"error": {"code": "BadRequest","message": "Invalid"}}`) 199 | }), 200 | ) 201 | defer ts.Close() 202 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 203 | as := New(adapter, "client", "secret", false) 204 | assert.NotNil(t, as) 205 | uc, err := as.V2GetUserCode() 206 | assert.Nil(t, uc) 207 | assert.NotNil(t, err) 208 | assert.Equal(t, ErrUnauthorized, err) 209 | } 210 | -------------------------------------------------------------------------------- /services/authentication/mock.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | import ( 4 | "github.com/stretchr/testify/mock" 5 | ) 6 | 7 | // AuthMock mock of auth service 8 | type AuthMock struct { 9 | mock.Mock 10 | } 11 | 12 | // V2GetUserCode mock of auth service 13 | func (a *AuthMock) V2GetUserCode() (uc *UserCode, err error) { 14 | args := a.Called() 15 | if res, ok := args.Get(0).(*UserCode); ok { 16 | return res, nil 17 | } 18 | return nil, args.Error(1) 19 | } 20 | 21 | func (a *AuthMock) V2Authenticate(authType, authCode, authCodeVerifier, refreshToken string) (c *V2Credentials, err error) { 22 | args := a.Called(authType, authCode, authCodeVerifier, refreshToken) 23 | if res, ok := args.Get(0).(*V2Credentials); ok { 24 | return res, nil 25 | } 26 | return nil, args.Error(1) 27 | } 28 | 29 | // Authenticate mock of auth service 30 | func (a *AuthMock) Authenticate(user, pass string) (c *Credentials, err error) { 31 | args := a.Called(user, pass) 32 | if res, ok := args.Get(0).(*Credentials); ok { 33 | return res, nil 34 | } 35 | return nil, args.Error(1) 36 | } 37 | 38 | // Validate mock of auth service 39 | func (a *AuthMock) Validate() (err error) { 40 | args := a.Called() 41 | return args.Error(0) 42 | } 43 | 44 | // GetToken mock of auth service 45 | func (a *AuthMock) GetToken() (token string) { 46 | args := a.Called() 47 | return args.Get(0).(string) 48 | } 49 | -------------------------------------------------------------------------------- /services/catalog/catalog.go: -------------------------------------------------------------------------------- 1 | package catalog 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | 9 | "github.com/arxdsilva/golang-ifood-sdk/adapters" 10 | auth "github.com/arxdsilva/golang-ifood-sdk/services/authentication" 11 | "github.com/kpango/glg" 12 | ) 13 | 14 | const ( 15 | v2Endpoint = "/catalog/v2.0" 16 | ) 17 | 18 | type catalogService struct { 19 | adapter adapters.Http 20 | auth auth.Service 21 | } 22 | 23 | // New returns an implementation of the catalog service 24 | func New(adapter adapters.Http, authService auth.Service) *catalogService { 25 | return &catalogService{adapter, authService} 26 | } 27 | 28 | // ListAll catalogs from a Merchant 29 | func (c *catalogService) ListAllV2(merchantUUID string) (ct Catalogs, err error) { 30 | if merchantUUID == "" { 31 | err = ErrMerchantNotSpecified 32 | glg.Error("[SDK] Catalog ListAll: ", err.Error()) 33 | return 34 | } 35 | err = c.auth.Validate() 36 | if err != nil { 37 | glg.Error("[SDK] Catalog ListAll auth.Validate: ", err.Error()) 38 | return 39 | } 40 | headers := make(map[string]string) 41 | headers["Authorization"] = fmt.Sprintf("Bearer %s", c.auth.GetToken()) 42 | endpoint := v2Endpoint + fmt.Sprintf("/merchants/%s/catalogs", merchantUUID) 43 | resp, status, err := c.adapter.DoRequest(http.MethodGet, endpoint, nil, headers) 44 | if err != nil { 45 | glg.Error("[SDK] Catalog ListAll adapter.DoRequest: ", err.Error()) 46 | return 47 | } 48 | if status != http.StatusOK { 49 | glg.Error("[SDK] Catalog ListAll status code: ", status, " merchant: ", merchantUUID) 50 | err = fmt.Errorf("Merchant '%s' could not list catalogs", merchantUUID) 51 | glg.Error("[SDK] Catalog ListAll err: ", err) 52 | return 53 | } 54 | glg.Info("[SDK] ListAll catalogs success") 55 | return ct, json.Unmarshal(resp, &ct) 56 | } 57 | 58 | // ListChangelogs not implemented 59 | func (c *catalogService) ListChangelogs(merchantUUID string) (ct Catalogs, err error) { 60 | return 61 | } 62 | 63 | // ListUnsellableItems returns all blocked sellable items and why 64 | func (c *catalogService) ListUnsellableItems(merchantUUID, catalogID string) (ur UnsellableResponse, err error) { 65 | if err = verifyCategoryItems(merchantUUID, catalogID, "category"); err != nil { 66 | glg.Error("[SDK] Catalog ListUnsellableItems: ", err.Error()) 67 | return 68 | } 69 | err = c.auth.Validate() 70 | if err != nil { 71 | glg.Error("[SDK] Catalog ListUnsellableItems auth.Validate: ", err.Error()) 72 | return 73 | } 74 | headers := make(map[string]string) 75 | headers["Authorization"] = fmt.Sprintf("Bearer %s", c.auth.GetToken()) 76 | endpoint := v2Endpoint + fmt.Sprintf( 77 | "/merchants/%s/catalogs/%s/unsellable-items", merchantUUID, catalogID) 78 | resp, status, err := c.adapter.DoRequest(http.MethodGet, endpoint, nil, headers) 79 | if err != nil { 80 | glg.Error("[SDK] Catalog ListUnsellableItems adapter.DoRequest: ", err.Error()) 81 | return 82 | } 83 | if status != http.StatusOK { 84 | glg.Error("[SDK] Catalog ListUnsellableItems status code: ", status, " merchant: ", merchantUUID) 85 | err = fmt.Errorf( 86 | "Merchant '%s' could not list unsellable items, catalog: '%s'", 87 | merchantUUID, catalogID) 88 | glg.Error("[SDK] Catalog ListUnsellableItems err: ", err) 89 | return 90 | } 91 | glg.Info("[SDK] List Unsellable Items success") 92 | return ur, json.Unmarshal(resp, &ur) 93 | } 94 | 95 | func verifyNewCategoryInCatalog(merchantUUID, catalogID, name, resourceStatus, template string) (err error) { 96 | if merchantUUID == "" { 97 | err = ErrMerchantNotSpecified 98 | return 99 | } 100 | if catalogID == "" { 101 | return ErrCatalogNotSpecified 102 | } 103 | if len(name) > 100 { 104 | err = errors.New("Category name needs to have less than 100 characters") 105 | return 106 | } 107 | if name == "" { 108 | err = fmt.Errorf("Category name on catalog '%s' was not specified", catalogID) 109 | return 110 | } 111 | if (resourceStatus != "AVAILABLE") && (resourceStatus != "UNAVAILABLE") { 112 | err = fmt.Errorf( 113 | "Category status on catalog '%s' should be 'AVAILABLE' or 'UNAVAILABLE' and is '%s'", 114 | catalogID, resourceStatus) 115 | return 116 | } 117 | if (template != "DEFAULT") && (template != "PIZZA") { 118 | err = fmt.Errorf( 119 | "Category template on catalog '%s' should be 'DEFAULT' or 'PIZZA' and is '%s'", 120 | catalogID, template) 121 | return 122 | } 123 | return 124 | } 125 | 126 | func verifyCategoryItems(merchantID, catalogID, categoryID string) (err error) { 127 | if merchantID == "" { 128 | return ErrMerchantNotSpecified 129 | } 130 | if catalogID == "" { 131 | return ErrCatalogNotSpecified 132 | } 133 | if categoryID == "" { 134 | return ErrCategoryNotSpecified 135 | } 136 | return 137 | } 138 | -------------------------------------------------------------------------------- /services/catalog/catalog_test.go: -------------------------------------------------------------------------------- 1 | package catalog 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 11 | "github.com/arxdsilva/golang-ifood-sdk/mocks" 12 | auth "github.com/arxdsilva/golang-ifood-sdk/services/authentication" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/mock" 15 | ) 16 | 17 | var catalogsv2 = `[ 18 | { 19 | "catalogId":"string", 20 | "status":"AVAILABLE", 21 | "context":[ 22 | "DEFAULT", 23 | "INDOOR" 24 | ], 25 | "modifiedAt":"2021-03-28T13:16:56.574Z" 26 | }, 27 | { 28 | "catalogId":"string", 29 | "status":"AVAILABLE", 30 | "context":[ 31 | "DEFAULT", 32 | "INDOOR" 33 | ], 34 | "modifiedAt":"2021-03-28T13:16:56.574Z" 35 | } 36 | ]` 37 | 38 | var unsellableResp = `{ 39 | "categories": [{ 40 | "id": "string", 41 | "status": "string", 42 | "template": "string", 43 | "restrictions": [ 44 | "HAS_VIOLATION", 45 | "CATEGORY_PAUSED" 46 | ], 47 | "unsellableItems": [{ 48 | "id": "string", 49 | "productId": "string" 50 | }] 51 | }] 52 | }` 53 | 54 | func Test_verifyNewCategoryInCatalog_no_merchant(t *testing.T) { 55 | err := verifyNewCategoryInCatalog("", "", "", "", "") 56 | assert.NotNil(t, err) 57 | assert.Equal(t, err, ErrMerchantNotSpecified) 58 | } 59 | 60 | func Test_verifyNewCategoryInCatalog_no_category(t *testing.T) { 61 | err := verifyNewCategoryInCatalog("merchant", "", "", "", "") 62 | assert.NotNil(t, err) 63 | assert.Equal(t, err, ErrCatalogNotSpecified) 64 | } 65 | 66 | func Test_verifyNewCategoryInCatalog_name_too_big(t *testing.T) { 67 | name := "namenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamename" 68 | err := verifyNewCategoryInCatalog("merchant", "catalog", name, "", "") 69 | assert.NotNil(t, err) 70 | assert.Contains(t, err.Error(), "less than 100 characters") 71 | } 72 | 73 | func Test_verifyNewCategoryInCatalog_no_name(t *testing.T) { 74 | name := "" 75 | err := verifyNewCategoryInCatalog("merchant", "catalog", name, "", "") 76 | assert.NotNil(t, err) 77 | assert.Contains(t, err.Error(), "name on catalog") 78 | } 79 | 80 | func Test_verifyNewCategoryInCatalog_no_resource(t *testing.T) { 81 | name := "name" 82 | err := verifyNewCategoryInCatalog("merchant", "catalog", name, "", "") 83 | assert.NotNil(t, err) 84 | assert.Contains(t, err.Error(), "should be 'AVAILABLE' or 'UNAVAILABLE'") 85 | } 86 | 87 | func Test_verifyNewCategoryInCatalog_no_template(t *testing.T) { 88 | name := "name" 89 | err := verifyNewCategoryInCatalog("merchant", "catalog", name, "AVAILABLE", "") 90 | assert.NotNil(t, err) 91 | assert.Contains(t, err.Error(), "'DEFAULT' or 'PIZZA' and") 92 | } 93 | 94 | func Test_verifyNewCategoryInCatalog_OK(t *testing.T) { 95 | name := "name" 96 | err := verifyNewCategoryInCatalog("merchant", "catalog", name, "AVAILABLE", "DEFAULT") 97 | assert.Nil(t, err) 98 | } 99 | 100 | func TestListAllV2_OK(t *testing.T) { 101 | ts := httptest.NewServer( 102 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 103 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs", r.URL.Path) 104 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 105 | assert.Equal(t, r.Method, http.MethodGet) 106 | w.WriteHeader(http.StatusOK) 107 | fmt.Fprintf(w, catalogsv2) 108 | }), 109 | ) 110 | defer ts.Close() 111 | am := auth.AuthMock{} 112 | am.On("Validate").Once().Return(nil) 113 | am.On("GetToken").Once().Return("token") 114 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 115 | ordersService := New(adapter, &am) 116 | assert.NotNil(t, ordersService) 117 | list, err := ordersService.ListAllV2("merchant_id") 118 | assert.Nil(t, err) 119 | assert.Equal(t, 2, len(list)) 120 | } 121 | 122 | func TestListAllV2_NoRefereceID(t *testing.T) { 123 | am := auth.AuthMock{} 124 | am.On("Validate").Once().Return(nil) 125 | am.On("GetToken").Once().Return("token") 126 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 127 | ordersService := New(adapter, &am) 128 | assert.NotNil(t, ordersService) 129 | _, err := ordersService.ListAllV2("") 130 | assert.NotNil(t, err) 131 | assert.Equal(t, ErrMerchantNotSpecified, err) 132 | } 133 | 134 | func TestListAllV2_ValidateErr(t *testing.T) { 135 | am := auth.AuthMock{} 136 | am.On("Validate").Once().Return(errors.New("some err")) 137 | am.On("GetToken").Once().Return("token") 138 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 139 | ordersService := New(adapter, &am) 140 | assert.NotNil(t, ordersService) 141 | _, err := ordersService.ListAllV2("merchant_id") 142 | assert.NotNil(t, err) 143 | assert.Contains(t, err.Error(), "some") 144 | } 145 | 146 | func TestListAllV2_StatusBadRequest(t *testing.T) { 147 | ts := httptest.NewServer( 148 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 149 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs", r.URL.Path) 150 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 151 | assert.Equal(t, r.Method, http.MethodGet) 152 | w.WriteHeader(http.StatusBadRequest) 153 | }), 154 | ) 155 | defer ts.Close() 156 | am := auth.AuthMock{} 157 | am.On("Validate").Once().Return(nil) 158 | am.On("GetToken").Once().Return("token") 159 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 160 | ordersService := New(adapter, &am) 161 | assert.NotNil(t, ordersService) 162 | _, err := ordersService.ListAllV2("merchant_id") 163 | assert.NotNil(t, err) 164 | assert.Contains(t, err.Error(), "could not list catalogs") 165 | } 166 | 167 | func TestListAllV2_DoReqErr(t *testing.T) { 168 | am := auth.AuthMock{} 169 | am.On("Validate").Once().Return(nil) 170 | am.On("GetToken").Once().Return("token") 171 | httpmock := &mocks.HttpClientMock{} 172 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 173 | adapter := httpadapter.New(httpmock, "") 174 | ordersService := New(adapter, &am) 175 | assert.NotNil(t, ordersService) 176 | _, err := ordersService.ListAllV2("reference_id") 177 | assert.NotNil(t, err) 178 | assert.Contains(t, err.Error(), "some") 179 | } 180 | 181 | func TestListUnsellableItems_OK(t *testing.T) { 182 | ts := httptest.NewServer( 183 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 184 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog/unsellable-items", r.URL.Path) 185 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 186 | assert.Equal(t, r.Method, http.MethodGet) 187 | w.WriteHeader(http.StatusOK) 188 | fmt.Fprintf(w, unsellableResp) 189 | }), 190 | ) 191 | defer ts.Close() 192 | am := auth.AuthMock{} 193 | am.On("Validate").Once().Return(nil) 194 | am.On("GetToken").Once().Return("token") 195 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 196 | ordersService := New(adapter, &am) 197 | assert.NotNil(t, ordersService) 198 | list, err := ordersService.ListUnsellableItems("merchant_id", "catalog") 199 | assert.Nil(t, err) 200 | assert.Equal(t, "string", list.Categories[0].ID) 201 | } 202 | 203 | func TestListUnsellableItems_NoRefereceID(t *testing.T) { 204 | am := auth.AuthMock{} 205 | am.On("Validate").Once().Return(nil) 206 | am.On("GetToken").Once().Return("token") 207 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 208 | ordersService := New(adapter, &am) 209 | assert.NotNil(t, ordersService) 210 | _, err := ordersService.ListUnsellableItems("", "catalog") 211 | assert.NotNil(t, err) 212 | assert.Equal(t, ErrMerchantNotSpecified, err) 213 | } 214 | 215 | func TestListUnsellableItems_ValidateErr(t *testing.T) { 216 | am := auth.AuthMock{} 217 | am.On("Validate").Once().Return(errors.New("some err")) 218 | am.On("GetToken").Once().Return("token") 219 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 220 | ordersService := New(adapter, &am) 221 | assert.NotNil(t, ordersService) 222 | _, err := ordersService.ListUnsellableItems("merchant_id", "catalog") 223 | assert.NotNil(t, err) 224 | assert.Contains(t, err.Error(), "some") 225 | } 226 | 227 | func TestListUnsellableItems_StatusBadRequest(t *testing.T) { 228 | ts := httptest.NewServer( 229 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 230 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog/unsellable-items", r.URL.Path) 231 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 232 | assert.Equal(t, r.Method, http.MethodGet) 233 | w.WriteHeader(http.StatusBadRequest) 234 | }), 235 | ) 236 | defer ts.Close() 237 | am := auth.AuthMock{} 238 | am.On("Validate").Once().Return(nil) 239 | am.On("GetToken").Once().Return("token") 240 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 241 | ordersService := New(adapter, &am) 242 | assert.NotNil(t, ordersService) 243 | _, err := ordersService.ListUnsellableItems("merchant_id", "catalog") 244 | assert.NotNil(t, err) 245 | assert.Contains(t, err.Error(), "could not list unsellable items") 246 | } 247 | 248 | func TestListUnsellableItems_DoReqErr(t *testing.T) { 249 | am := auth.AuthMock{} 250 | am.On("Validate").Once().Return(nil) 251 | am.On("GetToken").Once().Return("token") 252 | httpmock := &mocks.HttpClientMock{} 253 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 254 | adapter := httpadapter.New(httpmock, "") 255 | ordersService := New(adapter, &am) 256 | assert.NotNil(t, ordersService) 257 | _, err := ordersService.ListUnsellableItems("reference_id", "catalog") 258 | assert.NotNil(t, err) 259 | assert.Contains(t, err.Error(), "some") 260 | } 261 | 262 | func Test_verifyCategoryItems_NoCatalog(t *testing.T) { 263 | err := verifyCategoryItems("m_id", "", "") 264 | assert.NotNil(t, err) 265 | assert.Equal(t, ErrCatalogNotSpecified, err) 266 | } 267 | 268 | func Test_verifyCategoryItems_NoCategory(t *testing.T) { 269 | err := verifyCategoryItems("m_id", "catalog", "") 270 | assert.NotNil(t, err) 271 | assert.Equal(t, ErrCategoryNotSpecified, err) 272 | } 273 | -------------------------------------------------------------------------------- /services/catalog/category.go: -------------------------------------------------------------------------------- 1 | package catalog 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 9 | "github.com/kpango/glg" 10 | ) 11 | 12 | // ListAllCategoriesInCatalog gets categories in a catalog 13 | func (c *catalogService) ListAllCategoriesInCatalog(merchantUUID, catalogID string) (cr CategoryResponse, err error) { 14 | if err = verifyCategoryItems(merchantUUID, catalogID, "category"); err != nil { 15 | glg.Error("[SDK] Catalog ListAllCategoriesInCatalog: ", err.Error()) 16 | return 17 | } 18 | err = c.auth.Validate() 19 | if err != nil { 20 | glg.Error("[SDK] Catalog ListAllCategoriesInCatalog auth.Validate: ", err.Error()) 21 | return 22 | } 23 | headers := make(map[string]string) 24 | headers["Authorization"] = fmt.Sprintf("Bearer %s", c.auth.GetToken()) 25 | endpoint := v2Endpoint + fmt.Sprintf( 26 | "/merchants/%s/catalogs/%s/categories", merchantUUID, catalogID) 27 | resp, status, err := c.adapter.DoRequest(http.MethodGet, endpoint, nil, headers) 28 | if err != nil { 29 | glg.Error("[SDK] Catalog ListAllCategoriesInCatalog adapter.DoRequest: ", err.Error()) 30 | return 31 | } 32 | if status != http.StatusOK { 33 | glg.Error("[SDK] Catalog ListAllCategoriesInCatalog status code: ", status, " merchant: ", merchantUUID) 34 | err = fmt.Errorf( 35 | "Merchant '%s' could not list categories in catalog '%s'", 36 | merchantUUID, catalogID) 37 | glg.Error("[SDK] Catalog ListAllCategoriesInCatalog err: ", err) 38 | return 39 | } 40 | glg.Info("[SDK] ListAll Categories success") 41 | return cr, json.Unmarshal(resp, &cr) 42 | } 43 | 44 | // CreateCategoryInCatalog adds a category in a specified catalog 45 | // 46 | // resource status = [AVAILABLE || UNAVAILABLE] 47 | // template = [DEFAULT || PIZZA] 48 | // 49 | func (c *catalogService) CreateCategoryInCatalog(merchantUUID, catalogID, name, resourceStatus, template, externalCode string) (cr CategoryCreateResponse, err error) { 50 | err = verifyNewCategoryInCatalog(merchantUUID, catalogID, name, resourceStatus, template) 51 | if err != nil { 52 | glg.Error("[SDK] Catalog CreateCategoryInCatalog verifyNewCategoryInCatalog: ", err.Error()) 53 | return 54 | } 55 | err = c.auth.Validate() 56 | if err != nil { 57 | glg.Error("[SDK] Catalog CreateCategoryInCatalog auth.Validate: ", err.Error()) 58 | return 59 | } 60 | headers := make(map[string]string) 61 | headers["Authorization"] = fmt.Sprintf("Bearer %s", c.auth.GetToken()) 62 | headers["Content-Type"] = "application/json" 63 | endpoint := v2Endpoint + fmt.Sprintf( 64 | "/merchants/%s/catalogs/%s/categories", merchantUUID, catalogID) 65 | ci := CategoryItem{Name: name, Status: resourceStatus, Template: template, ExternalCode: externalCode} 66 | reader, err := httpadapter.NewJsonReader(ci) 67 | if err != nil { 68 | glg.Error("[SDK] Catalog CreateCategoryInCatalog NewJsonReader error: ", err.Error()) 69 | return 70 | } 71 | resp, status, err := c.adapter.DoRequest(http.MethodPost, endpoint, reader, headers) 72 | if err != nil { 73 | glg.Error("[SDK] Catalog CreateCategoryInCatalog adapter.DoRequest: ", err.Error()) 74 | return 75 | } 76 | if status != http.StatusCreated { 77 | glg.Error("[SDK] Catalog CreateCategoryInCatalog status code: ", status, " merchant: ", merchantUUID) 78 | err = fmt.Errorf( 79 | "Merchant '%s' could not create category in catalog '%s'", 80 | merchantUUID, catalogID) 81 | glg.Error("[SDK] Catalog CreateCategoryInCatalog err: ", err) 82 | return 83 | } 84 | glg.Info("[SDK] Get Category success") 85 | return cr, json.Unmarshal(resp, &cr) 86 | } 87 | 88 | // GetCategoryInCatalog lists a category in a specified catalog 89 | func (c *catalogService) GetCategoryInCatalog(merchantUUID, catalogID, categoryID string) (cr CategoryResponse, err error) { 90 | if err = verifyCategoryItems(merchantUUID, catalogID, categoryID); err != nil { 91 | glg.Error("[SDK] Catalog GetCategoryInCatalog verifyCategoryItems: ", err.Error()) 92 | return 93 | } 94 | err = c.auth.Validate() 95 | if err != nil { 96 | glg.Error("[SDK] Catalog GetCategoryInCatalog auth.Validate: ", err.Error()) 97 | return 98 | } 99 | headers := make(map[string]string) 100 | headers["Authorization"] = fmt.Sprintf("Bearer %s", c.auth.GetToken()) 101 | endpoint := v2Endpoint + fmt.Sprintf( 102 | "/merchants/%s/catalogs/%s/categories/%s", merchantUUID, catalogID, categoryID) 103 | resp, status, err := c.adapter.DoRequest(http.MethodGet, endpoint, nil, headers) 104 | if err != nil { 105 | glg.Error("[SDK] Catalog GetCategoryInCatalog adapter.DoRequest: ", err.Error()) 106 | return 107 | } 108 | if status != http.StatusOK { 109 | glg.Error("[SDK] Catalog GetCategoryInCatalog status code: ", status, " merchant: ", merchantUUID) 110 | err = fmt.Errorf( 111 | "Merchant '%s' could not get category '%s' in catalog '%s'", 112 | merchantUUID, categoryID, catalogID) 113 | glg.Error("[SDK] Catalog GetCategoryInCatalog err: ", err) 114 | return 115 | } 116 | glg.Info("[SDK] Get Category success") 117 | return cr, json.Unmarshal(resp, &cr) 118 | } 119 | 120 | // EditCategoryInCatalog changes a category in a specified catalog 121 | // 122 | // resource status = [AVAILABLE || UNAVAILABLE] 123 | func (c *catalogService) EditCategoryInCatalog(merchantUUID, catalogID, categoryID, name, resourceStatus, externalCode string, sequence int) (cr CategoryCreateResponse, err error) { 124 | err = verifyNewCategoryInCatalog(merchantUUID, catalogID, name, resourceStatus, "DEFAULT") 125 | if err != nil { 126 | glg.Error("[SDK] Catalog EditCategoryInCatalog verifyNewCategoryInCatalog: ", err.Error()) 127 | return 128 | } 129 | err = c.auth.Validate() 130 | if err != nil { 131 | glg.Error("[SDK] Catalog EditCategoryInCatalog auth.Validate: ", err.Error()) 132 | return 133 | } 134 | headers := make(map[string]string) 135 | headers["Authorization"] = fmt.Sprintf("Bearer %s", c.auth.GetToken()) 136 | headers["Content-Type"] = "application/json" 137 | endpoint := v2Endpoint + fmt.Sprintf( 138 | "/merchants/%s/catalogs/%s/categories/%s", merchantUUID, catalogID, categoryID) 139 | ci := CategoryItem{ 140 | Status: resourceStatus, 141 | ExternalCode: externalCode, 142 | Name: name, 143 | Sequence: sequence, 144 | } 145 | body, err := httpadapter.NewJsonReader(ci) 146 | if err != nil { 147 | glg.Error("[SDK] Catalog EditCategoryInCatalog NewJsonReader error: ", err.Error()) 148 | return 149 | } 150 | resp, status, err := c.adapter.DoRequest(http.MethodPatch, endpoint, body, headers) 151 | if err != nil { 152 | glg.Error("[SDK] Catalog EditCategoryInCatalog adapter.DoRequest: ", err.Error()) 153 | return 154 | } 155 | if status != http.StatusOK { 156 | glg.Error("[SDK] Catalog EditCategoryInCatalog status code: ", status, " merchant: ", merchantUUID) 157 | err = fmt.Errorf( 158 | "Merchant '%s' could not edit category '%s' in catalog '%s'", 159 | merchantUUID, catalogID, catalogID) 160 | glg.Error("[SDK] Catalog EditCategoryInCatalog err: ", err) 161 | return 162 | } 163 | glg.Info("[SDK] Edit Category success") 164 | return cr, json.Unmarshal(resp, &cr) 165 | } 166 | 167 | // DeleteCategoryInCatalog removes a category in a specified catalog 168 | func (c *catalogService) DeleteCategoryInCatalog(merchantUUID, catalogID, categoryID string) (err error) { 169 | if err = verifyCategoryItems(merchantUUID, catalogID, categoryID); err != nil { 170 | glg.Error("[SDK] Catalog DeleteCategoryInCatalog verifyCategoryItems: ", err.Error()) 171 | return 172 | } 173 | err = c.auth.Validate() 174 | if err != nil { 175 | glg.Error("[SDK] Catalog DeleteCategoryInCatalog auth.Validate: ", err.Error()) 176 | return 177 | } 178 | headers := make(map[string]string) 179 | headers["Authorization"] = fmt.Sprintf("Bearer %s", c.auth.GetToken()) 180 | endpoint := v2Endpoint + fmt.Sprintf( 181 | "/merchants/%s/catalogs/%s/categories/%s", merchantUUID, catalogID, categoryID) 182 | _, status, err := c.adapter.DoRequest(http.MethodDelete, endpoint, nil, headers) 183 | if err != nil { 184 | glg.Error("[SDK] Catalog DeleteCategoryInCatalog adapter.DoRequest: ", err.Error()) 185 | return 186 | } 187 | if status >= http.StatusBadRequest { 188 | glg.Error("[SDK] Catalog DeleteCategoryInCatalog status code: ", status, " merchant: ", merchantUUID) 189 | err = fmt.Errorf( 190 | "Merchant '%s' could not delete category '%s' in catalog '%s'", 191 | merchantUUID, catalogID, catalogID) 192 | glg.Error("[SDK] Catalog DeleteCategoryInCatalog err: ", err) 193 | return 194 | } 195 | glg.Info("[SDK] Delete product success") 196 | return 197 | } 198 | 199 | func (c *CategoryItem) verify() (err error) { 200 | empty := Price{} 201 | if c.Price == empty { 202 | return ErrNoPrice 203 | } 204 | if c.Price.Value == 0 { 205 | return ErrNoItemPrice 206 | } 207 | if (c.Status != "AVAILABLE") && (c.Status != "UNAVAILABLE") { 208 | return ErrInvalidStatus 209 | } 210 | if len(c.Shifts) == 0 { 211 | return ErrNoShifts 212 | } 213 | return 214 | } 215 | -------------------------------------------------------------------------------- /services/catalog/category_test.go: -------------------------------------------------------------------------------- 1 | package catalog 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 11 | "github.com/arxdsilva/golang-ifood-sdk/mocks" 12 | auth "github.com/arxdsilva/golang-ifood-sdk/services/authentication" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/mock" 15 | ) 16 | 17 | func TestListAllCategoriesInCatalog_OK(t *testing.T) { 18 | categories := `{ 19 | "id": "string", 20 | "sequence": 10, 21 | "name": "string", 22 | "externalCode": "string", 23 | "status": "string", 24 | "items": [{}], 25 | "template": "string", 26 | "pizza": { 27 | "id": "string", 28 | "sizes": [] 29 | } 30 | }` 31 | ts := httptest.NewServer( 32 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 33 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog_id/categories", r.URL.Path) 34 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 35 | assert.Equal(t, r.Method, http.MethodGet) 36 | w.WriteHeader(http.StatusOK) 37 | fmt.Fprintf(w, categories) 38 | }), 39 | ) 40 | defer ts.Close() 41 | am := auth.AuthMock{} 42 | am.On("Validate").Once().Return(nil) 43 | am.On("GetToken").Once().Return("token") 44 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 45 | ordersService := New(adapter, &am) 46 | assert.NotNil(t, ordersService) 47 | catalog, err := ordersService.ListAllCategoriesInCatalog("merchant_id", "catalog_id") 48 | assert.Nil(t, err) 49 | assert.Equal(t, 10, catalog.Sequence) 50 | } 51 | 52 | func TestListAllCategoriesInCatalog_NoMerchantID(t *testing.T) { 53 | am := auth.AuthMock{} 54 | am.On("Validate").Once().Return(nil) 55 | am.On("GetToken").Once().Return("token") 56 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 57 | ordersService := New(adapter, &am) 58 | assert.NotNil(t, ordersService) 59 | _, err := ordersService.ListAllCategoriesInCatalog("", "catalog_id") 60 | assert.NotNil(t, err) 61 | assert.Equal(t, ErrMerchantNotSpecified, err) 62 | } 63 | 64 | func TestListAllCategoriesInCatalog_ValidateErr(t *testing.T) { 65 | am := auth.AuthMock{} 66 | am.On("Validate").Once().Return(errors.New("some err")) 67 | am.On("GetToken").Once().Return("token") 68 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 69 | ordersService := New(adapter, &am) 70 | assert.NotNil(t, ordersService) 71 | _, err := ordersService.ListAllCategoriesInCatalog("merchant_id", "catalog_id") 72 | assert.NotNil(t, err) 73 | assert.Contains(t, err.Error(), "some") 74 | } 75 | 76 | func TestListAllCategoriesInCatalog_StatusBadRequest(t *testing.T) { 77 | ts := httptest.NewServer( 78 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 79 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog_id/categories", r.URL.Path) 80 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 81 | assert.Equal(t, r.Method, http.MethodGet) 82 | w.WriteHeader(http.StatusBadRequest) 83 | }), 84 | ) 85 | defer ts.Close() 86 | am := auth.AuthMock{} 87 | am.On("Validate").Once().Return(nil) 88 | am.On("GetToken").Once().Return("token") 89 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 90 | ordersService := New(adapter, &am) 91 | assert.NotNil(t, ordersService) 92 | _, err := ordersService.ListAllCategoriesInCatalog("merchant_id", "catalog_id") 93 | assert.NotNil(t, err) 94 | assert.Contains(t, err.Error(), "could not list categories in catalog") 95 | } 96 | 97 | func TestListAllCategoriesInCatalog_DoReqErr(t *testing.T) { 98 | am := auth.AuthMock{} 99 | am.On("Validate").Once().Return(nil) 100 | am.On("GetToken").Once().Return("token") 101 | httpmock := &mocks.HttpClientMock{} 102 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 103 | adapter := httpadapter.New(httpmock, "") 104 | ordersService := New(adapter, &am) 105 | assert.NotNil(t, ordersService) 106 | _, err := ordersService.ListAllCategoriesInCatalog("merchant_id", "catalog_id") 107 | assert.NotNil(t, err) 108 | assert.Contains(t, err.Error(), "some") 109 | } 110 | 111 | func TestCreateCategoryInCatalog_OK(t *testing.T) { 112 | resp := `{ 113 | "id":"string", 114 | "name":"string", 115 | "externalCode":"string", 116 | "status":"AVAILABLE", 117 | "sequence":10, 118 | "template":"DEFAULT" 119 | }` 120 | ts := httptest.NewServer( 121 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 122 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog_id/categories", r.URL.Path) 123 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 124 | assert.Equal(t, r.Method, http.MethodPost) 125 | w.WriteHeader(http.StatusCreated) 126 | fmt.Fprintf(w, resp) 127 | }), 128 | ) 129 | defer ts.Close() 130 | am := auth.AuthMock{} 131 | am.On("Validate").Once().Return(nil) 132 | am.On("GetToken").Once().Return("token") 133 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 134 | ordersService := New(adapter, &am) 135 | assert.NotNil(t, ordersService) 136 | category, err := ordersService.CreateCategoryInCatalog("merchant_id", "catalog_id", "name", "AVAILABLE", "DEFAULT", "") 137 | assert.Nil(t, err) 138 | assert.Equal(t, 10, category.Sequence) 139 | } 140 | 141 | func TestCreateCategoryInCatalog_NoMerchantID(t *testing.T) { 142 | am := auth.AuthMock{} 143 | am.On("Validate").Once().Return(nil) 144 | am.On("GetToken").Once().Return("token") 145 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 146 | ordersService := New(adapter, &am) 147 | assert.NotNil(t, ordersService) 148 | _, err := ordersService.CreateCategoryInCatalog("", "catalog_id", "name", "AVAILABLE", "DEFAULT", "") 149 | assert.NotNil(t, err) 150 | assert.Equal(t, ErrMerchantNotSpecified, err) 151 | } 152 | 153 | func TestCreateCategoryInCatalog_ValidateErr(t *testing.T) { 154 | am := auth.AuthMock{} 155 | am.On("Validate").Once().Return(errors.New("some err")) 156 | am.On("GetToken").Once().Return("token") 157 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 158 | ordersService := New(adapter, &am) 159 | assert.NotNil(t, ordersService) 160 | _, err := ordersService.CreateCategoryInCatalog("merchant_id", "catalog_id", "name", "AVAILABLE", "DEFAULT", "") 161 | assert.NotNil(t, err) 162 | assert.Contains(t, err.Error(), "some") 163 | } 164 | 165 | func TestCreateCategoryInCatalog_StatusBadRequest(t *testing.T) { 166 | ts := httptest.NewServer( 167 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 168 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog_id/categories", r.URL.Path) 169 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 170 | assert.Equal(t, r.Method, http.MethodPost) 171 | w.WriteHeader(http.StatusBadRequest) 172 | }), 173 | ) 174 | defer ts.Close() 175 | am := auth.AuthMock{} 176 | am.On("Validate").Once().Return(nil) 177 | am.On("GetToken").Once().Return("token") 178 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 179 | ordersService := New(adapter, &am) 180 | assert.NotNil(t, ordersService) 181 | _, err := ordersService.CreateCategoryInCatalog("merchant_id", "catalog_id", "name", "AVAILABLE", "DEFAULT", "") 182 | assert.NotNil(t, err) 183 | assert.Contains(t, err.Error(), "could not create category") 184 | } 185 | 186 | func TestCreateCategoryInCatalog_DoReqErr(t *testing.T) { 187 | am := auth.AuthMock{} 188 | am.On("Validate").Once().Return(nil) 189 | am.On("GetToken").Once().Return("token") 190 | httpmock := &mocks.HttpClientMock{} 191 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 192 | adapter := httpadapter.New(httpmock, "") 193 | ordersService := New(adapter, &am) 194 | assert.NotNil(t, ordersService) 195 | _, err := ordersService.CreateCategoryInCatalog("merchant_id", "catalog_id", "name", "AVAILABLE", "DEFAULT", "") 196 | assert.NotNil(t, err) 197 | assert.Contains(t, err.Error(), "some") 198 | } 199 | 200 | func TestGetCategoryInCatalog_OK(t *testing.T) { 201 | resp := `{ 202 | "id": "string", 203 | "sequence": 10, 204 | "name": "string", 205 | "externalCode": "string", 206 | "status": "string", 207 | "items": [{}], 208 | "template": "string", 209 | "pizza": { 210 | "id": "string" 211 | } 212 | }` 213 | ts := httptest.NewServer( 214 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 215 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog_id/categories/category_id", r.URL.Path) 216 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 217 | assert.Equal(t, r.Method, http.MethodGet) 218 | w.WriteHeader(http.StatusOK) 219 | fmt.Fprintf(w, resp) 220 | }), 221 | ) 222 | defer ts.Close() 223 | am := auth.AuthMock{} 224 | am.On("Validate").Once().Return(nil) 225 | am.On("GetToken").Once().Return("token") 226 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 227 | ordersService := New(adapter, &am) 228 | assert.NotNil(t, ordersService) 229 | category, err := ordersService.GetCategoryInCatalog("merchant_id", "catalog_id", "category_id") 230 | assert.Nil(t, err) 231 | assert.Equal(t, 10, category.Sequence) 232 | } 233 | 234 | func TestGetCategoryInCatalog_NoMerchantID(t *testing.T) { 235 | am := auth.AuthMock{} 236 | am.On("Validate").Once().Return(nil) 237 | am.On("GetToken").Once().Return("token") 238 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 239 | ordersService := New(adapter, &am) 240 | assert.NotNil(t, ordersService) 241 | _, err := ordersService.GetCategoryInCatalog("", "catalog_id", "category_id") 242 | assert.NotNil(t, err) 243 | assert.Equal(t, ErrMerchantNotSpecified, err) 244 | } 245 | 246 | func TestGetCategoryInCatalog_ValidateErr(t *testing.T) { 247 | am := auth.AuthMock{} 248 | am.On("Validate").Once().Return(errors.New("some err")) 249 | am.On("GetToken").Once().Return("token") 250 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 251 | ordersService := New(adapter, &am) 252 | assert.NotNil(t, ordersService) 253 | _, err := ordersService.GetCategoryInCatalog("merchant_id", "catalog_id", "category_id") 254 | assert.NotNil(t, err) 255 | assert.Contains(t, err.Error(), "some") 256 | } 257 | 258 | func TestGetCategoryInCatalog_StatusBadRequest(t *testing.T) { 259 | ts := httptest.NewServer( 260 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 261 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog_id/categories/category_id", r.URL.Path) 262 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 263 | assert.Equal(t, r.Method, http.MethodGet) 264 | w.WriteHeader(http.StatusBadRequest) 265 | }), 266 | ) 267 | defer ts.Close() 268 | am := auth.AuthMock{} 269 | am.On("Validate").Once().Return(nil) 270 | am.On("GetToken").Once().Return("token") 271 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 272 | ordersService := New(adapter, &am) 273 | assert.NotNil(t, ordersService) 274 | _, err := ordersService.GetCategoryInCatalog("merchant_id", "catalog_id", "category_id") 275 | assert.NotNil(t, err) 276 | assert.Contains(t, err.Error(), "could not get category") 277 | } 278 | 279 | func TestGetCategoryInCatalog_DoReqErr(t *testing.T) { 280 | am := auth.AuthMock{} 281 | am.On("Validate").Once().Return(nil) 282 | am.On("GetToken").Once().Return("token") 283 | httpmock := &mocks.HttpClientMock{} 284 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 285 | adapter := httpadapter.New(httpmock, "") 286 | ordersService := New(adapter, &am) 287 | assert.NotNil(t, ordersService) 288 | _, err := ordersService.GetCategoryInCatalog("merchant_id", "catalog_id", "category_id") 289 | assert.NotNil(t, err) 290 | assert.Contains(t, err.Error(), "some") 291 | } 292 | 293 | func TestEditCategoryInCatalog_OK(t *testing.T) { 294 | resp := `{ 295 | "name":"string", 296 | "externalCode":"string", 297 | "status":"AVAILABLE", 298 | "sequence":2 299 | }` 300 | ts := httptest.NewServer( 301 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 302 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog_id/categories/category_id", r.URL.Path) 303 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 304 | assert.Equal(t, r.Method, http.MethodPatch) 305 | w.WriteHeader(http.StatusOK) 306 | fmt.Fprintf(w, resp) 307 | }), 308 | ) 309 | defer ts.Close() 310 | am := auth.AuthMock{} 311 | am.On("Validate").Once().Return(nil) 312 | am.On("GetToken").Once().Return("token") 313 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 314 | ordersService := New(adapter, &am) 315 | assert.NotNil(t, ordersService) 316 | category, err := ordersService.EditCategoryInCatalog("merchant_id", "catalog_id", "category_id", "name", "AVAILABLE", "code", 2) 317 | assert.Nil(t, err) 318 | assert.Equal(t, 2, category.Sequence) 319 | } 320 | 321 | func TestEditCategoryInCatalog_NoMerchantID(t *testing.T) { 322 | am := auth.AuthMock{} 323 | am.On("Validate").Once().Return(nil) 324 | am.On("GetToken").Once().Return("token") 325 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 326 | ordersService := New(adapter, &am) 327 | assert.NotNil(t, ordersService) 328 | _, err := ordersService.EditCategoryInCatalog("", "catalog_id", "category_id", "name", "AVAILABLE", "code", 2) 329 | assert.NotNil(t, err) 330 | assert.Equal(t, ErrMerchantNotSpecified, err) 331 | } 332 | 333 | func TestEditCategoryInCatalog_ValidateErr(t *testing.T) { 334 | am := auth.AuthMock{} 335 | am.On("Validate").Once().Return(errors.New("some err")) 336 | am.On("GetToken").Once().Return("token") 337 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 338 | ordersService := New(adapter, &am) 339 | assert.NotNil(t, ordersService) 340 | _, err := ordersService.EditCategoryInCatalog("merchant_id", "catalog_id", "category_id", "name", "AVAILABLE", "code", 2) 341 | assert.NotNil(t, err) 342 | assert.Contains(t, err.Error(), "some") 343 | } 344 | 345 | func TestEditCategoryInCatalog_StatusBadRequest(t *testing.T) { 346 | ts := httptest.NewServer( 347 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 348 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog_id/categories/category_id", r.URL.Path) 349 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 350 | assert.Equal(t, r.Method, http.MethodPatch) 351 | w.WriteHeader(http.StatusBadRequest) 352 | }), 353 | ) 354 | defer ts.Close() 355 | am := auth.AuthMock{} 356 | am.On("Validate").Once().Return(nil) 357 | am.On("GetToken").Once().Return("token") 358 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 359 | ordersService := New(adapter, &am) 360 | assert.NotNil(t, ordersService) 361 | _, err := ordersService.EditCategoryInCatalog("merchant_id", "catalog_id", "category_id", "name", "AVAILABLE", "code", 2) 362 | assert.NotNil(t, err) 363 | assert.Contains(t, err.Error(), "could not edit category") 364 | } 365 | 366 | func TestEditCategoryInCatalog_DoReqErr(t *testing.T) { 367 | am := auth.AuthMock{} 368 | am.On("Validate").Once().Return(nil) 369 | am.On("GetToken").Once().Return("token") 370 | httpmock := &mocks.HttpClientMock{} 371 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 372 | adapter := httpadapter.New(httpmock, "") 373 | ordersService := New(adapter, &am) 374 | assert.NotNil(t, ordersService) 375 | _, err := ordersService.EditCategoryInCatalog("merchant_id", "catalog_id", "category_id", "name", "AVAILABLE", "code", 2) 376 | assert.NotNil(t, err) 377 | assert.Contains(t, err.Error(), "some") 378 | } 379 | 380 | func TestDeleteCategoryInCatalog_OK(t *testing.T) { 381 | resp := `{ 382 | "name":"string", 383 | "externalCode":"string", 384 | "status":"AVAILABLE", 385 | "sequence":2 386 | }` 387 | ts := httptest.NewServer( 388 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 389 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog_id/categories/category_id", r.URL.Path) 390 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 391 | assert.Equal(t, r.Method, http.MethodDelete) 392 | w.WriteHeader(http.StatusOK) 393 | fmt.Fprintf(w, resp) 394 | }), 395 | ) 396 | defer ts.Close() 397 | am := auth.AuthMock{} 398 | am.On("Validate").Once().Return(nil) 399 | am.On("GetToken").Once().Return("token") 400 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 401 | ordersService := New(adapter, &am) 402 | assert.NotNil(t, ordersService) 403 | err := ordersService.DeleteCategoryInCatalog("merchant_id", "catalog_id", "category_id") 404 | assert.Nil(t, err) 405 | } 406 | 407 | func TestDeleteCategoryInCatalog_NoMerchantID(t *testing.T) { 408 | am := auth.AuthMock{} 409 | am.On("Validate").Once().Return(nil) 410 | am.On("GetToken").Once().Return("token") 411 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 412 | ordersService := New(adapter, &am) 413 | assert.NotNil(t, ordersService) 414 | err := ordersService.DeleteCategoryInCatalog("", "catalog_id", "category_id") 415 | assert.NotNil(t, err) 416 | assert.Equal(t, ErrMerchantNotSpecified, err) 417 | } 418 | 419 | func TestDeleteCategoryInCatalog_ValidateErr(t *testing.T) { 420 | am := auth.AuthMock{} 421 | am.On("Validate").Once().Return(errors.New("some err")) 422 | am.On("GetToken").Once().Return("token") 423 | adapter := httpadapter.New(http.DefaultClient, "ts.URL") 424 | ordersService := New(adapter, &am) 425 | assert.NotNil(t, ordersService) 426 | err := ordersService.DeleteCategoryInCatalog("merchant_id", "catalog_id", "category_id") 427 | assert.NotNil(t, err) 428 | assert.Contains(t, err.Error(), "some") 429 | } 430 | 431 | func TestDeleteCategoryInCatalog_StatusBadRequest(t *testing.T) { 432 | ts := httptest.NewServer( 433 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 434 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/catalogs/catalog_id/categories/category_id", r.URL.Path) 435 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 436 | assert.Equal(t, r.Method, http.MethodDelete) 437 | w.WriteHeader(http.StatusBadRequest) 438 | }), 439 | ) 440 | defer ts.Close() 441 | am := auth.AuthMock{} 442 | am.On("Validate").Once().Return(nil) 443 | am.On("GetToken").Once().Return("token") 444 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 445 | ordersService := New(adapter, &am) 446 | assert.NotNil(t, ordersService) 447 | err := ordersService.DeleteCategoryInCatalog("merchant_id", "catalog_id", "category_id") 448 | assert.NotNil(t, err) 449 | assert.Contains(t, err.Error(), "could not delete category") 450 | } 451 | 452 | func TestDeleteCategoryInCatalog_DoReqErr(t *testing.T) { 453 | am := auth.AuthMock{} 454 | am.On("Validate").Once().Return(nil) 455 | am.On("GetToken").Once().Return("token") 456 | httpmock := &mocks.HttpClientMock{} 457 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 458 | adapter := httpadapter.New(httpmock, "") 459 | ordersService := New(adapter, &am) 460 | assert.NotNil(t, ordersService) 461 | err := ordersService.DeleteCategoryInCatalog("merchant_id", "catalog_id", "category_id") 462 | assert.NotNil(t, err) 463 | assert.Contains(t, err.Error(), "some") 464 | } 465 | -------------------------------------------------------------------------------- /services/catalog/errors.go: -------------------------------------------------------------------------------- 1 | package catalog 2 | 3 | import "errors" 4 | 5 | var ( 6 | // ErrMerchantNotSpecified no merchant given 7 | ErrMerchantNotSpecified = errors.New("merchant not specified") 8 | // ErrCatalogNotSpecified no catalog id given 9 | ErrCatalogNotSpecified = errors.New("Catalog ID was not specified") 10 | // ErrCategoryNotSpecified no categiry id given 11 | ErrCategoryNotSpecified = errors.New("Category ID was not specified") 12 | // ErrSizesNotSpecified no pizza size 13 | ErrSizesNotSpecified = errors.New("Pizza sizes were not specified") 14 | 15 | // ErrCrustsNotSpecified no pizza crust 16 | ErrCrustsNotSpecified = errors.New("Pizza crusts were not specified") 17 | // ErrCrustNameNotSpecified no pizza crust name 18 | ErrCrustNameNotSpecified = errors.New("Pizza crust name was not specified") 19 | 20 | // ErrEdgeNameNotSpecified no pizza edge name 21 | ErrEdgeNameNotSpecified = errors.New("Pizza edge name was not specified") 22 | // ErrEdgesNotSpecified no pizza edge 23 | ErrEdgesNotSpecified = errors.New("Pizza edges were not specified") 24 | 25 | // ErrToppingNameNotSpecified no pizza topping name 26 | ErrToppingNameNotSpecified = errors.New("Pizza Topping name was not specified") 27 | // ErrToppingsNotSpecified no pizza topping 28 | ErrToppingsNotSpecified = errors.New("Pizza Toppings were not specified") 29 | 30 | // ErrShiftsNotSpecified no shift 31 | ErrShiftsNotSpecified = errors.New("Pizza Shifts were not specified") 32 | 33 | // ErrSizeNameNotSpecified no pizza size 34 | ErrSizeNameNotSpecified = errors.New("Pizza size name was not specified") 35 | 36 | // ErrInvalidPizzaStatus no pizza status 37 | ErrInvalidPizzaStatus = errors.New("INVALID Pizza size status, it should be 'AVAILABLE' or 'UNAVAILABLE'") 38 | // ErrInvalidPizzaStartEndTime no pizza start/end time 39 | ErrInvalidPizzaStartEndTime = errors.New("INVALID Pizza start or end time, should be between 00:00 and 23:59") 40 | // ErrInvalidPizzaCrustStatus no pizza crust status 41 | ErrInvalidPizzaCrustStatus = errors.New("INVALID Pizza crust status, it should be 'AVAILABLE' or 'UNAVAILABLE'") 42 | // ErrInvalidPizzaEdgeStatus no pizza edge status 43 | ErrInvalidPizzaEdgeStatus = errors.New("INVALID Pizza edge status, it should be 'AVAILABLE' or 'UNAVAILABLE'") 44 | // ErrInvalidPizzaToppingStatus no pizza topping status 45 | ErrInvalidPizzaToppingStatus = errors.New("INVALID Pizza topping status, it should be 'AVAILABLE' or 'UNAVAILABLE'") 46 | 47 | // ErrNoAcceptedFractions no pizza fractions 48 | ErrNoAcceptedFractions = errors.New("Pizza needs at least one accepted fraction") 49 | 50 | // ErrNoProductName no product name 51 | ErrNoProductName = errors.New("Product needs a name") 52 | // ErrNoProductID no product id 53 | ErrNoProductID = errors.New("productID not specified") 54 | // ErrNoItemPrice no item price 55 | ErrNoItemPrice = errors.New("item needs the price value to be specified") 56 | // ErrNoPrice no price 57 | ErrNoPrice = errors.New("item needs the price struct filled") 58 | // ErrInvalidStatus invalid status 59 | ErrInvalidStatus = errors.New("INVALID status, it should be 'AVAILABLE' or 'UNAVAILABLE'") 60 | // ErrNoShifts no shift 61 | ErrNoShifts = errors.New("Item needs at least one shift") 62 | ) 63 | -------------------------------------------------------------------------------- /services/catalog/interface.go: -------------------------------------------------------------------------------- 1 | package catalog 2 | 3 | // Service describes the catalog abstraction 4 | type Service interface { 5 | ListAllV2(merchantID string) (Catalogs, error) 6 | ListUnsellableItems(merchantUUID, catalogID string) (UnsellableResponse, error) 7 | ListAllCategoriesInCatalog(merchantUUID, catalogID string) (CategoryResponse, error) 8 | CreateCategoryInCatalog(merchantUUID, catalogID, name, resourceStatus, template, externalCode string) (CategoryCreateResponse, error) 9 | GetCategoryInCatalog(merchantUUID, catalogID, categoryID string) (CategoryResponse, error) 10 | EditCategoryInCatalog(merchantUUID, catalogID, categoryID, name, resourceStatus, externalCode string, sequence int) (CategoryCreateResponse, error) 11 | DeleteCategoryInCatalog(merchantUUID, catalogID, categoryID string) error 12 | ListProducts(merchantUUID string) (Products, error) 13 | CreateProduct(merchantUUID string, product Product) (Product, error) 14 | EditProduct(merchantUUID string, product Product) (Product, error) 15 | DeleteProduct(merchantUUID, productID string) error 16 | UpdateProductStatus(merchantUUID, productID, productStatus string) error 17 | LinkProductToCategory(merchantUUID, categoryID string, product ProductLink) error 18 | CreatePizza(merchantUUID string, pizza Pizza) (Pizza, error) 19 | ListPizzas(merchantUUID string) (Pizzas, error) 20 | UpdatePizza(merchantUUID string, pizza Pizza) error 21 | UpdatePizzaStatus(merchantUUID, pizzaStatus, pizzaID string) error 22 | UnlinkPizzaCategory(merchantUUID, pizzaID, categoryID string) error 23 | LinkPizzaToCategory(merchantUUID, categoryID string, pizza Pizza) error 24 | UnlinkProductToCategory(merchantUUID, categoryID, productID string) error 25 | } 26 | -------------------------------------------------------------------------------- /services/catalog/item.go: -------------------------------------------------------------------------------- 1 | package catalog 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 9 | "github.com/kpango/glg" 10 | ) 11 | 12 | // CreateItem product-category association 13 | // 14 | // POST 15 | // 16 | // 201 created 17 | // 400 bad req 18 | // 404 not found 19 | // 409 conflict 20 | // 21 | // Response OK: 22 | // { 23 | // "status":"AVAILABLE", 24 | // "price":{ 25 | // "value":0, 26 | // "originalValue":0 27 | // }, 28 | // "externalCode":"string", 29 | // "sequence":0, 30 | // "shifts":[{...}] 31 | // } 32 | func (c *catalogService) CreateItem(merchantID, categoryID, productID string, ci CategoryItem) (cp ProductLink, err error) { 33 | err = verifyCategoryItems(merchantID, categoryID, productID) 34 | if err != nil { 35 | glg.Error("[SDK] Catalog CreateItem verifyCategoryItems: ", err.Error()) 36 | return 37 | } 38 | if err = ci.verify(); err != nil { 39 | glg.Error("[SDK] Catalog CreateItem verify: ", err.Error()) 40 | return 41 | } 42 | err = c.auth.Validate() 43 | if err != nil { 44 | glg.Error("[SDK] Catalog CreateItem auth.Validate: ", err.Error()) 45 | return 46 | } 47 | headers := make(map[string]string) 48 | headers["Authorization"] = fmt.Sprintf("Bearer %s", c.auth.GetToken()) 49 | headers["Content-Type"] = "application/json" 50 | endpoint := v2Endpoint + fmt.Sprintf( 51 | "/merchants/%s/categories/%s/products/%s", merchantID, categoryID, productID) 52 | reader, err := httpadapter.NewJsonReader(ci) 53 | if err != nil { 54 | glg.Error("[SDK] Catalog CreateItem NewJsonReader error: ", err.Error()) 55 | return 56 | } 57 | resp, status, err := c.adapter.DoRequest(http.MethodPost, endpoint, reader, headers) 58 | if err != nil { 59 | glg.Error("[SDK] Catalog CreateItem adapter.DoRequest: ", err.Error()) 60 | return 61 | } 62 | if status != http.StatusCreated { 63 | badResp := &apiError{} 64 | err = json.Unmarshal(resp, badResp) 65 | if err != nil { 66 | glg.Error("[SDK] Catalog DeleteProduct Unmarshal: ", err) 67 | return 68 | } 69 | glg.Error("[SDK] Catalog CreateItem status code: ", status, " merchant: ", merchantID) 70 | err = fmt.Errorf( 71 | "Merchant '%s' could not create item category '%s', code: '%s'", 72 | merchantID, categoryID, badResp.Details.Code) 73 | glg.Error("[SDK] Catalog CreateItem err: ", err) 74 | return 75 | } 76 | glg.Infof("[SDK] Catalog CreateItem success product id '%s', merchant '%s'", productID, merchantID) 77 | return cp, json.Unmarshal(resp, &cp) 78 | } 79 | 80 | // EditItem product-catalog association 81 | // 82 | // PATCH 83 | // 84 | // 200 OK 85 | // 400 bad req 86 | // 87 | // Response: 88 | // { 89 | // "status":"AVAILABLE", 90 | // "price":{ 91 | // "value":0, 92 | // "originalValue":0 93 | // }, 94 | // "externalCode":"string", 95 | // "sequence":0, 96 | // "shifts":[{...}] 97 | // } 98 | func (c *catalogService) EditItem(merchantID, categoryID, productID string, ci CategoryItem) (cp ProductLink, err error) { 99 | err = verifyCategoryItems(merchantID, categoryID, productID) 100 | if err != nil { 101 | glg.Error("[SDK] Catalog EditItem verifyCategoryItems: ", err.Error()) 102 | return 103 | } 104 | if err = ci.verify(); err != nil { 105 | glg.Error("[SDK] Catalog EditItem verify: ", err.Error()) 106 | return 107 | } 108 | err = c.auth.Validate() 109 | if err != nil { 110 | glg.Error("[SDK] Catalog EditItem auth.Validate: ", err.Error()) 111 | return 112 | } 113 | headers := make(map[string]string) 114 | headers["Authorization"] = fmt.Sprintf("Bearer %s", c.auth.GetToken()) 115 | headers["Content-Type"] = "application/json" 116 | endpoint := v2Endpoint + fmt.Sprintf( 117 | "/merchants/%s/categories/%s/products/%s", merchantID, categoryID, productID) 118 | reader, err := httpadapter.NewJsonReader(ci) 119 | if err != nil { 120 | glg.Error("[SDK] Catalog EditItem NewJsonReader error: ", err.Error()) 121 | return 122 | } 123 | resp, status, err := c.adapter.DoRequest(http.MethodPatch, endpoint, reader, headers) 124 | if err != nil { 125 | glg.Error("[SDK] Catalog EditItem adapter.DoRequest: ", err.Error()) 126 | return 127 | } 128 | if status != http.StatusOK { 129 | badResp := &apiError{} 130 | err = json.Unmarshal(resp, badResp) 131 | if err != nil { 132 | glg.Error("[SDK] Catalog DeleteProduct Unmarshal: ", err) 133 | return 134 | } 135 | glg.Error("[SDK] Catalog EditItem status code: ", status, " merchant: ", merchantID) 136 | err = fmt.Errorf( 137 | "Merchant '%s' could not create item category '%s', code: '%s'", 138 | merchantID, categoryID, badResp.Details.Code) 139 | glg.Error("[SDK] Catalog EditItem err: ", err) 140 | return 141 | } 142 | glg.Infof("[SDK] Catalog EditItem success product id '%s', merchant '%s'", productID, merchantID) 143 | return cp, json.Unmarshal(resp, &cp) 144 | } 145 | 146 | // DeleteItem product-catalog association 147 | // 148 | // PATCH 149 | // 150 | // 200 OK 151 | // 400 bad req 152 | // 404 not found 153 | // 154 | func (c *catalogService) DeleteItem(merchantID, categoryID, productID string) (err error) { 155 | err = verifyCategoryItems(merchantID, categoryID, productID) 156 | if err != nil { 157 | glg.Error("[SDK] Catalog DeleteItem verifyCategoryItems: ", err.Error()) 158 | return 159 | } 160 | err = c.auth.Validate() 161 | if err != nil { 162 | glg.Error("[SDK] Catalog DeleteItem auth.Validate: ", err.Error()) 163 | return 164 | } 165 | headers := make(map[string]string) 166 | headers["Authorization"] = fmt.Sprintf("Bearer %s", c.auth.GetToken()) 167 | endpoint := v2Endpoint + fmt.Sprintf( 168 | "/merchants/%s/categories/%s/products/%s", merchantID, categoryID, productID) 169 | resp, status, err := c.adapter.DoRequest(http.MethodDelete, endpoint, nil, headers) 170 | if err != nil { 171 | glg.Error("[SDK] Catalog DeleteItem adapter.DoRequest: ", err.Error()) 172 | return 173 | } 174 | if status != http.StatusOK { 175 | badResp := &apiError{} 176 | err = json.Unmarshal(resp, badResp) 177 | if err != nil { 178 | glg.Error("[SDK] Catalog DeleteProduct Unmarshal: ", err) 179 | return 180 | } 181 | glg.Error("[SDK] Catalog DeleteItem status code: ", status, " merchant: ", merchantID) 182 | err = fmt.Errorf( 183 | "Merchant '%s' could not create item category '%s', code: '%s'", 184 | merchantID, categoryID, badResp.Details.Code) 185 | glg.Error("[SDK] Catalog DeleteItem err: ", err) 186 | return 187 | } 188 | glg.Infof("[SDK] Catalog DeleteItem success product id '%s', merchant '%s'", productID, merchantID) 189 | return 190 | } 191 | -------------------------------------------------------------------------------- /services/catalog/item_test.go: -------------------------------------------------------------------------------- 1 | package catalog 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 10 | auth "github.com/arxdsilva/golang-ifood-sdk/services/authentication" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestCreateItem_OK(t *testing.T) { 15 | resp := `{ 16 | "status": "AVAILABLE", 17 | "price": { 18 | "value": 10, 19 | "originalValue": 0 20 | }, 21 | "externalCode": "string", 22 | "sequence": 1, 23 | "shifts": [{}] 24 | }` 25 | ts := httptest.NewServer( 26 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 27 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/categories/category_id/products/product_id", r.URL.Path) 28 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 29 | assert.Equal(t, "application/json", r.Header["Content-Type"][0]) 30 | assert.Equal(t, r.Method, http.MethodPost) 31 | w.WriteHeader(http.StatusCreated) 32 | fmt.Fprintf(w, resp) 33 | }), 34 | ) 35 | defer ts.Close() 36 | am := auth.AuthMock{} 37 | am.On("Validate").Once().Return(nil) 38 | am.On("GetToken").Once().Return("token") 39 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 40 | catalogService := New(adapter, &am) 41 | assert.NotNil(t, catalogService) 42 | ci := CategoryItem{ 43 | Name: "id", 44 | Status: "AVAILABLE", 45 | Price: Price{Value: 10}, 46 | Shifts: []Shift{ 47 | {StartTime: "00:00", EndTime: "23:59", Monday: true}, 48 | }, 49 | } 50 | respCI, err := catalogService.CreateItem("merchant_id", "category_id", "product_id", ci) 51 | assert.Nil(t, err) 52 | assert.Equal(t, float64(10), respCI.Price.Value) 53 | assert.Equal(t, 1, respCI.Sequence) 54 | } 55 | 56 | func TestEditItem_OK(t *testing.T) { 57 | resp := `{ 58 | "status": "AVAILABLE", 59 | "price": { 60 | "value": 10, 61 | "originalValue": 0 62 | }, 63 | "externalCode": "string", 64 | "sequence": 1, 65 | "shifts": [{}] 66 | }` 67 | ts := httptest.NewServer( 68 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 69 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/categories/category_id/products/product_id", r.URL.Path) 70 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 71 | assert.Equal(t, "application/json", r.Header["Content-Type"][0]) 72 | assert.Equal(t, r.Method, http.MethodPatch) 73 | w.WriteHeader(http.StatusOK) 74 | fmt.Fprintf(w, resp) 75 | }), 76 | ) 77 | defer ts.Close() 78 | am := auth.AuthMock{} 79 | am.On("Validate").Once().Return(nil) 80 | am.On("GetToken").Once().Return("token") 81 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 82 | catalogService := New(adapter, &am) 83 | assert.NotNil(t, catalogService) 84 | ci := CategoryItem{ 85 | Name: "id", 86 | Status: "AVAILABLE", 87 | Price: Price{Value: 10}, 88 | Shifts: []Shift{ 89 | {StartTime: "00:00", EndTime: "23:59", Monday: true}, 90 | }, 91 | } 92 | respCI, err := catalogService.EditItem("merchant_id", "category_id", "product_id", ci) 93 | assert.Nil(t, err) 94 | assert.Equal(t, float64(10), respCI.Price.Value) 95 | assert.Equal(t, 1, respCI.Sequence) 96 | } 97 | 98 | func TestDeleteItem_OK(t *testing.T) { 99 | ts := httptest.NewServer( 100 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 101 | assert.Equal(t, "/catalog/v2.0/merchants/merchant_id/categories/category_id/products/product_id", r.URL.Path) 102 | assert.Equal(t, "Bearer token", r.Header["Authorization"][0]) 103 | assert.Equal(t, r.Method, http.MethodDelete) 104 | w.WriteHeader(http.StatusOK) 105 | }), 106 | ) 107 | defer ts.Close() 108 | am := auth.AuthMock{} 109 | am.On("Validate").Once().Return(nil) 110 | am.On("GetToken").Once().Return("token") 111 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 112 | catalogService := New(adapter, &am) 113 | assert.NotNil(t, catalogService) 114 | err := catalogService.DeleteItem("merchant_id", "category_id", "product_id") 115 | assert.Nil(t, err) 116 | } 117 | -------------------------------------------------------------------------------- /services/catalog/types.go: -------------------------------------------------------------------------------- 1 | package catalog 2 | 3 | type ( 4 | // Catalogs group of Catalog 5 | Catalogs []Catalog 6 | 7 | // Catalog API response 8 | Catalog struct { 9 | ID string `json:"catalogId"` 10 | Context []string `json:"context"` 11 | Status string `json:"status"` 12 | ModifiedAt string `json:"modifiedAt"` 13 | } 14 | 15 | // UnsellableResponse API response 16 | UnsellableResponse struct { 17 | Categories []Category `json:"categories"` 18 | } 19 | 20 | // Category struct 21 | Category struct { 22 | ID string `json:"id"` 23 | Status string `json:"status"` 24 | Template string `json:"template"` 25 | Restrictions []string `json:"restrictions"` 26 | UnsellableItems []UnsellableItem `json:"unsellableItems"` 27 | UnsellablePizzaItems Pizza `json:"unsellablePizzaItems"` 28 | } 29 | 30 | // UnsellableItem part of Category struct 31 | UnsellableItem struct { 32 | ID string `json:"id"` 33 | ProductID string `json:"productId"` 34 | Restrictions []string `json:"restrictions"` 35 | } 36 | 37 | // CategoryResponse from API when creating 38 | CategoryResponse struct { 39 | ID string `json:"id"` 40 | Sequence int `json:"sequence"` 41 | Name string `json:"name"` 42 | ExternalCode string `json:"externalCode"` 43 | Status string `json:"status"` 44 | Items []Item `json:"items"` 45 | Template string `json:"template"` 46 | Pizza Pizza `json:"pizza"` 47 | } 48 | 49 | // Item product description 50 | Item struct { 51 | ID string `json:"id"` 52 | Name string `json:"name"` 53 | Description string `json:"description"` 54 | ExternalCode string `json:"externalCode"` 55 | Status string `json:"status"` 56 | ProductID string `json:"productId"` 57 | Sequence int `json:"sequence"` 58 | MagePath string `json:"magePath"` 59 | Price Price `json:"price"` 60 | Shifts []Shift `json:"shifts"` 61 | Serving string `json:"serving"` 62 | DietaryRestrictions []string `json:"dietaryRestrictions"` 63 | Ean string `json:"ean"` 64 | OptionGroups []struct { 65 | ID string `json:"id"` 66 | Name string `json:"name"` 67 | ExternalCode string `json:"externalCode"` 68 | Status string `json:"status"` 69 | Sequence int `json:"sequence"` 70 | Min int `json:"min"` 71 | Max int `json:"max"` 72 | Options struct { 73 | ID string `json:"id"` 74 | Status string `json:"status"` 75 | Sequence int `json:"sequence"` 76 | ProductID string `json:"productId"` 77 | Name string `json:"name"` 78 | Description string `json:"description"` 79 | ExternalCode string `json:"externalCode"` 80 | ImagePath string `json:"imagePath"` 81 | Price Price `json:"price"` 82 | } `json:"options"` 83 | } `json:"optionGroups"` 84 | // SellingOption struct { 85 | // } `json:"sellingOption"` 86 | } 87 | 88 | // CategoryItem linked product to a category 89 | CategoryItem struct { 90 | ID string `json:"id"` 91 | Name string `json:"name"` 92 | Status string `json:"status"` 93 | ExternalCode string `json:"externalCode"` 94 | Template string `json:"template"` 95 | AcceptedFractions []float64 `json:"acceptedFractions"` 96 | DietaryRestrictions []string `json:"dietaryRestrictions"` 97 | Sequence int `json:"sequence"` 98 | Price Price `json:"price"` 99 | Shifts []Shift `json:"shifts"` 100 | } 101 | 102 | // CategoryCreateResponse create API response 103 | CategoryCreateResponse struct { 104 | ID string `json:"id"` 105 | Name string `json:"name"` 106 | ExternalCode string `json:"externalCode"` 107 | Status string `json:"status"` 108 | Sequence int `json:"sequence"` 109 | Template string `json:"template"` 110 | } 111 | 112 | // Price of a product object 113 | Price struct { 114 | Value float64 `json:"value"` 115 | OriginalValue float64 `json:"originalValue"` 116 | } 117 | 118 | // Shift of a Item or CategoryItem 119 | Shift struct { 120 | StartTime string `json:"startTime"` 121 | EndTime string `json:"endTime"` 122 | Monday bool `json:"monday"` 123 | Tuesday bool `json:"tuesday"` 124 | Wednesday bool `json:"wednesday"` 125 | Thursday bool `json:"thursday"` 126 | Friday bool `json:"friday"` 127 | Saturday bool `json:"saturday"` 128 | Sunday bool `json:"sunday"` 129 | } 130 | 131 | apiError struct { 132 | Details struct { 133 | Code string `json:"code"` 134 | } `json:"details"` 135 | } 136 | ) 137 | -------------------------------------------------------------------------------- /services/events/events.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/arxdsilva/golang-ifood-sdk/adapters" 11 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 12 | auth "github.com/arxdsilva/golang-ifood-sdk/services/authentication" 13 | "github.com/kpango/glg" 14 | ) 15 | 16 | const ( 17 | v3Endpoint = "/v3.0/events" 18 | v1Endpoint = "/v1.0/events" 19 | v2APIEndpoint = "/order/v1.0" 20 | ) 21 | 22 | // ErrUnauthorized api error 23 | var ErrUnauthorized = errors.New("Unauthorized request") 24 | 25 | // ErrReqLimitExceeded API query limit exceeded 26 | var ErrReqLimitExceeded = errors.New("EVENTS POLL REQUEST LIMIT EXCEEDED") 27 | var ErrNotFound = errors.New("EVENTS POLL REQUEST RETURNED NOT FOUND") 28 | 29 | type ( 30 | // Service describes the event abstraction 31 | Service interface { 32 | Poll() ([]Event, error) 33 | V2Poll() (ml []V2Event, err error) 34 | Acknowledge([]Event) (err error) 35 | V2Acknowledge([]V2Event) (err error) 36 | } 37 | 38 | eventACK struct { 39 | ID string `json:"id"` 40 | } 41 | 42 | // Events is a group of Event 43 | Events []Event 44 | // V2Events is a group of Event 45 | V2Events []V2Event 46 | 47 | // Event returned by the API 48 | Event struct { 49 | Code string `json:"code"` 50 | CorrelationID string `json:"correlationId"` 51 | CreatedAt time.Time `json:"createdAt"` 52 | ID string `json:"id,omitempty"` 53 | Metadata map[string]interface{} `json:"metadata,omitempty"` 54 | } 55 | 56 | V2Event struct { 57 | Createdat time.Time `json:"createdAt"` 58 | Fullcode string `json:"fullCode"` 59 | Metadata map[string]interface{} `json:"metadata"` 60 | Code string `json:"code"` 61 | Orderid string `json:"orderId"` 62 | ID string `json:"id"` 63 | } 64 | 65 | ErrV2API struct { 66 | Error APIError `json:"error"` 67 | } 68 | 69 | APIError struct { 70 | Code string `json:"code"` 71 | Field string `json:"field"` 72 | Details []interface{} `json:"details"` 73 | Message string `json:"message"` 74 | } 75 | 76 | eventService struct { 77 | adapter adapters.Http 78 | auth auth.Service 79 | v2 bool 80 | } 81 | ) 82 | 83 | // New returns the event service implementation 84 | func New(adapter adapters.Http, authService auth.Service, v2 bool) Service { 85 | return &eventService{adapter, authService, v2} 86 | } 87 | 88 | // Poll queries the iFood API for new events 89 | func (ev *eventService) Poll() (ml []Event, err error) { 90 | err = ev.auth.Validate() 91 | if err != nil { 92 | glg.Error("[SDK] Event auth.Validate: ", err.Error()) 93 | return 94 | } 95 | headers := make(map[string]string) 96 | headers["Authorization"] = fmt.Sprintf("Bearer %s", ev.auth.GetToken()) 97 | endpoint := v3Endpoint + ":polling" 98 | resp, status, err := ev.adapter.DoRequest( 99 | http.MethodGet, endpoint, nil, headers) 100 | if err != nil { 101 | glg.Error("[SDK] Event adapter.DoRequest: ", err.Error()) 102 | return 103 | } 104 | if status == http.StatusNotFound { 105 | glg.Info("[SDK] Event adapter.DoRequest No events to poll") 106 | return 107 | } 108 | if status == http.StatusTooManyRequests { 109 | err = ErrReqLimitExceeded 110 | glg.Warn("[SDK] Event adapter.DoRequest REQUEST LIMIT EXCEEDED") 111 | return 112 | } 113 | if status == http.StatusUnauthorized { 114 | err = ErrUnauthorized 115 | glg.Warn("[SDK] Event adapter.DoRequest no auth") 116 | return 117 | } 118 | if status != http.StatusOK { 119 | err = errors.New("Events could not get polled") 120 | glg.Errorf("[SDK] Event adapter.DoRequest status '%d' err: %s", status, err.Error()) 121 | return 122 | } 123 | glg.Info("[SDK] Poll was successfull") 124 | return ml, json.Unmarshal(resp, &ml) 125 | } 126 | 127 | // V2Poll queries the iFood API for new events 128 | // in the future we'll add a merchants param to allow filtering, 129 | // for now this works with <100 merchants 130 | // V2Poll(merchants []string) 131 | // req.Header.Set("X-Polling-Merchants", "m1,m2") 132 | func (ev *eventService) V2Poll() (ml []V2Event, err error) { 133 | err = ev.auth.Validate() 134 | if err != nil { 135 | glg.Error("[SDK] (Event V2Poll) auth.Validate: ", err.Error()) 136 | return 137 | } 138 | headers := make(map[string]string) 139 | headers["Authorization"] = fmt.Sprintf("Bearer %s", ev.auth.GetToken()) 140 | endpoint := v2APIEndpoint + "/events:polling" 141 | resp, status, err := ev.adapter.DoRequest( 142 | http.MethodGet, endpoint, nil, headers) 143 | if err != nil { 144 | glg.Error("[SDK] (Event V2Poll) adapter.DoRequest: ", err.Error()) 145 | return 146 | } 147 | if status == http.StatusNotFound { 148 | glg.Info("[SDK] (Event V2Poll) adapter.DoRequest No events to poll") 149 | return 150 | } 151 | if status == http.StatusForbidden { 152 | err = ErrUnauthorized 153 | glg.Warn("[SDK] (Event V2Poll) adapter.DoRequest no auth", err.Error()) 154 | return 155 | } 156 | if status != http.StatusOK { 157 | errMsg := ErrV2API{} 158 | json.Unmarshal(resp, &errMsg) 159 | err = errors.New(errMsg.Error.Message) 160 | glg.Errorf("[SDK] (Event V2Poll) adapter.DoRequest status '%d' api message:'%s'", status, errMsg.Error.Message) 161 | return 162 | } 163 | glg.Info("[SDK] (V2Poll) was successfull") 164 | return ml, json.Unmarshal(resp, &ml) 165 | } 166 | 167 | // Acknowledge queries the iFood API to set events as 'polled' 168 | func (ev *eventService) Acknowledge(events []Event) (err error) { 169 | err = ev.auth.Validate() 170 | if err != nil { 171 | glg.Error("[SDK] Event auth.Validate: ", err.Error()) 172 | return 173 | } 174 | eACK := []eventACK{} 175 | for _, e := range events { 176 | eACK = append(eACK, eventACK{e.ID}) 177 | } 178 | reader, err := httpadapter.NewJsonReader(eACK) 179 | if err != nil { 180 | glg.Error("[SDK] Event NewJsonReader: ", err.Error()) 181 | return 182 | } 183 | headers := make(map[string]string) 184 | headers["Content-Type"] = "application/json" 185 | headers["Cache-Control"] = "no-cache" 186 | headers["Authorization"] = fmt.Sprintf("Bearer %s", ev.auth.GetToken()) 187 | endpoint := v1Endpoint + "/acknowledgment" 188 | _, status, err := ev.adapter.DoRequest( 189 | http.MethodPost, endpoint, reader, headers) 190 | if err != nil { 191 | glg.Error("[SDK] Event adapter.DoRequest: ", err.Error()) 192 | return 193 | } 194 | if status == http.StatusUnauthorized { 195 | glg.Warn("[SDK] Event AUTH error status code: ", status) 196 | err = ErrUnauthorized 197 | return 198 | } 199 | if status != http.StatusOK { 200 | err = errors.New("Events could not get polled") 201 | glg.Errorf("[SDK] Event Acknowledge status '%d' err: %s", status, err.Error()) 202 | return 203 | } 204 | glg.Info("[SDK] Acknowledge was successfull") 205 | return 206 | } 207 | 208 | // V2Acknowledge queries the iFood API to set events as 'polled' 209 | func (ev *eventService) V2Acknowledge(events []V2Event) (err error) { 210 | err = ev.auth.Validate() 211 | if err != nil { 212 | glg.Error("[SDK] (Event V2ACK) auth.Validate: ", err.Error()) 213 | return 214 | } 215 | body, err := httpadapter.NewJsonReader(events) 216 | if err != nil { 217 | glg.Error("[SDK] (Event V2ACK) NewJsonReader: ", err.Error()) 218 | return 219 | } 220 | headers := make(map[string]string) 221 | headers["Content-Type"] = "application/json" 222 | headers["Cache-Control"] = "no-cache" 223 | headers["Authorization"] = fmt.Sprintf("Bearer %s", ev.auth.GetToken()) 224 | endpoint := v2APIEndpoint + "/acknowledgment" 225 | resp, status, err := ev.adapter.DoRequest( 226 | http.MethodPost, endpoint, body, headers) 227 | if err != nil { 228 | glg.Error("[SDK] (Event V2ACK) adapter.DoRequest: ", err.Error()) 229 | return 230 | } 231 | if status == http.StatusForbidden { 232 | glg.Warn("[SDK] (Event V2ACK) AUTH error status code: ", status) 233 | err = ErrUnauthorized 234 | return 235 | } 236 | if status != http.StatusAccepted { 237 | errMsg := ErrV2API{} 238 | json.Unmarshal(resp, &errMsg) 239 | err = errors.New(errMsg.Error.Message) 240 | glg.Errorf("[SDK] (Event V2ACK) Acknowledge status '%d' err: %s", status, err.Error()) 241 | return 242 | } 243 | glg.Info("[SDK] (Event V2ACK) was successfull") 244 | return 245 | } 246 | -------------------------------------------------------------------------------- /services/events/events_test.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | 11 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 12 | "github.com/arxdsilva/golang-ifood-sdk/mocks" 13 | auth "github.com/arxdsilva/golang-ifood-sdk/services/authentication" 14 | "github.com/stretchr/testify/assert" 15 | "github.com/stretchr/testify/mock" 16 | "github.com/stretchr/testify/require" 17 | ) 18 | 19 | const pollAPIResponse = `[{ 20 | "code": "PLACED", 21 | "correlationId": "1234567890012", 22 | "createdAt": "2017-05-02T16:01:16.567Z", 23 | "id": "abc-456-afge-451-n15484" 24 | }, 25 | { 26 | "code": "CANCELLED", 27 | "correlationId": "9876543210123", 28 | "createdAt": "2017-05-02T16:01:16.567Z", 29 | "metadata": { 30 | "CANCEL_STAGE": "[PRE_CONFIRMED]", 31 | "CANCEL_CODE": "902", 32 | "CANCELLATION_OCCURRENCE": { 33 | "RESTAURANT": { 34 | "FINANCIAL_OCCURRENCE": "NA", 35 | "PAYMENT_TYPE": "NA" 36 | }, 37 | "CONSUMER": { 38 | "FINANCIAL_OCCURRENCE": "NA", 39 | "PAYMENT_TYPE": "NA" 40 | }, 41 | "LOGISTIC": { 42 | "FINANCIAL_OCCURRENCE": "NA", 43 | "PAYMENT_TYPE": "NA" 44 | } 45 | } 46 | } 47 | }]` 48 | 49 | const pollV2APIResponse = `[{ 50 | "createdAt": "2019-09-19T13:40:11.822Z", 51 | "fullCode": "PLACED", 52 | "metadata": {}, 53 | "code": "PLC", 54 | "orderId": "07110e1b-8191-4670-baed-407219481ffb", 55 | "id": "cd40582b-0ef2-4d52-bc7c-507fdff12e21" 56 | }]` 57 | 58 | func TestPoll_OK(t *testing.T) { 59 | ts := httptest.NewServer( 60 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 61 | assert.Equal(t, "/v3.0/events:polling", r.URL.Path) 62 | assert.Equal(t, r.Method, http.MethodGet) 63 | w.WriteHeader(http.StatusOK) 64 | fmt.Fprintf(w, pollAPIResponse) 65 | }), 66 | ) 67 | defer ts.Close() 68 | am := auth.AuthMock{} 69 | am.On("Validate").Once().Return(nil) 70 | am.On("GetToken").Once().Return("token") 71 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 72 | eventsService := New(adapter, &am, false) 73 | assert.NotNil(t, eventsService) 74 | events, err := eventsService.Poll() 75 | assert.Nil(t, err) 76 | assert.Equal(t, 2, len(events)) 77 | } 78 | 79 | func TestPoll_BadRequest(t *testing.T) { 80 | ts := httptest.NewServer( 81 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 82 | assert.Equal(t, "/v3.0/events:polling", r.URL.Path) 83 | assert.Equal(t, r.Method, http.MethodGet) 84 | w.WriteHeader(http.StatusBadRequest) 85 | }), 86 | ) 87 | defer ts.Close() 88 | am := auth.AuthMock{} 89 | am.On("Validate").Once().Return(nil) 90 | am.On("GetToken").Once().Return("token") 91 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 92 | eventsService := New(adapter, &am, false) 93 | assert.NotNil(t, eventsService) 94 | events, err := eventsService.Poll() 95 | assert.NotNil(t, err) 96 | assert.Equal(t, 0, len(events)) 97 | } 98 | 99 | func TestPoll_Unauthorized(t *testing.T) { 100 | ts := httptest.NewServer( 101 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 102 | assert.Equal(t, "/v3.0/events:polling", r.URL.Path) 103 | assert.Equal(t, r.Method, http.MethodGet) 104 | w.WriteHeader(http.StatusUnauthorized) 105 | }), 106 | ) 107 | defer ts.Close() 108 | am := auth.AuthMock{} 109 | am.On("Validate").Once().Return(nil) 110 | am.On("GetToken").Once().Return("token") 111 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 112 | eventsService := New(adapter, &am, false) 113 | assert.NotNil(t, eventsService) 114 | events, err := eventsService.Poll() 115 | assert.NotNil(t, err) 116 | assert.Equal(t, 0, len(events)) 117 | assert.Equal(t, ErrUnauthorized, err) 118 | } 119 | 120 | func TestPoll_StatusTooManyRequests(t *testing.T) { 121 | ts := httptest.NewServer( 122 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 123 | assert.Equal(t, "/v3.0/events:polling", r.URL.Path) 124 | assert.Equal(t, r.Method, http.MethodGet) 125 | w.WriteHeader(http.StatusTooManyRequests) 126 | }), 127 | ) 128 | defer ts.Close() 129 | am := auth.AuthMock{} 130 | am.On("Validate").Once().Return(nil) 131 | am.On("GetToken").Once().Return("token") 132 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 133 | eventsService := New(adapter, &am, false) 134 | assert.NotNil(t, eventsService) 135 | events, err := eventsService.Poll() 136 | assert.NotNil(t, err) 137 | assert.Equal(t, 0, len(events)) 138 | assert.Equal(t, ErrReqLimitExceeded, err) 139 | } 140 | 141 | func TestPoll_StatusNotFound(t *testing.T) { 142 | ts := httptest.NewServer( 143 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 144 | assert.Equal(t, "/v3.0/events:polling", r.URL.Path) 145 | assert.Equal(t, r.Method, http.MethodGet) 146 | w.WriteHeader(http.StatusNotFound) 147 | }), 148 | ) 149 | defer ts.Close() 150 | am := auth.AuthMock{} 151 | am.On("Validate").Once().Return(nil) 152 | am.On("GetToken").Once().Return("token") 153 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 154 | eventsService := New(adapter, &am, false) 155 | assert.NotNil(t, eventsService) 156 | events, err := eventsService.Poll() 157 | assert.Nil(t, err) 158 | assert.Equal(t, 0, len(events)) 159 | } 160 | 161 | func TestPoll_ValidateErr(t *testing.T) { 162 | ts := httptest.NewServer( 163 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 164 | assert.Equal(t, "/v3.0/events:polling", r.URL.Path) 165 | assert.Equal(t, r.Method, http.MethodGet) 166 | w.WriteHeader(http.StatusNotFound) 167 | }), 168 | ) 169 | defer ts.Close() 170 | am := auth.AuthMock{} 171 | am.On("Validate").Once().Return(errors.New("some err")) 172 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 173 | eventsService := New(adapter, &am, false) 174 | assert.NotNil(t, eventsService) 175 | events, err := eventsService.Poll() 176 | assert.NotNil(t, err) 177 | assert.Equal(t, 0, len(events)) 178 | } 179 | 180 | func TestAcknowledge_ValidateErr(t *testing.T) { 181 | ts := httptest.NewServer( 182 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 183 | assert.Equal(t, r.Header["Content-Type"][0], "application/json") 184 | assert.Equal(t, r.Header["Cache-Control"][0], "no-cache") 185 | assert.NotNil(t, r.Header["Authorization"][0]) 186 | assert.Equal(t, "/v1.0/events/acknowledgment", r.URL.Path) 187 | assert.Equal(t, r.Method, http.MethodGet) 188 | w.WriteHeader(http.StatusNotFound) 189 | }), 190 | ) 191 | defer ts.Close() 192 | am := auth.AuthMock{} 193 | am.On("Validate").Once().Return(errors.New("some err")) 194 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 195 | eventsService := New(adapter, &am, false) 196 | assert.NotNil(t, eventsService) 197 | events := Events{} 198 | err := json.Unmarshal([]byte(pollAPIResponse), &events) 199 | assert.Nil(t, err) 200 | err = eventsService.Acknowledge(events) 201 | assert.NotNil(t, err) 202 | assert.Contains(t, err.Error(), "some") 203 | } 204 | 205 | func TestAcknowledge_OK(t *testing.T) { 206 | ts := httptest.NewServer( 207 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 208 | assert.Equal(t, r.Header["Content-Type"][0], "application/json") 209 | assert.Equal(t, r.Header["Cache-Control"][0], "no-cache") 210 | assert.NotNil(t, r.Header["Authorization"][0]) 211 | assert.Equal(t, "/v1.0/events/acknowledgment", r.URL.Path) 212 | assert.Equal(t, r.Method, http.MethodPost) 213 | w.WriteHeader(http.StatusOK) 214 | }), 215 | ) 216 | defer ts.Close() 217 | am := auth.AuthMock{} 218 | am.On("Validate").Once().Return(nil) 219 | am.On("GetToken").Once().Return("token") 220 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 221 | eventsService := New(adapter, &am, false) 222 | assert.NotNil(t, eventsService) 223 | events := Events{} 224 | err := json.Unmarshal([]byte(pollAPIResponse), &events) 225 | assert.Nil(t, err) 226 | err = eventsService.Acknowledge(events) 227 | assert.Nil(t, err) 228 | } 229 | 230 | func TestAcknowledge_StatusUnauthorized(t *testing.T) { 231 | ts := httptest.NewServer( 232 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 233 | assert.Equal(t, r.Header["Content-Type"][0], "application/json") 234 | assert.Equal(t, r.Header["Cache-Control"][0], "no-cache") 235 | assert.NotNil(t, r.Header["Authorization"][0]) 236 | assert.Equal(t, "/v1.0/events/acknowledgment", r.URL.Path) 237 | assert.Equal(t, r.Method, http.MethodPost) 238 | w.WriteHeader(http.StatusUnauthorized) 239 | }), 240 | ) 241 | defer ts.Close() 242 | am := auth.AuthMock{} 243 | am.On("Validate").Once().Return(nil) 244 | am.On("GetToken").Once().Return("token") 245 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 246 | eventsService := New(adapter, &am, false) 247 | assert.NotNil(t, eventsService) 248 | events := Events{} 249 | err := json.Unmarshal([]byte(pollAPIResponse), &events) 250 | assert.Nil(t, err) 251 | err = eventsService.Acknowledge(events) 252 | assert.NotNil(t, err) 253 | assert.Equal(t, ErrUnauthorized, err) 254 | } 255 | 256 | func TestAcknowledge_StatusRequestEntityTooLarge(t *testing.T) { 257 | ts := httptest.NewServer( 258 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 259 | assert.Equal(t, r.Header["Content-Type"][0], "application/json") 260 | assert.Equal(t, r.Header["Cache-Control"][0], "no-cache") 261 | assert.NotNil(t, r.Header["Authorization"][0]) 262 | assert.Equal(t, "/v1.0/events/acknowledgment", r.URL.Path) 263 | assert.Equal(t, r.Method, http.MethodPost) 264 | w.WriteHeader(http.StatusRequestEntityTooLarge) 265 | }), 266 | ) 267 | defer ts.Close() 268 | am := auth.AuthMock{} 269 | am.On("Validate").Once().Return(nil) 270 | am.On("GetToken").Once().Return("token") 271 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 272 | eventsService := New(adapter, &am, false) 273 | assert.NotNil(t, eventsService) 274 | var events Events 275 | err := json.Unmarshal([]byte(pollAPIResponse), &events) 276 | assert.Nil(t, err) 277 | err = eventsService.Acknowledge(events) 278 | assert.NotNil(t, err) 279 | assert.Contains(t, err.Error(), "not get polled") 280 | } 281 | 282 | func Test_V2Poll_OK(t *testing.T) { 283 | ts := httptest.NewServer( 284 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 285 | require.Equal(t, "/order/v1.0/events:polling", r.URL.Path) 286 | require.Equal(t, r.Method, http.MethodGet) 287 | w.WriteHeader(http.StatusOK) 288 | fmt.Fprintf(w, pollV2APIResponse) 289 | }), 290 | ) 291 | defer ts.Close() 292 | am := auth.AuthMock{} 293 | am.On("Validate").Once().Return(nil) 294 | am.On("GetToken").Once().Return("token") 295 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 296 | eventsService := New(adapter, &am, false) 297 | assert.NotNil(t, eventsService) 298 | events, err := eventsService.V2Poll() 299 | assert.Nil(t, err) 300 | assert.Equal(t, 1, len(events)) 301 | } 302 | 303 | func Test_V2Poll_NotFound(t *testing.T) { 304 | resp := `{ 305 | "error": { 306 | "code": "string", 307 | "field": "string", 308 | "details": [null], 309 | "message": "string" 310 | } 311 | }` 312 | ts := httptest.NewServer( 313 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 314 | require.Equal(t, "/order/v1.0/events:polling", r.URL.Path) 315 | require.Equal(t, r.Method, http.MethodGet) 316 | w.WriteHeader(http.StatusNotFound) 317 | fmt.Fprintf(w, resp) 318 | }), 319 | ) 320 | defer ts.Close() 321 | am := auth.AuthMock{} 322 | am.On("Validate").Once().Return(nil) 323 | am.On("GetToken").Once().Return("token") 324 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 325 | eventsService := New(adapter, &am, false) 326 | assert.NotNil(t, eventsService) 327 | events, err := eventsService.V2Poll() 328 | assert.Nil(t, err) 329 | assert.Equal(t, 0, len(events)) 330 | } 331 | 332 | func Test_V2Poll_Forbidden(t *testing.T) { 333 | resp := `{ 334 | "error": { 335 | "code": "string", 336 | "field": "string", 337 | "details": [null], 338 | "message": "string" 339 | } 340 | }` 341 | ts := httptest.NewServer( 342 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 343 | require.Equal(t, "/order/v1.0/events:polling", r.URL.Path) 344 | require.Equal(t, r.Method, http.MethodGet) 345 | w.WriteHeader(http.StatusForbidden) 346 | fmt.Fprintf(w, resp) 347 | }), 348 | ) 349 | defer ts.Close() 350 | am := auth.AuthMock{} 351 | am.On("Validate").Once().Return(nil) 352 | am.On("GetToken").Once().Return("token") 353 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 354 | eventsService := New(adapter, &am, false) 355 | assert.NotNil(t, eventsService) 356 | events, err := eventsService.V2Poll() 357 | assert.NotNil(t, err) 358 | assert.Equal(t, 0, len(events)) 359 | assert.Equal(t, ErrUnauthorized, err) 360 | } 361 | 362 | func Test_V2Poll_StatusRequestEntityTooLarge(t *testing.T) { 363 | resp := `{ 364 | "error": { 365 | "code": "string", 366 | "field": "string", 367 | "details": [null], 368 | "message": "too many merchants" 369 | } 370 | }` 371 | ts := httptest.NewServer( 372 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 373 | require.Equal(t, "/order/v1.0/events:polling", r.URL.Path) 374 | require.Equal(t, r.Method, http.MethodGet) 375 | w.WriteHeader(http.StatusRequestEntityTooLarge) 376 | fmt.Fprintf(w, resp) 377 | }), 378 | ) 379 | defer ts.Close() 380 | am := auth.AuthMock{} 381 | am.On("Validate").Once().Return(nil) 382 | am.On("GetToken").Once().Return("token") 383 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 384 | eventsService := New(adapter, &am, false) 385 | assert.NotNil(t, eventsService) 386 | events, err := eventsService.V2Poll() 387 | assert.NotNil(t, err) 388 | assert.Equal(t, 0, len(events)) 389 | assert.Equal(t, "too many merchants", err.Error()) 390 | } 391 | 392 | func Test_V2Poll_ValidateErr(t *testing.T) { 393 | ts := httptest.NewServer( 394 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 395 | require.Equal(t, "/order/v1.0/events:polling", r.URL.Path) 396 | require.Equal(t, r.Method, http.MethodGet) 397 | w.WriteHeader(http.StatusRequestEntityTooLarge) 398 | fmt.Fprintf(w, `{}`) 399 | }), 400 | ) 401 | defer ts.Close() 402 | am := auth.AuthMock{} 403 | am.On("Validate").Once().Return(errors.New("error")) 404 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 405 | eventsService := New(adapter, &am, false) 406 | assert.NotNil(t, eventsService) 407 | events, err := eventsService.V2Poll() 408 | require.NotNil(t, err) 409 | assert.Equal(t, 0, len(events)) 410 | assert.Equal(t, "error", err.Error()) 411 | } 412 | 413 | func Test_V2Acknowledge_OK(t *testing.T) { 414 | ts := httptest.NewServer( 415 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 416 | require.Equal(t, r.Header["Content-Type"][0], "application/json") 417 | require.Equal(t, r.Header["Cache-Control"][0], "no-cache") 418 | require.NotNil(t, r.Header["Authorization"][0]) 419 | require.Equal(t, "/order/v1.0/acknowledgment", r.URL.Path) 420 | require.Equal(t, r.Method, http.MethodPost) 421 | w.WriteHeader(http.StatusAccepted) 422 | }), 423 | ) 424 | defer ts.Close() 425 | am := auth.AuthMock{} 426 | am.On("Validate").Once().Return(nil) 427 | am.On("GetToken").Once().Return("token") 428 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 429 | eventsService := New(adapter, &am, false) 430 | assert.NotNil(t, eventsService) 431 | events := V2Events{} 432 | err := json.Unmarshal([]byte(pollV2APIResponse), &events) 433 | assert.Nil(t, err) 434 | err = eventsService.V2Acknowledge(events) 435 | assert.Nil(t, err) 436 | } 437 | 438 | func Test_V2Acknowledge_BadRequest(t *testing.T) { 439 | resp := `{ 440 | "error": { 441 | "code": "string", 442 | "field": "string", 443 | "details": [null], 444 | "message": "bad request" 445 | } 446 | }` 447 | ts := httptest.NewServer( 448 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 449 | require.Equal(t, r.Header["Content-Type"][0], "application/json") 450 | require.Equal(t, r.Header["Cache-Control"][0], "no-cache") 451 | require.NotNil(t, r.Header["Authorization"][0]) 452 | require.Equal(t, "/order/v1.0/acknowledgment", r.URL.Path) 453 | require.Equal(t, r.Method, http.MethodPost) 454 | w.WriteHeader(http.StatusBadRequest) 455 | fmt.Fprintf(w, resp) 456 | }), 457 | ) 458 | defer ts.Close() 459 | am := auth.AuthMock{} 460 | am.On("Validate").Once().Return(nil) 461 | am.On("GetToken").Once().Return("token") 462 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 463 | eventsService := New(adapter, &am, false) 464 | assert.NotNil(t, eventsService) 465 | events := V2Events{} 466 | err := json.Unmarshal([]byte(pollV2APIResponse), &events) 467 | assert.Nil(t, err) 468 | err = eventsService.V2Acknowledge(events) 469 | assert.NotNil(t, err) 470 | assert.Equal(t, "bad request", err.Error()) 471 | } 472 | 473 | func Test_V2Acknowledge_Forbidden(t *testing.T) { 474 | resp := `{ 475 | "error": { 476 | "code": "string", 477 | "field": "string", 478 | "details": [null], 479 | "message": "forbidden" 480 | } 481 | }` 482 | ts := httptest.NewServer( 483 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 484 | require.Equal(t, r.Header["Content-Type"][0], "application/json") 485 | require.Equal(t, r.Header["Cache-Control"][0], "no-cache") 486 | require.NotNil(t, r.Header["Authorization"][0]) 487 | require.Equal(t, "/order/v1.0/acknowledgment", r.URL.Path) 488 | require.Equal(t, r.Method, http.MethodPost) 489 | w.WriteHeader(http.StatusForbidden) 490 | fmt.Fprintf(w, resp) 491 | }), 492 | ) 493 | defer ts.Close() 494 | am := auth.AuthMock{} 495 | am.On("Validate").Once().Return(nil) 496 | am.On("GetToken").Once().Return("token") 497 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 498 | eventsService := New(adapter, &am, false) 499 | assert.NotNil(t, eventsService) 500 | events := V2Events{} 501 | err := json.Unmarshal([]byte(pollV2APIResponse), &events) 502 | assert.Nil(t, err) 503 | err = eventsService.V2Acknowledge(events) 504 | assert.NotNil(t, err) 505 | assert.Equal(t, ErrUnauthorized, err) 506 | } 507 | 508 | func Test_V2Acknowledge_ValidateErr(t *testing.T) { 509 | am := auth.AuthMock{} 510 | am.On("Validate").Once().Return(errors.New("validate")) 511 | adapter := httpadapter.New(http.DefaultClient, "") 512 | eventsService := New(adapter, &am, false) 513 | assert.NotNil(t, eventsService) 514 | events := V2Events{} 515 | err := json.Unmarshal([]byte(pollV2APIResponse), &events) 516 | assert.Nil(t, err) 517 | err = eventsService.V2Acknowledge(events) 518 | assert.NotNil(t, err) 519 | assert.Equal(t, "validate", err.Error()) 520 | } 521 | 522 | func Test_V2Acknowledge_DoReqErr(t *testing.T) { 523 | am := auth.AuthMock{} 524 | am.On("Validate").Once().Return(nil) 525 | am.On("GetToken").Once().Return("token") 526 | httpmock := &mocks.HttpClientMock{} 527 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 528 | adapter := httpadapter.New(httpmock, "") 529 | service := New(adapter, &am, true) 530 | assert.NotNil(t, service) 531 | events := V2Events{} 532 | err := json.Unmarshal([]byte(pollV2APIResponse), &events) 533 | err = service.V2Acknowledge(events) 534 | assert.NotNil(t, err) 535 | assert.Equal(t, "some err", err.Error()) 536 | } 537 | 538 | func Test_Acknowledge_DoReqErr(t *testing.T) { 539 | am := auth.AuthMock{} 540 | am.On("Validate").Once().Return(nil) 541 | am.On("GetToken").Once().Return("token") 542 | httpmock := &mocks.HttpClientMock{} 543 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 544 | adapter := httpadapter.New(httpmock, "") 545 | service := New(adapter, &am, true) 546 | assert.NotNil(t, service) 547 | events := Events{} 548 | err := json.Unmarshal([]byte(pollAPIResponse), &events) 549 | err = service.Acknowledge(events) 550 | assert.NotNil(t, err) 551 | assert.Equal(t, "some err", err.Error()) 552 | } 553 | 554 | func Test_V2Poll_DoReqErr(t *testing.T) { 555 | am := auth.AuthMock{} 556 | am.On("Validate").Once().Return(nil) 557 | am.On("GetToken").Once().Return("token") 558 | httpmock := &mocks.HttpClientMock{} 559 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 560 | adapter := httpadapter.New(httpmock, "") 561 | service := New(adapter, &am, true) 562 | assert.NotNil(t, service) 563 | _, err := service.V2Poll() 564 | assert.NotNil(t, err) 565 | assert.Equal(t, "some err", err.Error()) 566 | } 567 | 568 | func Test_Poll_DoReqErr(t *testing.T) { 569 | am := auth.AuthMock{} 570 | am.On("Validate").Once().Return(nil) 571 | am.On("GetToken").Once().Return("token") 572 | httpmock := &mocks.HttpClientMock{} 573 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 574 | adapter := httpadapter.New(httpmock, "") 575 | service := New(adapter, &am, false) 576 | assert.NotNil(t, service) 577 | _, err := service.Poll() 578 | assert.NotNil(t, err) 579 | assert.Equal(t, "some err", err.Error()) 580 | } 581 | -------------------------------------------------------------------------------- /services/events/types.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | // ValidEventsByCodeName API events by code:name 4 | var ValidEventsByCodeName = map[string]string{ 5 | "COL": "PLACED", 6 | "REC": "INTEGRATED", 7 | "CFM": "CONFIRMED", 8 | "CAR": "CANCELLATION_REQUESTED", 9 | "CAF": "CANCELLATION_REQUEST_FAILED", 10 | "CAN": "CANCELLED", 11 | "DRE": "GOING_TO_ORIGIN", 12 | "NRE": "ARRIVED_AT_ORIGIN", 13 | "RTD": "READY_TO_DELIVER", 14 | "CLT": "COLLECTED", 15 | "DCL": "DISPATCHED", 16 | "NCL": "DELIVERED", 17 | "CON": "CONCLUDED", 18 | "PAA": "PICKUP_AREA_ASSIGNED", 19 | "DNO": "DELAY_NOTIFICATION", 20 | "CPT": "CHANGE_PREPARATION_TIME", 21 | "RDA": "REQUEST_DRIVER_AVAILABILITY", 22 | "RDR": "REQUEST_DRIVER", 23 | "RDS": "REQUEST_DRIVER_SUCCESS", 24 | "RDF": "REQUEST_DRIVER_FAILED", 25 | "ADR": "ASSIGN_DRIVER", 26 | "CCR": "CONSUMER_CANCELLATION_REQUESTED", 27 | "CCA": "CONSUMER_CANCELLATION_ACCEPTED", 28 | "CCD": "CONSUMER_CANCELLATION_DENIED", 29 | "ATG": "ADDED_TO_GROUP", 30 | "EWG": "EXECUTED_WITH_GROUP", 31 | "CWG": "CANCELLED_WITH_GROUP", 32 | "CIG": "COLLECTED_IN_GROUP", 33 | "AWG": "ASSIGNED_WITH_GROUP", 34 | "UPR": "UPDATE_REQUESTED", 35 | "UPD": "UPDATE_DENIED", 36 | "UPT": "UPDATED", 37 | "BOA": "BOX_ASSIGNED", 38 | "RPS": "RECOMMENDED_PREPARATION_START", 39 | } 40 | 41 | // ValidEventsByNameCode API events by name:code 42 | var ValidEventsByNameCode = map[string]string{ 43 | "PLACED": "COL", 44 | "INTEGRATED": "REC", 45 | "CONFIRMED": "CFM", 46 | "CANCELLATION_REQUESTED": "CAR", 47 | "CANCELLATION_REQUEST_FAILED": "CAF", 48 | "CANCELLED": "CAN", 49 | "GOING_TO_ORIGIN": "DRE", 50 | "ARRIVED_AT_ORIGIN": "NRE", 51 | "READY_TO_DELIVER": "RTD", 52 | "COLLECTED": "CLT", 53 | "DISPATCHED": "DCL", 54 | "DELIVERED": "NCL", 55 | "CONCLUDED": "CON", 56 | "PICKUP_AREA_ASSIGNED": "PAA", 57 | "DELAY_NOTIFICATION": "DNO", 58 | "CHANGE_PREPARATION_TIME": "CPT", 59 | "REQUEST_DRIVER_AVAILABILITY": "RDA", 60 | "REQUEST_DRIVER": "RDR", 61 | "REQUEST_DRIVER_SUCCESS": "RDS", 62 | "REQUEST_DRIVER_FAILED": "RDF", 63 | "ASSIGN_DRIVER": "ADR", 64 | "CONSUMER_CANCELLATION_REQUESTED": "CCR", 65 | "CONSUMER_CANCELLATION_ACCEPTED": "CCA", 66 | "CONSUMER_CANCELLATION_DENIED": "CCD", 67 | "ADDED_TO_GROUP": "ATG", 68 | "EXECUTED_WITH_GROUP": "EWG", 69 | "CANCELLED_WITH_GROUP": "CWG", 70 | "COLLECTED_IN_GROUP": "CIG", 71 | "ASSIGNED_WITH_GROUP": "AWG", 72 | "UPDATE_REQUESTED": "UPR", 73 | "UPDATE_DENIED": "UPD", 74 | "UPDATED": "UPT", 75 | "BOX_ASSIGNED": "BOA", 76 | "RECOMMENDED_PREPARATION_START": "RPS", 77 | } 78 | -------------------------------------------------------------------------------- /services/merchant/merchant.go: -------------------------------------------------------------------------------- 1 | package merchant 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | 9 | "github.com/arxdsilva/golang-ifood-sdk/adapters" 10 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 11 | auth "github.com/arxdsilva/golang-ifood-sdk/services/authentication" 12 | "github.com/kpango/glg" 13 | ) 14 | 15 | const ( 16 | v1Endpoint = "/v1.0/merchants" 17 | v2Endpoint = "/v2.0/merchants" 18 | ) 19 | 20 | var ( 21 | // ErrMerchantNotSpecified no merchant 22 | ErrMerchantNotSpecified = errors.New("merchant not specified") 23 | // ErrMerchantORUnavailabilityIDNotSpecified no merchant or unavailability 24 | ErrMerchantORUnavailabilityIDNotSpecified = errors.New("merchant or unavailability not specified") 25 | ) 26 | 27 | type ( 28 | // Service describes the merchant API abstraction 29 | Service interface { 30 | ListAll() ([]Merchant, error) 31 | Unavailabilities(merchantUUID string) (Unavailabilities, error) 32 | CreateUnavailabilityNow(merchantUUID, description string, pauseMinutes int32) (UnavailabilityResponse, error) 33 | DeleteUnavailability(merchantUUID, unavailabilityID string) error 34 | Availability(merchantUUID string) (AvailabilityResponse, error) 35 | } 36 | 37 | merchantService struct { 38 | adapter adapters.Http 39 | auth auth.Service 40 | } 41 | ) 42 | 43 | // New returns a new merchant service 44 | func New(adapter adapters.Http, authService auth.Service) *merchantService { 45 | return &merchantService{adapter, authService} 46 | } 47 | 48 | // ListAll lista merchants cuja autenticacao tem permissao 49 | func (m *merchantService) ListAll() (ml []Merchant, err error) { 50 | if err = m.auth.Validate(); err != nil { 51 | glg.Error("[SDK] Merchant ListAll auth.Validate: ", err.Error()) 52 | return 53 | } 54 | headers := make(map[string]string) 55 | headers["Authorization"] = fmt.Sprintf("Bearer %s", m.auth.GetToken()) 56 | resp, status, err := m.adapter.DoRequest(http.MethodGet, 57 | v1Endpoint, nil, headers) 58 | if err != nil { 59 | glg.Error("[SDK] Merchant ListAll adapter.DoRequest error: ", err.Error()) 60 | return 61 | } 62 | if status != http.StatusOK { 63 | glg.Error("[SDK] Merchant ListAll status code: ", status) 64 | err = errors.New("Could not list merchants") 65 | glg.Error("[SDK] Merchant ListAll err: ", err) 66 | return 67 | } 68 | glg.Info("[SDK] Merchant ListAll success") 69 | return ml, json.Unmarshal(resp, &ml) 70 | } 71 | 72 | // Unavailabilities lista indisponibilidades do merchant 73 | func (m *merchantService) Unavailabilities(merchantUUID string) (mu Unavailabilities, err error) { 74 | if merchantUUID == "" { 75 | err = ErrMerchantNotSpecified 76 | glg.Error("[SDK] Merchant Unavailabilities: ", err.Error()) 77 | return 78 | } 79 | if err = m.auth.Validate(); err != nil { 80 | glg.Error("[SDK] Merchant Unavailabilities auth.Validate: ", err.Error()) 81 | return 82 | } 83 | headers := make(map[string]string) 84 | headers["Content-Type"] = "application/json" 85 | headers["Authorization"] = fmt.Sprintf("Bearer %s", m.auth.GetToken()) 86 | endpoint := fmt.Sprintf("%s/%s/unavailabilities", v1Endpoint, merchantUUID) 87 | resp, status, err := m.adapter.DoRequest(http.MethodGet, endpoint, nil, headers) 88 | if err != nil { 89 | glg.Error("[SDK] Merchant Unavailabilities adapter.DoRequest error: ", err.Error()) 90 | return 91 | } 92 | if status != http.StatusOK { 93 | glg.Error("[SDK] Merchant Unavailabilities status code: ", status, " merchant: ", merchantUUID) 94 | err = fmt.Errorf("Merchant '%s' could not get 'unavailabilities'", merchantUUID) 95 | glg.Error("[SDK] Merchant Unavailabilities err: ", err) 96 | return 97 | } 98 | glg.Info("[SDK] Merchant Unavailabilities success") 99 | return mu, json.Unmarshal(resp, &mu) 100 | } 101 | 102 | // CreateUnavailabilityNow cadastra indisponibilidade no merchant 103 | func (m *merchantService) CreateUnavailabilityNow(merchantUUID, description string, pauseMinutes int32) (ur UnavailabilityResponse, err error) { 104 | if merchantUUID == "" { 105 | err = ErrMerchantNotSpecified 106 | glg.Error("[SDK] Merchant CreateUnavailabilityNow: ", err.Error()) 107 | return 108 | } 109 | if err = m.auth.Validate(); err != nil { 110 | glg.Error("[SDK] Merchant CreateUnavailabilityNow auth.Validate: ", err.Error()) 111 | return 112 | } 113 | headers := make(map[string]string) 114 | headers["Content-Type"] = "application/json" 115 | headers["Authorization"] = fmt.Sprintf("Bearer %s", m.auth.GetToken()) 116 | endpoint := fmt.Sprintf("%s/%s/unavailabilities:now", v1Endpoint, merchantUUID) 117 | unv := unavailability{Description: description, Minutes: pauseMinutes} 118 | reader, err := httpadapter.NewJsonReader(unv) 119 | if err != nil { 120 | glg.Error("[SDK] Merchant CreateUnavailabilityNow NewJsonReader error: ", err.Error()) 121 | return 122 | } 123 | resp, status, err := m.adapter.DoRequest(http.MethodPost, endpoint, reader, headers) 124 | if err != nil { 125 | glg.Error("[SDK] Merchant CreateUnavailabilityNow adapter.DoRequest error: ", err.Error()) 126 | return 127 | } 128 | if status != http.StatusOK { 129 | glg.Error("[SDK] Merchant CreateUnavailabilityNow status code: ", status, " merchant: ", merchantUUID) 130 | err = fmt.Errorf("Merchant '%s' could not create 'unavailability'", merchantUUID) 131 | glg.Error("[SDK] Merchant CreateUnavailabilityNow err: ", err) 132 | return 133 | } 134 | return ur, json.Unmarshal(resp, &ur) 135 | } 136 | 137 | // DeleteUnavailability remove indisponibilidade no merchant 138 | func (m *merchantService) DeleteUnavailability(merchantUUID, unavailabilityID string) (err error) { 139 | if (merchantUUID == "") || (unavailabilityID == "") { 140 | err = ErrMerchantORUnavailabilityIDNotSpecified 141 | glg.Error("[SDK] Merchant DeleteUnavailability: ", err.Error()) 142 | return 143 | } 144 | if err = m.auth.Validate(); err != nil { 145 | glg.Error("[SDK] Merchant DeleteUnavailability auth.Validate: ", err.Error()) 146 | return 147 | } 148 | headers := make(map[string]string) 149 | headers["Content-Type"] = "application/json" 150 | headers["Authorization"] = fmt.Sprintf("Bearer %s", m.auth.GetToken()) 151 | endpoint := fmt.Sprintf("%s/%s/unavailabilities/%s", v1Endpoint, merchantUUID, unavailabilityID) 152 | _, status, err := m.adapter.DoRequest(http.MethodDelete, endpoint, nil, headers) 153 | if err != nil { 154 | glg.Error("[SDK] Merchant DeleteUnavailability adapter.DoRequest error: ", err.Error()) 155 | return 156 | } 157 | if status != http.StatusOK { 158 | glg.Error("[SDK] Merchant DeleteUnavailability status code: ", status, " merchant: ", merchantUUID) 159 | err = fmt.Errorf("Merchant '%s' could not delete unavailability id '%s' ", merchantUUID, unavailabilityID) 160 | glg.Error("[SDK] Merchant DeleteUnavailability err: ", err) 161 | return 162 | } 163 | return 164 | } 165 | 166 | // Availability recebe o status de disponibilidade de um merchant 167 | func (m *merchantService) Availability(merchantUUID string) (ar AvailabilityResponse, err error) { 168 | if merchantUUID == "" { 169 | err = ErrMerchantNotSpecified 170 | glg.Error("[SDK] Merchant Availability: ", err.Error()) 171 | return 172 | } 173 | if err = m.auth.Validate(); err != nil { 174 | glg.Error("[SDK] Merchant Availability auth.Validate: ", err.Error()) 175 | return 176 | } 177 | headers := make(map[string]string) 178 | headers["Content-Type"] = "application/json" 179 | headers["Authorization"] = fmt.Sprintf("Bearer %s", m.auth.GetToken()) 180 | endpoint := fmt.Sprintf("/merchant%s/%s/availabilities", v2Endpoint, merchantUUID) 181 | resp, status, err := m.adapter.DoRequest(http.MethodGet, endpoint, nil, headers) 182 | if err != nil { 183 | glg.Error("[SDK] Merchant Availability adapter.DoRequest error: ", err.Error()) 184 | return 185 | } 186 | if status != http.StatusOK { 187 | glg.Error("[SDK] Merchant Availability status code: ", status, " merchant: ", merchantUUID) 188 | err = fmt.Errorf("Merchant '%s' could not get availability", merchantUUID) 189 | glg.Error("[SDK] Merchant Availability err: ", err) 190 | return 191 | } 192 | return ar, json.Unmarshal(resp, &ar) 193 | } 194 | -------------------------------------------------------------------------------- /services/merchant/merchant_test.go: -------------------------------------------------------------------------------- 1 | package merchant 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 11 | "github.com/arxdsilva/golang-ifood-sdk/mocks" 12 | auth "github.com/arxdsilva/golang-ifood-sdk/services/authentication" 13 | "github.com/gofrs/uuid" 14 | "github.com/stretchr/testify/assert" 15 | "github.com/stretchr/testify/mock" 16 | ) 17 | 18 | var merchants = `[ 19 | { 20 | "id": "0fd7a60b-930c-49f5-a8d9-b721bb86f7c0", 21 | "name": "Test" 22 | } 23 | ]` 24 | 25 | var unavailabilities = `[ 26 | { 27 | "id": "84ab1175-5360-4b03-8598-3d16faaa560d", 28 | "storeId": "3d1b6527-99f2-498b-a6ad-23b4d2bf9999", 29 | "description": "Teste de Fechamento", 30 | "authorId": "Id", 31 | "start": "2020-04-04T14:30:00", 32 | "end": "2020-04-04T18:10:00" 33 | } 34 | ]` 35 | 36 | var unavNowResponse = `{ 37 | "id": "d0fd503f-7a2f-4bbb-8a5b-cee335ee4233", 38 | "storeId": "3d1b6527-99f2-498b-a6ad-23b4d2bfc999", 39 | "description": "Teste de Pausa Programada | Client id: username", 40 | "authorId": "9999999", 41 | "start": "2020-10-19T11:20:41.640899", 42 | "end": "2020-10-19T11:35:41.640899" 43 | }` 44 | 45 | var available = ` 46 | [ 47 | { 48 | "context": "delivery", 49 | "available": true, 50 | "state": "OK", 51 | "reopenable": { 52 | "identifier": null, 53 | "type": null, 54 | "reopenable": false 55 | }, 56 | "validations": [ 57 | { 58 | "id": "opening-hours", 59 | "code": "during.opening-hours.config", 60 | "state": "OK", 61 | "message": { 62 | "title": "Dentro do horário de funcionamento", 63 | "subtitle": "quarta-feira, das 00:00 às 23:59", 64 | "description": "", 65 | "priority": 27 66 | } 67 | }, 68 | { 69 | "id": "is-connected", 70 | "code": "is.connected.config", 71 | "state": "OK", 72 | "message": { 73 | "title": "Loja conectada à rede do iFood", 74 | "subtitle": "", 75 | "description": "", 76 | "priority": 999 77 | } 78 | } 79 | ], 80 | "message": { 81 | "title": "Loja aberta", 82 | "subtitle": "", 83 | "description": "", 84 | "priority": 999 85 | } 86 | } 87 | ]` 88 | 89 | func TestListAll_OK(t *testing.T) { 90 | ts := httptest.NewServer( 91 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 92 | assert.Equal(t, "/v1.0/merchants", r.URL.Path) 93 | assert.NotNil(t, r.Header["Authorization"][0]) 94 | assert.Equal(t, r.Method, http.MethodGet) 95 | w.WriteHeader(http.StatusOK) 96 | fmt.Fprintf(w, merchants) 97 | }), 98 | ) 99 | defer ts.Close() 100 | am := auth.AuthMock{} 101 | am.On("Validate").Once().Return(nil) 102 | am.On("GetToken").Once().Return("token") 103 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 104 | merchantService := New(adapter, &am) 105 | assert.NotNil(t, merchantService) 106 | events, err := merchantService.ListAll() 107 | assert.Nil(t, err) 108 | assert.Equal(t, 1, len(events)) 109 | } 110 | 111 | func TestListAll_StatusNotFound(t *testing.T) { 112 | ts := httptest.NewServer( 113 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 114 | assert.Equal(t, "/v1.0/merchants", r.URL.Path) 115 | assert.NotNil(t, r.Header["Authorization"][0]) 116 | assert.Equal(t, r.Method, http.MethodGet) 117 | w.WriteHeader(http.StatusNotFound) 118 | }), 119 | ) 120 | defer ts.Close() 121 | am := auth.AuthMock{} 122 | am.On("Validate").Once().Return(nil) 123 | am.On("GetToken").Once().Return("token") 124 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 125 | merchantService := New(adapter, &am) 126 | assert.NotNil(t, merchantService) 127 | events, err := merchantService.ListAll() 128 | assert.NotNil(t, err) 129 | assert.Equal(t, 0, len(events)) 130 | } 131 | 132 | func TestListAll_ValidateErr(t *testing.T) { 133 | ts := httptest.NewServer( 134 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 135 | assert.Equal(t, "/v1.0/merchants", r.URL.Path) 136 | assert.NotNil(t, r.Header["Authorization"][0]) 137 | assert.Equal(t, r.Method, http.MethodGet) 138 | w.WriteHeader(http.StatusNotFound) 139 | }), 140 | ) 141 | defer ts.Close() 142 | am := auth.AuthMock{} 143 | am.On("Validate").Once().Return(errors.New("some err")) 144 | am.On("GetToken").Once().Return("token") 145 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 146 | merchantService := New(adapter, &am) 147 | assert.NotNil(t, merchantService) 148 | events, err := merchantService.ListAll() 149 | assert.NotNil(t, err) 150 | assert.Equal(t, 0, len(events)) 151 | } 152 | 153 | func TestUnavailabilities_OK(t *testing.T) { 154 | ts := httptest.NewServer( 155 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 156 | assert.Contains(t, r.URL.Path, "unavailabilities") 157 | assert.NotNil(t, r.Header["Authorization"][0]) 158 | assert.Equal(t, r.Method, http.MethodGet) 159 | w.WriteHeader(http.StatusOK) 160 | fmt.Fprintf(w, unavailabilities) 161 | }), 162 | ) 163 | defer ts.Close() 164 | am := auth.AuthMock{} 165 | am.On("Validate").Once().Return(nil) 166 | am.On("GetToken").Once().Return("token") 167 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 168 | merchantService := New(adapter, &am) 169 | assert.NotNil(t, merchantService) 170 | id, err := uuid.NewV1() 171 | assert.Nil(t, err) 172 | unavs, err := merchantService.Unavailabilities(id.String()) 173 | assert.Nil(t, err) 174 | assert.Equal(t, 1, len(unavs)) 175 | assert.NotNil(t, unavs[0].ID) 176 | } 177 | 178 | func TestUnavailabilities_ErrMerchantNotSpecified(t *testing.T) { 179 | ts := httptest.NewServer( 180 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 181 | assert.Contains(t, r.URL.Path, "unavailabilities") 182 | assert.NotNil(t, r.Header["Authorization"][0]) 183 | assert.Equal(t, r.Method, http.MethodGet) 184 | w.WriteHeader(http.StatusOK) 185 | fmt.Fprintf(w, unavailabilities) 186 | }), 187 | ) 188 | defer ts.Close() 189 | am := auth.AuthMock{} 190 | am.On("Validate").Once().Return(nil) 191 | am.On("GetToken").Once().Return("token") 192 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 193 | merchantService := New(adapter, &am) 194 | assert.NotNil(t, merchantService) 195 | unavs, err := merchantService.Unavailabilities("") 196 | assert.NotNil(t, err) 197 | assert.Equal(t, ErrMerchantNotSpecified, err) 198 | assert.Equal(t, 0, len(unavs)) 199 | } 200 | 201 | func TestUnavailabilities_StatusBadRequest(t *testing.T) { 202 | ts := httptest.NewServer( 203 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 204 | assert.Contains(t, r.URL.Path, "unavailabilities") 205 | assert.NotNil(t, r.Header["Authorization"][0]) 206 | assert.Equal(t, r.Method, http.MethodGet) 207 | w.WriteHeader(http.StatusBadRequest) 208 | }), 209 | ) 210 | defer ts.Close() 211 | am := auth.AuthMock{} 212 | am.On("Validate").Once().Return(nil) 213 | am.On("GetToken").Once().Return("token") 214 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 215 | merchantService := New(adapter, &am) 216 | assert.NotNil(t, merchantService) 217 | id, err := uuid.NewV1() 218 | assert.Nil(t, err) 219 | unavs, err := merchantService.Unavailabilities(id.String()) 220 | assert.NotNil(t, err) 221 | assert.Equal(t, 0, len(unavs)) 222 | } 223 | 224 | func TestUnavailability_ValidateErr(t *testing.T) { 225 | am := auth.AuthMock{} 226 | am.On("Validate").Once().Return(errors.New("some err")) 227 | am.On("GetToken").Once().Return("token") 228 | adapter := httpadapter.New(http.DefaultClient, "") 229 | merchantService := New(adapter, &am) 230 | assert.NotNil(t, merchantService) 231 | id, err := uuid.NewV1() 232 | assert.Nil(t, err) 233 | _, err = merchantService.Unavailabilities(id.String()) 234 | assert.NotNil(t, err) 235 | assert.Contains(t, err.Error(), "some") 236 | } 237 | 238 | func TestCreateUnavailabilyNow_ErrMerchantNotSpecified(t *testing.T) { 239 | ts := httptest.NewServer( 240 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 241 | assert.Contains(t, r.URL.Path, "unavailabilities:now") 242 | assert.NotNil(t, r.Header["Authorization"][0]) 243 | assert.Equal(t, r.Method, http.MethodPost) 244 | w.WriteHeader(http.StatusOK) 245 | fmt.Fprintf(w, unavNowResponse) 246 | }), 247 | ) 248 | defer ts.Close() 249 | am := auth.AuthMock{} 250 | am.On("Validate").Once().Return(nil) 251 | am.On("GetToken").Once().Return("token") 252 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 253 | merchantService := New(adapter, &am) 254 | assert.NotNil(t, merchantService) 255 | unav, err := merchantService.CreateUnavailabilityNow("", "", 10) 256 | assert.NotNil(t, err) 257 | assert.Equal(t, ErrMerchantNotSpecified, err) 258 | assert.Equal(t, UnavailabilityResponse{}, unav) 259 | } 260 | 261 | func TestCreateUnavailabilyNow_OK(t *testing.T) { 262 | ts := httptest.NewServer( 263 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 264 | assert.Contains(t, r.URL.Path, "unavailabilities:now") 265 | assert.NotNil(t, r.Header["Authorization"][0]) 266 | assert.Equal(t, r.Method, http.MethodPost) 267 | w.WriteHeader(http.StatusOK) 268 | fmt.Fprintf(w, unavNowResponse) 269 | }), 270 | ) 271 | defer ts.Close() 272 | am := auth.AuthMock{} 273 | am.On("Validate").Once().Return(nil) 274 | am.On("GetToken").Once().Return("token") 275 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 276 | merchantService := New(adapter, &am) 277 | assert.NotNil(t, merchantService) 278 | id, err := uuid.NewV1() 279 | assert.Nil(t, err) 280 | unav, err := merchantService.CreateUnavailabilityNow(id.String(), "", 10) 281 | assert.Nil(t, err) 282 | assert.NotNil(t, unav.ID) 283 | } 284 | 285 | func TestCreateUnavailabilyNow_StatusBadRequest(t *testing.T) { 286 | ts := httptest.NewServer( 287 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 288 | assert.Contains(t, r.URL.Path, "unavailabilities:now") 289 | assert.NotNil(t, r.Header["Authorization"][0]) 290 | assert.Equal(t, r.Method, http.MethodPost) 291 | w.WriteHeader(http.StatusBadRequest) 292 | }), 293 | ) 294 | defer ts.Close() 295 | am := auth.AuthMock{} 296 | am.On("Validate").Once().Return(nil) 297 | am.On("GetToken").Once().Return("token") 298 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 299 | merchantService := New(adapter, &am) 300 | assert.NotNil(t, merchantService) 301 | id, err := uuid.NewV1() 302 | assert.Nil(t, err) 303 | unav, err := merchantService.CreateUnavailabilityNow(id.String(), "", 10) 304 | assert.NotNil(t, err) 305 | assert.Equal(t, UnavailabilityResponse{}, unav) 306 | assert.Contains(t, err.Error(), "not create") 307 | } 308 | 309 | func TestCreateUnavailabilityNow_ValidateErr(t *testing.T) { 310 | am := auth.AuthMock{} 311 | am.On("Validate").Once().Return(errors.New("some err")) 312 | am.On("GetToken").Once().Return("token") 313 | adapter := httpadapter.New(http.DefaultClient, "") 314 | merchantService := New(adapter, &am) 315 | assert.NotNil(t, merchantService) 316 | id, err := uuid.NewV1() 317 | assert.Nil(t, err) 318 | _, err = merchantService.CreateUnavailabilityNow(id.String(), "", 10) 319 | assert.NotNil(t, err) 320 | assert.Contains(t, err.Error(), "some") 321 | } 322 | 323 | func TestDeleteUnavailability_OK(t *testing.T) { 324 | ts := httptest.NewServer( 325 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 326 | assert.Contains(t, r.URL.Path, "unavailabilities") 327 | assert.Contains(t, r.URL.Path, "v1.0") 328 | assert.NotNil(t, r.Header["Authorization"][0]) 329 | assert.Equal(t, r.Method, http.MethodDelete) 330 | w.WriteHeader(http.StatusOK) 331 | }), 332 | ) 333 | defer ts.Close() 334 | am := auth.AuthMock{} 335 | am.On("Validate").Once().Return(nil) 336 | am.On("GetToken").Once().Return("token") 337 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 338 | merchantService := New(adapter, &am) 339 | assert.NotNil(t, merchantService) 340 | id, err := uuid.NewV1() 341 | assert.Nil(t, err) 342 | err = merchantService.DeleteUnavailability(id.String(), id.String()) 343 | assert.Nil(t, err) 344 | } 345 | 346 | func TestDeleteUnavailabily_NoMerchant(t *testing.T) { 347 | ts := httptest.NewServer( 348 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 349 | assert.Contains(t, r.URL.Path, "unavailabilities") 350 | assert.Contains(t, r.URL.Path, "v1.0") 351 | assert.NotNil(t, r.Header["Authorization"][0]) 352 | assert.Equal(t, r.Method, http.MethodDelete) 353 | w.WriteHeader(http.StatusOK) 354 | }), 355 | ) 356 | defer ts.Close() 357 | am := auth.AuthMock{} 358 | am.On("Validate").Once().Return(nil) 359 | am.On("GetToken").Once().Return("token") 360 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 361 | merchantService := New(adapter, &am) 362 | assert.NotNil(t, merchantService) 363 | err := merchantService.DeleteUnavailability("", "") 364 | assert.NotNil(t, err) 365 | assert.Equal(t, ErrMerchantORUnavailabilityIDNotSpecified, err) 366 | } 367 | 368 | func TestDeleteUnavailabily_ValidateErr(t *testing.T) { 369 | am := auth.AuthMock{} 370 | am.On("Validate").Once().Return(errors.New("some err")) 371 | am.On("GetToken").Once().Return("token") 372 | adapter := httpadapter.New(http.DefaultClient, "") 373 | merchantService := New(adapter, &am) 374 | assert.NotNil(t, merchantService) 375 | id, err := uuid.NewV1() 376 | assert.Nil(t, err) 377 | err = merchantService.DeleteUnavailability(id.String(), id.String()) 378 | assert.NotNil(t, err) 379 | assert.Contains(t, err.Error(), "some") 380 | } 381 | 382 | func TestDeleteUnavailabily_StatusBadRequest(t *testing.T) { 383 | ts := httptest.NewServer( 384 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 385 | assert.Contains(t, r.URL.Path, "unavailabilities") 386 | assert.Contains(t, r.URL.Path, "v1.0") 387 | assert.NotNil(t, r.Header["Authorization"][0]) 388 | assert.Equal(t, r.Method, http.MethodDelete) 389 | w.WriteHeader(http.StatusBadRequest) 390 | }), 391 | ) 392 | defer ts.Close() 393 | am := auth.AuthMock{} 394 | am.On("Validate").Once().Return(nil) 395 | am.On("GetToken").Once().Return("token") 396 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 397 | merchantService := New(adapter, &am) 398 | assert.NotNil(t, merchantService) 399 | id, err := uuid.NewV1() 400 | assert.Nil(t, err) 401 | err = merchantService.DeleteUnavailability(id.String(), id.String()) 402 | assert.NotNil(t, err) 403 | assert.Contains(t, err.Error(), "could not delete") 404 | } 405 | 406 | func TestAvailabily_OK(t *testing.T) { 407 | ts := httptest.NewServer( 408 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 409 | assert.Contains(t, r.URL.Path, "/availabilities") 410 | assert.Contains(t, r.URL.Path, "/merchant/v2.0/merchants/") 411 | assert.NotNil(t, r.Header["Authorization"][0]) 412 | assert.Equal(t, r.Method, http.MethodGet) 413 | w.WriteHeader(http.StatusOK) 414 | fmt.Fprintf(w, available) 415 | }), 416 | ) 417 | defer ts.Close() 418 | am := auth.AuthMock{} 419 | am.On("Validate").Once().Return(nil) 420 | am.On("GetToken").Once().Return("token") 421 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 422 | merchantService := New(adapter, &am) 423 | assert.NotNil(t, merchantService) 424 | id, err := uuid.NewV1() 425 | assert.Nil(t, err) 426 | ar, err := merchantService.Availability(id.String()) 427 | assert.Nil(t, err) 428 | assert.Equal(t, 1, len(ar)) 429 | assert.Equal(t, true, ar[0].Available) 430 | } 431 | 432 | func TestAvailabily_StatusBadRequest(t *testing.T) { 433 | ts := httptest.NewServer( 434 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 435 | assert.Contains(t, r.URL.Path, "/availabilities") 436 | assert.Contains(t, r.URL.Path, "/merchant/v2.0/merchants/") 437 | assert.NotNil(t, r.Header["Authorization"][0]) 438 | assert.Equal(t, r.Method, http.MethodGet) 439 | w.WriteHeader(http.StatusBadRequest) 440 | }), 441 | ) 442 | defer ts.Close() 443 | am := auth.AuthMock{} 444 | am.On("Validate").Once().Return(nil) 445 | am.On("GetToken").Once().Return("token") 446 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 447 | merchantService := New(adapter, &am) 448 | assert.NotNil(t, merchantService) 449 | id, err := uuid.NewV1() 450 | assert.Nil(t, err) 451 | ar, err := merchantService.Availability(id.String()) 452 | assert.NotNil(t, err) 453 | assert.Equal(t, 0, len(ar)) 454 | } 455 | 456 | func TestAvailabily_ValidateErr(t *testing.T) { 457 | ts := httptest.NewServer( 458 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 459 | assert.Contains(t, r.URL.Path, "/availabilities") 460 | assert.Contains(t, r.URL.Path, "/merchant/v2.0/merchants/") 461 | assert.NotNil(t, r.Header["Authorization"][0]) 462 | assert.Equal(t, r.Method, http.MethodGet) 463 | w.WriteHeader(http.StatusBadRequest) 464 | }), 465 | ) 466 | defer ts.Close() 467 | am := auth.AuthMock{} 468 | am.On("Validate").Once().Return(errors.New("some err")) 469 | am.On("GetToken").Once().Return("token") 470 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 471 | merchantService := New(adapter, &am) 472 | assert.NotNil(t, merchantService) 473 | id, err := uuid.NewV1() 474 | assert.Nil(t, err) 475 | _, err = merchantService.Availability(id.String()) 476 | assert.NotNil(t, err) 477 | assert.Contains(t, err.Error(), "some") 478 | } 479 | 480 | func TestAvailabily_ErrMerchantNotSpecified(t *testing.T) { 481 | ts := httptest.NewServer( 482 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 483 | assert.Contains(t, r.URL.Path, "/availabilities") 484 | assert.Contains(t, r.URL.Path, "/merchant/v2.0/merchants/") 485 | assert.NotNil(t, r.Header["Authorization"][0]) 486 | assert.Equal(t, r.Method, http.MethodGet) 487 | w.WriteHeader(http.StatusBadRequest) 488 | }), 489 | ) 490 | defer ts.Close() 491 | am := auth.AuthMock{} 492 | am.On("Validate").Once().Return(errors.New("some err")) 493 | am.On("GetToken").Once().Return("token") 494 | adapter := httpadapter.New(http.DefaultClient, ts.URL) 495 | merchantService := New(adapter, &am) 496 | assert.NotNil(t, merchantService) 497 | _, err := merchantService.Availability("") 498 | assert.NotNil(t, err) 499 | assert.Equal(t, ErrMerchantNotSpecified, err) 500 | } 501 | 502 | func TestAvailabily_DoReqErr(t *testing.T) { 503 | am := auth.AuthMock{} 504 | am.On("Validate").Once().Return(nil) 505 | am.On("GetToken").Once().Return("token") 506 | httpmock := &mocks.HttpClientMock{} 507 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 508 | adapter := httpadapter.New(httpmock, "") 509 | merchantService := New(adapter, &am) 510 | assert.NotNil(t, merchantService) 511 | id, err := uuid.NewV1() 512 | assert.Nil(t, err) 513 | _, err = merchantService.Availability(id.String()) 514 | assert.NotNil(t, err) 515 | assert.Contains(t, err.Error(), "some") 516 | } 517 | 518 | func TestDeleteUnavailability_DoReqErr(t *testing.T) { 519 | am := auth.AuthMock{} 520 | am.On("Validate").Once().Return(nil) 521 | am.On("GetToken").Once().Return("token") 522 | httpmock := &mocks.HttpClientMock{} 523 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 524 | adapter := httpadapter.New(httpmock, "") 525 | merchantService := New(adapter, &am) 526 | assert.NotNil(t, merchantService) 527 | id, err := uuid.NewV1() 528 | assert.Nil(t, err) 529 | err = merchantService.DeleteUnavailability(id.String(), id.String()) 530 | assert.NotNil(t, err) 531 | assert.Contains(t, err.Error(), "some") 532 | } 533 | 534 | func TestCreateUnavailabilityNow_DoReqErr(t *testing.T) { 535 | am := auth.AuthMock{} 536 | am.On("Validate").Once().Return(nil) 537 | am.On("GetToken").Once().Return("token") 538 | httpmock := &mocks.HttpClientMock{} 539 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 540 | adapter := httpadapter.New(httpmock, "") 541 | merchantService := New(adapter, &am) 542 | assert.NotNil(t, merchantService) 543 | id, err := uuid.NewV1() 544 | assert.Nil(t, err) 545 | _, err = merchantService.CreateUnavailabilityNow(id.String(), "", 10) 546 | assert.NotNil(t, err) 547 | assert.Contains(t, err.Error(), "some") 548 | } 549 | 550 | func TestUnavailabilities_DoReqErr(t *testing.T) { 551 | am := auth.AuthMock{} 552 | am.On("Validate").Once().Return(nil) 553 | am.On("GetToken").Once().Return("token") 554 | httpmock := &mocks.HttpClientMock{} 555 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 556 | adapter := httpadapter.New(httpmock, "") 557 | merchantService := New(adapter, &am) 558 | assert.NotNil(t, merchantService) 559 | id, err := uuid.NewV1() 560 | assert.Nil(t, err) 561 | _, err = merchantService.Unavailabilities(id.String()) 562 | assert.NotNil(t, err) 563 | assert.Contains(t, err.Error(), "some") 564 | } 565 | 566 | func TestListAll_DoReqErr(t *testing.T) { 567 | am := auth.AuthMock{} 568 | am.On("Validate").Once().Return(nil) 569 | am.On("GetToken").Once().Return("token") 570 | httpmock := &mocks.HttpClientMock{} 571 | httpmock.On("Do", mock.Anything).Once().Return(nil, errors.New("some err")) 572 | adapter := httpadapter.New(httpmock, "") 573 | merchantService := New(adapter, &am) 574 | assert.NotNil(t, merchantService) 575 | _, err := merchantService.ListAll() 576 | assert.NotNil(t, err) 577 | assert.Contains(t, err.Error(), "some") 578 | } 579 | -------------------------------------------------------------------------------- /services/merchant/types.go: -------------------------------------------------------------------------------- 1 | package merchant 2 | 3 | type ( 4 | // Merchant API response 5 | Merchant struct { 6 | ID string `json:"id"` 7 | Name string `json:"name"` 8 | Phones []string `json:"phones"` 9 | Address Address `json:"address"` 10 | } 11 | 12 | // Address in a merchant 13 | Address struct { 14 | Formattedaddress string `json:"formattedAddress"` 15 | Country string `json:"country"` 16 | State string `json:"state"` 17 | City string `json:"city"` 18 | Neighborhood string `json:"neighborhood"` 19 | Streetname string `json:"streetName"` 20 | Streetnumber string `json:"streetNumber"` 21 | Postalcode string `json:"postalCode"` 22 | } 23 | 24 | // Unavailabilities group of Unavailability 25 | Unavailabilities []Unavailability 26 | 27 | // Unavailability API response 28 | Unavailability struct { 29 | ID string `json:"id"` 30 | Storeid string `json:"storeId"` 31 | Description string `json:"description"` 32 | Authorid string `json:"authorId"` 33 | Start string `json:"start"` 34 | End string `json:"end"` 35 | } 36 | 37 | unavailability struct { 38 | Description string `json:"description"` 39 | Minutes int32 `json:"minutes"` 40 | } 41 | 42 | // UnavailabilityResponse API response 43 | UnavailabilityResponse struct { 44 | ID string `json:"id"` 45 | Storeid string `json:"storeId"` 46 | Description string `json:"description"` 47 | Authorid string `json:"authorId"` 48 | Start string `json:"start"` 49 | End string `json:"end"` 50 | } 51 | 52 | // AvailabilityResponse group of Availability 53 | AvailabilityResponse []Availability 54 | 55 | // Availability struct to API validate 56 | Availability struct { 57 | Context string `json:"context"` 58 | Available bool `json:"available"` 59 | State string `json:"state"` 60 | Reopenable struct { 61 | // Identifier interface{} `json:"identifier"` 62 | // Type interface{} `json:"type"` 63 | Reopenable bool `json:"reopenable"` 64 | } `json:"reopenable"` 65 | Validations []struct { 66 | ID string `json:"id"` 67 | Code string `json:"code"` 68 | State string `json:"state"` 69 | Message struct { 70 | Title string `json:"title"` 71 | Subtitle string `json:"subtitle"` 72 | Description string `json:"description"` 73 | Priority int `json:"priority"` 74 | } `json:"message"` 75 | } `json:"validations"` 76 | Message struct { 77 | Title string `json:"title"` 78 | Subtitle string `json:"subtitle"` 79 | Description string `json:"description"` 80 | Priority int `json:"priority"` 81 | } `json:"message"` 82 | } 83 | ) 84 | -------------------------------------------------------------------------------- /services/orders/errors.go: -------------------------------------------------------------------------------- 1 | package orders 2 | 3 | import "errors" 4 | 5 | var ( 6 | // ErrOrderReferenceNotSpecified no order_id specified 7 | ErrOrderReferenceNotSpecified = errors.New("Order reference not specified") 8 | // ErrCancelCodeNotSpecified no cancel code provided 9 | ErrCancelCodeNotSpecified = errors.New("Order cancel code not specified") 10 | ) 11 | -------------------------------------------------------------------------------- /services/orders/orders.go: -------------------------------------------------------------------------------- 1 | package orders 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | 9 | "github.com/arxdsilva/golang-ifood-sdk/adapters" 10 | httpadapter "github.com/arxdsilva/golang-ifood-sdk/adapters/http" 11 | auth "github.com/arxdsilva/golang-ifood-sdk/services/authentication" 12 | "github.com/arxdsilva/golang-ifood-sdk/services/events" 13 | "github.com/kpango/glg" 14 | ) 15 | 16 | const ( 17 | v1Endpoint = "/v1.0/orders" 18 | v2Endpoint = "/v2.0/orders" 19 | v3Endpoint = "/v3.0/orders" 20 | newV2Endpoint = "/order/v1.0/orders/" 21 | ) 22 | 23 | var ( 24 | // CancelCodes are all valid iFood API cancellation codes 25 | CancelCodes = map[string]string{ 26 | "501": "PROBLEMAS DE SISTEMA", 27 | "502": "PEDIDO EM DUPLICIDADE", 28 | "503": "ITEM INDISPONÍVEL", 29 | "504": "RESTAURANTE SEM MOTOBOY", 30 | "505": "CARDÁPIO DESATUALIZADO", 31 | "506": "PEDIDO FORA DA ÁREA DE ENTREGA", 32 | "507": "CLIENTE GOLPISTA / TROTE", 33 | "508": "FORA DO HORÁRIO DO DELIVERY", 34 | "509": "DIFICULDADES INTERNAS DO RESTAURANTE", 35 | "511": "ÁREA DE RISCO", 36 | "512": "RESTAURANTE ABRIRÁ MAIS TARDE", 37 | "513": "RESTAURANTE FECHOU MAIS CEDO", 38 | "803": "ITEM INDISPONÍVEL", 39 | "805": "RESTAURANTE SEM MOTOBOY", 40 | "801": "PROBLEMAS DE SISTEMA", 41 | "804": "CADASTRO DO CLIENTE INCOMPLETO - CLIENTE NÃO ATENDE", 42 | "807": "PEDIDO FORA DA ÁREA DE ENTREGA", 43 | "808": "CLIENTE GOLPISTA / TROTE", 44 | "809": "FORA DO HORÁRIO DO DELIVERY", 45 | "815": "DIFICULDADES INTERNAS DO RESTAURANTE", 46 | "818": "TAXA DE ENTREGA INCONSISTENTE", 47 | "820": "ÁREA DE RISCO", 48 | } 49 | ) 50 | 51 | type ( 52 | // Service determinates the order's interface 53 | Service interface { 54 | GetDetails(reference string) (OrderDetails, error) 55 | V2GetDetails(reference string) (V2OrderDetails, error) 56 | SetIntegrateStatus(reference string) error 57 | SetConfirmStatus(reference string) error 58 | V2SetConfirmStatus(reference string) error 59 | SetDispatchStatus(reference string) error 60 | V2SetDispatchStatus(reference string) error 61 | SetReadyToDeliverStatus(reference string) error 62 | V2SetReadyToPickupStatus(reference string) error 63 | SetCancelStatus(reference, code string) error 64 | V2RequestCancelStatus(reference, code string) error 65 | ClientCancellationStatus(reference string, accepted bool) error 66 | V2ClientCancellationStatus(reference string, accepted bool) error 67 | Tracking(orderUUID string) (TrackingResponse, error) 68 | DeliveryInformation(orderUUID string) (DeliveryInformationResponse, error) 69 | } 70 | 71 | ordersService struct { 72 | adapter adapters.Http 73 | auth auth.Service 74 | } 75 | ) 76 | 77 | // New returns a new order service 78 | func New(adapter adapters.Http, authService auth.Service) Service { 79 | return &ordersService{adapter, authService} 80 | } 81 | 82 | func (o *ordersService) GetDetails(orderReference string) (od OrderDetails, err error) { 83 | if orderReference == "" { 84 | err = ErrOrderReferenceNotSpecified 85 | glg.Error("[SDK] Orders GetDetails: ", err.Error()) 86 | return 87 | } 88 | err = o.auth.Validate() 89 | if err != nil { 90 | glg.Error("[SDK] Orders GetDetails auth.Validate: ", err.Error()) 91 | return 92 | } 93 | headers := make(map[string]string) 94 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 95 | endpoint := fmt.Sprintf("%s/%s", v3Endpoint, orderReference) 96 | resp, status, err := o.adapter.DoRequest(http.MethodGet, endpoint, nil, headers) 97 | if err != nil { 98 | glg.Error("[SDK] Orders GetDetails adapter.DoRequest error: ", err.Error()) 99 | return 100 | } 101 | if status != http.StatusOK { 102 | glg.Error("[SDK] Orders GetDetails status code: ", status) 103 | err = fmt.Errorf("Order reference '%s' could not retrieve details", orderReference) 104 | glg.Error("[SDK] Orders GetDetails err: ", err) 105 | return 106 | } 107 | glg.Debugf("[SDK] (Orders GetDetails) '%s' OK", orderReference) 108 | return od, json.Unmarshal(resp, &od) 109 | } 110 | 111 | func (o *ordersService) V2GetDetails(orderUUID string) (od V2OrderDetails, err error) { 112 | if orderUUID == "" { 113 | err = ErrOrderReferenceNotSpecified 114 | glg.Error("[SDK] (Orders V2GetDetails): ", err.Error()) 115 | return 116 | } 117 | err = o.auth.Validate() 118 | if err != nil { 119 | glg.Error("[SDK] (Orders V2GetDetails) auth.Validate: ", err.Error()) 120 | return 121 | } 122 | headers := make(map[string]string) 123 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 124 | endpoint := fmt.Sprintf("%s%s", newV2Endpoint, orderUUID) 125 | resp, status, err := o.adapter.DoRequest(http.MethodGet, endpoint, nil, headers) 126 | if err != nil { 127 | glg.Error("[SDK] (Orders V2GetDetails) adapter.DoRequest error: ", err.Error()) 128 | return 129 | } 130 | if status != http.StatusOK { 131 | errMsg := events.ErrV2API{} 132 | json.Unmarshal(resp, &errMsg) 133 | err = errors.New(errMsg.Error.Message) 134 | glg.Error("[SDK] (Orders V2GetDetails) status '%d' err: '%s'", status, err.Error()) 135 | return 136 | } 137 | glg.Debugf("[SDK] (Orders V2GetDetails) '%s' OK", orderUUID) 138 | return od, json.Unmarshal(resp, &od) 139 | } 140 | 141 | func (o *ordersService) SetIntegrateStatus(orderReference string) (err error) { 142 | if orderReference == "" { 143 | err = ErrOrderReferenceNotSpecified 144 | glg.Error("[SDK] Orders SetIntegrateStatus: ", err.Error()) 145 | return 146 | } 147 | err = o.auth.Validate() 148 | if err != nil { 149 | glg.Error("[SDK] Orders SetIntegrateStatus auth.Validate: ", err.Error()) 150 | return 151 | } 152 | headers := make(map[string]string) 153 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 154 | endpoint := fmt.Sprintf("%s/%s/statuses/integration", v1Endpoint, orderReference) 155 | _, status, err := o.adapter.DoRequest(http.MethodPost, endpoint, nil, headers) 156 | if err != nil { 157 | glg.Error("[SDK] Orders SetIntegrateStatus adapter.DoRequest error: ", err.Error()) 158 | return 159 | } 160 | if status != http.StatusAccepted { 161 | glg.Error("[SDK] Orders SetIntegrateStatus status code: ", status, " orderReference: ", orderReference) 162 | err = fmt.Errorf("Order reference %s could not be integrated", orderReference) 163 | glg.Error("[SDK] Orders SetIntegrateStatus err: ", err) 164 | return 165 | } 166 | glg.Debugf("[SDK] (Orders SetIntegrateStatus) '%s' OK", orderReference) 167 | return 168 | } 169 | 170 | func (o *ordersService) SetConfirmStatus(orderReference string) (err error) { 171 | if orderReference == "" { 172 | err = ErrOrderReferenceNotSpecified 173 | glg.Error("[SDK] Orders SetConfirmStatus: ", err.Error()) 174 | return 175 | } 176 | err = o.auth.Validate() 177 | if err != nil { 178 | glg.Error("[SDK] Orders SetConfirmStatus auth.Validate: ", err.Error()) 179 | return 180 | } 181 | headers := make(map[string]string) 182 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 183 | endpoint := fmt.Sprintf("%s/%s/statuses/confirmation", v1Endpoint, orderReference) 184 | _, status, err := o.adapter.DoRequest(http.MethodPost, endpoint, nil, headers) 185 | if err != nil { 186 | glg.Error("[SDK] Orders SetConfirmStatus adapter.DoRequest error: ", err.Error()) 187 | return 188 | } 189 | if status != http.StatusAccepted { 190 | glg.Error("[SDK] Orders SetConfirmStatus status code: ", status, " orderReference: ", orderReference) 191 | err = fmt.Errorf("Order reference '%s' could not be confirmed", orderReference) 192 | glg.Error("[SDK] Orders SetConfirmStatus err: ", err) 193 | return 194 | } 195 | glg.Debugf("[SDK] (Orders SetConfirmStatus) '%s' OK", orderReference) 196 | return 197 | } 198 | 199 | // V2SetConfirmStatus trys to update an order to confirmed status 200 | func (o *ordersService) V2SetConfirmStatus(orderReference string) (err error) { 201 | if orderReference == "" { 202 | err = ErrOrderReferenceNotSpecified 203 | glg.Error("[SDK] (Orders V2SetConfirmStatus): ", err.Error()) 204 | return 205 | } 206 | err = o.auth.Validate() 207 | if err != nil { 208 | glg.Error("[SDK] (Orders V2SetConfirmStatus) auth.Validate: ", err.Error()) 209 | return 210 | } 211 | headers := make(map[string]string) 212 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 213 | endpoint := fmt.Sprintf("%s%s/confirm", newV2Endpoint, orderReference) 214 | resp, status, err := o.adapter.DoRequest(http.MethodPost, endpoint, nil, headers) 215 | if err != nil { 216 | glg.Error("[SDK] (Orders V2SetConfirmStatus) adapter.DoRequest error: ", err.Error()) 217 | return 218 | } 219 | if status != http.StatusAccepted { 220 | errMsg := events.ErrV2API{} 221 | json.Unmarshal(resp, &errMsg) 222 | err = errors.New(errMsg.Error.Message) 223 | glg.Error("[SDK] (Orders V2SetConfirmStatus) status '%d' err: '%s'", status, err.Error()) 224 | return 225 | } 226 | glg.Debugf("[SDK] (Orders V2SetConfirmStatus) '%s' OK", orderReference) 227 | return 228 | } 229 | 230 | func (o *ordersService) SetDispatchStatus(orderReference string) (err error) { 231 | if orderReference == "" { 232 | err = ErrOrderReferenceNotSpecified 233 | glg.Error("[SDK] Orders SetDispatchStatus: ", err.Error()) 234 | return 235 | } 236 | err = o.auth.Validate() 237 | if err != nil { 238 | glg.Error("[SDK] Orders SetDispatchStatus auth.Validate: ", err.Error()) 239 | return 240 | } 241 | headers := make(map[string]string) 242 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 243 | endpoint := fmt.Sprintf("%s/%s/statuses/dispatch", v1Endpoint, orderReference) 244 | _, status, err := o.adapter.DoRequest(http.MethodPost, endpoint, nil, headers) 245 | if err != nil { 246 | glg.Error("[SDK] Orders SetDispatchStatus adapter.DoRequest error: ", err.Error()) 247 | return 248 | } 249 | if status != http.StatusAccepted { 250 | glg.Error("[SDK] Orders SetDispatchStatus status code: ", status, " orderReference: ", orderReference) 251 | err = fmt.Errorf("Order reference '%s' could not be dispatched", orderReference) 252 | glg.Error("[SDK] Orders SetDispatchStatus err: ", err) 253 | return 254 | } 255 | glg.Debugf("[SDK] (Orders SetDispatchStatus) '%s' OK", orderReference) 256 | return 257 | } 258 | 259 | func (o *ordersService) V2SetDispatchStatus(orderReference string) (err error) { 260 | if orderReference == "" { 261 | err = ErrOrderReferenceNotSpecified 262 | glg.Error("[SDK] (Orders V2SetDispatchStatus): ", err.Error()) 263 | return 264 | } 265 | err = o.auth.Validate() 266 | if err != nil { 267 | glg.Error("[SDK] (Orders V2SetDispatchStatus) auth.Validate: ", err.Error()) 268 | return 269 | } 270 | headers := make(map[string]string) 271 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 272 | endpoint := fmt.Sprintf("%s%s/dispatch", newV2Endpoint, orderReference) 273 | resp, status, err := o.adapter.DoRequest(http.MethodPost, endpoint, nil, headers) 274 | if err != nil { 275 | glg.Error("[SDK] (Orders V2SetDispatchStatus) adapter.DoRequest error: ", err.Error()) 276 | return 277 | } 278 | if status != http.StatusAccepted { 279 | errMsg := events.ErrV2API{} 280 | json.Unmarshal(resp, &errMsg) 281 | err = errors.New(errMsg.Error.Message) 282 | glg.Error("[SDK] (Orders V2SetDispatchStatus) status '%d' err: '%s'", status, err.Error()) 283 | return 284 | } 285 | glg.Debugf("[SDK] (Orders V2SetDispatchStatus) '%s' OK", orderReference) 286 | return 287 | } 288 | 289 | func (o *ordersService) SetReadyToDeliverStatus(orderReference string) (err error) { 290 | if orderReference == "" { 291 | err = ErrOrderReferenceNotSpecified 292 | glg.Error("[SDK] Orders SetReadyToDeliverStatus: ", err.Error()) 293 | return 294 | } 295 | err = o.auth.Validate() 296 | if err != nil { 297 | glg.Error("[SDK] Orders SetReadyToDeliverStatus auth.Validate: ", err.Error()) 298 | return 299 | } 300 | headers := make(map[string]string) 301 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 302 | endpoint := fmt.Sprintf("%s/%s/statuses/readyToDeliver", v2Endpoint, orderReference) 303 | _, status, err := o.adapter.DoRequest(http.MethodPost, endpoint, nil, headers) 304 | if err != nil { 305 | glg.Error("[SDK] Orders SetReadyToDeliverStatus adapter.DoRequest error: ", err.Error()) 306 | return 307 | } 308 | if status != http.StatusAccepted { 309 | glg.Error("[SDK] Orders SetReadyToDeliverStatus status code: ", status, " orderReference: ", orderReference) 310 | err = fmt.Errorf("Order reference '%s' could not be set as 'ready to deliver'", orderReference) 311 | glg.Error("[SDK] Orders SetReadyToDeliverStatus err: ", err) 312 | return 313 | } 314 | glg.Debugf("[SDK] (Orders SetReadyToDeliverStatus) '%s' OK", orderReference) 315 | return 316 | } 317 | 318 | func (o *ordersService) V2SetReadyToPickupStatus(orderReference string) (err error) { 319 | if orderReference == "" { 320 | err = ErrOrderReferenceNotSpecified 321 | glg.Error("[SDK] (Orders V2SetReadyToPickupStatus): ", err.Error()) 322 | return 323 | } 324 | err = o.auth.Validate() 325 | if err != nil { 326 | glg.Error("[SDK] (Orders V2SetReadyToPickupStatus) auth.Validate: ", err.Error()) 327 | return 328 | } 329 | headers := make(map[string]string) 330 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 331 | endpoint := fmt.Sprintf("%s%s/readyToPickup", newV2Endpoint, orderReference) 332 | resp, status, err := o.adapter.DoRequest(http.MethodPost, endpoint, nil, headers) 333 | if err != nil { 334 | glg.Error("[SDK] (Orders V2SetReadyToPickupStatus) adapter.DoRequest error: ", err.Error()) 335 | return 336 | } 337 | if status != http.StatusAccepted { 338 | errMsg := events.ErrV2API{} 339 | json.Unmarshal(resp, &errMsg) 340 | err = errors.New(errMsg.Error.Message) 341 | glg.Error("[SDK] (Orders V2SetReadyToPickupStatus) status '%d' err: '%s'", status, err.Error()) 342 | return 343 | } 344 | glg.Debugf("[SDK] (Orders V2SetReadyToPickupStatus) '%s' OK", orderReference) 345 | return 346 | } 347 | 348 | func (o *ordersService) SetCancelStatus(orderReference, code string) (err error) { 349 | if err = verifyCancel(orderReference, code); err != nil { 350 | glg.Error("[SDK] Orders SetCancelStatus verifyCancel: ", err.Error()) 351 | return 352 | } 353 | err = o.auth.Validate() 354 | if err != nil { 355 | glg.Error("[SDK] Orders SetCancelStatus auth.Validate: ", err.Error()) 356 | return 357 | } 358 | headers := make(map[string]string) 359 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 360 | endpoint := fmt.Sprintf("%s/%s/statuses/cancellationRequested", v3Endpoint, orderReference) 361 | detail := CancelCodes[code] 362 | co := cancelOrder{Code: code, Details: detail} 363 | reader, err := httpadapter.NewJsonReader(co) 364 | if err != nil { 365 | glg.Error("[SDK] Orders SetCancelStatus NewJsonReader error: ", err.Error()) 366 | return 367 | } 368 | _, status, err := o.adapter.DoRequest(http.MethodPost, endpoint, reader, headers) 369 | if err != nil { 370 | glg.Error("[SDK] Orders SetCancelStatus adapter.DoRequest error: ", err.Error()) 371 | return 372 | } 373 | if status != http.StatusAccepted { 374 | glg.Error("[SDK] Orders SetCancelStatus status code: ", status, " orderReference: ", orderReference) 375 | err = fmt.Errorf( 376 | "Order reference '%s' could not be set as 'cancelled' code '%s', detail '%s'", 377 | orderReference, code, detail) 378 | glg.Error("[SDK] Orders SetCancelStatus err: ", err) 379 | return 380 | } 381 | glg.Debugf("[SDK] (Orders SetCancelStatus) '%s' OK", orderReference) 382 | return 383 | } 384 | 385 | // V2RequestCancelStatus on ifood v2 API 386 | 387 | func (o *ordersService) V2RequestCancelStatus(orderReference, code string) (err error) { 388 | if err = verifyCancel(orderReference, code); err != nil { 389 | glg.Error("[SDK] (Orders::V2RequestCancelStatus) verifyCancel: ", err.Error()) 390 | return 391 | } 392 | err = o.auth.Validate() 393 | if err != nil { 394 | glg.Error("[SDK] (Orders::V2RequestCancelStatus) auth.Validate: ", err.Error()) 395 | return 396 | } 397 | headers := make(map[string]string) 398 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 399 | endpoint := fmt.Sprintf("%s%s/requestCancellation", newV2Endpoint, orderReference) 400 | detail := CancelCodes[code] 401 | co := v2CancelOrder{Reason: detail, CancellationCode: code} 402 | reader, err := httpadapter.NewJsonReader(co) 403 | if err != nil { 404 | glg.Error("[SDK] (Orders::V2RequestCancelStatus) NewJsonReader error: ", err.Error()) 405 | return 406 | } 407 | resp, status, err := o.adapter.DoRequest(http.MethodPost, endpoint, reader, headers) 408 | if err != nil { 409 | glg.Error("[SDK] (Orders::V2RequestCancelStatus) adapter.DoRequest error: ", err.Error()) 410 | return 411 | } 412 | if status != http.StatusAccepted { 413 | errMsg := events.ErrV2API{} 414 | json.Unmarshal(resp, &errMsg) 415 | err = errors.New(errMsg.Error.Message) 416 | glg.Error("[SDK] (Orders::V2RequestCancelStatus) status '%d' err: '%s'", status, err.Error()) 417 | return 418 | } 419 | glg.Debugf("[SDK] (Orders::V2RequestCancelStatus) '%s' OK", orderReference) 420 | return 421 | } 422 | 423 | // ClientCancellationStatus lida com o cancelamento do pedido por parte do cliente 424 | // 425 | // link: https://developer.ifood.com.br/reference#handshake-cancelamento 426 | // 427 | // reference: order reference id 428 | // accepted: aceitacao pelo e-PDV do cancelamento do pedido 429 | func (o *ordersService) ClientCancellationStatus(orderReference string, accepted bool) (err error) { 430 | if orderReference == "" { 431 | err = ErrOrderReferenceNotSpecified 432 | glg.Error("[SDK] Orders ClientCancellationStatus: ", err.Error()) 433 | return 434 | } 435 | if err = o.auth.Validate(); err != nil { 436 | glg.Error("[SDK] Orders ClientCancellationStatus auth.Validate: ", err.Error()) 437 | return 438 | } 439 | cancelStatus := "consumerCancellationDenied" 440 | if accepted { 441 | cancelStatus = "consumerCancellationAccepted" 442 | } 443 | headers := make(map[string]string) 444 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 445 | endpoint := fmt.Sprintf("%s/%s/statuses/%s", v2Endpoint, orderReference, cancelStatus) 446 | _, status, err := o.adapter.DoRequest(http.MethodPost, endpoint, nil, headers) 447 | if err != nil { 448 | glg.Error("[SDK] Orders ClientCancellationStatus adapter.DoRequest error: ", err.Error()) 449 | return 450 | } 451 | if status != http.StatusAccepted { 452 | glg.Error("[SDK] Orders ClientCancellationStatus status code: ", status, " orderReference: ", orderReference) 453 | err = fmt.Errorf( 454 | "Order reference '%s' could not set 'client cancellation' status '%s'", 455 | orderReference, cancelStatus) 456 | glg.Error("[SDK] Orders ClientCancellationStatus err: ", err) 457 | return 458 | } 459 | glg.Debugf("[SDK] (Orders ClientCancellationStatus) '%s' OK", orderReference) 460 | return 461 | } 462 | 463 | // V2AcceptCancellationStatus lida com o cancelamento do pedido por parte do cliente 464 | func (o *ordersService) V2ClientCancellationStatus(orderReference string, accepted bool) (err error) { 465 | if orderReference == "" { 466 | err = ErrOrderReferenceNotSpecified 467 | glg.Error("[SDK] Orders V2AcceptCancellationStatus: ", err.Error()) 468 | return 469 | } 470 | if err = o.auth.Validate(); err != nil { 471 | glg.Error("[SDK] Orders V2AcceptCancellationStatus auth.Validate: ", err.Error()) 472 | return 473 | } 474 | cancelStatus := "denyCancellation" 475 | if accepted { 476 | cancelStatus = "acceptCancellation" 477 | } 478 | headers := make(map[string]string) 479 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 480 | endpoint := fmt.Sprintf("%s%s/%s", newV2Endpoint, orderReference, cancelStatus) 481 | resp, status, err := o.adapter.DoRequest(http.MethodPost, endpoint, nil, headers) 482 | if err != nil { 483 | glg.Error("[SDK] Orders V2AcceptCancellationStatus adapter.DoRequest error: ", err.Error()) 484 | return 485 | } 486 | if status != http.StatusAccepted { 487 | errMsg := events.ErrV2API{} 488 | json.Unmarshal(resp, &errMsg) 489 | err = errors.New(errMsg.Error.Message) 490 | glg.Error("[SDK] (Orders::V2AcceptCancellationStatus) status '%d' err: '%s'", status, err.Error()) 491 | return 492 | } 493 | glg.Debugf("[SDK] (Orders V2AcceptCancellationStatus) '%s' OK", orderReference) 494 | return 495 | } 496 | 497 | // Tracking retorna a posicao do entregador 498 | func (o *ordersService) Tracking(orderUUID string) (tr TrackingResponse, err error) { 499 | if orderUUID == "" { 500 | err = ErrOrderReferenceNotSpecified 501 | glg.Error("[SDK] Orders Tracking: ", orderUUID, " err: ", err.Error()) 502 | return 503 | } 504 | if err = o.auth.Validate(); err != nil { 505 | glg.Error("[SDK] Orders Tracking auth.Validate: ", err.Error()) 506 | return 507 | } 508 | headers := make(map[string]string) 509 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 510 | endpoint := fmt.Sprintf("%s/%s/tracking", v2Endpoint, orderUUID) 511 | resp, status, err := o.adapter.DoRequest(http.MethodGet, endpoint, nil, headers) 512 | if err != nil { 513 | glg.Error("[SDK] Orders Tracking adapter.DoRequest error: ", err.Error()) 514 | return 515 | } 516 | if status != http.StatusAccepted { 517 | glg.Error("[SDK] Orders Tracking status code: ", status, " order uuid: ", orderUUID) 518 | err = fmt.Errorf("Order reference '%s' could not get tracking information", orderUUID) 519 | glg.Error("[SDK] Orders Tracking err: ", err) 520 | return 521 | } 522 | glg.Debugf("[SDK] (Orders Tracking) '%s' OK", orderUUID) 523 | return tr, json.Unmarshal(resp, &tr) 524 | } 525 | 526 | // DeliveryInformation retorna informacoes da entrega 527 | func (o *ordersService) DeliveryInformation(orderUUID string) (di DeliveryInformationResponse, err error) { 528 | if orderUUID == "" { 529 | err = ErrOrderReferenceNotSpecified 530 | glg.Error("[SDK] Orders DeliveryInformation: ", orderUUID, " err: ", err.Error()) 531 | return 532 | } 533 | if err = o.auth.Validate(); err != nil { 534 | glg.Error("[SDK] Orders DeliveryInformation auth.Validate: ", err.Error()) 535 | return 536 | } 537 | headers := make(map[string]string) 538 | headers["Authorization"] = fmt.Sprintf("Bearer %s", o.auth.GetToken()) 539 | endpoint := fmt.Sprintf("%s/%s/delivery-information", v2Endpoint, orderUUID) 540 | resp, status, err := o.adapter.DoRequest(http.MethodGet, endpoint, nil, headers) 541 | if err != nil { 542 | glg.Error("[SDK] Orders DeliveryInformation adapter.DoRequest error: ", err.Error()) 543 | return 544 | } 545 | if status != http.StatusAccepted { 546 | glg.Error("[SDK] Orders DeliveryInformation status code: ", status, " order uuid: ", orderUUID) 547 | err = fmt.Errorf("Order uuid '%s' could get delivery information", orderUUID) 548 | glg.Error("[SDK] Orders DeliveryInformation err: ", err) 549 | return 550 | } 551 | glg.Debugf("[SDK] (Orders DeliveryInformation) '%s' OK", orderUUID) 552 | return di, json.Unmarshal(resp, &di) 553 | } 554 | 555 | func verifyCancel(reference, code string) (err error) { 556 | if reference == "" { 557 | err = ErrOrderReferenceNotSpecified 558 | return 559 | } 560 | if code == "" { 561 | err = ErrCancelCodeNotSpecified 562 | return 563 | } 564 | _, ok := CancelCodes[code] 565 | if !ok { 566 | err = fmt.Errorf( 567 | "cancel code '%s' is invalid, verify docs: https://developer.ifood.com.br/reference#pedido-de-cancelamento-30", 568 | code) 569 | return 570 | } 571 | return 572 | } 573 | -------------------------------------------------------------------------------- /services/orders/types.go: -------------------------------------------------------------------------------- 1 | package orders 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/arxdsilva/golang-ifood-sdk/services/merchant" 7 | ) 8 | 9 | type ( 10 | // OrderDetails endpoint return 11 | OrderDetails struct { 12 | ID string `json:"id"` 13 | Reference string `json:"reference"` 14 | Shortreference string `json:"shortReference"` 15 | Createdat string `json:"createdAt"` 16 | Type string `json:"type"` 17 | Merchant merchant.Merchant `json:"merchant"` 18 | Payments []Payment `json:"payments"` 19 | Customer Customer `json:"customer"` 20 | Items []Item `json:"items"` 21 | Subtotal string `json:"subTotal"` 22 | Totalprice string `json:"totalPrice"` 23 | Deliveryfee string `json:"deliveryFee"` 24 | Deliveryaddress DeliveryAddress `json:"deliveryAddress"` 25 | Deliverydatetime string `json:"deliveryDateTime"` 26 | Preparationtimeinseconds string `json:"preparationTimeInSeconds"` 27 | } 28 | 29 | // Payment details 30 | Payment struct { 31 | Name string `json:"name"` 32 | Code string `json:"code"` 33 | Value string `json:"value"` 34 | Prepaid string `json:"prepaid"` 35 | Issuer string `json:"issuer"` 36 | Collector string `json:"collector,omitempty"` 37 | } 38 | 39 | // Customer details 40 | Customer struct { 41 | ID string `json:"id"` 42 | UUID string `json:"uuid"` 43 | Name string `json:"name"` 44 | Taxpayeridentificationnumber string `json:"taxPayerIdentificationNumber"` 45 | Phone string `json:"phone"` 46 | Orderscountonrestaurant string `json:"ordersCountOnRestaurant"` 47 | } 48 | 49 | V2Customer struct { 50 | Phone struct { 51 | Number string `json:"number"` 52 | Localizer string `json:"localizer"` 53 | Localizerexpiration time.Time `json:"localizerExpiration"` 54 | } `json:"phone"` 55 | Documentnumber string `json:"documentNumber"` 56 | Name string `json:"name"` 57 | Orderscountonmerchant int `json:"ordersCountOnMerchant"` 58 | ID string `json:"id"` 59 | } 60 | 61 | // DeliveryAddress from customer 62 | DeliveryAddress struct { 63 | Formattedaddress string `json:"formattedAddress"` 64 | Country string `json:"country"` 65 | State string `json:"state"` 66 | City string `json:"city"` 67 | Coordinates Coordinates `json:"coordinates"` 68 | Neighborhood string `json:"neighborhood"` 69 | Streetname string `json:"streetName"` 70 | Streetnumber string `json:"streetNumber"` 71 | Postalcode string `json:"postalCode"` 72 | Reference string `json:"reference"` 73 | Complement string `json:"complement"` 74 | } 75 | 76 | // Coordinates of a delivery 77 | Coordinates struct { 78 | Latitude string `json:"latitude"` 79 | Longitude string `json:"longitude"` 80 | } 81 | 82 | // Item of the order 83 | Item struct { 84 | Name string `json:"name"` 85 | Quantity string `json:"quantity"` 86 | Price string `json:"price"` 87 | Subitemsprice string `json:"subItemsPrice"` 88 | Totalprice string `json:"totalPrice"` 89 | Discount string `json:"discount"` 90 | Addition string `json:"addition"` 91 | Externalcode string `json:"externalCode,omitempty"` 92 | Subitems []Subitem `json:"subItems,omitempty"` 93 | Observations string `json:"observations,omitempty"` 94 | } 95 | 96 | // Subitem of the order 97 | Subitem struct { 98 | Name string `json:"name"` 99 | Quantity string `json:"quantity"` 100 | Price string `json:"price"` 101 | Totalprice string `json:"totalPrice"` 102 | Discount string `json:"discount"` 103 | Addition string `json:"addition"` 104 | Externalcode string `json:"externalCode"` 105 | } 106 | 107 | // TrackingResponse API response of tracking 108 | TrackingResponse struct { 109 | Date interface{} `json:"date"` 110 | DeliveryTime time.Time `json:"deliveryTime"` 111 | Eta int `json:"eta"` 112 | EtaToDestination int `json:"etaToDestination"` 113 | EtaToOrigin int `json:"etaToOrigin"` 114 | Latitude float64 `json:"latitude"` 115 | Longitude float64 `json:"longitude"` 116 | OrderID string `json:"orderId"` 117 | TrackDate time.Time `json:"trackDate"` 118 | } 119 | 120 | // DeliveryInformationResponse API response of the delivery 121 | DeliveryInformationResponse struct { 122 | ExternalID string `json:"externalId"` 123 | OrderStatus string `json:"orderStatus"` 124 | WorkerName string `json:"workerName"` 125 | WorkerPhone string `json:"workerPhone"` 126 | WorkerPhoto string `json:"workerPhoto"` 127 | VehicleType string `json:"vehicleType"` 128 | VehiclePlateNumber interface{} `json:"vehiclePlateNumber"` 129 | LogisticCompany string `json:"logisticCompany"` 130 | Latitude float64 `json:"latitude"` 131 | Longitude float64 `json:"longitude"` 132 | Eta int `json:"eta"` 133 | } 134 | cancelOrder struct { 135 | Code string `json:"cancellationCode"` 136 | Details string `json:"details"` 137 | } 138 | 139 | v2CancelOrder struct { 140 | Reason string `json:"reason"` 141 | CancellationCode string `json:"cancellationCode"` 142 | } 143 | 144 | V2OrderDetails struct { 145 | Benefits []V2Benefit `json:"benefits"` 146 | Ordertype string `json:"orderType"` 147 | Payments V2Payments `json:"payments"` 148 | Merchant V2MerchantInfos `json:"merchant"` 149 | SalesChannel string `json:"salesChannel"` 150 | OrderTiming string `json:"orderTiming"` 151 | CreatedAt time.Time `json:"createdAt"` 152 | Total V2OrderValues `json:"total"` 153 | PreparationStartDatetime time.Time `json:"preparationStartDateTime"` 154 | ID string `json:"id"` 155 | DisplayID string `json:"displayId"` 156 | Items []V2Item `json:"items"` 157 | Customer V2Customer `json:"customer"` 158 | ExtraInfo string `json:"extraInfo"` 159 | Delivery V2DeliveryInformation `json:"delivery"` 160 | Schedule V2Schedule `json:"schedule"` 161 | Indoor V2Indoor `json:"indoor"` 162 | Takeout V2Takeout `json:"takeout"` 163 | } 164 | 165 | V2Item struct { 166 | Unitprice int `json:"unitPrice"` 167 | Quantity int `json:"quantity"` 168 | Externalcode string `json:"externalCode"` 169 | Totalprice int `json:"totalPrice"` 170 | Index int `json:"index"` 171 | Unit string `json:"unit"` 172 | Ean string `json:"ean"` 173 | Price int `json:"price"` 174 | Observations string `json:"observations"` 175 | Name string `json:"name"` 176 | Options []struct { 177 | Unitprice int `json:"unitPrice"` 178 | Unit string `json:"unit"` 179 | Ean string `json:"ean"` 180 | Quantity int `json:"quantity"` 181 | Externalcode string `json:"externalCode"` 182 | Price int `json:"price"` 183 | Name string `json:"name"` 184 | Index int `json:"index"` 185 | ID string `json:"id"` 186 | } `json:"options"` 187 | ID string `json:"id"` 188 | Optionsprice int `json:"optionsPrice"` 189 | } 190 | 191 | V2DeliveryInformation struct { 192 | Mode string `json:"mode"` 193 | Deliveredby string `json:"deliveredBy"` 194 | Deliveryaddress struct { 195 | Reference string `json:"reference"` 196 | Country string `json:"country"` 197 | Streetname string `json:"streetName"` 198 | Formattedaddress string `json:"formattedAddress"` 199 | Streetnumber string `json:"streetNumber"` 200 | City string `json:"city"` 201 | Postalcode string `json:"postalCode"` 202 | Coordinates struct { 203 | Latitude int `json:"latitude"` 204 | Longitude int `json:"longitude"` 205 | } `json:"coordinates"` 206 | Neighborhood string `json:"neighborhood"` 207 | State string `json:"state"` 208 | Complement string `json:"complement"` 209 | } `json:"deliveryAddress"` 210 | Deliverydatetime time.Time `json:"deliveryDateTime"` 211 | } 212 | 213 | V2Payments struct { 214 | Methods []struct { 215 | Wallet struct { 216 | Name string `json:"name"` 217 | } `json:"wallet"` 218 | Method string `json:"method"` 219 | Prepaid bool `json:"prepaid"` 220 | Currency string `json:"currency"` 221 | Type string `json:"type"` 222 | Value int `json:"value"` 223 | Cash struct { 224 | Changefor int `json:"changeFor"` 225 | } `json:"cash"` 226 | Card struct { 227 | Brand string `json:"brand"` 228 | } `json:"card"` 229 | } `json:"methods"` 230 | Pending int `json:"pending"` 231 | Prepaid int `json:"prepaid"` 232 | } 233 | 234 | V2OrderValues struct { 235 | Benefits int `json:"benefits"` 236 | Deliveryfee int `json:"deliveryFee"` 237 | Orderamount int `json:"orderAmount"` 238 | Subtotal int `json:"subTotal"` 239 | } 240 | 241 | V2Benefit struct { 242 | Targetid string `json:"targetId"` 243 | Sponsorshipvalues []struct { 244 | Name string `json:"name"` 245 | Value int `json:"value"` 246 | } `json:"sponsorshipValues"` 247 | Value int `json:"value"` 248 | Target string `json:"target"` 249 | } 250 | 251 | V2MerchantInfos struct { 252 | Name string `json:"name"` 253 | ID string `json:"id"` 254 | } 255 | 256 | V2Schedule struct { 257 | Deliverydatetimestart time.Time `json:"deliveryDateTimeStart"` 258 | Deliverydatetimeend time.Time `json:"deliveryDateTimeEnd"` 259 | } 260 | 261 | V2Indoor struct { 262 | Mode string `json:"mode"` 263 | Deliverydatetime time.Time `json:"deliveryDateTime"` 264 | Table string `json:"table"` 265 | } 266 | 267 | V2Takeout struct { 268 | Mode string `json:"mode"` 269 | Takeoutdatetime time.Time `json:"takeoutDateTime"` 270 | } 271 | ) 272 | --------------------------------------------------------------------------------