From 198b679072b55cf9b58ae593f0cc25549aaf5ed5 Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Mon, 5 Feb 2024 18:34:23 -0300 Subject: [PATCH 01/55] Add client card --- pkg/customer/customercard/card.go | 166 ++++++++++++++++++ pkg/customer/customercard/request.go | 5 + pkg/customer/customercard/response.go | 52 ++++++ .../{client.go => payment_method.go} | 0 pkg/paymentmethod/response.go | 2 +- 5 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 pkg/customer/customercard/card.go create mode 100644 pkg/customer/customercard/request.go create mode 100644 pkg/customer/customercard/response.go rename pkg/paymentmethod/{client.go => payment_method.go} (100%) diff --git a/pkg/customer/customercard/card.go b/pkg/customer/customercard/card.go new file mode 100644 index 00000000..63cf9bf3 --- /dev/null +++ b/pkg/customer/customercard/card.go @@ -0,0 +1,166 @@ +package customercard + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/internal/httpclient" +) + +const ( + baseURL = "https://api.mercadopago.com/v1/customers/{customer_id}" + cardsURL = baseURL + "/cards" + cardsByIDURL = baseURL + cardsURL + "/{card_id}" +) + +// Client contains the methods to interact with the Payment Methods API. +type Client interface { + // Create a new customer card. + // It is a post request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards + // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards/post + Create(ctx context.Context, customerID string, request Request) (*Response, error) + + // Get a customer card by ID. + // It is a get request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} + // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/get + Get(ctx context.Context, customerID, cardID string) (*Response, error) + + // Update a customer card by ID. + // It is a put request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} + // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/put + Update(ctx context.Context, customerID, cardID string) (*Response, error) + + // Delete deletes a customer card by ID. + // It is a delete request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} + // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/delete + Delete(ctx context.Context, customerID, cardID string) (*Response, error) + + // List all customers. + // It is a get request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards + // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards/get + List(ctx context.Context, customerID string) ([]Response, error) +} + +// client is the implementation of Client. +type client struct { + config *config.Config +} + +// NewClient returns a new Payment Methods API Client. +func NewClient(c *config.Config) Client { + return &client{ + config: c, + } +} + +func (c *client) Create(ctx context.Context, customerID string, request Request) (*Response, error) { + body, err := json.Marshal(&request) + if err != nil { + return nil, fmt.Errorf("error marshaling request body: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, cardsURL, strings.NewReader(string(body))) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.config, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) Get(ctx context.Context, customerID, cardID string) (*Response, error) { + url := strings.Replace(cardsByIDURL, "{customer_id}", customerID, 1) + url = strings.Replace(cardsByIDURL, "{card_id}", cardID, 1) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.config, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) Update(ctx context.Context, customerID, cardID string) (*Response, error) { + conv := strconv.Itoa(int(id)) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.Replace(getURL, "{id}", conv, 1), nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.config, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) Delete(ctx context.Context, customerID, cardID string) (*Response, error) { + conv := strconv.Itoa(int(id)) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.Replace(getURL, "{id}", conv, 1), nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.config, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) List(ctx context.Context, customerID string) ([]Response, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.config, req) + if err != nil { + return nil, err + } + + var formatted []Response + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, err + } + + return formatted, nil +} diff --git a/pkg/customer/customercard/request.go b/pkg/customer/customercard/request.go new file mode 100644 index 00000000..66740317 --- /dev/null +++ b/pkg/customer/customercard/request.go @@ -0,0 +1,5 @@ +package customercard + +type Request struct { + Token string `json:"token"` +} diff --git a/pkg/customer/customercard/response.go b/pkg/customer/customercard/response.go new file mode 100644 index 00000000..07d33bed --- /dev/null +++ b/pkg/customer/customercard/response.go @@ -0,0 +1,52 @@ +package customercard + +type Response struct { + ID string `json:"id"` + Name string `json:"name"` + PaymentTypeID string `json:"payment_type_id"` + Status string `json:"status"` + SecureThumbnail string `json:"secure_thumbnail"` + Thumbnail string `json:"thumbnail"` + DeferredCapture string `json:"deferred_capture"` + AdditionalInfoNeeded []string `json:"additional_info_needed"` + ProcessingModes []string `json:"processing_modes"` + AccreditationTime int64 `json:"accreditation_time"` + MinAllowedAmount float64 `json:"min_allowed_amount"` + MaxAllowedAmount float64 `json:"max_allowed_amount"` + + Settings []SettingsResponse `json:"settings"` + FinancialInstitutions []FinancialInstitutionResponse `json:"financial_institutions"` +} + +// SettingsResponse represents payment method settings. +type SettingsResponse struct { + Bin *SettingsBinResponse `json:"bin"` + CardNumber *SettingsCardNumberResponse `json:"card_number"` + SecurityCode *SettingsSecurityCodeResponse `json:"security_code"` +} + +// SettingsBinResponse represents BIN (Bank Identification Number) settings. +type SettingsBinResponse struct { + Pattern string `json:"pattern"` + ExclusionPattern string `json:"exclusion_pattern"` + InstallmentsPattern string `json:"installments_pattern"` +} + +// SettingsCardNumberResponse represents customer number settings. +type SettingsCardNumberResponse struct { + Length int `json:"length"` + Validation string `json:"validation"` +} + +// SettingsSecurityCodeResponse represents security code settings. +type SettingsSecurityCodeResponse struct { + Mode string `json:"mode"` + Length int `json:"length"` + CardLocation string `json:"card_location"` +} + +// FinancialInstitutionResponse represents financial institution settings. +type FinancialInstitutionResponse struct { + ID string `json:"id"` + Description string `json:"description"` +} diff --git a/pkg/paymentmethod/client.go b/pkg/paymentmethod/payment_method.go similarity index 100% rename from pkg/paymentmethod/client.go rename to pkg/paymentmethod/payment_method.go diff --git a/pkg/paymentmethod/response.go b/pkg/paymentmethod/response.go index 5cffb157..aeec6fac 100644 --- a/pkg/paymentmethod/response.go +++ b/pkg/paymentmethod/response.go @@ -32,7 +32,7 @@ type SettingsBinResponse struct { InstallmentsPattern string `json:"installments_pattern"` } -// SettingsCardNumberResponse represents card number settings. +// SettingsCardNumberResponse represents customer number settings. type SettingsCardNumberResponse struct { Length int `json:"length"` Validation string `json:"validation"` From 1b49a3c61dd964302210e69786a78f6cec937ac7 Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Fri, 9 Feb 2024 14:53:16 -0300 Subject: [PATCH 02/55] add options http --- examples/apis/customercard/create/main.go | 30 +++ examples/apis/customercard/delete/main.go | 28 +++ examples/apis/customercard/get/main.go | 28 +++ examples/apis/customercard/list/main.go | 30 +++ examples/apis/customercard/update/main.go | 30 +++ pkg/customer/customercard/response.go | 52 ----- .../card.go => customercard/client.go} | 100 +++------ pkg/customercard/client_test.go | 194 ++++++++++++++++++ pkg/{customer => }/customercard/request.go | 0 pkg/customercard/response.go | 63 ++++++ pkg/internal/httpclient/client.go | 76 ++++++- pkg/internal/httpclient/client_option.go | 27 +++ pkg/internal/httpclient/client_test.go | 136 ++++++++++++ 13 files changed, 660 insertions(+), 134 deletions(-) create mode 100644 examples/apis/customercard/create/main.go create mode 100644 examples/apis/customercard/delete/main.go create mode 100644 examples/apis/customercard/get/main.go create mode 100644 examples/apis/customercard/list/main.go create mode 100644 examples/apis/customercard/update/main.go delete mode 100644 pkg/customer/customercard/response.go rename pkg/{customer/customercard/card.go => customercard/client.go} (51%) create mode 100644 pkg/customercard/client_test.go rename pkg/{customer => }/customercard/request.go (100%) create mode 100644 pkg/customercard/response.go create mode 100644 pkg/internal/httpclient/client_option.go create mode 100644 pkg/internal/httpclient/client_test.go diff --git a/examples/apis/customercard/create/main.go b/examples/apis/customercard/create/main.go new file mode 100644 index 00000000..f82906b4 --- /dev/null +++ b/examples/apis/customercard/create/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/customercard" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + req := customercard.Request{Token: "{{CARD_TOKEN}}"} + + client := customercard.NewClient(cfg) + card, err := client.Create(context.Background(), "{{CUSTOMER_ID}}", req) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(card) +} diff --git a/examples/apis/customercard/delete/main.go b/examples/apis/customercard/delete/main.go new file mode 100644 index 00000000..1d627f9e --- /dev/null +++ b/examples/apis/customercard/delete/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/customercard" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + client := customercard.NewClient(cfg) + card, err := client.Delete(context.Background(), "{{CUSTOMER_ID}}", "{{CARD_ID}}") + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(card) +} diff --git a/examples/apis/customercard/get/main.go b/examples/apis/customercard/get/main.go new file mode 100644 index 00000000..e0a314a0 --- /dev/null +++ b/examples/apis/customercard/get/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/customercard" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + client := customercard.NewClient(cfg) + card, err := client.Get(context.Background(), "{{CUSTOMER_ID}}", "{{CARD_ID}}") + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(card) +} diff --git a/examples/apis/customercard/list/main.go b/examples/apis/customercard/list/main.go new file mode 100644 index 00000000..03ff7aa1 --- /dev/null +++ b/examples/apis/customercard/list/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/customercard" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + client := customercard.NewClient(cfg) + cards, err := client.List(context.Background(), "{{CUSTOMER_ID}}") + if err != nil { + fmt.Println(err) + return + } + + for _, c := range cards { + fmt.Println(c) + } +} diff --git a/examples/apis/customercard/update/main.go b/examples/apis/customercard/update/main.go new file mode 100644 index 00000000..882d3f44 --- /dev/null +++ b/examples/apis/customercard/update/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/customercard" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + req := customercard.Request{Token: "{{CARD_TOKEN}}"} + + client := customercard.NewClient(cfg) + card, err := client.Update(context.Background(), "{{CUSTOMER_ID}}", "{{CARD_ID}}", req) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(card) +} diff --git a/pkg/customer/customercard/response.go b/pkg/customer/customercard/response.go deleted file mode 100644 index 07d33bed..00000000 --- a/pkg/customer/customercard/response.go +++ /dev/null @@ -1,52 +0,0 @@ -package customercard - -type Response struct { - ID string `json:"id"` - Name string `json:"name"` - PaymentTypeID string `json:"payment_type_id"` - Status string `json:"status"` - SecureThumbnail string `json:"secure_thumbnail"` - Thumbnail string `json:"thumbnail"` - DeferredCapture string `json:"deferred_capture"` - AdditionalInfoNeeded []string `json:"additional_info_needed"` - ProcessingModes []string `json:"processing_modes"` - AccreditationTime int64 `json:"accreditation_time"` - MinAllowedAmount float64 `json:"min_allowed_amount"` - MaxAllowedAmount float64 `json:"max_allowed_amount"` - - Settings []SettingsResponse `json:"settings"` - FinancialInstitutions []FinancialInstitutionResponse `json:"financial_institutions"` -} - -// SettingsResponse represents payment method settings. -type SettingsResponse struct { - Bin *SettingsBinResponse `json:"bin"` - CardNumber *SettingsCardNumberResponse `json:"card_number"` - SecurityCode *SettingsSecurityCodeResponse `json:"security_code"` -} - -// SettingsBinResponse represents BIN (Bank Identification Number) settings. -type SettingsBinResponse struct { - Pattern string `json:"pattern"` - ExclusionPattern string `json:"exclusion_pattern"` - InstallmentsPattern string `json:"installments_pattern"` -} - -// SettingsCardNumberResponse represents customer number settings. -type SettingsCardNumberResponse struct { - Length int `json:"length"` - Validation string `json:"validation"` -} - -// SettingsSecurityCodeResponse represents security code settings. -type SettingsSecurityCodeResponse struct { - Mode string `json:"mode"` - Length int `json:"length"` - CardLocation string `json:"card_location"` -} - -// FinancialInstitutionResponse represents financial institution settings. -type FinancialInstitutionResponse struct { - ID string `json:"id"` - Description string `json:"description"` -} diff --git a/pkg/customer/customercard/card.go b/pkg/customercard/client.go similarity index 51% rename from pkg/customer/customercard/card.go rename to pkg/customercard/client.go index 63cf9bf3..2a1c424c 100644 --- a/pkg/customer/customercard/card.go +++ b/pkg/customercard/client.go @@ -2,20 +2,15 @@ package customercard import ( "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "strings" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/internal/httpclient" ) const ( - baseURL = "https://api.mercadopago.com/v1/customers/{customer_id}" - cardsURL = baseURL + "/cards" - cardsByIDURL = baseURL + cardsURL + "/{card_id}" + urlBase = "https://api.mercadopago.com/v1/customers/{customer_id}" + urlCards = urlBase + "/cards" + urlCardsWithID = urlCards + "/{card_id}" ) // Client contains the methods to interact with the Payment Methods API. @@ -33,7 +28,7 @@ type Client interface { // Update a customer card by ID. // It is a put request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/put - Update(ctx context.Context, customerID, cardID string) (*Response, error) + Update(ctx context.Context, customerID, cardID string, request Request) (*Response, error) // Delete deletes a customer card by ID. // It is a delete request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} @@ -59,108 +54,69 @@ func NewClient(c *config.Config) Client { } func (c *client) Create(ctx context.Context, customerID string, request Request) (*Response, error) { - body, err := json.Marshal(&request) - if err != nil { - return nil, fmt.Errorf("error marshaling request body: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, cardsURL, strings.NewReader(string(body))) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) + params := map[string]string{ + "customer_id": customerID, } - res, err := httpclient.Send(ctx, c.config, req) + res, err := httpclient.Post[Response](ctx, c.config, urlCards, request, httpclient.WithPathParams(params)) if err != nil { return nil, err } - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { - return nil, fmt.Errorf("error unmarshaling response: %w", err) - } - - return formatted, nil + return res, nil } func (c *client) Get(ctx context.Context, customerID, cardID string) (*Response, error) { - url := strings.Replace(cardsByIDURL, "{customer_id}", customerID, 1) - url = strings.Replace(cardsByIDURL, "{card_id}", cardID, 1) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) + params := map[string]string{ + "customer_id": customerID, + "card_id": cardID, } - res, err := httpclient.Send(ctx, c.config, req) + res, err := httpclient.Get[Response](ctx, c.config, urlCardsWithID, httpclient.WithPathParams(params)) if err != nil { return nil, err } - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { - return nil, fmt.Errorf("error unmarshaling response: %w", err) - } - - return formatted, nil + return res, nil } -func (c *client) Update(ctx context.Context, customerID, cardID string) (*Response, error) { - conv := strconv.Itoa(int(id)) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.Replace(getURL, "{id}", conv, 1), nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) +func (c *client) Update(ctx context.Context, customerID, cardID string, request Request) (*Response, error) { + params := map[string]string{ + "customer_id": customerID, + "card_id": cardID, } - res, err := httpclient.Send(ctx, c.config, req) + res, err := httpclient.Put[Response](ctx, c.config, urlCardsWithID, request, httpclient.WithPathParams(params)) if err != nil { return nil, err } - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { - return nil, fmt.Errorf("error unmarshaling response: %w", err) - } - - return formatted, nil + return res, nil } func (c *client) Delete(ctx context.Context, customerID, cardID string) (*Response, error) { - conv := strconv.Itoa(int(id)) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.Replace(getURL, "{id}", conv, 1), nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) + params := map[string]string{ + "customer_id": customerID, + "card_id": cardID, } - res, err := httpclient.Send(ctx, c.config, req) + res, err := httpclient.Delete[Response](ctx, c.config, urlCardsWithID, nil, httpclient.WithPathParams(params)) if err != nil { return nil, err } - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { - return nil, fmt.Errorf("error unmarshaling response: %w", err) - } - - return formatted, nil + return res, nil } func (c *client) List(ctx context.Context, customerID string) ([]Response, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) + params := map[string]string{ + "customer_id": customerID, } - res, err := httpclient.Send(ctx, c.config, req) + res, err := httpclient.Get[[]Response](ctx, c.config, urlCards, httpclient.WithPathParams(params)) if err != nil { return nil, err } - var formatted []Response - if err := json.Unmarshal(res, &formatted); err != nil { - return nil, err - } - - return formatted, nil + return *res, nil } diff --git a/pkg/customercard/client_test.go b/pkg/customercard/client_test.go new file mode 100644 index 00000000..deb99d37 --- /dev/null +++ b/pkg/customercard/client_test.go @@ -0,0 +1,194 @@ +package customercard + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/internal/httpclient" +) + +var ( +// listResponseJSON, _ = os.Open("../../resources/mocks/payment_method/list_response.json") +// listResponse, _ = io.ReadAll(listResponseJSON) +) + +func TestCreate(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + customerID string + req Request + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr string + }{ + { + name: "should_return_error_when_send_request", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "any", + req: Request{}, + }, + want: nil, + wantErr: "transport level error: some error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{tt.fields.config} + got, err := c.Create(tt.args.ctx, tt.args.customerID, tt.args.req) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("client.Create() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.Create() = %v, want %v", got, tt.want) + } + }) + } +} + +// +//func TestList(t *testing.T) { +// type fields struct { +// config *config.Config +// } +// type args struct { +// ctx context.Context +// } +// tests := []struct { +// name string +// fields fields +// args args +// want []Response +// wantErr string +// }{ +// { +// name: "should_return_error_when_creating_request", +// fields: fields{ +// config: nil, +// }, +// args: args{ +// ctx: nil, +// }, +// want: nil, +// wantErr: "error creating request: net/http: nil Context", +// }, +// { +// name: "should_return_error_when_send_request", +// fields: fields{ +// config: &config.Config{ +// Requester: &httpclient.Mock{ +// DoMock: func(req *http.Request) (*http.Response, error) { +// return nil, fmt.Errorf("some error") +// }, +// }, +// }, +// }, +// args: args{ +// ctx: context.Background(), +// }, +// want: nil, +// wantErr: "transport level error: some error", +// }, +// { +// name: "should_return_error_unmarshal_response", +// fields: fields{ +// config: &config.Config{ +// Requester: &httpclient.Mock{ +// DoMock: func(req *http.Request) (*http.Response, error) { +// stringReader := strings.NewReader("invalid json") +// stringReadCloser := io.NopCloser(stringReader) +// return &http.Response{ +// Body: stringReadCloser, +// }, nil +// }, +// }, +// }, +// }, +// args: args{ +// ctx: context.Background(), +// }, +// want: nil, +// wantErr: "invalid character 'i' looking for beginning of value", +// }, +// { +// name: "should_return_formatted_response", +// fields: fields{ +// config: &config.Config{ +// Requester: &httpclient.Mock{ +// DoMock: func(req *http.Request) (*http.Response, error) { +// stringReader := strings.NewReader(string(listResponse)) +// stringReadCloser := io.NopCloser(stringReader) +// return &http.Response{ +// Body: stringReadCloser, +// }, nil +// }, +// }, +// }, +// }, +// args: args{ +// ctx: context.Background(), +// }, +// want: []Response{ +// { +// ID: "debmaster", +// Name: "Mastercard Débito", +// PaymentTypeID: "debit_card", +// Status: "testing", +// SecureThumbnail: "https://www.mercadopago.com/org-img/MP3/API/logos/debmaster.gif", +// Thumbnail: "https://www.mercadopago.com/org-img/MP3/API/logos/debmaster.gif", +// }, +// { +// ID: "cabal", +// Name: "Cabal", +// PaymentTypeID: "credit_card", +// Status: "testing", +// SecureThumbnail: "https://www.mercadopago.com/org-img/MP3/API/logos/cabal.gif", +// Thumbnail: "https://www.mercadopago.com/org-img/MP3/API/logos/cabal.gif", +// }, +// }, +// wantErr: "", +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// c := &client{tt.fields.config} +// got, err := c.List(tt.args.ctx) +// gotErr := "" +// if err != nil { +// gotErr = err.Error() +// } +// +// if gotErr != tt.wantErr { +// t.Errorf("client.List() error = %v, wantErr %v", err, tt.wantErr) +// } +// if !reflect.DeepEqual(got, tt.want) { +// t.Errorf("client.List() = %v, want %v", got, tt.want) +// } +// }) +// } +//} diff --git a/pkg/customer/customercard/request.go b/pkg/customercard/request.go similarity index 100% rename from pkg/customer/customercard/request.go rename to pkg/customercard/request.go diff --git a/pkg/customercard/response.go b/pkg/customercard/response.go new file mode 100644 index 00000000..982bbfd2 --- /dev/null +++ b/pkg/customercard/response.go @@ -0,0 +1,63 @@ +package customercard + +import "time" + +// Response represents a customer card. +type Response struct { + ID string `json:"id"` + CustomerID string `json:"customer_id"` + UserID string `json:"user_id"` + CardNumberID string `json:"card_number_id"` + FirstSixDigits string `json:"first_six_digits"` + LastFourDigits string `json:"last_four_digits"` + ExpirationMonth int `json:"expiration_month"` + ExpirationYear int `json:"expiration_year"` + LiveMode bool `json:"live_mode"` + DateCreated *time.Time `json:"date_created"` + DateLastUpdated *time.Time `json:"date_last_updated"` + Issuer IssuerResponse `json:"issuer"` + Cardholder CardholderResponse `json:"cardholder"` + AdditionalInfo AdditionalInfoResponse `json:"additional_info"` + PaymentMethod PaymentMethodResponse `json:"payment_method"` + SecurityCode SecurityCode `json:"security_code"` +} + +// AdditionalInfoResponse represents additional customer card information. +type AdditionalInfoResponse struct { + RequestPublic string `json:"request_public"` + ApiClientApplication string `json:"api_client_application"` + ApiClientScope string `json:"api_client_scope"` +} + +// CardholderResponse represents information about the cardholder. +type CardholderResponse struct { + Name string `json:"name"` + IdentificationResponse `json:"identification"` +} + +// IdentificationResponse represents the cardholder's document. +type IdentificationResponse struct { + Number string `json:"number"` + Type string `json:"type"` +} + +// IssuerResponse represents the card issuer code +type IssuerResponse struct { + ID int `json:"id"` + Name string `json:"name"` +} + +// PaymentMethodResponse represents the card's payment method +type PaymentMethodResponse struct { + ID string `json:"id"` + Name string `json:"name"` + PaymentTypeId string `json:"payment_type_id"` + Thumbnail string `json:"thumbnail"` + SecureThumbnail string `json:"secure_thumbnail"` +} + +// SecurityCode represents the card's security code +type SecurityCode struct { + Length int `json:"length"` + CardLocation string `json:"card_location"` +} diff --git a/pkg/internal/httpclient/client.go b/pkg/internal/httpclient/client.go index 18c8c864..b92884b0 100644 --- a/pkg/internal/httpclient/client.go +++ b/pkg/internal/httpclient/client.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "net/url" "runtime" "strings" @@ -40,8 +41,8 @@ var ( // Get makes requests with the GET method // Will return the struct specified in Generics -func Get[T any](ctx context.Context, cfg *config.Config, path string) (*T, error) { - req, err := makeRequest(ctx, cfg, http.MethodGet, path, nil) +func Get[T any](ctx context.Context, cfg *config.Config, url string, opts ...Option) (*T, error) { + req, err := makeRequest(ctx, cfg, http.MethodGet, url, nil, opts...) if err != nil { return nil, err } @@ -51,8 +52,8 @@ func Get[T any](ctx context.Context, cfg *config.Config, path string) (*T, error // Post makes requests with the POST method // Will return the struct specified in Generics -func Post[T any](ctx context.Context, cfg *config.Config, path string, body any) (*T, error) { - req, err := makeRequest(ctx, cfg, http.MethodPost, path, body) +func Post[T any](ctx context.Context, cfg *config.Config, url string, body any, opts ...Option) (*T, error) { + req, err := makeRequest(ctx, cfg, http.MethodPost, url, body, opts...) if err != nil { return nil, err } @@ -62,8 +63,8 @@ func Post[T any](ctx context.Context, cfg *config.Config, path string, body any) // Put makes requests with the PUT method // Will return the struct specified in Generics -func Put[T any](ctx context.Context, cfg *config.Config, path string, body any) (*T, error) { - req, err := makeRequest(ctx, cfg, http.MethodPut, path, body) +func Put[T any](ctx context.Context, cfg *config.Config, url string, body any, opts ...Option) (*T, error) { + req, err := makeRequest(ctx, cfg, http.MethodPut, url, body, opts...) if err != nil { return nil, err } @@ -73,8 +74,8 @@ func Put[T any](ctx context.Context, cfg *config.Config, path string, body any) // Delete makes requests with the DELETE method // Will return the struct specified in Generics -func Delete[T any](ctx context.Context, cfg *config.Config, path string, body any) (*T, error) { - req, err := makeRequest(ctx, cfg, http.MethodDelete, path, body) +func Delete[T any](ctx context.Context, cfg *config.Config, url string, body any, opts ...Option) (*T, error) { + req, err := makeRequest(ctx, cfg, http.MethodDelete, url, body, opts...) if err != nil { return nil, err } @@ -82,13 +83,24 @@ func Delete[T any](ctx context.Context, cfg *config.Config, path string, body an return send[T](cfg.Requester, req) } -func makeRequest(ctx context.Context, cfg *config.Config, method, path string, body any) (*http.Request, error) { - req, err := buildHTTPRequest(ctx, method, path, body) +func makeRequest(ctx context.Context, cfg *config.Config, method, url string, body any, opts ...Option) (*http.Request, error) { + req, err := buildHTTPRequest(ctx, method, url, body) if err != nil { return nil, fmt.Errorf("error creating request: %w", err) } + // Apply all the functional options to configure the client. + opt := clientOption{} + for _, o := range opts { + o(opt) + } + makeHeaders(req, cfg) + makeQueryParams(req, opt.queryParams) + + if err = makePathParams(req, opt.pathParams); err != nil { + return nil, err + } return req, nil } @@ -135,3 +147,47 @@ func buildBody(body any) (io.Reader, error) { return strings.NewReader(string(b)), nil } + +func makePathParams(req *http.Request, params map[string]string) error { + pathURL := req.URL.Path + + for k, v := range params { + pathParam := ":" + k + if strings.Contains(pathURL, pathParam) { + pathURL = strings.Replace(pathURL, pathParam, v, 1) + } + } + + if err := validatePathParams(pathURL); err != nil { + return err + } + + req.URL.Path = pathURL + + return nil +} + +func makeQueryParams(req *http.Request, params map[string]string) { + queryParams := url.Values{} + + for k, v := range params { + queryParams.Add(k, v) + } + + req.URL.RawQuery = queryParams.Encode() +} + +func validatePathParams(pathURL string) error { + if strings.Contains(pathURL, ":") { + words := strings.Split(pathURL, "/") + var paramsNotReplaced []string + for _, word := range words { + if strings.Contains(word, ":") { + paramsNotReplaced = append(paramsNotReplaced, strings.Replace(word, ":", "", 1)) + } + } + return fmt.Errorf("path parameters not informed: %s", strings.Join(paramsNotReplaced, ",")) + } + + return nil +} diff --git a/pkg/internal/httpclient/client_option.go b/pkg/internal/httpclient/client_option.go new file mode 100644 index 00000000..fafd98e1 --- /dev/null +++ b/pkg/internal/httpclient/client_option.go @@ -0,0 +1,27 @@ +package httpclient + +// ClientOption allows sending options in the http client +type clientOption struct { + pathParams map[string]string + queryParams map[string]string +} + +type Option func(clientOption) + +// WithPathParams allows sending path parameters in the request. +func WithPathParams(params map[string]string) Option { + return func(c clientOption) { + if params != nil { + c.pathParams = params + } + } +} + +// WithQueryParams allows sending query parameters in the request. +func WithQueryParams(params map[string]string) Option { + return func(c clientOption) { + if params != nil { + c.queryParams = params + } + } +} diff --git a/pkg/internal/httpclient/client_test.go b/pkg/internal/httpclient/client_test.go new file mode 100644 index 00000000..cddbe3ec --- /dev/null +++ b/pkg/internal/httpclient/client_test.go @@ -0,0 +1,136 @@ +package httpclient + +import ( + "net/http" + "testing" +) + +func TestMakeParams(t *testing.T) { + type args struct { + url string + params map[string]string + } + tests := []struct { + name string + args args + wantURL string + wantMsgErr string + }{ + { + name: "should_replace_one_path_param", + args: args{ + url: "http://localhost/payments/:payment_id", + params: map[string]string{ + "payment_id": "1234567890", + }, + }, + wantURL: "http://localhost/payments/1234567890", + }, + { + name: "should_replace_one_path_param_and_add_query_param", + args: args{ + url: "http://localhost/payments/:payment_id/search", + params: map[string]string{ + "payment_id": "1234567890", + "external_resource": "abcd12345", + }, + }, + wantURL: "http://localhost/payments/1234567890/search?external_resource=abcd12345", + }, + { + name: "should_return_path_param_not_informed", + args: args{ + url: "http://localhost/customers/:customer_id", + params: map[string]string{ + "payment_id": "1234567890", + }, + }, + wantMsgErr: "path parameters not informed: customer_id", + }, + { + name: "should_return_two_path_params_not_informed", + args: args{ + url: "http://localhost/tests/:test_id/units/:unit_id", + params: map[string]string{ + "integrate_id": "1234567890", + }, + }, + wantMsgErr: "path parameters not informed: test_id,unit_id", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req, _ := http.NewRequest(http.MethodGet, tt.args.url, nil) + + err := makePathParams(req, tt.args.params) + if err != nil && err.Error() != tt.wantMsgErr { + t.Errorf("makeParams() msgError = %v, wantMsgErr %v", err.Error(), tt.wantMsgErr) + return + } + + if err == nil && tt.wantURL != req.URL.String() { + t.Errorf("makeParams() wantURL = %v, gotURL %v", tt.wantURL, req.URL.String()) + } + }) + } +} + +// +//func TestMakeParams(t *testing.T) { +// type args struct { +// url string +// params map[string]string +// } +// tests := []struct { +// name string +// args args +// wantURL string +// wantErr error +// }{ +// { +// name: "test_replace_one_path_param", +// args: args{ +// url: "http://localhost/payments/:payment_id", +// params: map[string]string{ +// "payment_id": "1234567890", +// }, +// }, +// wantURL: "http://localhost/payments/1234567890", +// }, +// { +// name: "should_return_path_param_not_informed", +// args: args{ +// url: "http://localhost/customers/:customer_id", +// params: map[string]string{ +// "payment_id": "1234567890", +// }, +// }, +// wantErr: errors.New("path parameters not informed: customer_id"), +// }, +// { +// name: "should_return_two_path_params_not_informed", +// args: args{ +// url: "http://localhost/tests/:test_id/units/:unit_id", +// params: map[string]string{ +// "integrate_id": "1234567890", +// }, +// }, +// wantErr: errors.New("path parameters not informed: test_id,unit_id"), +// }, +// } +// +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// req, _ := http.NewRequest(http.MethodPost, tt.args.url, nil) +// gotErr := makeParams(req, tt.args.params) +// +// if tt.wantErr != nil && gotErr != tt.wantErr.Error() { +// t.Errorf("makeParams() wantErr = %v, gotErr = %v", tt.wantErr, gotErr) +// } +// +// if tt.wantErr == nil && tt.wantURL != req.URL.String() { +// t.Errorf("makeParams() wantURL = %v, gotURL %v", tt.wantURL, req.URL.String()) +// } +// }) +// } +//} From 159ca45e55f8449ab0007c919c7f09e904f1e3e9 Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Fri, 9 Feb 2024 15:50:50 -0300 Subject: [PATCH 03/55] adjusts tests --- pkg/customercard/client.go | 19 +- pkg/customercard/client_test.go | 660 ++++++++++++++---- pkg/internal/httpclient/client_test.go | 133 ++-- .../mocks/customer_card/card_response.json | 40 ++ .../mocks/customer_card/list_response.json | 42 ++ test/integration/payment_method_test.go | 2 +- 6 files changed, 693 insertions(+), 203 deletions(-) create mode 100644 resources/mocks/customer_card/card_response.json create mode 100644 resources/mocks/customer_card/list_response.json diff --git a/pkg/customercard/client.go b/pkg/customercard/client.go index 2a1c424c..77a136f6 100644 --- a/pkg/customercard/client.go +++ b/pkg/customercard/client.go @@ -11,6 +11,9 @@ const ( urlBase = "https://api.mercadopago.com/v1/customers/{customer_id}" urlCards = urlBase + "/cards" urlCardsWithID = urlCards + "/{card_id}" + + paramCustomerID = "customer_id" + paramCardID = "card_id" ) // Client contains the methods to interact with the Payment Methods API. @@ -55,7 +58,7 @@ func NewClient(c *config.Config) Client { func (c *client) Create(ctx context.Context, customerID string, request Request) (*Response, error) { params := map[string]string{ - "customer_id": customerID, + paramCustomerID: customerID, } res, err := httpclient.Post[Response](ctx, c.config, urlCards, request, httpclient.WithPathParams(params)) @@ -68,8 +71,8 @@ func (c *client) Create(ctx context.Context, customerID string, request Request) func (c *client) Get(ctx context.Context, customerID, cardID string) (*Response, error) { params := map[string]string{ - "customer_id": customerID, - "card_id": cardID, + paramCustomerID: customerID, + paramCardID: cardID, } res, err := httpclient.Get[Response](ctx, c.config, urlCardsWithID, httpclient.WithPathParams(params)) @@ -82,8 +85,8 @@ func (c *client) Get(ctx context.Context, customerID, cardID string) (*Response, func (c *client) Update(ctx context.Context, customerID, cardID string, request Request) (*Response, error) { params := map[string]string{ - "customer_id": customerID, - "card_id": cardID, + paramCustomerID: customerID, + paramCardID: cardID, } res, err := httpclient.Put[Response](ctx, c.config, urlCardsWithID, request, httpclient.WithPathParams(params)) @@ -96,8 +99,8 @@ func (c *client) Update(ctx context.Context, customerID, cardID string, request func (c *client) Delete(ctx context.Context, customerID, cardID string) (*Response, error) { params := map[string]string{ - "customer_id": customerID, - "card_id": cardID, + paramCustomerID: customerID, + paramCardID: cardID, } res, err := httpclient.Delete[Response](ctx, c.config, urlCardsWithID, nil, httpclient.WithPathParams(params)) @@ -110,7 +113,7 @@ func (c *client) Delete(ctx context.Context, customerID, cardID string) (*Respon func (c *client) List(ctx context.Context, customerID string) ([]Response, error) { params := map[string]string{ - "customer_id": customerID, + paramCustomerID: customerID, } res, err := httpclient.Get[[]Response](ctx, c.config, urlCards, httpclient.WithPathParams(params)) diff --git a/pkg/customercard/client_test.go b/pkg/customercard/client_test.go index deb99d37..2be6dc8e 100644 --- a/pkg/customercard/client_test.go +++ b/pkg/customercard/client_test.go @@ -3,17 +3,24 @@ package customercard import ( "context" "fmt" + "io" "net/http" + "os" "reflect" + "strings" "testing" + "time" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/internal/httpclient" ) var ( -// listResponseJSON, _ = os.Open("../../resources/mocks/payment_method/list_response.json") -// listResponse, _ = io.ReadAll(listResponseJSON) + responseJSON, _ = os.Open("../../resources/mocks/customer_card/card_response.json") + response, _ = io.ReadAll(responseJSON) + + listResponseJSON, _ = os.Open("../../resources/mocks/customer_card/list_response.json") + listResponse, _ = io.ReadAll(listResponseJSON) ) func TestCreate(t *testing.T) { @@ -32,6 +39,67 @@ func TestCreate(t *testing.T) { want *Response wantErr string }{ + { + name: "should_return_card_response", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(response)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "1111111111-pDci63MBohR7c", + req: Request{Token: "938e19c9848b89fb207105b8a17e97ce"}, + }, + want: &Response{ + ID: "9999999999", + CustomerID: "1111111111-pDci63MBohR7c", + UserID: "0000000000", + FirstSixDigits: "123456", + LastFourDigits: "1234", + ExpirationMonth: 12, + ExpirationYear: 2025, + LiveMode: true, + DateCreated: parseDate("2024-02-07T16:28:38.000-04:00"), + DateLastUpdated: parseDate("2024-02-07T16:31:06.964-04:00"), + Issuer: IssuerResponse{ + ID: 24, + Name: "Mastercard", + }, + Cardholder: CardholderResponse{ + Name: "APRO", + IdentificationResponse: IdentificationResponse{ + Number: "19119119100", + Type: "CPF", + }, + }, + AdditionalInfo: AdditionalInfoResponse{ + RequestPublic: "true", + ApiClientApplication: "traffic-layer", + ApiClientScope: "mapi-pci-tl", + }, + PaymentMethod: PaymentMethodResponse{ + ID: "master", + Name: "Mastercard", + PaymentTypeId: "credit_card", + Thumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + SecureThumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + }, + SecurityCode: SecurityCode{ + Length: 3, + CardLocation: "back", + }, + }, + wantErr: "", + }, { name: "should_return_error_when_send_request", fields: fields{ @@ -71,124 +139,470 @@ func TestCreate(t *testing.T) { } } -// -//func TestList(t *testing.T) { -// type fields struct { -// config *config.Config -// } -// type args struct { -// ctx context.Context -// } -// tests := []struct { -// name string -// fields fields -// args args -// want []Response -// wantErr string -// }{ -// { -// name: "should_return_error_when_creating_request", -// fields: fields{ -// config: nil, -// }, -// args: args{ -// ctx: nil, -// }, -// want: nil, -// wantErr: "error creating request: net/http: nil Context", -// }, -// { -// name: "should_return_error_when_send_request", -// fields: fields{ -// config: &config.Config{ -// Requester: &httpclient.Mock{ -// DoMock: func(req *http.Request) (*http.Response, error) { -// return nil, fmt.Errorf("some error") -// }, -// }, -// }, -// }, -// args: args{ -// ctx: context.Background(), -// }, -// want: nil, -// wantErr: "transport level error: some error", -// }, -// { -// name: "should_return_error_unmarshal_response", -// fields: fields{ -// config: &config.Config{ -// Requester: &httpclient.Mock{ -// DoMock: func(req *http.Request) (*http.Response, error) { -// stringReader := strings.NewReader("invalid json") -// stringReadCloser := io.NopCloser(stringReader) -// return &http.Response{ -// Body: stringReadCloser, -// }, nil -// }, -// }, -// }, -// }, -// args: args{ -// ctx: context.Background(), -// }, -// want: nil, -// wantErr: "invalid character 'i' looking for beginning of value", -// }, -// { -// name: "should_return_formatted_response", -// fields: fields{ -// config: &config.Config{ -// Requester: &httpclient.Mock{ -// DoMock: func(req *http.Request) (*http.Response, error) { -// stringReader := strings.NewReader(string(listResponse)) -// stringReadCloser := io.NopCloser(stringReader) -// return &http.Response{ -// Body: stringReadCloser, -// }, nil -// }, -// }, -// }, -// }, -// args: args{ -// ctx: context.Background(), -// }, -// want: []Response{ -// { -// ID: "debmaster", -// Name: "Mastercard Débito", -// PaymentTypeID: "debit_card", -// Status: "testing", -// SecureThumbnail: "https://www.mercadopago.com/org-img/MP3/API/logos/debmaster.gif", -// Thumbnail: "https://www.mercadopago.com/org-img/MP3/API/logos/debmaster.gif", -// }, -// { -// ID: "cabal", -// Name: "Cabal", -// PaymentTypeID: "credit_card", -// Status: "testing", -// SecureThumbnail: "https://www.mercadopago.com/org-img/MP3/API/logos/cabal.gif", -// Thumbnail: "https://www.mercadopago.com/org-img/MP3/API/logos/cabal.gif", -// }, -// }, -// wantErr: "", -// }, -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// c := &client{tt.fields.config} -// got, err := c.List(tt.args.ctx) -// gotErr := "" -// if err != nil { -// gotErr = err.Error() -// } -// -// if gotErr != tt.wantErr { -// t.Errorf("client.List() error = %v, wantErr %v", err, tt.wantErr) -// } -// if !reflect.DeepEqual(got, tt.want) { -// t.Errorf("client.List() = %v, want %v", got, tt.want) -// } -// }) -// } -//} +func TestUpdate(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + customerID string + cardID string + req Request + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr string + }{ + { + name: "should_return_card_response", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(response)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "1111111111-pDci63MBohR7c", + cardID: "9999999999", + req: Request{Token: "938e19c9848b89fb207105b8a17e97ce"}, + }, + want: &Response{ + ID: "9999999999", + CustomerID: "1111111111-pDci63MBohR7c", + UserID: "0000000000", + FirstSixDigits: "123456", + LastFourDigits: "1234", + ExpirationMonth: 12, + ExpirationYear: 2025, + LiveMode: true, + DateCreated: parseDate("2024-02-07T16:28:38.000-04:00"), + DateLastUpdated: parseDate("2024-02-07T16:31:06.964-04:00"), + Issuer: IssuerResponse{ + ID: 24, + Name: "Mastercard", + }, + Cardholder: CardholderResponse{ + Name: "APRO", + IdentificationResponse: IdentificationResponse{ + Number: "19119119100", + Type: "CPF", + }, + }, + AdditionalInfo: AdditionalInfoResponse{ + RequestPublic: "true", + ApiClientApplication: "traffic-layer", + ApiClientScope: "mapi-pci-tl", + }, + PaymentMethod: PaymentMethodResponse{ + ID: "master", + Name: "Mastercard", + PaymentTypeId: "credit_card", + Thumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + SecureThumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + }, + SecurityCode: SecurityCode{ + Length: 3, + CardLocation: "back", + }, + }, + wantErr: "", + }, + { + name: "should_return_error_when_send_request", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "any", + req: Request{}, + }, + want: nil, + wantErr: "transport level error: some error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{tt.fields.config} + got, err := c.Update(tt.args.ctx, tt.args.customerID, tt.args.cardID, tt.args.req) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("client.Create() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.Create() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGet(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + customerID string + cardID string + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr string + }{ + { + name: "should_return_card_response", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(response)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "1111111111-pDci63MBohR7c", + cardID: "9999999999", + }, + want: &Response{ + ID: "9999999999", + CustomerID: "1111111111-pDci63MBohR7c", + UserID: "0000000000", + FirstSixDigits: "123456", + LastFourDigits: "1234", + ExpirationMonth: 12, + ExpirationYear: 2025, + LiveMode: true, + DateCreated: parseDate("2024-02-07T16:28:38.000-04:00"), + DateLastUpdated: parseDate("2024-02-07T16:31:06.964-04:00"), + Issuer: IssuerResponse{ + ID: 24, + Name: "Mastercard", + }, + Cardholder: CardholderResponse{ + Name: "APRO", + IdentificationResponse: IdentificationResponse{ + Number: "19119119100", + Type: "CPF", + }, + }, + AdditionalInfo: AdditionalInfoResponse{ + RequestPublic: "true", + ApiClientApplication: "traffic-layer", + ApiClientScope: "mapi-pci-tl", + }, + PaymentMethod: PaymentMethodResponse{ + ID: "master", + Name: "Mastercard", + PaymentTypeId: "credit_card", + Thumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + SecureThumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + }, + SecurityCode: SecurityCode{ + Length: 3, + CardLocation: "back", + }, + }, + wantErr: "", + }, + { + name: "should_return_error_when_send_request", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "any", + }, + want: nil, + wantErr: "transport level error: some error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{tt.fields.config} + got, err := c.Get(tt.args.ctx, tt.args.customerID, tt.args.cardID) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("client.Create() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.Create() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDelete(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + customerID string + cardID string + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr string + }{ + { + name: "should_return_card_response", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(response)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "1111111111-pDci63MBohR7c", + cardID: "9999999999", + }, + want: &Response{ + ID: "9999999999", + CustomerID: "1111111111-pDci63MBohR7c", + UserID: "0000000000", + FirstSixDigits: "123456", + LastFourDigits: "1234", + ExpirationMonth: 12, + ExpirationYear: 2025, + LiveMode: true, + DateCreated: parseDate("2024-02-07T16:28:38.000-04:00"), + DateLastUpdated: parseDate("2024-02-07T16:31:06.964-04:00"), + Issuer: IssuerResponse{ + ID: 24, + Name: "Mastercard", + }, + Cardholder: CardholderResponse{ + Name: "APRO", + IdentificationResponse: IdentificationResponse{ + Number: "19119119100", + Type: "CPF", + }, + }, + AdditionalInfo: AdditionalInfoResponse{ + RequestPublic: "true", + ApiClientApplication: "traffic-layer", + ApiClientScope: "mapi-pci-tl", + }, + PaymentMethod: PaymentMethodResponse{ + ID: "master", + Name: "Mastercard", + PaymentTypeId: "credit_card", + Thumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + SecureThumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + }, + SecurityCode: SecurityCode{ + Length: 3, + CardLocation: "back", + }, + }, + wantErr: "", + }, + { + name: "should_return_error_when_send_request", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "any", + }, + want: nil, + wantErr: "transport level error: some error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{tt.fields.config} + got, err := c.Delete(tt.args.ctx, tt.args.customerID, tt.args.cardID) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("client.Create() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.Create() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestList(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + customerID string + } + tests := []struct { + name string + fields fields + args args + want []Response + wantErr string + }{ + { + name: "should_return_card_response", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(listResponse)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "1111111111-pDci63MBohR7c", + }, + want: []Response{ + { + ID: "9999999999", + CustomerID: "1111111111-pDci63MBohR7c", + UserID: "0000000000", + FirstSixDigits: "123456", + LastFourDigits: "1234", + ExpirationMonth: 12, + ExpirationYear: 2025, + LiveMode: true, + DateCreated: parseDate("2024-02-07T16:28:38.000-04:00"), + DateLastUpdated: parseDate("2024-02-07T16:31:06.964-04:00"), + Issuer: IssuerResponse{ + ID: 24, + Name: "Mastercard", + }, + Cardholder: CardholderResponse{ + Name: "APRO", + IdentificationResponse: IdentificationResponse{ + Number: "19119119100", + Type: "CPF", + }, + }, + AdditionalInfo: AdditionalInfoResponse{ + RequestPublic: "true", + ApiClientApplication: "traffic-layer", + ApiClientScope: "mapi-pci-tl", + }, + PaymentMethod: PaymentMethodResponse{ + ID: "master", + Name: "Mastercard", + PaymentTypeId: "credit_card", + Thumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + SecureThumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + }, + SecurityCode: SecurityCode{ + Length: 3, + CardLocation: "back", + }, + }, + }, + wantErr: "", + }, + { + name: "should_return_error_when_send_request", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "any", + }, + want: nil, + wantErr: "transport level error: some error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{tt.fields.config} + got, err := c.List(tt.args.ctx, tt.args.customerID) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("client.Create() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.Create() = %v, want %v", got, tt.want) + } + }) + } +} + +func parseDate(s string) *time.Time { + d, _ := time.Parse(time.RFC3339, s) + return &d +} diff --git a/pkg/internal/httpclient/client_test.go b/pkg/internal/httpclient/client_test.go index cddbe3ec..9e06bd0d 100644 --- a/pkg/internal/httpclient/client_test.go +++ b/pkg/internal/httpclient/client_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestMakeParams(t *testing.T) { +func TestMakePathParams(t *testing.T) { type args struct { url string params map[string]string @@ -26,17 +26,6 @@ func TestMakeParams(t *testing.T) { }, wantURL: "http://localhost/payments/1234567890", }, - { - name: "should_replace_one_path_param_and_add_query_param", - args: args{ - url: "http://localhost/payments/:payment_id/search", - params: map[string]string{ - "payment_id": "1234567890", - "external_resource": "abcd12345", - }, - }, - wantURL: "http://localhost/payments/1234567890/search?external_resource=abcd12345", - }, { name: "should_return_path_param_not_informed", args: args{ @@ -57,6 +46,16 @@ func TestMakeParams(t *testing.T) { }, wantMsgErr: "path parameters not informed: test_id,unit_id", }, + { + name: "should_return_the_same_path_url", + args: args{ + url: "http://localhost/tests/", + params: map[string]string{ + "integrate_id": "1234567890", + }, + }, + wantURL: "http://localhost/tests/", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -75,62 +74,54 @@ func TestMakeParams(t *testing.T) { } } -// -//func TestMakeParams(t *testing.T) { -// type args struct { -// url string -// params map[string]string -// } -// tests := []struct { -// name string -// args args -// wantURL string -// wantErr error -// }{ -// { -// name: "test_replace_one_path_param", -// args: args{ -// url: "http://localhost/payments/:payment_id", -// params: map[string]string{ -// "payment_id": "1234567890", -// }, -// }, -// wantURL: "http://localhost/payments/1234567890", -// }, -// { -// name: "should_return_path_param_not_informed", -// args: args{ -// url: "http://localhost/customers/:customer_id", -// params: map[string]string{ -// "payment_id": "1234567890", -// }, -// }, -// wantErr: errors.New("path parameters not informed: customer_id"), -// }, -// { -// name: "should_return_two_path_params_not_informed", -// args: args{ -// url: "http://localhost/tests/:test_id/units/:unit_id", -// params: map[string]string{ -// "integrate_id": "1234567890", -// }, -// }, -// wantErr: errors.New("path parameters not informed: test_id,unit_id"), -// }, -// } -// -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// req, _ := http.NewRequest(http.MethodPost, tt.args.url, nil) -// gotErr := makeParams(req, tt.args.params) -// -// if tt.wantErr != nil && gotErr != tt.wantErr.Error() { -// t.Errorf("makeParams() wantErr = %v, gotErr = %v", tt.wantErr, gotErr) -// } -// -// if tt.wantErr == nil && tt.wantURL != req.URL.String() { -// t.Errorf("makeParams() wantURL = %v, gotURL %v", tt.wantURL, req.URL.String()) -// } -// }) -// } -//} +func TestMakeQueryParams(t *testing.T) { + type args struct { + url string + params map[string]string + } + tests := []struct { + name string + args args + wantURL string + }{ + { + name: "should_add_one_query_param", + args: args{ + url: "http://localhost/payments/1234567890/search", + params: map[string]string{ + "external_resource": "as2f12345", + }, + }, + wantURL: "http://localhost/payments/1234567890/search?external_resource=as2f12345", + }, + { + name: "should_add_two_query_params", + args: args{ + url: "http://localhost/payments/1234567890/search", + params: map[string]string{ + "external_resource": "as2f12345", + "offset": "2", + }, + }, + wantURL: "http://localhost/payments/1234567890/search?external_resource=as2f12345&offset=2", + }, + { + name: "should_return_the_same_path_url", + args: args{ + url: "http://localhost/tests/", + params: map[string]string{}, + }, + wantURL: "http://localhost/tests/", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req, _ := http.NewRequest(http.MethodGet, tt.args.url, nil) + + makeQueryParams(req, tt.args.params) + if tt.wantURL != req.URL.String() { + t.Errorf("makeQueryParams() wantURL = %v, gotURL %v", tt.wantURL, req.URL.String()) + } + }) + } +} diff --git a/resources/mocks/customer_card/card_response.json b/resources/mocks/customer_card/card_response.json new file mode 100644 index 00000000..40675ba4 --- /dev/null +++ b/resources/mocks/customer_card/card_response.json @@ -0,0 +1,40 @@ +{ + "additional_info": { + "request_public": "true", + "api_client_application": "traffic-layer", + "api_client_scope": "mapi-pci-tl" + }, + "card_number_id": null, + "cardholder": { + "name": "APRO", + "identification": { + "number": "19119119100", + "type": "CPF" + } + }, + "customer_id": "1111111111-pDci63MBohR7c", + "date_created": "2024-02-07T16:28:38.000-04:00", + "date_last_updated": "2024-02-07T16:31:06.964-04:00", + "expiration_month": 12, + "expiration_year": 2025, + "first_six_digits": "123456", + "id": "9999999999", + "issuer": { + "id": 24, + "name": "Mastercard" + }, + "last_four_digits": "1234", + "live_mode": true, + "payment_method": { + "id": "master", + "name": "Mastercard", + "payment_type_id": "credit_card", + "thumbnail": "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + "secure_thumbnail": "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png" + }, + "security_code": { + "length": 3, + "card_location": "back" + }, + "user_id": "0000000000" +} \ No newline at end of file diff --git a/resources/mocks/customer_card/list_response.json b/resources/mocks/customer_card/list_response.json new file mode 100644 index 00000000..92202c39 --- /dev/null +++ b/resources/mocks/customer_card/list_response.json @@ -0,0 +1,42 @@ +[ + { + "additional_info": { + "request_public": "true", + "api_client_application": "traffic-layer", + "api_client_scope": "mapi-pci-tl" + }, + "card_number_id": null, + "cardholder": { + "name": "APRO", + "identification": { + "number": "19119119100", + "type": "CPF" + } + }, + "customer_id": "1111111111-pDci63MBohR7c", + "date_created": "2024-02-07T16:28:38.000-04:00", + "date_last_updated": "2024-02-07T16:31:06.964-04:00", + "expiration_month": 12, + "expiration_year": 2025, + "first_six_digits": "123456", + "id": "9999999999", + "issuer": { + "id": 24, + "name": "Mastercard" + }, + "last_four_digits": "1234", + "live_mode": true, + "payment_method": { + "id": "master", + "name": "Mastercard", + "payment_type_id": "credit_card", + "thumbnail": "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", + "secure_thumbnail": "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png" + }, + "security_code": { + "length": 3, + "card_location": "back" + }, + "user_id": "0000000000" + } +] \ No newline at end of file diff --git a/test/integration/payment_method_test.go b/test/integration/payment_method_test.go index 92aee1a9..b72da871 100644 --- a/test/integration/payment_method_test.go +++ b/test/integration/payment_method_test.go @@ -11,7 +11,7 @@ import ( func TestPaymentMethod(t *testing.T) { t.Run("should_list_payment_methods", func(t *testing.T) { - cfg, err := config.New(os.Getenv("at")) + cfg, err := config.New(os.Getenv("ACCESS_TOKEN")) if err != nil { t.Fatal(err) } From 034c26d8deefae42c68ed037c0e93c79dba630cd Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Wed, 31 Jan 2024 16:18:05 -0300 Subject: [PATCH 04/55] add user client api --- examples/apis/user/get/main.go | 28 ++++++ pkg/user/response.go | 11 ++ pkg/user/user.go | 51 ++++++++++ pkg/user/user_test.go | 134 +++++++++++++++++++++++++ resources/mocks/user/get_response.json | 11 ++ test/integration/user/user_test.go | 29 ++++++ 6 files changed, 264 insertions(+) create mode 100644 examples/apis/user/get/main.go create mode 100644 pkg/user/response.go create mode 100644 pkg/user/user.go create mode 100644 pkg/user/user_test.go create mode 100644 resources/mocks/user/get_response.json create mode 100644 test/integration/user/user_test.go diff --git a/examples/apis/user/get/main.go b/examples/apis/user/get/main.go new file mode 100644 index 00000000..fec0b150 --- /dev/null +++ b/examples/apis/user/get/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/user" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + client := user.NewClient(cfg) + user, err := client.Get(context.Background()) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(user) +} diff --git a/pkg/user/response.go b/pkg/user/response.go new file mode 100644 index 00000000..fd013044 --- /dev/null +++ b/pkg/user/response.go @@ -0,0 +1,11 @@ +package user + +type Response struct { + ID int64 `json:"id"` + Nickname string `json:"nickname"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + CountryID string `json:"country_id"` + Email string `json:"email"` + SiteID string `json:"site_id"` +} diff --git a/pkg/user/user.go b/pkg/user/user.go new file mode 100644 index 00000000..75e6308e --- /dev/null +++ b/pkg/user/user.go @@ -0,0 +1,51 @@ +package user + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/internal/httpclient" +) + +const url = "https://api.mercadopago.com/users/me" + +// Client contains the method to interact with the User API. +type Client interface { + // Get get user information. + // It is a get request to the endpoint: https://api.mercadopago.com/users/me + Get(ctx context.Context) (*Response, error) +} + +// client is the implementation of Client. +type client struct { + config *config.Config +} + +// NewClient returns a new User API Client. +func NewClient(c *config.Config) Client { + return &client{ + config: c, + } +} + +func (c *client) Get(ctx context.Context) (*Response, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.config, req) + if err != nil { + return nil, err + } + + var formatted Response + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, err + } + + return &formatted, nil +} diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go new file mode 100644 index 00000000..14aca159 --- /dev/null +++ b/pkg/user/user_test.go @@ -0,0 +1,134 @@ +package user + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "reflect" + "strings" + "testing" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/internal/httpclient" +) + +var ( + userResponseJSON, _ = os.Open("../../resources/mocks/user/get_response.json") + userResponse, _ = io.ReadAll(userResponseJSON) +) + +func TestGet(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr string + }{ + { + name: "should_return_error_when_creating_request", + fields: fields{ + config: nil, + }, + args: args{ + ctx: nil, + }, + want: nil, + wantErr: "error creating request: net/http: nil Context", + }, + { + name: "should_return_error_when_send_request", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "transport level error: some error", + }, + { + name: "should_return_error_unmarshal_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader("invalid json") + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "invalid character 'i' looking for beginning of value", + }, + { + name: "should_return_formatted_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(userResponse)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: &Response{ + ID: 1340175910, + Nickname: "TEST_USER_658045679", + FirstName: "Test", + LastName: "Test", + CountryID: "BR", + Email: "test_user_658045679@testuser.com", + SiteID: "MLB", + }, + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + config: tt.fields.config, + } + got, err := c.Get(tt.args.ctx) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("client.Get() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.Get() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/resources/mocks/user/get_response.json b/resources/mocks/user/get_response.json new file mode 100644 index 00000000..856006f0 --- /dev/null +++ b/resources/mocks/user/get_response.json @@ -0,0 +1,11 @@ +{ + "id": 1340175910, + "nickname": "TEST_USER_658045679", + "registration_date": "2023-03-27T20:26:10.298-04:00", + "first_name": "Test", + "last_name": "Test", + "gender": "", + "country_id": "BR", + "email": "test_user_658045679@testuser.com", + "site_id": "MLB" +} \ No newline at end of file diff --git a/test/integration/user/user_test.go b/test/integration/user/user_test.go new file mode 100644 index 00000000..8ee2ee38 --- /dev/null +++ b/test/integration/user/user_test.go @@ -0,0 +1,29 @@ +package integration + +import ( + "context" + "os" + "testing" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/user" +) + +func TestUser(t *testing.T) { + t.Run("should_get_user_information", func(t *testing.T) { + c, err := config.New(os.Getenv("at")) + if err != nil { + t.Fatal(err) + } + + pmc := user.NewClient(c) + res, err := pmc.Get(context.Background()) + + if res == nil { + t.Error("res can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + }) +} From 9b51325818ef2ad648cab88e58abb98612577aa8 Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Wed, 31 Jan 2024 17:49:49 -0300 Subject: [PATCH 05/55] adjust pointer in object --- pkg/user/user.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/user/user.go b/pkg/user/user.go index 75e6308e..214e515a 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -42,10 +42,10 @@ func (c *client) Get(ctx context.Context) (*Response, error) { return nil, err } - var formatted Response + var formatted *Response if err := json.Unmarshal(res, &formatted); err != nil { return nil, err } - return &formatted, nil + return formatted, nil } From 89a3d9faed0f50e84df91b28a5814f55cc14d048 Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Fri, 9 Feb 2024 15:58:14 -0300 Subject: [PATCH 06/55] adjusts merge --- pkg/payment/payment.go | 218 +++++++++++++ pkg/payment/request.go | 236 ++++++++++++++ pkg/payment/response.go | 304 ++++++++++++++++++ pkg/payment/search_request.go | 40 +++ pkg/payment/search_response.go | 14 + pkg/payment/update_request.go | 12 + .../{payment_method.go => client.go} | 9 + 7 files changed, 833 insertions(+) create mode 100644 pkg/payment/payment.go create mode 100644 pkg/payment/request.go create mode 100644 pkg/payment/response.go create mode 100644 pkg/payment/search_request.go create mode 100644 pkg/payment/search_response.go create mode 100644 pkg/payment/update_request.go rename pkg/paymentmethod/{payment_method.go => client.go} (81%) diff --git a/pkg/payment/payment.go b/pkg/payment/payment.go new file mode 100644 index 00000000..96f780f7 --- /dev/null +++ b/pkg/payment/payment.go @@ -0,0 +1,218 @@ +package payment + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/mercadopago/sdk-go/pkg/credential" + "github.com/mercadopago/sdk-go/pkg/internal/httpclient" + "github.com/mercadopago/sdk-go/pkg/option" +) + +const ( + postURL = "https://api.mercadopago.com/v1/payments" + searchURL = "https://api.mercadopago.com/v1/payments/search" + getURL = "https://api.mercadopago.com/v1/payments/{id}" + putURL = "https://api.mercadopago.com/v1/payments/{id}" +) + +// Client contains the methods to interact with the Payments API. +type Client interface { + // Create creates a new payment. + // It is a post request to the endpoint: https://api.mercadopago.com/v1/payments + // Reference: https://www.mercadopago.com.br/developers/pt/reference/payments/_payments/post/ + Create(ctx context.Context, dto Request) (*Response, error) + + // Search searches for payments. + // It is a get request to the endpoint: https://api.mercadopago.com/v1/payments/search + // Reference: https://www.mercadopago.com.br/developers/pt/reference/payments/_payments_search/get/ + Search(ctx context.Context, f Filters) (*SearchResponse, error) + + // Get gets a payment by its ID. + // It is a get request to the endpoint: https://api.mercadopago.com/v1/payments/{id} + // Reference: https://www.mercadopago.com.br/developers/pt/reference/payments/_payments_id/get/ + Get(ctx context.Context, id int64) (*Response, error) + + // Cancel cancels a payment by its ID. + // It is a put request to the endpoint: https://api.mercadopago.com/v1/payments/{id} + Cancel(ctx context.Context, id int64) (*Response, error) + + // Capture captures a payment by its ID. + // It is a put request to the endpoint: https://api.mercadopago.com/v1/payments/{id} + Capture(ctx context.Context, id int64) (*Response, error) + + // CaptureAmount captures amount of a payment by its ID. + // It is a put request to the endpoint: https://api.mercadopago.com/v1/payments/{id} + CaptureAmount(ctx context.Context, id int64, amount float64) (*Response, error) +} + +// client is the implementation of Client. +type client struct { + credential *credential.Credential + config *option.ClientOptions +} + +// NewClient returns a new Payments API Client. +func NewClient(cdt *credential.Credential, opts ...option.ClientOption) Client { + c := option.ApplyClientOptions(opts...) + + return &client{ + credential: cdt, + config: c, + } +} + +func (c *client) Create(ctx context.Context, dto Request) (*Response, error) { + body, err := json.Marshal(&dto) + if err != nil { + return nil, fmt.Errorf("error marshaling request body: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, strings.NewReader(string(body))) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) Search(ctx context.Context, f Filters) (*SearchResponse, error) { + params := url.Values{} + params.Add("sort", f.Sort) + params.Add("criteria", f.Criteria) + params.Add("external_reference", f.ExternalReference) + params.Add("range", f.Range) + params.Add("begin_date", f.BeginDate) + params.Add("end_date", f.EndDate) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL+"?"+params.Encode(), nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + if err != nil { + return nil, err + } + + var formatted *SearchResponse + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) Get(ctx context.Context, id int64) (*Response, error) { + conv := strconv.Itoa(int(id)) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.Replace(getURL, "{id}", conv, 1), nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) Cancel(ctx context.Context, id int64) (*Response, error) { + dto := &CancelRequest{Status: "cancelled"} + body, err := json.Marshal(dto) + if err != nil { + return nil, fmt.Errorf("error marshaling request body: %w", err) + } + + conv := strconv.Itoa(int(id)) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, strings.Replace(putURL, "{id}", conv, 1), strings.NewReader(string(body))) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) Capture(ctx context.Context, id int64) (*Response, error) { + dto := &CaptureRequest{Capture: true} + body, err := json.Marshal(dto) + if err != nil { + return nil, fmt.Errorf("error marshaling request body: %w", err) + } + + conv := strconv.Itoa(int(id)) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, strings.Replace(putURL, "{id}", conv, 1), strings.NewReader(string(body))) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) CaptureAmount(ctx context.Context, id int64, amount float64) (*Response, error) { + dto := &CaptureRequest{TransactionAmount: amount, Capture: true} + body, err := json.Marshal(dto) + if err != nil { + return nil, fmt.Errorf("error marshaling request body: %w", err) + } + + conv := strconv.Itoa(int(id)) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, strings.Replace(putURL, "{id}", conv, 1), strings.NewReader(string(body))) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} diff --git a/pkg/payment/request.go b/pkg/payment/request.go new file mode 100644 index 00000000..0b44a978 --- /dev/null +++ b/pkg/payment/request.go @@ -0,0 +1,236 @@ +package payment + +import ( + "time" +) + +// Request represents a request for creating or updating a payment. +type Request struct { + CallbackURL string `json:"callback_url,omitempty"` + CouponCode string `json:"coupon_code,omitempty"` + Description string `json:"description,omitempty"` + ExternalReference string `json:"external_reference,omitempty"` + IssuerID string `json:"issuer_id,omitempty"` + MerchantAccountID string `json:"merchant_account_id,omitempty"` + NotificationURL string `json:"notification_url,omitempty"` + PaymentMethodID string `json:"payment_method_id,omitempty"` + ProcessingMode string `json:"processing_mode,omitempty"` + Token string `json:"token,omitempty"` + PaymentMethodOptionID string `json:"payment_method_option_id,omitempty"` + StatementDescriptor string `json:"statement_descriptor,omitempty"` + ThreeDSecureMode string `json:"three_d_secure_mode,omitempty"` + Installments int `json:"installments,omitempty"` + CampaignID int64 `json:"campaign_id,omitempty"` + DifferentialPricingID int64 `json:"differential_pricing_id,omitempty"` + SponsorID int64 `json:"sponsor_id,omitempty"` + BinaryMode bool `json:"binary_mode,omitempty"` + Capture bool `json:"capture,omitempty"` + ApplicationFee float64 `json:"application_fee,omitempty"` + CouponAmount float64 `json:"coupon_amount,omitempty"` + NetAmount float64 `json:"net_amount,omitempty"` + TransactionAmount float64 `json:"transaction_amount,omitempty"` + Metadata map[string]any `json:"metadata,omitempty"` + + DateOfExpiration *time.Time `json:"date_of_expiration,omitempty"` + AdditionalInfo *AdditionalInfoRequest `json:"additional_info,omitempty"` + MerchantServices *MerchantServicesRequest `json:"merchant_services,omitempty"` + Order *OrderRequest `json:"order,omitempty"` + Payer *PayerRequest `json:"payer,omitempty"` + TransactionDetails *TransactionDetailsRequest `json:"transaction_details,omitempty"` + PointOfInteraction *PointOfInteractionRequest `json:"point_of_interaction,omitempty"` + PaymentMethod *PaymentMethodRequest `json:"payment_method,omitempty"` + Taxes []TaxRequest `json:"taxes,omitempty"` +} + +// AdditionalInfoRequest represents additional information request within Request. +type AdditionalInfoRequest struct { + IPAddress string `json:"ip_address,omitempty"` + + Payer *AdditionalInfoPayerRequest `json:"payer,omitempty"` + Shipments *ShipmentsRequest `json:"shipments,omitempty"` + Barcode *AdditionalInfoBarcodeRequest `json:"barcode,omitempty"` + Items []ItemRequest `json:"items,omitempty"` +} + +// AdditionalInfoPayerRequest represents payer information request within AdditionalInfoPayerRequest. +type AdditionalInfoPayerRequest struct { + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + AuthenticationType string `json:"authentication_type,omitempty"` + IsPrimeUser bool `json:"is_prime_user,omitempty"` + IsFirstPurchaseOnline bool `json:"is_first_purchase_online,omitempty"` + + RegistrationDate *time.Time `json:"registration_date,omitempty"` + LastPurchase *time.Time `json:"last_purchase,omitempty"` + Phone *PhoneRequest `json:"phone,omitempty"` + Address *AddressRequest `json:"address,omitempty"` +} + +// PhoneRequest represents phone request within AdditionalInfoPayerRequest. +type PhoneRequest struct { + AreaCode string `json:"area_code,omitempty"` + Number string `json:"number,omitempty"` +} + +// AddressRequest represents address request within AdditionalInfoPayerRequest. +type AddressRequest struct { + ZipCode string `json:"zip_code,omitempty"` + StreetName string `json:"street_name,omitempty"` + StreetNumber string `json:"street_number,omitempty"` +} + +// ShipmentsRequest represents shipments request within AdditionalInfoRequest. +type ShipmentsRequest struct { + LocalPickup bool `json:"local_pickup,omitempty"` + ExpressShipment bool `json:"express_shipment,omitempty"` + + ReceiverAddress *ReceiverAddressRequest `json:"receiver_address,omitempty"` +} + +// ReceiverAddressRequest represents receiver address request within ShipmentsRequest. +type ReceiverAddressRequest struct { + StateName string `json:"state_name,omitempty"` + CityName string `json:"city_name,omitempty"` + Floor string `json:"floor,omitempty"` + Apartment string `json:"apartment,omitempty"` + ZipCode string `json:"zip_code,omitempty"` + StreetName string `json:"street_name,omitempty"` + StreetNumber string `json:"street_number,omitempty"` +} + +// AdditionalInfoBarcodeRequest represents barcode request within AdditionalInfoRequest. +type AdditionalInfoBarcodeRequest struct { + Type string `json:"type,omitempty"` + Content string `json:"content,omitempty"` + Width float64 `json:"width,omitempty"` + Height float64 `json:"height,omitempty"` +} + +// ItemRequest represents an item request within AdditionalInfoRequest. +type ItemRequest struct { + ID string `json:"id,omitempty"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + PictureURL string `json:"picture_url,omitempty"` + CategoryID string `json:"category_id,omitempty"` + Quantity int `json:"quantity,omitempty"` + UnitPrice float64 `json:"unit_price,omitempty"` + Warranty bool `json:"warranty,omitempty"` + + EventDate *time.Time `json:"event_date,omitempty"` + CategoryDescriptor *CategoryDescriptorRequest `json:"category_descriptor,omitempty"` +} + +// CategoryDescriptorRequest represents category descriptor request within ItemRequest. +type CategoryDescriptorRequest struct { + Passenger *PassengerRequest `json:"passenger,omitempty"` + Route *RouteRequest `json:"route,omitempty"` +} + +// PassengerRequest represents passenger request within CategoryDescriptorRequest. +type PassengerRequest struct { + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + + Identification *IdentificationRequest `json:"identification,omitempty"` +} + +// IdentificationRequest represents identification request within PaymentPassengerRequest. +type IdentificationRequest struct { + Type string `json:"type,omitempty"` + Number string `json:"number,omitempty"` +} + +// RouteRequest represents route request within CategoryDescriptorRequest. +type RouteRequest struct { + Departure string `json:"departure,omitempty"` + Destination string `json:"destination,omitempty"` + Company string `json:"company,omitempty"` + + DepartureDateTime *time.Time `json:"departure_date_time,omitempty"` + ArrivalDateTime *time.Time `json:"arrival_date_time,omitempty"` +} + +// MerchantServicesRequest represents merchant services request within Request. +type MerchantServicesRequest struct { + FraudScoring bool `json:"fraud_scoring,omitempty"` + FraudManualReview bool `json:"fraud_manual_review,omitempty"` +} + +// OrderRequest represents order request within Request. +type OrderRequest struct { + Type string `json:"type,omitempty"` + ID int64 `json:"id,omitempty"` +} + +// PayerRequest represents payer request within Request. +type PayerRequest struct { + Type string `json:"type,omitempty"` + ID string `json:"id,omitempty"` + Email string `json:"email,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + EntityType string `json:"entity_type,omitempty"` + + Identification *IdentificationRequest `json:"identification,omitempty"` + Address *PayerAddressRequest `json:"address,omitempty"` +} + +// PayerAddressRequest represents payer address request within PayerRequest. +type PayerAddressRequest struct { + Neighborhood string `json:"neighborhood,omitempty"` + City string `json:"city,omitempty"` + FederalUnit string `json:"federal_unit,omitempty"` + ZipCode string `json:"zip_code,omitempty"` + StreetName string `json:"street_name,omitempty"` + StreetNumber string `json:"street_number,omitempty"` +} + +// TransactionDetailsRequest represents transaction details request within Request. +type TransactionDetailsRequest struct { + FinancialInstitution string `json:"financial_institution,omitempty"` +} + +// PointOfInteractionRequest represents point of interaction request within Request. +type PointOfInteractionRequest struct { + LinkedTo string `json:"linked_to,omitempty"` + Type string `json:"type,omitempty"` +} + +// PaymentMethodRequest represents payment method request within Request. +type PaymentMethodRequest struct { + Data *DataRequest `json:"data,omitempty"` +} + +// DataRequest represents payment data request within PaymentMethodRequest. +type DataRequest struct { + Rules *RulesRequest `json:"rules,omitempty"` +} + +// RulesRequest represents payment rules request within DataRequest. +type RulesRequest struct { + Fine *FeeRequest `json:"fine,omitempty"` + Interest *FeeRequest `json:"interest,omitempty"` + Discounts []DiscountRequest `json:"discounts,omitempty"` +} + +// FeeRequest represents fee request within RulesRequest. +type FeeRequest struct { + Type string `json:"type,omitempty"` + Value float64 `json:"value,omitempty"` +} + +// DiscountRequest represents discount request within RulesRequest. +type DiscountRequest struct { + Type string `json:"type,omitempty"` + Value float64 `json:"value,omitempty"` + + LimitDate *time.Time `json:"limit_date,omitempty"` +} + +// TaxRequest represents tax request within Request. +type TaxRequest struct { + Type string `json:"type,omitempty"` + Value float64 `json:"value,omitempty"` + Percentage bool `json:"percentage,omitempty"` +} diff --git a/pkg/payment/response.go b/pkg/payment/response.go new file mode 100644 index 00000000..de0ecaa8 --- /dev/null +++ b/pkg/payment/response.go @@ -0,0 +1,304 @@ +package payment + +import ( + "time" +) + +// Response is the response from the Payments API. +type Response struct { + DifferentialPricingID string `json:"differential_pricing_id,omitempty"` + MoneyReleaseSchema string `json:"money_release_schema,omitempty"` + OperationType string `json:"operation_type,omitempty"` + IssuerID string `json:"issuer_id,omitempty"` + PaymentMethodID string `json:"payment_method_id,omitempty"` + PaymentTypeID string `json:"payment_type_id,omitempty"` + Status string `json:"status,omitempty"` + StatusDetail string `json:"status_detail,omitempty"` + CurrencyID string `json:"currency_id,omitempty"` + Description string `json:"description,omitempty"` + AuthorizationCode string `json:"authorization_code,omitempty"` + IntegratorID string `json:"integrator_id,omitempty"` + PlatformID string `json:"platform_id,omitempty"` + CorporationID string `json:"corporation_id,omitempty"` + NotificationURL string `json:"notification_url,omitempty"` + CallbackURL string `json:"callback_url,omitempty"` + ProcessingMode string `json:"processing_mode,omitempty"` + MerchantAccountID string `json:"merchant_account_id,omitempty"` + MerchantNumber string `json:"merchant_number,omitempty"` + CouponCode string `json:"coupon_code,omitempty"` + ExternalReference string `json:"external_reference,omitempty"` + PaymentMethodOptionID string `json:"payment_method_option_id,omitempty"` + PosID string `json:"pos_id,omitempty"` + StoreID string `json:"store_id,omitempty"` + DeductionSchema string `json:"deduction_schema,omitempty"` + CounterCurrency string `json:"counter_currency,omitempty"` + CallForAuthorizeID string `json:"call_for_authorize_id,omitempty"` + StatementDescriptor string `json:"statement_descriptor,omitempty"` + Installments int `json:"installments,omitempty"` + ID int64 `json:"id,omitempty"` + SponsorID int64 `json:"sponsor_id,omitempty"` + CollectorID int64 `json:"collector_id,omitempty"` + TransactionAmount float64 `json:"transaction_amount,omitempty"` + TransactionAmountRefunded float64 `json:"transaction_amount_refunded,omitempty"` + CouponAmount float64 `json:"coupon_amount,omitempty"` + TaxesAmount float64 `json:"taxes_amount,omitempty"` + ShippingAmount float64 `json:"shipping_amount,omitempty"` + NetAmount float64 `json:"net_amount,omitempty"` + LiveMode bool `json:"live_mode,omitempty"` + Captured bool `json:"captured,omitempty"` + BinaryMode bool `json:"binary_mode,omitempty"` + Metadata map[string]any `json:"metadata,omitempty"` + InternalMetadata map[string]any `json:"internal_metadata,omitempty"` + + DateCreated *time.Time `json:"date_created,omitempty"` + DateApproved *time.Time `json:"date_approved,omitempty"` + DateLastUpdated *time.Time `json:"date_last_updated,omitempty"` + DateOfExpiration *time.Time `json:"date_of_expiration,omitempty"` + MoneyReleaseDate *time.Time `json:"money_release_date,omitempty"` + Payer *PayerResponse `json:"payer,omitempty"` + AdditionalInfo *AdditionalInfoResponse `json:"additional_info,omitempty"` + Order *OrderResponse `json:"order,omitempty"` + TransactionDetails *TransactionDetailsResponse `json:"transaction_details,omitempty"` + Card *CardResponse `json:"card,omitempty"` + PointOfInteraction *PointOfInteractionResponse `json:"point_of_interaction,omitempty"` + PaymentMethod *PaymentMethodResponse `json:"payment_method,omitempty"` + ThreeDSInfo *ThreeDSInfoResponse `json:"three_ds_info,omitempty"` + FeeDetails []FeeDetailResponse `json:"fee_details,omitempty"` + Taxes []TaxResponse `json:"taxes,omitempty"` + Refunds []RefundResponse `json:"refunds,omitempty"` +} + +// PayerResponse represents the payer of the payment. +type PayerResponse struct { + Type string `json:"type,omitempty"` + ID string `json:"id,omitempty"` + Email string `json:"email,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + EntityType string `json:"entity_type,omitempty"` + + Identification *IdentificationResponse `json:"identification,omitempty"` +} + +// IdentificationResponse represents payer's personal identification. +type IdentificationResponse struct { + Type string `json:"type,omitempty"` + Number string `json:"number,omitempty"` +} + +// AdditionalInfoResponse represents additional information about a payment. +type AdditionalInfoResponse struct { + IPAddress string `json:"ip_address,omitempty"` + + Payer *AdditionalInfoPayerResponse `json:"payer,omitempty"` + Shipments *ShipmentsResponse `json:"shipments,omitempty"` + Items []ItemResponse `json:"items,omitempty"` +} + +// ItemResponse represents an item. +type ItemResponse struct { + ID string `json:"id,omitempty"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + PictureURL string `json:"picture_url,omitempty"` + CategoryID string `json:"category_id,omitempty"` + Quantity int `json:"quantity,omitempty"` + UnitPrice float64 `json:"unit_price,omitempty"` +} + +// AdditionalInfoPayerResponse represents payer's additional information. +type AdditionalInfoPayerResponse struct { + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + + RegistrationDate *time.Time `json:"registration_date,omitempty"` + Phone *PhoneResponse `json:"phone,omitempty"` + Address *AddressResponse `json:"address,omitempty"` +} + +// PhoneResponse represents phone information. +type PhoneResponse struct { + AreaCode string `json:"area_code,omitempty"` + Number string `json:"number,omitempty"` +} + +// AddressResponse represents address information. +type AddressResponse struct { + ZipCode string `json:"zip_code,omitempty"` + StreetName string `json:"street_name,omitempty"` + StreetNumber string `json:"street_number,omitempty"` +} + +// ShipmentsResponse represents shipment information. +type ShipmentsResponse struct { + ReceiverAddress *ReceiverAddressResponse `json:"receiver_address,omitempty"` +} + +// ReceiverAddressResponse represents the receiver's address within ShipmentsResponse. +type ReceiverAddressResponse struct { + StateName string `json:"state_name,omitempty"` + CityName string `json:"city_name,omitempty"` + Floor string `json:"floor,omitempty"` + Apartment string `json:"apartment,omitempty"` + + Address *AddressResponse `json:"address,omitempty"` +} + +// OrderResponse represents order information. +type OrderResponse struct { + ID int `json:"id,omitempty"` + Type string `json:"type,omitempty"` +} + +// TransactionDetailsResponse represents transaction details. +type TransactionDetailsResponse struct { + FinancialInstitution string `json:"financial_institution,omitempty"` + ExternalResourceURL string `json:"external_resource_url,omitempty"` + PaymentMethodReferenceID string `json:"payment_method_reference_id,omitempty"` + AcquirerReference string `json:"acquirer_reference,omitempty"` + NetReceivedAmount float64 `json:"net_received_amount,omitempty"` + TotalPaidAmount float64 `json:"total_paid_amount,omitempty"` + InstallmentAmount float64 `json:"installment_amount,omitempty"` + OverpaidAmount float64 `json:"overpaid_amount,omitempty"` +} + +// CardResponse represents card information. +type CardResponse struct { + ID string `json:"id,omitempty"` + LastFourDigits string `json:"last_four_digits,omitempty"` + FirstSixDigits string `json:"first_six_digits,omitempty"` + ExpirationYear int `json:"expiration_year,omitempty"` + ExpirationMonth int `json:"expiration_month,omitempty"` + + DateCreated *time.Time `json:"date_created,omitempty"` + DateLastUpdated *time.Time `json:"date_last_updated,omitempty"` + Cardholder *CardholderResponse `json:"cardholder,omitempty"` +} + +// CardholderResponse represents cardholder information. +type CardholderResponse struct { + Name string `json:"name,omitempty"` + + Identification *IdentificationResponse `json:"identification,omitempty"` +} + +// PointOfInteractionResponse represents point of interaction information. +type PointOfInteractionResponse struct { + Type string `json:"type,omitempty"` + SubType string `json:"sub_type,omitempty"` + LinkedTo string `json:"linked_to,omitempty"` + + ApplicationData *ApplicationDataResponse `json:"application_data,omitempty"` + TransactionData *TransactionDataResponse `json:"transaction_data,omitempty"` +} + +// ApplicationDataResponse represents application data within PointOfInteractionResponse. +type ApplicationDataResponse struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` +} + +// TransactionDataResponse represents transaction data within PointOfInteractionResponse. +type TransactionDataResponse struct { + QRCode string `json:"qr_code,omitempty"` + QRCodeBase64 string `json:"qr_code_base64,omitempty"` + TransactionID string `json:"transaction_id,omitempty"` + TicketURL string `json:"ticket_url,omitempty"` + BankTransferID int64 `json:"bank_transfer_id,omitempty"` + FinancialInstitution int64 `json:"financial_institution,omitempty"` + + BankInfo *BankInfoResponse `json:"bank_info,omitempty"` +} + +// BankInfoResponse represents bank information. +type BankInfoResponse struct { + IsSameBankAccountOwner string `json:"is_same_bank_account_owner,omitempty"` + + Payer *BankInfoPayerResponse `json:"payer,omitempty"` + Collector *BankInfoCollectorResponse `json:"collector,omitempty"` +} + +// BankInfoPayerResponse represents payer information within BankInfoResponse. +type BankInfoPayerResponse struct { + Email string `json:"email,omitempty"` + LongName string `json:"long_name,omitempty"` + AccountID int64 `json:"account_id,omitempty"` +} + +// BankInfoCollectorResponse represents collector information within BankInfoResponse. +type BankInfoCollectorResponse struct { + LongName string `json:"long_name,omitempty"` + AccountID int64 `json:"account_id,omitempty"` +} + +// PaymentMethodResponse represents payment method information. +type PaymentMethodResponse struct { + Data *DataResponse `json:"data,omitempty"` +} + +// DataResponse represents data within PaymentMethodResponse. +type DataResponse struct { + Rules *RulesResponse `json:"rules,omitempty"` +} + +// RulesResponse represents payment rules. +type RulesResponse struct { + Fine *FeeResponse `json:"fine,omitempty"` + Interest *FeeResponse `json:"interest,omitempty"` + Discounts []DiscountResponse `json:"discounts,omitempty"` +} + +// DiscountResponse represents payment discount information. +type DiscountResponse struct { + Type string `json:"type,omitempty"` + Value float64 `json:"value,omitempty"` + + LimitDate *time.Time `json:"limit_date,omitempty"` +} + +// FeeResponse represents payment fee information. +type FeeResponse struct { + Type string `json:"type,omitempty"` + Value float64 `json:"value,omitempty"` +} + +// ThreeDSInfoResponse represents 3DS (Three-Domain Secure) information. +type ThreeDSInfoResponse struct { + ExternalResourceURL string `json:"external_resource_url,omitempty"` + Creq string `json:"creq,omitempty"` +} + +// FeeDetailResponse represents payment fee detail information. +type FeeDetailResponse struct { + Type string `json:"type,omitempty"` + FeePayer string `json:"fee_payer,omitempty"` + Amount float64 `json:"amount,omitempty"` +} + +// TaxResponse represents tax information. +type TaxResponse struct { + Type string `json:"type,omitempty"` + Value float64 `json:"value,omitempty"` +} + +// RefundResponse represents refund information. +type RefundResponse struct { + Status string `json:"status,omitempty"` + RefundMode string `json:"refund_mode,omitempty"` + Reason string `json:"reason,omitempty"` + UniqueSequenceNumber string `json:"unique_sequence_number,omitempty"` + ID int64 `json:"id,omitempty"` + PaymentID int64 `json:"payment_id,omitempty"` + Amount float64 `json:"amount,omitempty"` + AdjustmentAmount float64 `json:"adjustment_amount,omitempty"` + + DateCreated *time.Time `json:"date_created,omitempty"` + Source *SourceResponse `json:"source,omitempty"` +} + +// SourceResponse represents source information. +type SourceResponse struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` +} diff --git a/pkg/payment/search_request.go b/pkg/payment/search_request.go new file mode 100644 index 00000000..b869d9ba --- /dev/null +++ b/pkg/payment/search_request.go @@ -0,0 +1,40 @@ +package payment + +// Filters is the filters to search for payments. +type Filters struct { + // Sort is a field used to sort a list of payments. + // The sort can be done by the following attributes: + // - "date_approved" + // - "date_created" + // - "date_last_updated" + // - "id" + // - "money_release_date" + Sort string + + // Criteria is a field used to define the order of the result list. + // Can be "asc" or "desc". + Criteria string + + // ExternalReference is an external reference of the payment. + // It can be, for example, a hashcode from the Central Bank, working as an identifier of the transaction origin. + ExternalReference string + + // Range is a field used to define the range of the search. + // The Range can be related to the following attributes: + // - "date_created" + // - "date_last_updated" + // - "date_approved" + // - "money_release_date" + // If not informed, it uses "date_created" by default. + Range string + + // BeginDate is a field used to define the start of the search interval. + // Its format can be a relative date - "NOW-XDAYS", "NOW-XMONTHS" - or an absolute date - ISO8601. + // If not informed, it uses "NOW-3MONTHS" by default. + BeginDate string + + // EndDate is a field used to define the end of the search interval. + // Its format can be a relative date - "NOW-XDAYS", "NOW-XMONTHS" - or an absolute date - ISO8601. + // If not informed, it uses "NOW" by default. + EndDate string +} diff --git a/pkg/payment/search_response.go b/pkg/payment/search_response.go new file mode 100644 index 00000000..829d4610 --- /dev/null +++ b/pkg/payment/search_response.go @@ -0,0 +1,14 @@ +package payment + +// SearchResponse represents the response from the search endpoint. +type SearchResponse struct { + Results []Response `json:"results"` + Paging PagingResponse `json:"paging"` +} + +// PagingResponse represents the paging information within SearchResponse. +type PagingResponse struct { + Total int64 `json:"total"` + Limit int64 `json:"limit"` + Offset int64 `json:"offset"` +} diff --git a/pkg/payment/update_request.go b/pkg/payment/update_request.go new file mode 100644 index 00000000..3c6cdf8a --- /dev/null +++ b/pkg/payment/update_request.go @@ -0,0 +1,12 @@ +package payment + +// CancelRequest represents a payment cancellation request. +type CancelRequest struct { + Status string `json:"status,omitempty"` +} + +// CaptureRequest represents a payment capture request. +type CaptureRequest struct { + TransactionAmount float64 `json:"transaction_amount,omitempty"` + Capture bool `json:"capture"` +} diff --git a/pkg/paymentmethod/payment_method.go b/pkg/paymentmethod/client.go similarity index 81% rename from pkg/paymentmethod/payment_method.go rename to pkg/paymentmethod/client.go index 5cc52d52..a0e46abd 100644 --- a/pkg/paymentmethod/payment_method.go +++ b/pkg/paymentmethod/client.go @@ -33,5 +33,14 @@ func (c *client) List(ctx context.Context) ([]Response, error) { return nil, err } +<<<<<<< HEAD return *res, nil +======= + var formatted []Response + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +>>>>>>> 8b22954 (Update client) } From 958cb52882de2557553effda90f86885577dc16e Mon Sep 17 00:00:00 2001 From: gabs Date: Thu, 25 Jan 2024 15:13:56 -0300 Subject: [PATCH 07/55] Improve structs --- pkg/payment/request.go | 51 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/pkg/payment/request.go b/pkg/payment/request.go index 0b44a978..1da2ae03 100644 --- a/pkg/payment/request.go +++ b/pkg/payment/request.go @@ -60,20 +60,20 @@ type AdditionalInfoPayerRequest struct { IsPrimeUser bool `json:"is_prime_user,omitempty"` IsFirstPurchaseOnline bool `json:"is_first_purchase_online,omitempty"` - RegistrationDate *time.Time `json:"registration_date,omitempty"` - LastPurchase *time.Time `json:"last_purchase,omitempty"` - Phone *PhoneRequest `json:"phone,omitempty"` - Address *AddressRequest `json:"address,omitempty"` + RegistrationDate *time.Time `json:"registration_date,omitempty"` + LastPurchase *time.Time `json:"last_purchase,omitempty"` + Phone *AdditionalInfoPayerPhoneRequest `json:"phone,omitempty"` + Address *AdditionalInfoPayerAddressRequest `json:"address,omitempty"` } -// PhoneRequest represents phone request within AdditionalInfoPayerRequest. -type PhoneRequest struct { +// AdditionalInfoPayerPhoneRequest represents phone request within AdditionalInfoPayerRequest. +type AdditionalInfoPayerPhoneRequest struct { AreaCode string `json:"area_code,omitempty"` Number string `json:"number,omitempty"` } -// AddressRequest represents address request within AdditionalInfoPayerRequest. -type AddressRequest struct { +// AdditionalInfoPayerAddressRequest represents address request within AdditionalInfoPayerRequest. +type AdditionalInfoPayerAddressRequest struct { ZipCode string `json:"zip_code,omitempty"` StreetName string `json:"street_name,omitempty"` StreetNumber string `json:"street_number,omitempty"` @@ -113,7 +113,7 @@ type ItemRequest struct { Description string `json:"description,omitempty"` PictureURL string `json:"picture_url,omitempty"` CategoryID string `json:"category_id,omitempty"` - Quantity int `json:"quantity,omitempty"` + Quantity int64 `json:"quantity,omitempty"` UnitPrice float64 `json:"unit_price,omitempty"` Warranty bool `json:"warranty,omitempty"` @@ -174,6 +174,7 @@ type PayerRequest struct { Identification *IdentificationRequest `json:"identification,omitempty"` Address *PayerAddressRequest `json:"address,omitempty"` + Phone *PayerPhoneRequest `json:"phone,omitempty"` } // PayerAddressRequest represents payer address request within PayerRequest. @@ -186,6 +187,12 @@ type PayerAddressRequest struct { StreetNumber string `json:"street_number,omitempty"` } +// PayerPhoneRequest represents payer phone request within PayerRequest. +type PayerPhoneRequest struct { + AreaCode string `json:"area_code,omitempty"` + Number string `json:"number,omitempty"` +} + // TransactionDetailsRequest represents transaction details request within Request. type TransactionDetailsRequest struct { FinancialInstitution string `json:"financial_institution,omitempty"` @@ -195,6 +202,32 @@ type TransactionDetailsRequest struct { type PointOfInteractionRequest struct { LinkedTo string `json:"linked_to,omitempty"` Type string `json:"type,omitempty"` + + TransactionData *TransactionDataRequest `json:"transaction_data,omitempty"` +} + +type TransactionDataRequest struct { + SubscriptionID string `json:"subscription_id,omitempty"` + BillingDate string `json:"billing_date,omitempty"` + FirstTimeUse bool `json:"first_time_use,omitempty"` + + SubscriptionSequence *SubscriptionSequenceRequest `json:"subscription_sequence,omitempty"` + InvoicePeriod *InvoicePeriodRequest `json:"invoice_period,omitempty"` + PaymentReference *PaymentReferenceRequest `json:"payment_reference,omitempty"` +} + +type SubscriptionSequenceRequest struct { + Number int64 `json:"number,omitempty"` + Total int64 `json:"total,omitempty"` +} + +type InvoicePeriodRequest struct { + Type string `json:"type,omitempty"` + Period int64 `json:"period,omitempty"` +} + +type PaymentReferenceRequest struct { + ID string `json:"id,omitempty"` } // PaymentMethodRequest represents payment method request within Request. From 144158d3ecc0cac6f5a539a745f6ca35a01fa237 Mon Sep 17 00:00:00 2001 From: gabs Date: Thu, 25 Jan 2024 15:54:37 -0300 Subject: [PATCH 08/55] Add more fields to response struct --- pkg/payment/response.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/pkg/payment/response.go b/pkg/payment/response.go index de0ecaa8..465a6f2e 100644 --- a/pkg/payment/response.go +++ b/pkg/payment/response.go @@ -156,6 +156,7 @@ type TransactionDetailsResponse struct { ExternalResourceURL string `json:"external_resource_url,omitempty"` PaymentMethodReferenceID string `json:"payment_method_reference_id,omitempty"` AcquirerReference string `json:"acquirer_reference,omitempty"` + TransactionID string `json:"transaction_id,omitempty"` NetReceivedAmount float64 `json:"net_received_amount,omitempty"` TotalPaidAmount float64 `json:"total_paid_amount,omitempty"` InstallmentAmount float64 `json:"installment_amount,omitempty"` @@ -204,10 +205,16 @@ type TransactionDataResponse struct { QRCodeBase64 string `json:"qr_code_base64,omitempty"` TransactionID string `json:"transaction_id,omitempty"` TicketURL string `json:"ticket_url,omitempty"` + SubscriptionID string `json:"subscription_id,omitempty"` + BillingDate string `json:"billing_date,omitempty"` BankTransferID int64 `json:"bank_transfer_id,omitempty"` FinancialInstitution int64 `json:"financial_institution,omitempty"` + FirstTimeUse bool `json:"first_time_use,omitempty"` - BankInfo *BankInfoResponse `json:"bank_info,omitempty"` + BankInfo *BankInfoResponse `json:"bank_info,omitempty"` + SubscriptionSequence *SubscriptionSequenceResponse `json:"subscription_sequence,omitempty"` + InvoicePeriod *InvoicePeriodResponse `json:"invoice_period,omitempty"` + PaymentReference *PaymentReferenceResponse `json:"payment_reference,omitempty"` } // BankInfoResponse represents bank information. @@ -218,6 +225,23 @@ type BankInfoResponse struct { Collector *BankInfoCollectorResponse `json:"collector,omitempty"` } +// SubscriptionSequenceResponse represents subscription sequence. +type SubscriptionSequenceResponse struct { + Number int64 `json:"number,omitempty"` + Total int64 `json:"total,omitempty"` +} + +// InvoicePeriodResponse represents invoice period. +type InvoicePeriodResponse struct { + Type string `json:"type,omitempty"` + Period int64 `json:"period,omitempty"` +} + +// PaymentReferenceResponse represents payment reference. +type PaymentReferenceResponse struct { + ID string `json:"id,omitempty"` +} + // BankInfoPayerResponse represents payer information within BankInfoResponse. type BankInfoPayerResponse struct { Email string `json:"email,omitempty"` From fa60eb865aad7c6e68b7f4e42f8cae42ed734005 Mon Sep 17 00:00:00 2001 From: gabs Date: Fri, 26 Jan 2024 09:59:12 -0300 Subject: [PATCH 09/55] Adjust search --- pkg/payment/payment.go | 16 ++------ pkg/payment/search_request.go | 77 ++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 49 deletions(-) diff --git a/pkg/payment/payment.go b/pkg/payment/payment.go index 96f780f7..6d5b4b98 100644 --- a/pkg/payment/payment.go +++ b/pkg/payment/payment.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "net/http" - "net/url" "strconv" "strings" @@ -31,7 +30,7 @@ type Client interface { // Search searches for payments. // It is a get request to the endpoint: https://api.mercadopago.com/v1/payments/search // Reference: https://www.mercadopago.com.br/developers/pt/reference/payments/_payments_search/get/ - Search(ctx context.Context, f Filters) (*SearchResponse, error) + Search(ctx context.Context, dto SearchRequest) (*SearchResponse, error) // Get gets a payment by its ID. // It is a get request to the endpoint: https://api.mercadopago.com/v1/payments/{id} @@ -91,16 +90,9 @@ func (c *client) Create(ctx context.Context, dto Request) (*Response, error) { return formatted, nil } -func (c *client) Search(ctx context.Context, f Filters) (*SearchResponse, error) { - params := url.Values{} - params.Add("sort", f.Sort) - params.Add("criteria", f.Criteria) - params.Add("external_reference", f.ExternalReference) - params.Add("range", f.Range) - params.Add("begin_date", f.BeginDate) - params.Add("end_date", f.EndDate) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL+"?"+params.Encode(), nil) +func (c *client) Search(ctx context.Context, dto SearchRequest) (*SearchResponse, error) { + params := dto.Parameters() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL+"?"+params, nil) if err != nil { return nil, fmt.Errorf("error creating request: %w", err) } diff --git a/pkg/payment/search_request.go b/pkg/payment/search_request.go index b869d9ba..95d1cf04 100644 --- a/pkg/payment/search_request.go +++ b/pkg/payment/search_request.go @@ -1,40 +1,43 @@ package payment -// Filters is the filters to search for payments. -type Filters struct { - // Sort is a field used to sort a list of payments. - // The sort can be done by the following attributes: - // - "date_approved" - // - "date_created" - // - "date_last_updated" - // - "id" - // - "money_release_date" - Sort string - - // Criteria is a field used to define the order of the result list. - // Can be "asc" or "desc". - Criteria string - - // ExternalReference is an external reference of the payment. - // It can be, for example, a hashcode from the Central Bank, working as an identifier of the transaction origin. - ExternalReference string - - // Range is a field used to define the range of the search. - // The Range can be related to the following attributes: - // - "date_created" - // - "date_last_updated" - // - "date_approved" - // - "money_release_date" - // If not informed, it uses "date_created" by default. - Range string - - // BeginDate is a field used to define the start of the search interval. - // Its format can be a relative date - "NOW-XDAYS", "NOW-XMONTHS" - or an absolute date - ISO8601. - // If not informed, it uses "NOW-3MONTHS" by default. - BeginDate string - - // EndDate is a field used to define the end of the search interval. - // Its format can be a relative date - "NOW-XDAYS", "NOW-XMONTHS" - or an absolute date - ISO8601. - // If not informed, it uses "NOW" by default. - EndDate string +import ( + "net/url" + "strings" +) + +// SearchRequest is the request to search services. +// Filters field can receive a lot of paramaters. For details, see: +// https://www.mercadopago.com.br/developers/pt/reference/payments/_payments_search/get. +type SearchRequest struct { + Limit string + Offset string + + Filters map[string]string +} + +// Parameters transforms SearchRequest into url params. +func (s SearchRequest) Parameters() string { + params := url.Values{} + + var limitKey, offsetKey bool + for k, v := range s.Filters { + params.Add(k, v) + + if strings.EqualFold(k, "limit") { + limitKey = true + continue + } + if strings.EqualFold(k, "offset") { + offsetKey = true + } + } + + if !limitKey { + params.Add("limit", s.Limit) + } + if !offsetKey { + params.Add("offset", s.Offset) + } + + return params.Encode() } From 5bce1d780ca181a0661cd3260b5bb42662b32bae Mon Sep 17 00:00:00 2001 From: gabs Date: Fri, 26 Jan 2024 14:00:48 -0300 Subject: [PATCH 10/55] Add some tests --- pkg/payment/payment_test.go | 194 +++++++++++++++++++ pkg/payment/response.go | 5 + resources/mocks/payment/create_response.json | 143 ++++++++++++++ 3 files changed, 342 insertions(+) create mode 100644 pkg/payment/payment_test.go create mode 100644 resources/mocks/payment/create_response.json diff --git a/pkg/payment/payment_test.go b/pkg/payment/payment_test.go new file mode 100644 index 00000000..ac042726 --- /dev/null +++ b/pkg/payment/payment_test.go @@ -0,0 +1,194 @@ +package payment + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "reflect" + "strings" + "testing" + "time" + + "github.com/mercadopago/sdk-go/pkg/credential" + "github.com/mercadopago/sdk-go/pkg/internal/httpclient" + "github.com/mercadopago/sdk-go/pkg/option" +) + +var ( + cred, _ = credential.New("") + + createResponseJSON, _ = os.Open("../../resources/mocks/payment/create_response.json") + createResponse, _ = io.ReadAll(createResponseJSON) +) + +func TestCreate(t *testing.T) { + type fields struct { + credential *credential.Credential + config *option.ClientOptions + } + type args struct { + ctx context.Context + dto Request + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr string + }{ + { + name: "should_fail_to_marshal_dto", + fields: fields{ + credential: cred, + config: nil, + }, + args: args{ + ctx: nil, + dto: Request{ + Metadata: map[string]any{ + "fail": make(chan int), + }, + }, + }, + want: nil, + wantErr: "error marshaling request body: json: unsupported type: chan int", + }, + { + name: "should_fail_to_create_request", + fields: fields{ + credential: cred, + config: nil, + }, + args: args{ + ctx: nil, + }, + want: nil, + wantErr: "error creating request: net/http: nil Context", + }, + { + name: "should_fail_to_send_request", + fields: fields{ + credential: cred, + config: option.ApplyClientOptions( + option.WithCustomClient( + &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "transport level error: some error", + }, + { + name: "should_fail_to_unmarshaling_response", + fields: fields{ + credential: cred, + config: option.ApplyClientOptions( + option.WithCustomClient( + &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader("invalid json") + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + }, + { + name: "should_return_formatted_response", + fields: fields{ + credential: cred, + config: option.ApplyClientOptions( + option.WithCustomClient( + &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(createResponse)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + }, + want: &Response{ + ID: 1316782634, + DateCreated: func() *time.Time { t := time.Date(2024, 1, 26, 10, 1, 4, 655, time.UTC); return &t }(), + DateLastUpdated: func() *time.Time { t := time.Date(2024, 1, 26, 10, 1, 4, 655, time.UTC); return &t }(), + DateOfExpiration: nil, + MoneyReleaseDate: nil, + MoneyReleaseStatus: "released", + OperationType: "regular_payment", + IssuerID: "162", + PaymentMethodID: "master", + PaymentTypeID: "credit_card", + PaymentMethod: &PaymentMethodResponse{ + ID: "master", + Type: "credit_card", + IssuerID: "162", + }, + // Status: "pending", + // Status_detail: "pending_challenge", + // CurrencyID: "MXN", + // Description: nil, + // LiveMode: false, + // SponsorID: nil, + // authorization_code nil, + // money_release_schema nil, + // taxes_amount 0, + // counter_currency nil, + // brand_id nil, + // shipping_amount 0, + // build_version "54.20.12-hotfix-9", + // pos_id nil, + // store_id nil, + // integrator_id nil, + // platform_id nil, + // corporation_id nil, + }, + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + credential: tt.fields.credential, + config: tt.fields.config, + } + got, err := c.Create(tt.args.ctx, tt.args.dto) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("client.Create() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.Create() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/payment/response.go b/pkg/payment/response.go index 465a6f2e..61c1dfdd 100644 --- a/pkg/payment/response.go +++ b/pkg/payment/response.go @@ -34,6 +34,7 @@ type Response struct { CounterCurrency string `json:"counter_currency,omitempty"` CallForAuthorizeID string `json:"call_for_authorize_id,omitempty"` StatementDescriptor string `json:"statement_descriptor,omitempty"` + MoneyReleaseStatus string `json:"money_release_status,omitempty"` Installments int `json:"installments,omitempty"` ID int64 `json:"id,omitempty"` SponsorID int64 `json:"sponsor_id,omitempty"` @@ -257,6 +258,10 @@ type BankInfoCollectorResponse struct { // PaymentMethodResponse represents payment method information. type PaymentMethodResponse struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + IssuerID string `json:"issuer_id,omitempty"` + Data *DataResponse `json:"data,omitempty"` } diff --git a/resources/mocks/payment/create_response.json b/resources/mocks/payment/create_response.json new file mode 100644 index 00000000..5023cffe --- /dev/null +++ b/resources/mocks/payment/create_response.json @@ -0,0 +1,143 @@ +{ + "id": 1316782634, + "date_created": "2024-01-26T10:01:04.655-04:00", + "date_approved": null, + "date_last_updated": "2024-01-26T10:01:04.655-04:00", + "date_of_expiration": null, + "money_release_date": null, + "money_release_status": "released", + "operation_type": "regular_payment", + "issuer_id": "162", + "payment_method_id": "master", + "payment_type_id": "credit_card", + "payment_method": { + "id": "master", + "type": "credit_card", + "issuer_id": "162" + }, + "status": "pending", + "status_detail": "pending_challenge", + "currency_id": "MXN", + "description": null, + "live_mode": false, + "sponsor_id": null, + "authorization_code": null, + "money_release_schema": null, + "taxes_amount": 0, + "counter_currency": null, + "brand_id": null, + "shipping_amount": 0, + "build_version": "54.20.12-hotfix-9", + "pos_id": null, + "store_id": null, + "integrator_id": null, + "platform_id": null, + "corporation_id": null, + "payer": { + "identification": { + "number": "32659430", + "type": "DNI" + }, + "entity_type": null, + "phone": { + "number": null, + "extension": null, + "area_code": null + }, + "last_name": null, + "id": "1655897964", + "type": null, + "first_name": null, + "email": "test_user_80507629@testuser.com" + }, + "collector_id": 1227208776, + "marketplace_owner": null, + "metadata": {}, + "additional_info": { + "available_balance": null, + "nsu_processadora": null, + "authentication_code": null + }, + "order": {}, + "external_reference": null, + "transaction_amount": 500, + "transaction_amount_refunded": 0, + "coupon_amount": 0, + "differential_pricing_id": null, + "financing_group": null, + "deduction_schema": null, + "installments": 1, + "transaction_details": { + "payment_method_reference_id": null, + "acquirer_reference": null, + "net_received_amount": 0, + "total_paid_amount": 500, + "overpaid_amount": 0, + "external_resource_url": null, + "installment_amount": 500, + "financial_institution": null, + "payable_deferral_period": null + }, + "fee_details": [], + "charges_details": [ + { + "id": "1316782634-001", + "name": "mercadopago_fee", + "type": "fee", + "accounts": { + "from": "collector", + "to": "mp" + }, + "client_id": 0, + "date_created": "2024-01-26T10:01:04.658-04:00", + "last_updated": "2024-01-26T10:01:04.658-04:00", + "amounts": { + "original": 24.89, + "refunded": 0 + }, + "metadata": {}, + "reserve_id": null, + "refund_charges": [] + } + ], + "captured": true, + "binary_mode": false, + "call_for_authorize_id": null, + "statement_descriptor": null, + "card": { + "id": null, + "first_six_digits": "548392", + "last_four_digits": "4623", + "expiration_month": 11, + "expiration_year": 2025, + "date_created": "2024-01-26T10:01:04.000-04:00", + "date_last_updated": "2024-01-26T10:01:04.000-04:00", + "cardholder": { + "name": "GABS", + "identification": { + "number": null, + "type": null + } + } + }, + "notification_url": null, + "refunds": [], + "processing_mode": "aggregator", + "merchant_account_id": null, + "merchant_number": null, + "acquirer_reconciliation": [], + "point_of_interaction": { + "type": "UNSPECIFIED", + "business_info": { + "unit": "online_payments", + "sub_unit": "default", + "branch": null + } + }, + "accounts_info": null, + "three_ds_info": { + "external_resource_url": "https://api.mercadopago.com/cardholder_authenticator/v2/prod/browser-challenges", + "creq": "" + }, + "tags": null +} \ No newline at end of file From 16c25a2dcb34bffdd946d68693e6157da3e15ec9 Mon Sep 17 00:00:00 2001 From: gabs Date: Fri, 26 Jan 2024 19:54:12 -0300 Subject: [PATCH 11/55] Refactor tests --- pkg/payment/payment.go | 25 ++++------ pkg/payment/payment_test.go | 78 +++++++++++++------------------- pkg/paymentmethod/client_test.go | 2 +- 3 files changed, 42 insertions(+), 63 deletions(-) diff --git a/pkg/payment/payment.go b/pkg/payment/payment.go index 6d5b4b98..0de4e2db 100644 --- a/pkg/payment/payment.go +++ b/pkg/payment/payment.go @@ -8,9 +8,8 @@ import ( "strconv" "strings" - "github.com/mercadopago/sdk-go/pkg/credential" + "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/internal/httpclient" - "github.com/mercadopago/sdk-go/pkg/option" ) const ( @@ -52,17 +51,13 @@ type Client interface { // client is the implementation of Client. type client struct { - credential *credential.Credential - config *option.ClientOptions + config *config.Config } // NewClient returns a new Payments API Client. -func NewClient(cdt *credential.Credential, opts ...option.ClientOption) Client { - c := option.ApplyClientOptions(opts...) - +func NewClient(c *config.Config) Client { return &client{ - credential: cdt, - config: c, + config: c, } } @@ -77,7 +72,7 @@ func (c *client) Create(ctx context.Context, dto Request) (*Response, error) { return nil, fmt.Errorf("error creating request: %w", err) } - res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + res, err := httpclient.Send(ctx, c.config, req) if err != nil { return nil, err } @@ -97,7 +92,7 @@ func (c *client) Search(ctx context.Context, dto SearchRequest) (*SearchResponse return nil, fmt.Errorf("error creating request: %w", err) } - res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + res, err := httpclient.Send(ctx, c.config, req) if err != nil { return nil, err } @@ -118,7 +113,7 @@ func (c *client) Get(ctx context.Context, id int64) (*Response, error) { return nil, fmt.Errorf("error creating request: %w", err) } - res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + res, err := httpclient.Send(ctx, c.config, req) if err != nil { return nil, err } @@ -144,7 +139,7 @@ func (c *client) Cancel(ctx context.Context, id int64) (*Response, error) { return nil, fmt.Errorf("error creating request: %w", err) } - res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + res, err := httpclient.Send(ctx, c.config, req) if err != nil { return nil, err } @@ -170,7 +165,7 @@ func (c *client) Capture(ctx context.Context, id int64) (*Response, error) { return nil, fmt.Errorf("error creating request: %w", err) } - res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + res, err := httpclient.Send(ctx, c.config, req) if err != nil { return nil, err } @@ -196,7 +191,7 @@ func (c *client) CaptureAmount(ctx context.Context, id int64, amount float64) (* return nil, fmt.Errorf("error creating request: %w", err) } - res, err := httpclient.Send(ctx, c.credential, c.config.Requester, req) + res, err := httpclient.Send(ctx, c.config, req) if err != nil { return nil, err } diff --git a/pkg/payment/payment_test.go b/pkg/payment/payment_test.go index ac042726..2255085e 100644 --- a/pkg/payment/payment_test.go +++ b/pkg/payment/payment_test.go @@ -11,22 +11,18 @@ import ( "testing" "time" - "github.com/mercadopago/sdk-go/pkg/credential" + "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/internal/httpclient" - "github.com/mercadopago/sdk-go/pkg/option" ) var ( - cred, _ = credential.New("") - createResponseJSON, _ = os.Open("../../resources/mocks/payment/create_response.json") createResponse, _ = io.ReadAll(createResponseJSON) ) func TestCreate(t *testing.T) { type fields struct { - credential *credential.Credential - config *option.ClientOptions + config *config.Config } type args struct { ctx context.Context @@ -42,8 +38,7 @@ func TestCreate(t *testing.T) { { name: "should_fail_to_marshal_dto", fields: fields{ - credential: cred, - config: nil, + config: nil, }, args: args{ ctx: nil, @@ -59,8 +54,7 @@ func TestCreate(t *testing.T) { { name: "should_fail_to_create_request", fields: fields{ - credential: cred, - config: nil, + config: nil, }, args: args{ ctx: nil, @@ -71,16 +65,13 @@ func TestCreate(t *testing.T) { { name: "should_fail_to_send_request", fields: fields{ - credential: cred, - config: option.ApplyClientOptions( - option.WithCustomClient( - &httpclient.Mock{ - DoMock: func(req *http.Request) (*http.Response, error) { - return nil, fmt.Errorf("some error") - }, + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") }, - ), - ), + }, + }, }, args: args{ ctx: context.Background(), @@ -91,20 +82,17 @@ func TestCreate(t *testing.T) { { name: "should_fail_to_unmarshaling_response", fields: fields{ - credential: cred, - config: option.ApplyClientOptions( - option.WithCustomClient( - &httpclient.Mock{ - DoMock: func(req *http.Request) (*http.Response, error) { - stringReader := strings.NewReader("invalid json") - stringReadCloser := io.NopCloser(stringReader) - return &http.Response{ - Body: stringReadCloser, - }, nil - }, + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader("invalid json") + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil }, - ), - ), + }, + }, }, args: args{ ctx: context.Background(), @@ -115,20 +103,17 @@ func TestCreate(t *testing.T) { { name: "should_return_formatted_response", fields: fields{ - credential: cred, - config: option.ApplyClientOptions( - option.WithCustomClient( - &httpclient.Mock{ - DoMock: func(req *http.Request) (*http.Response, error) { - stringReader := strings.NewReader(string(createResponse)) - stringReadCloser := io.NopCloser(stringReader) - return &http.Response{ - Body: stringReadCloser, - }, nil - }, + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(createResponse)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil }, - ), - ), + }, + }, }, args: args{ ctx: context.Background(), @@ -174,8 +159,7 @@ func TestCreate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &client{ - credential: tt.fields.credential, - config: tt.fields.config, + config: tt.fields.config, } got, err := c.Create(tt.args.ctx, tt.args.dto) gotErr := "" diff --git a/pkg/paymentmethod/client_test.go b/pkg/paymentmethod/client_test.go index 1729ffca..cd11e2eb 100644 --- a/pkg/paymentmethod/client_test.go +++ b/pkg/paymentmethod/client_test.go @@ -80,7 +80,7 @@ func TestList(t *testing.T) { ctx: context.Background(), }, want: nil, - wantErr: "invalid character 'i' looking for beginning of value", + wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", }, { name: "should_return_formatted_response", From 70554e3e4f9bfbb54cf57805f56896f6d8587502 Mon Sep 17 00:00:00 2001 From: gabs Date: Fri, 26 Jan 2024 20:42:18 -0300 Subject: [PATCH 12/55] Add payment examples --- examples/apis/payment/cancel/main.go | 27 +++++++++++ examples/apis/payment/capture/main.go | 47 ++++++++++++++++++++ examples/apis/payment/capture_amount/main.go | 47 ++++++++++++++++++++ examples/apis/payment/create/main.go | 38 ++++++++++++++++ examples/apis/payment/get/main.go | 27 +++++++++++ examples/apis/payment/search/main.go | 33 ++++++++++++++ pkg/payment/search_request.go | 12 ++++- 7 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 examples/apis/payment/cancel/main.go create mode 100644 examples/apis/payment/capture/main.go create mode 100644 examples/apis/payment/capture_amount/main.go create mode 100644 examples/apis/payment/create/main.go create mode 100644 examples/apis/payment/get/main.go create mode 100644 examples/apis/payment/search/main.go diff --git a/examples/apis/payment/cancel/main.go b/examples/apis/payment/cancel/main.go new file mode 100644 index 00000000..c08dd7ea --- /dev/null +++ b/examples/apis/payment/cancel/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/payment" +) + +func main() { + at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" + c, err := config.New(at) + if err != nil { + fmt.Println(err) + return + } + + client := payment.NewClient(c) + result, err := client.Cancel(context.Background(), 123) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(result) +} diff --git a/examples/apis/payment/capture/main.go b/examples/apis/payment/capture/main.go new file mode 100644 index 00000000..a7149447 --- /dev/null +++ b/examples/apis/payment/capture/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/payment" +) + +func main() { + at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" + c, err := config.New(at) + if err != nil { + fmt.Println(err) + return + } + + // Create payment. + dto := payment.Request{ + TransactionAmount: 105.1, + PaymentMethodID: "visa", + Payer: &payment.PayerRequest{ + Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), + }, + Token: "cdec5028665c41976be212a7981437d6", + Installments: 1, + Capture: false, + } + + client := payment.NewClient(c) + result, err := client.Create(context.Background(), dto) + if err != nil { + fmt.Println(err) + return + } + + // Capture. + result, err = client.Capture(context.Background(), result.ID) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(result) +} diff --git a/examples/apis/payment/capture_amount/main.go b/examples/apis/payment/capture_amount/main.go new file mode 100644 index 00000000..871b7576 --- /dev/null +++ b/examples/apis/payment/capture_amount/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/payment" +) + +func main() { + at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" + c, err := config.New(at) + if err != nil { + fmt.Println(err) + return + } + + // Create payment. + dto := payment.Request{ + TransactionAmount: 105.1, + PaymentMethodID: "visa", + Payer: &payment.PayerRequest{ + Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), + }, + Token: "cdec5028665c41976be212a7981437d6", + Installments: 1, + Capture: false, + } + + client := payment.NewClient(c) + result, err := client.Create(context.Background(), dto) + if err != nil { + fmt.Println(err) + return + } + + // Capture amount. + result, err = client.CaptureAmount(context.Background(), result.ID, 100.1) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(result) +} diff --git a/examples/apis/payment/create/main.go b/examples/apis/payment/create/main.go new file mode 100644 index 00000000..fa61afff --- /dev/null +++ b/examples/apis/payment/create/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/payment" +) + +func main() { + at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" + c, err := config.New(at) + if err != nil { + fmt.Println(err) + return + } + + dto := payment.Request{ + TransactionAmount: 105.1, + PaymentMethodID: "visa", + Payer: &payment.PayerRequest{ + Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), + }, + Token: "cdec5028665c41976be212a7981437d6", + Installments: 1, + } + + client := payment.NewClient(c) + result, err := client.Create(context.Background(), dto) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(result) +} diff --git a/examples/apis/payment/get/main.go b/examples/apis/payment/get/main.go new file mode 100644 index 00000000..d53b86ed --- /dev/null +++ b/examples/apis/payment/get/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/payment" +) + +func main() { + at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" + c, err := config.New(at) + if err != nil { + fmt.Println(err) + return + } + + client := payment.NewClient(c) + result, err := client.Get(context.Background(), 123) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(result) +} diff --git a/examples/apis/payment/search/main.go b/examples/apis/payment/search/main.go new file mode 100644 index 00000000..71f6c1a9 --- /dev/null +++ b/examples/apis/payment/search/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/payment" +) + +func main() { + at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" + c, err := config.New(at) + if err != nil { + fmt.Println(err) + return + } + + dto := payment.SearchRequest{ + Filters: map[string]string{ + "external_reference": "abc_def_ghi_123_456123", + }, + } + + client := payment.NewClient(c) + result, err := client.Search(context.Background(), dto) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(result) +} diff --git a/pkg/payment/search_request.go b/pkg/payment/search_request.go index 95d1cf04..4a054052 100644 --- a/pkg/payment/search_request.go +++ b/pkg/payment/search_request.go @@ -33,10 +33,18 @@ func (s SearchRequest) Parameters() string { } if !limitKey { - params.Add("limit", s.Limit) + limit := "30" + if s.Limit != "" { + limit = s.Limit + } + params.Add("limit", limit) } if !offsetKey { - params.Add("offset", s.Offset) + offset := "0" + if s.Limit != "" { + offset = s.Limit + } + params.Add("offset", offset) } return params.Encode() From aba10c39f9b3a5213736fad7080ec93e95d94093 Mon Sep 17 00:00:00 2001 From: gabs Date: Fri, 26 Jan 2024 21:00:07 -0300 Subject: [PATCH 13/55] Search test --- pkg/payment/payment_test.go | 165 +++++++++++++++---- resources/mocks/payment/create_response.json | 142 +--------------- resources/mocks/payment/search_response.json | 19 +++ 3 files changed, 152 insertions(+), 174 deletions(-) create mode 100644 resources/mocks/payment/search_response.json diff --git a/pkg/payment/payment_test.go b/pkg/payment/payment_test.go index 2255085e..f91f7efc 100644 --- a/pkg/payment/payment_test.go +++ b/pkg/payment/payment_test.go @@ -9,7 +9,6 @@ import ( "reflect" "strings" "testing" - "time" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/internal/httpclient" @@ -18,6 +17,9 @@ import ( var ( createResponseJSON, _ = os.Open("../../resources/mocks/payment/create_response.json") createResponse, _ = io.ReadAll(createResponseJSON) + + searchResponseJSON, _ = os.Open("../../resources/mocks/payment/search_response.json") + searchResponse, _ = io.ReadAll(searchResponseJSON) ) func TestCreate(t *testing.T) { @@ -119,39 +121,9 @@ func TestCreate(t *testing.T) { ctx: context.Background(), }, want: &Response{ - ID: 1316782634, - DateCreated: func() *time.Time { t := time.Date(2024, 1, 26, 10, 1, 4, 655, time.UTC); return &t }(), - DateLastUpdated: func() *time.Time { t := time.Date(2024, 1, 26, 10, 1, 4, 655, time.UTC); return &t }(), - DateOfExpiration: nil, - MoneyReleaseDate: nil, - MoneyReleaseStatus: "released", - OperationType: "regular_payment", - IssuerID: "162", - PaymentMethodID: "master", - PaymentTypeID: "credit_card", - PaymentMethod: &PaymentMethodResponse{ - ID: "master", - Type: "credit_card", - IssuerID: "162", - }, - // Status: "pending", - // Status_detail: "pending_challenge", - // CurrencyID: "MXN", - // Description: nil, - // LiveMode: false, - // SponsorID: nil, - // authorization_code nil, - // money_release_schema nil, - // taxes_amount 0, - // counter_currency nil, - // brand_id nil, - // shipping_amount 0, - // build_version "54.20.12-hotfix-9", - // pos_id nil, - // store_id nil, - // integrator_id nil, - // platform_id nil, - // corporation_id nil, + ID: 123, + Status: "pending", + StatusDetail: "pending_challenge", }, wantErr: "", }, @@ -176,3 +148,128 @@ func TestCreate(t *testing.T) { }) } } + +func TestSearch(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + dto SearchRequest + } + tests := []struct { + name string + fields fields + args args + want *SearchResponse + wantErr string + }{ + { + name: "should_fail_to_create_request", + fields: fields{ + config: nil, + }, + args: args{ + ctx: nil, + }, + want: nil, + wantErr: "error creating request: net/http: nil Context", + }, + { + name: "should_fail_to_send_request", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "transport level error: some error", + }, + { + name: "should_fail_to_unmarshaling_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader("invalid json") + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + }, + { + name: "should_return_formatted_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(searchResponse)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: &SearchResponse{ + Paging: PagingResponse{ + Total: 2, + Limit: 30, + Offset: 0, + }, + Results: []Response{ + { + ID: 123, + Status: "approved", + StatusDetail: "accredited", + }, + { + ID: 456, + Status: "pending", + StatusDetail: "pending_waiting_transfer", + }, + }, + }, + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + config: tt.fields.config, + } + got, err := c.Search(tt.args.ctx, tt.args.dto) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("client.Search() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.Search() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/resources/mocks/payment/create_response.json b/resources/mocks/payment/create_response.json index 5023cffe..74a04fad 100644 --- a/resources/mocks/payment/create_response.json +++ b/resources/mocks/payment/create_response.json @@ -1,143 +1,5 @@ { - "id": 1316782634, - "date_created": "2024-01-26T10:01:04.655-04:00", - "date_approved": null, - "date_last_updated": "2024-01-26T10:01:04.655-04:00", - "date_of_expiration": null, - "money_release_date": null, - "money_release_status": "released", - "operation_type": "regular_payment", - "issuer_id": "162", - "payment_method_id": "master", - "payment_type_id": "credit_card", - "payment_method": { - "id": "master", - "type": "credit_card", - "issuer_id": "162" - }, + "id": 123, "status": "pending", - "status_detail": "pending_challenge", - "currency_id": "MXN", - "description": null, - "live_mode": false, - "sponsor_id": null, - "authorization_code": null, - "money_release_schema": null, - "taxes_amount": 0, - "counter_currency": null, - "brand_id": null, - "shipping_amount": 0, - "build_version": "54.20.12-hotfix-9", - "pos_id": null, - "store_id": null, - "integrator_id": null, - "platform_id": null, - "corporation_id": null, - "payer": { - "identification": { - "number": "32659430", - "type": "DNI" - }, - "entity_type": null, - "phone": { - "number": null, - "extension": null, - "area_code": null - }, - "last_name": null, - "id": "1655897964", - "type": null, - "first_name": null, - "email": "test_user_80507629@testuser.com" - }, - "collector_id": 1227208776, - "marketplace_owner": null, - "metadata": {}, - "additional_info": { - "available_balance": null, - "nsu_processadora": null, - "authentication_code": null - }, - "order": {}, - "external_reference": null, - "transaction_amount": 500, - "transaction_amount_refunded": 0, - "coupon_amount": 0, - "differential_pricing_id": null, - "financing_group": null, - "deduction_schema": null, - "installments": 1, - "transaction_details": { - "payment_method_reference_id": null, - "acquirer_reference": null, - "net_received_amount": 0, - "total_paid_amount": 500, - "overpaid_amount": 0, - "external_resource_url": null, - "installment_amount": 500, - "financial_institution": null, - "payable_deferral_period": null - }, - "fee_details": [], - "charges_details": [ - { - "id": "1316782634-001", - "name": "mercadopago_fee", - "type": "fee", - "accounts": { - "from": "collector", - "to": "mp" - }, - "client_id": 0, - "date_created": "2024-01-26T10:01:04.658-04:00", - "last_updated": "2024-01-26T10:01:04.658-04:00", - "amounts": { - "original": 24.89, - "refunded": 0 - }, - "metadata": {}, - "reserve_id": null, - "refund_charges": [] - } - ], - "captured": true, - "binary_mode": false, - "call_for_authorize_id": null, - "statement_descriptor": null, - "card": { - "id": null, - "first_six_digits": "548392", - "last_four_digits": "4623", - "expiration_month": 11, - "expiration_year": 2025, - "date_created": "2024-01-26T10:01:04.000-04:00", - "date_last_updated": "2024-01-26T10:01:04.000-04:00", - "cardholder": { - "name": "GABS", - "identification": { - "number": null, - "type": null - } - } - }, - "notification_url": null, - "refunds": [], - "processing_mode": "aggregator", - "merchant_account_id": null, - "merchant_number": null, - "acquirer_reconciliation": [], - "point_of_interaction": { - "type": "UNSPECIFIED", - "business_info": { - "unit": "online_payments", - "sub_unit": "default", - "branch": null - } - }, - "accounts_info": null, - "three_ds_info": { - "external_resource_url": "https://api.mercadopago.com/cardholder_authenticator/v2/prod/browser-challenges", - "creq": "" - }, - "tags": null + "status_detail": "pending_challenge" } \ No newline at end of file diff --git a/resources/mocks/payment/search_response.json b/resources/mocks/payment/search_response.json new file mode 100644 index 00000000..5b9f31fd --- /dev/null +++ b/resources/mocks/payment/search_response.json @@ -0,0 +1,19 @@ +{ + "paging": { + "total": 2, + "limit": 30, + "offset": 0 + }, + "results": [ + { + "id": 123, + "status": "approved", + "status_detail": "accredited" + }, + { + "id": 456, + "status": "pending", + "status_detail": "pending_waiting_transfer" + } + ] +} \ No newline at end of file From 139ec82e503f973a039f88f20c10dd53f74d0ead Mon Sep 17 00:00:00 2001 From: gabs Date: Fri, 26 Jan 2024 21:03:41 -0300 Subject: [PATCH 14/55] Add get tests --- pkg/payment/payment_test.go | 148 ++++++++++++++++++++++ resources/mocks/payment/get_response.json | 5 + 2 files changed, 153 insertions(+) create mode 100644 resources/mocks/payment/get_response.json diff --git a/pkg/payment/payment_test.go b/pkg/payment/payment_test.go index f91f7efc..578fbd7b 100644 --- a/pkg/payment/payment_test.go +++ b/pkg/payment/payment_test.go @@ -20,6 +20,9 @@ var ( searchResponseJSON, _ = os.Open("../../resources/mocks/payment/search_response.json") searchResponse, _ = io.ReadAll(searchResponseJSON) + + getResponseJSON, _ = os.Open("../../resources/mocks/payment/get_response.json") + getResponse, _ = io.ReadAll(getResponseJSON) ) func TestCreate(t *testing.T) { @@ -273,3 +276,148 @@ func TestSearch(t *testing.T) { }) } } + +func TestGet(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + id int64 + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr string + }{ + { + name: "should_fail_to_create_request", + fields: fields{ + config: nil, + }, + args: args{ + ctx: nil, + }, + want: nil, + wantErr: "error creating request: net/http: nil Context", + }, + { + name: "should_fail_to_send_request", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "transport level error: some error", + }, + { + name: "should_fail_to_unmarshaling_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader("invalid json") + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + }, + { + name: "should_return_formatted_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(getResponse)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: &Response{ + ID: 789, + Status: "refunded", + StatusDetail: "refunded", + }, + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + config: tt.fields.config, + } + got, err := c.Get(tt.args.ctx, tt.args.id) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("client.Get() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.Get() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_client_Cancel(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + id int64 + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + config: tt.fields.config, + } + got, err := c.Cancel(tt.args.ctx, tt.args.id) + if (err != nil) != tt.wantErr { + t.Errorf("client.Cancel() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.Cancel() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/resources/mocks/payment/get_response.json b/resources/mocks/payment/get_response.json new file mode 100644 index 00000000..daaf07a1 --- /dev/null +++ b/resources/mocks/payment/get_response.json @@ -0,0 +1,5 @@ +{ + "id": 789, + "status": "refunded", + "status_detail": "refunded" +} \ No newline at end of file From e1e6e03b9b4a33eaae31ad61d92ac9d33e9c0ee7 Mon Sep 17 00:00:00 2001 From: gabs Date: Fri, 26 Jan 2024 21:07:04 -0300 Subject: [PATCH 15/55] Add cancel test --- pkg/payment/payment_test.go | 90 ++++++++++++++++++-- resources/mocks/payment/cancel_response.json | 5 ++ 2 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 resources/mocks/payment/cancel_response.json diff --git a/pkg/payment/payment_test.go b/pkg/payment/payment_test.go index 578fbd7b..42a9619d 100644 --- a/pkg/payment/payment_test.go +++ b/pkg/payment/payment_test.go @@ -23,6 +23,9 @@ var ( getResponseJSON, _ = os.Open("../../resources/mocks/payment/get_response.json") getResponse, _ = io.ReadAll(getResponseJSON) + + cancelResponseJSON, _ = os.Open("../../resources/mocks/payment/cancel_response.json") + cancelResponse, _ = io.ReadAll(cancelResponseJSON) ) func TestCreate(t *testing.T) { @@ -388,7 +391,7 @@ func TestGet(t *testing.T) { } } -func Test_client_Cancel(t *testing.T) { +func TestCancel(t *testing.T) { type fields struct { config *config.Config } @@ -401,9 +404,82 @@ func Test_client_Cancel(t *testing.T) { fields fields args args want *Response - wantErr bool + wantErr string }{ - // TODO: Add test cases. + { + name: "should_fail_to_create_request", + fields: fields{ + config: nil, + }, + args: args{ + ctx: nil, + }, + want: nil, + wantErr: "error creating request: net/http: nil Context", + }, + { + name: "should_fail_to_send_request", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "transport level error: some error", + }, + { + name: "should_fail_to_unmarshaling_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader("invalid json") + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + }, + { + name: "should_return_formatted_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(cancelResponse)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: &Response{ + ID: 123, + Status: "cancelled", + StatusDetail: "by_collector", + }, + wantErr: "", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -411,9 +487,13 @@ func Test_client_Cancel(t *testing.T) { config: tt.fields.config, } got, err := c.Cancel(tt.args.ctx, tt.args.id) - if (err != nil) != tt.wantErr { + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { t.Errorf("client.Cancel() error = %v, wantErr %v", err, tt.wantErr) - return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("client.Cancel() = %v, want %v", got, tt.want) diff --git a/resources/mocks/payment/cancel_response.json b/resources/mocks/payment/cancel_response.json new file mode 100644 index 00000000..937183f5 --- /dev/null +++ b/resources/mocks/payment/cancel_response.json @@ -0,0 +1,5 @@ +{ + "id": 123, + "status": "cancelled", + "status_detail": "by_collector" +} \ No newline at end of file From 889b1dca266513e5eb28f47adf61bb3c47bd735c Mon Sep 17 00:00:00 2001 From: gabs Date: Fri, 26 Jan 2024 21:09:58 -0300 Subject: [PATCH 16/55] Add capture test --- pkg/payment/payment_test.go | 114 ++++++++++++++++++ resources/mocks/payment/capture_response.json | 5 + 2 files changed, 119 insertions(+) create mode 100644 resources/mocks/payment/capture_response.json diff --git a/pkg/payment/payment_test.go b/pkg/payment/payment_test.go index 42a9619d..3c206d36 100644 --- a/pkg/payment/payment_test.go +++ b/pkg/payment/payment_test.go @@ -26,6 +26,9 @@ var ( cancelResponseJSON, _ = os.Open("../../resources/mocks/payment/cancel_response.json") cancelResponse, _ = io.ReadAll(cancelResponseJSON) + + captureResponseJSON, _ = os.Open("../../resources/mocks/payment/capture_response.json") + captureResponse, _ = io.ReadAll(captureResponseJSON) ) func TestCreate(t *testing.T) { @@ -501,3 +504,114 @@ func TestCancel(t *testing.T) { }) } } + +func TestCapture(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + id int64 + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr string + }{ + { + name: "should_fail_to_create_request", + fields: fields{ + config: nil, + }, + args: args{ + ctx: nil, + }, + want: nil, + wantErr: "error creating request: net/http: nil Context", + }, + { + name: "should_fail_to_send_request", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "transport level error: some error", + }, + { + name: "should_fail_to_unmarshaling_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader("invalid json") + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + }, + { + name: "should_return_formatted_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(captureResponse)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: &Response{ + ID: 123, + Status: "approved", + StatusDetail: "accredited", + }, + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + config: tt.fields.config, + } + got, err := c.Capture(tt.args.ctx, tt.args.id) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("client.Capture() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.Capture() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/resources/mocks/payment/capture_response.json b/resources/mocks/payment/capture_response.json new file mode 100644 index 00000000..4e346a77 --- /dev/null +++ b/resources/mocks/payment/capture_response.json @@ -0,0 +1,5 @@ +{ + "id": 123, + "status": "approved", + "status_detail": "accredited" +} \ No newline at end of file From 20967cfb35da539b008c9e33026f8ccf61b55923 Mon Sep 17 00:00:00 2001 From: gabs Date: Fri, 26 Jan 2024 21:12:04 -0300 Subject: [PATCH 17/55] Add capture amount test --- pkg/payment/payment_test.go | 115 ++++++++++++++++++ .../payment/capture_amount_response.json | 5 + 2 files changed, 120 insertions(+) create mode 100644 resources/mocks/payment/capture_amount_response.json diff --git a/pkg/payment/payment_test.go b/pkg/payment/payment_test.go index 3c206d36..f2091e10 100644 --- a/pkg/payment/payment_test.go +++ b/pkg/payment/payment_test.go @@ -29,6 +29,9 @@ var ( captureResponseJSON, _ = os.Open("../../resources/mocks/payment/capture_response.json") captureResponse, _ = io.ReadAll(captureResponseJSON) + + captureAmountResponseJSON, _ = os.Open("../../resources/mocks/payment/capture_amount_response.json") + captureAmountResponse, _ = io.ReadAll(captureAmountResponseJSON) ) func TestCreate(t *testing.T) { @@ -615,3 +618,115 @@ func TestCapture(t *testing.T) { }) } } + +func TestCaptureAmount(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + id int64 + amount float64 + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr string + }{ + { + name: "should_fail_to_create_request", + fields: fields{ + config: nil, + }, + args: args{ + ctx: nil, + }, + want: nil, + wantErr: "error creating request: net/http: nil Context", + }, + { + name: "should_fail_to_send_request", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "transport level error: some error", + }, + { + name: "should_fail_to_unmarshaling_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader("invalid json") + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + }, + { + name: "should_return_formatted_response", + fields: fields{ + config: &config.Config{ + HTTPClient: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(captureAmountResponse)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: &Response{ + ID: 123, + Status: "approved", + StatusDetail: "accredited", + }, + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + config: tt.fields.config, + } + got, err := c.CaptureAmount(tt.args.ctx, tt.args.id, tt.args.amount) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("client.CaptureAmount() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("client.CaptureAmount() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/resources/mocks/payment/capture_amount_response.json b/resources/mocks/payment/capture_amount_response.json new file mode 100644 index 00000000..4e346a77 --- /dev/null +++ b/resources/mocks/payment/capture_amount_response.json @@ -0,0 +1,5 @@ +{ + "id": 123, + "status": "approved", + "status_detail": "accredited" +} \ No newline at end of file From 2f4c1153372eed737d63f32441159b40ccaf2e6b Mon Sep 17 00:00:00 2001 From: gabs Date: Fri, 26 Jan 2024 21:28:01 -0300 Subject: [PATCH 18/55] Add integration tests --- test/integration/payment_test.go | 184 +++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 test/integration/payment_test.go diff --git a/test/integration/payment_test.go b/test/integration/payment_test.go new file mode 100644 index 00000000..dde12ff3 --- /dev/null +++ b/test/integration/payment_test.go @@ -0,0 +1,184 @@ +package integration + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/google/uuid" + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/payment" +) + +func TestPayment(t *testing.T) { + t.Run("should_create_payment", func(t *testing.T) { + c, err := config.New(os.Getenv("at")) + if err != nil { + t.Fatal(err) + } + + client := payment.NewClient(c) + + dto := payment.Request{ + TransactionAmount: 105.1, + PaymentMethodID: "pix", + Payer: &payment.PayerRequest{ + Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), + }, + } + + result, err := client.Create(context.Background(), dto) + if result == nil { + t.Error("result can't be nil") + } + if result.ID == 0 { + t.Error("id can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + }) + + t.Run("should_search_payment", func(t *testing.T) { + c, err := config.New(os.Getenv("at")) + if err != nil { + t.Fatal(err) + } + + dto := payment.SearchRequest{ + Filters: map[string]string{ + "external_reference": "abc_def_ghi_123_456123", + }, + } + + client := payment.NewClient(c) + result, err := client.Search(context.Background(), dto) + if result == nil { + t.Error("result can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + }) + + t.Run("should_get_payment", func(t *testing.T) { + c, err := config.New(os.Getenv("at")) + if err != nil { + t.Fatal(err) + } + + client := payment.NewClient(c) + dto := payment.Request{ + TransactionAmount: 105.1, + PaymentMethodID: "pix", + Payer: &payment.PayerRequest{ + Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), + }, + } + + result, err := client.Create(context.Background(), dto) + if result == nil { + t.Error("result can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + + result, err = client.Get(context.Background(), result.ID) + if result == nil { + t.Error("result can't be nil") + } + if result.ID == 0 { + t.Error("id can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + }) + + t.Run("should_cancel_payment", func(t *testing.T) { + c, err := config.New(os.Getenv("at")) + if err != nil { + t.Fatal(err) + } + + client := payment.NewClient(c) + dto := payment.Request{ + TransactionAmount: 105.1, + PaymentMethodID: "pix", + Payer: &payment.PayerRequest{ + Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), + }, + } + + result, err := client.Create(context.Background(), dto) + if result == nil { + t.Error("result can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + + result, err = client.Cancel(context.Background(), result.ID) + if result == nil { + t.Error("result can't be nil") + } + if result.ID == 0 { + t.Error("id can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + }) + + // We should validate how to test capture and capture amount. + + // t.Run("should_capture_payment", func(t *testing.T) { + // c, err := config.New(os.Getenv("at")) + // if err != nil { + // t.Fatal(err) + // } + + // client := payment.NewClient(c) + // dto := payment.Request{ + // TransactionAmount: 105.1, + // PaymentMethodID: "pix", + // Payer: &payment.PayerRequest{ + // Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), + // }, + // } + + // result, err := client.Create(context.Background(), dto) + // if result == nil { + // t.Error("result can't be nil") + // } + // if err != nil { + // t.Errorf(err.Error()) + // } + + // result, err = client.Capture(context.Background(), result.ID) + // if result == nil { + // t.Error("result can't be nil") + // } + // if err != nil { + // t.Errorf(err.Error()) + // } + // }) + + // t.Run("should_capture_amount_payment", func(t *testing.T) { + // c, err := config.New(os.Getenv("at")) + // if err != nil { + // t.Fatal(err) + // } + + // client := payment.NewClient(c) + // result, err := client.CaptureAmount(context.Background(), 123, 100.1) + // if result == nil { + // t.Error("result can't be nil") + // } + // if err != nil { + // t.Errorf(err.Error()) + // } + // }) +} From f55b892f8a0eea0f268161cdbde2c41c3a048a42 Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Wed, 31 Jan 2024 16:31:52 -0300 Subject: [PATCH 19/55] merge main into feature payment implementation --- pkg/payment/payment_test.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pkg/payment/payment_test.go b/pkg/payment/payment_test.go index f2091e10..434f0f1f 100644 --- a/pkg/payment/payment_test.go +++ b/pkg/payment/payment_test.go @@ -80,7 +80,7 @@ func TestCreate(t *testing.T) { name: "should_fail_to_send_request", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { return nil, fmt.Errorf("some error") }, @@ -97,7 +97,7 @@ func TestCreate(t *testing.T) { name: "should_fail_to_unmarshaling_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader("invalid json") stringReadCloser := io.NopCloser(stringReader) @@ -118,7 +118,7 @@ func TestCreate(t *testing.T) { name: "should_return_formatted_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader(string(createResponse)) stringReadCloser := io.NopCloser(stringReader) @@ -191,7 +191,7 @@ func TestSearch(t *testing.T) { name: "should_fail_to_send_request", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { return nil, fmt.Errorf("some error") }, @@ -208,7 +208,7 @@ func TestSearch(t *testing.T) { name: "should_fail_to_unmarshaling_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader("invalid json") stringReadCloser := io.NopCloser(stringReader) @@ -229,7 +229,7 @@ func TestSearch(t *testing.T) { name: "should_return_formatted_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader(string(searchResponse)) stringReadCloser := io.NopCloser(stringReader) @@ -316,7 +316,7 @@ func TestGet(t *testing.T) { name: "should_fail_to_send_request", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { return nil, fmt.Errorf("some error") }, @@ -333,7 +333,7 @@ func TestGet(t *testing.T) { name: "should_fail_to_unmarshaling_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader("invalid json") stringReadCloser := io.NopCloser(stringReader) @@ -354,7 +354,7 @@ func TestGet(t *testing.T) { name: "should_return_formatted_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader(string(getResponse)) stringReadCloser := io.NopCloser(stringReader) @@ -427,7 +427,7 @@ func TestCancel(t *testing.T) { name: "should_fail_to_send_request", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { return nil, fmt.Errorf("some error") }, @@ -444,7 +444,7 @@ func TestCancel(t *testing.T) { name: "should_fail_to_unmarshaling_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader("invalid json") stringReadCloser := io.NopCloser(stringReader) @@ -465,7 +465,7 @@ func TestCancel(t *testing.T) { name: "should_return_formatted_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader(string(cancelResponse)) stringReadCloser := io.NopCloser(stringReader) @@ -538,7 +538,7 @@ func TestCapture(t *testing.T) { name: "should_fail_to_send_request", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { return nil, fmt.Errorf("some error") }, @@ -555,7 +555,7 @@ func TestCapture(t *testing.T) { name: "should_fail_to_unmarshaling_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader("invalid json") stringReadCloser := io.NopCloser(stringReader) @@ -576,7 +576,7 @@ func TestCapture(t *testing.T) { name: "should_return_formatted_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader(string(captureResponse)) stringReadCloser := io.NopCloser(stringReader) @@ -650,7 +650,7 @@ func TestCaptureAmount(t *testing.T) { name: "should_fail_to_send_request", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { return nil, fmt.Errorf("some error") }, @@ -667,7 +667,7 @@ func TestCaptureAmount(t *testing.T) { name: "should_fail_to_unmarshaling_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader("invalid json") stringReadCloser := io.NopCloser(stringReader) @@ -688,7 +688,7 @@ func TestCaptureAmount(t *testing.T) { name: "should_return_formatted_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader(string(captureAmountResponse)) stringReadCloser := io.NopCloser(stringReader) From eb243d6471483b659b7959292ec61639a2f7d812 Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Wed, 31 Jan 2024 16:34:31 -0300 Subject: [PATCH 20/55] adjust integration test --- test/integration/{ => payment}/payment_test.go | 0 test/integration/{ => payment_method}/payment_method_test.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/integration/{ => payment}/payment_test.go (100%) rename test/integration/{ => payment_method}/payment_method_test.go (100%) diff --git a/test/integration/payment_test.go b/test/integration/payment/payment_test.go similarity index 100% rename from test/integration/payment_test.go rename to test/integration/payment/payment_test.go diff --git a/test/integration/payment_method_test.go b/test/integration/payment_method/payment_method_test.go similarity index 100% rename from test/integration/payment_method_test.go rename to test/integration/payment_method/payment_method_test.go From 3852389d04bae92936b966786baa9a957ae2c9c4 Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Wed, 31 Jan 2024 17:39:59 -0300 Subject: [PATCH 21/55] adjust examples --- examples/apis/payment/cancel/main.go | 5 +++-- examples/apis/payment/capture/main.go | 5 +++-- examples/apis/payment/capture_amount/main.go | 5 +++-- examples/apis/payment/create/main.go | 5 +++-- examples/apis/payment/get/main.go | 5 +++-- examples/apis/payment/search/main.go | 5 +++-- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/examples/apis/payment/cancel/main.go b/examples/apis/payment/cancel/main.go index c08dd7ea..0636701c 100644 --- a/examples/apis/payment/cancel/main.go +++ b/examples/apis/payment/cancel/main.go @@ -9,8 +9,9 @@ import ( ) func main() { - at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" - c, err := config.New(at) + accessToken := "{{ACCESS_TOKEN}}" + + c, err := config.New(accessToken) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/capture/main.go b/examples/apis/payment/capture/main.go index a7149447..ea093038 100644 --- a/examples/apis/payment/capture/main.go +++ b/examples/apis/payment/capture/main.go @@ -10,8 +10,9 @@ import ( ) func main() { - at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" - c, err := config.New(at) + accessToken := "{{ACCESS_TOKEN}}" + + c, err := config.New(accessToken) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/capture_amount/main.go b/examples/apis/payment/capture_amount/main.go index 871b7576..187c76ad 100644 --- a/examples/apis/payment/capture_amount/main.go +++ b/examples/apis/payment/capture_amount/main.go @@ -10,8 +10,9 @@ import ( ) func main() { - at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" - c, err := config.New(at) + accessToken := "{{ACCESS_TOKEN}}" + + c, err := config.New(accessToken) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/create/main.go b/examples/apis/payment/create/main.go index fa61afff..980ab21a 100644 --- a/examples/apis/payment/create/main.go +++ b/examples/apis/payment/create/main.go @@ -10,8 +10,9 @@ import ( ) func main() { - at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" - c, err := config.New(at) + accessToken := "{{ACCESS_TOKEN}}" + + c, err := config.New(accessToken) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/get/main.go b/examples/apis/payment/get/main.go index d53b86ed..40f46b11 100644 --- a/examples/apis/payment/get/main.go +++ b/examples/apis/payment/get/main.go @@ -9,8 +9,9 @@ import ( ) func main() { - at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" - c, err := config.New(at) + accessToken := "{{ACCESS_TOKEN}}" + + c, err := config.New(accessToken) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/search/main.go b/examples/apis/payment/search/main.go index 71f6c1a9..400c7137 100644 --- a/examples/apis/payment/search/main.go +++ b/examples/apis/payment/search/main.go @@ -9,8 +9,9 @@ import ( ) func main() { - at := "TEST-640110472259637-071923-a761f639c4eb1f0835ff7611f3248628-793910800" - c, err := config.New(at) + accessToken := "{{ACCESS_TOKEN}}" + + c, err := config.New(accessToken) if err != nil { fmt.Println(err) return From cca8c951d8a7597b460d54b1ee5f4d8e9b99cc73 Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Thu, 1 Feb 2024 09:58:15 -0300 Subject: [PATCH 22/55] adjust capture payment integration tests --- test/integration/payment/payment_test.go | 93 ++++++++++++------------ 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/test/integration/payment/payment_test.go b/test/integration/payment/payment_test.go index dde12ff3..79706c9d 100644 --- a/test/integration/payment/payment_test.go +++ b/test/integration/payment/payment_test.go @@ -133,52 +133,51 @@ func TestPayment(t *testing.T) { }) // We should validate how to test capture and capture amount. + t.Run("should_capture_payment", func(t *testing.T) { + c, err := config.New(os.Getenv("at")) + if err != nil { + t.Fatal(err) + } + + client := payment.NewClient(c) + dto := payment.Request{ + TransactionAmount: 105.1, + PaymentMethodID: "pix", + Payer: &payment.PayerRequest{ + Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), + }, + } + + result, err := client.Create(context.Background(), dto) + if result == nil { + t.Error("result can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } - // t.Run("should_capture_payment", func(t *testing.T) { - // c, err := config.New(os.Getenv("at")) - // if err != nil { - // t.Fatal(err) - // } - - // client := payment.NewClient(c) - // dto := payment.Request{ - // TransactionAmount: 105.1, - // PaymentMethodID: "pix", - // Payer: &payment.PayerRequest{ - // Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), - // }, - // } - - // result, err := client.Create(context.Background(), dto) - // if result == nil { - // t.Error("result can't be nil") - // } - // if err != nil { - // t.Errorf(err.Error()) - // } - - // result, err = client.Capture(context.Background(), result.ID) - // if result == nil { - // t.Error("result can't be nil") - // } - // if err != nil { - // t.Errorf(err.Error()) - // } - // }) - - // t.Run("should_capture_amount_payment", func(t *testing.T) { - // c, err := config.New(os.Getenv("at")) - // if err != nil { - // t.Fatal(err) - // } - - // client := payment.NewClient(c) - // result, err := client.CaptureAmount(context.Background(), 123, 100.1) - // if result == nil { - // t.Error("result can't be nil") - // } - // if err != nil { - // t.Errorf(err.Error()) - // } - // }) + result, err = client.Capture(context.Background(), result.ID) + if result == nil { + t.Error("result can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + }) + + t.Run("should_capture_amount_payment", func(t *testing.T) { + c, err := config.New(os.Getenv("at")) + if err != nil { + t.Fatal(err) + } + + client := payment.NewClient(c) + result, err := client.CaptureAmount(context.Background(), 123, 100.1) + if result == nil { + t.Error("result can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + }) } From e79aee9e2098e0b4d71c436a076455b80eea99a8 Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Thu, 1 Feb 2024 10:52:16 -0300 Subject: [PATCH 23/55] adjust examples and integration test --- examples/apis/payment/cancel/main.go | 4 ++-- examples/apis/payment/capture/main.go | 9 ++++----- examples/apis/payment/capture_amount/main.go | 9 ++++----- examples/apis/payment/create/main.go | 9 ++++----- examples/apis/payment/get/main.go | 20 +++++++++++++++++--- examples/apis/payment/search/main.go | 4 ++-- test/integration/payment/payment_test.go | 8 +++++++- 7 files changed, 40 insertions(+), 23 deletions(-) diff --git a/examples/apis/payment/cancel/main.go b/examples/apis/payment/cancel/main.go index 0636701c..b79d8fd1 100644 --- a/examples/apis/payment/cancel/main.go +++ b/examples/apis/payment/cancel/main.go @@ -11,13 +11,13 @@ import ( func main() { accessToken := "{{ACCESS_TOKEN}}" - c, err := config.New(accessToken) + cfg, err := config.New(accessToken) if err != nil { fmt.Println(err) return } - client := payment.NewClient(c) + client := payment.NewClient(cfg) result, err := client.Cancel(context.Background(), 123) if err != nil { fmt.Println(err) diff --git a/examples/apis/payment/capture/main.go b/examples/apis/payment/capture/main.go index ea093038..39bc75e8 100644 --- a/examples/apis/payment/capture/main.go +++ b/examples/apis/payment/capture/main.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/google/uuid" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/payment" ) @@ -12,7 +11,7 @@ import ( func main() { accessToken := "{{ACCESS_TOKEN}}" - c, err := config.New(accessToken) + cfg, err := config.New(accessToken) if err != nil { fmt.Println(err) return @@ -23,14 +22,14 @@ func main() { TransactionAmount: 105.1, PaymentMethodID: "visa", Payer: &payment.PayerRequest{ - Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), + Email: "{{EMAIL}}", }, - Token: "cdec5028665c41976be212a7981437d6", + Token: "{{CARD_TOKEN}}", Installments: 1, Capture: false, } - client := payment.NewClient(c) + client := payment.NewClient(cfg) result, err := client.Create(context.Background(), dto) if err != nil { fmt.Println(err) diff --git a/examples/apis/payment/capture_amount/main.go b/examples/apis/payment/capture_amount/main.go index 187c76ad..835f2edc 100644 --- a/examples/apis/payment/capture_amount/main.go +++ b/examples/apis/payment/capture_amount/main.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/google/uuid" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/payment" ) @@ -12,7 +11,7 @@ import ( func main() { accessToken := "{{ACCESS_TOKEN}}" - c, err := config.New(accessToken) + cfg, err := config.New(accessToken) if err != nil { fmt.Println(err) return @@ -23,14 +22,14 @@ func main() { TransactionAmount: 105.1, PaymentMethodID: "visa", Payer: &payment.PayerRequest{ - Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), + Email: "{{EMAIL}}", }, - Token: "cdec5028665c41976be212a7981437d6", + Token: "{{CARD_TOKEN}}", Installments: 1, Capture: false, } - client := payment.NewClient(c) + client := payment.NewClient(cfg) result, err := client.Create(context.Background(), dto) if err != nil { fmt.Println(err) diff --git a/examples/apis/payment/create/main.go b/examples/apis/payment/create/main.go index 980ab21a..49561e13 100644 --- a/examples/apis/payment/create/main.go +++ b/examples/apis/payment/create/main.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/google/uuid" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/payment" ) @@ -12,7 +11,7 @@ import ( func main() { accessToken := "{{ACCESS_TOKEN}}" - c, err := config.New(accessToken) + cfg, err := config.New(accessToken) if err != nil { fmt.Println(err) return @@ -22,13 +21,13 @@ func main() { TransactionAmount: 105.1, PaymentMethodID: "visa", Payer: &payment.PayerRequest{ - Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), + Email: "{{EMAIL}}", }, - Token: "cdec5028665c41976be212a7981437d6", + Token: "{{CARD_TOKEN}}", Installments: 1, } - client := payment.NewClient(c) + client := payment.NewClient(cfg) result, err := client.Create(context.Background(), dto) if err != nil { fmt.Println(err) diff --git a/examples/apis/payment/get/main.go b/examples/apis/payment/get/main.go index 40f46b11..91ed5c5b 100644 --- a/examples/apis/payment/get/main.go +++ b/examples/apis/payment/get/main.go @@ -11,14 +11,28 @@ import ( func main() { accessToken := "{{ACCESS_TOKEN}}" - c, err := config.New(accessToken) + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + dto := payment.Request{ + TransactionAmount: 105.1, + PaymentMethodID: "pix", + Payer: &payment.PayerRequest{ + Email: "{{EMAIL}}", + }, + } + + client := payment.NewClient(cfg) + + result, err := client.Create(context.Background(), dto) if err != nil { fmt.Println(err) return } - client := payment.NewClient(c) - result, err := client.Get(context.Background(), 123) + result, err = client.Get(context.Background(), result.ID) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/search/main.go b/examples/apis/payment/search/main.go index 400c7137..3a513d59 100644 --- a/examples/apis/payment/search/main.go +++ b/examples/apis/payment/search/main.go @@ -11,7 +11,7 @@ import ( func main() { accessToken := "{{ACCESS_TOKEN}}" - c, err := config.New(accessToken) + cfg, err := config.New(accessToken) if err != nil { fmt.Println(err) return @@ -23,7 +23,7 @@ func main() { }, } - client := payment.NewClient(c) + client := payment.NewClient(cfg) result, err := client.Search(context.Background(), dto) if err != nil { fmt.Println(err) diff --git a/test/integration/payment/payment_test.go b/test/integration/payment/payment_test.go index 79706c9d..40c37aec 100644 --- a/test/integration/payment/payment_test.go +++ b/test/integration/payment/payment_test.go @@ -140,12 +140,18 @@ func TestPayment(t *testing.T) { } client := payment.NewClient(c) + + // Create payment. dto := payment.Request{ TransactionAmount: 105.1, - PaymentMethodID: "pix", + PaymentMethodID: "visa", Payer: &payment.PayerRequest{ Email: fmt.Sprintf("gabs_%s@testuser.com", uuid.New()), }, + // Need to get a token from a card. + Token: "", + Installments: 1, + Capture: false, } result, err := client.Create(context.Background(), dto) From d65833a17d50458585dff8604ea710421f093e71 Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Thu, 1 Feb 2024 11:43:31 -0300 Subject: [PATCH 24/55] adjust examples --- examples/apis/payment/cancel/main.go | 3 ++- examples/apis/payment/capture/main.go | 4 ++-- examples/apis/payment/capture_amount/main.go | 4 ++-- examples/apis/payment/create/main.go | 4 ++-- examples/apis/payment/search/main.go | 4 ++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/apis/payment/cancel/main.go b/examples/apis/payment/cancel/main.go index b79d8fd1..522a9378 100644 --- a/examples/apis/payment/cancel/main.go +++ b/examples/apis/payment/cancel/main.go @@ -18,7 +18,8 @@ func main() { } client := payment.NewClient(cfg) - result, err := client.Cancel(context.Background(), 123) + paymentID := int64(123) + result, err := client.Cancel(context.Background(), paymentID) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/capture/main.go b/examples/apis/payment/capture/main.go index 39bc75e8..39957409 100644 --- a/examples/apis/payment/capture/main.go +++ b/examples/apis/payment/capture/main.go @@ -18,7 +18,7 @@ func main() { } // Create payment. - dto := payment.Request{ + paymentRequest := payment.Request{ TransactionAmount: 105.1, PaymentMethodID: "visa", Payer: &payment.PayerRequest{ @@ -30,7 +30,7 @@ func main() { } client := payment.NewClient(cfg) - result, err := client.Create(context.Background(), dto) + result, err := client.Create(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/capture_amount/main.go b/examples/apis/payment/capture_amount/main.go index 835f2edc..420d4357 100644 --- a/examples/apis/payment/capture_amount/main.go +++ b/examples/apis/payment/capture_amount/main.go @@ -18,7 +18,7 @@ func main() { } // Create payment. - dto := payment.Request{ + paymentRequest := payment.Request{ TransactionAmount: 105.1, PaymentMethodID: "visa", Payer: &payment.PayerRequest{ @@ -30,7 +30,7 @@ func main() { } client := payment.NewClient(cfg) - result, err := client.Create(context.Background(), dto) + result, err := client.Create(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/create/main.go b/examples/apis/payment/create/main.go index 49561e13..79679f6f 100644 --- a/examples/apis/payment/create/main.go +++ b/examples/apis/payment/create/main.go @@ -17,7 +17,7 @@ func main() { return } - dto := payment.Request{ + paymentRequest := payment.Request{ TransactionAmount: 105.1, PaymentMethodID: "visa", Payer: &payment.PayerRequest{ @@ -28,7 +28,7 @@ func main() { } client := payment.NewClient(cfg) - result, err := client.Create(context.Background(), dto) + result, err := client.Create(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/search/main.go b/examples/apis/payment/search/main.go index 3a513d59..209c1d85 100644 --- a/examples/apis/payment/search/main.go +++ b/examples/apis/payment/search/main.go @@ -17,14 +17,14 @@ func main() { return } - dto := payment.SearchRequest{ + paymentRequest := payment.SearchRequest{ Filters: map[string]string{ "external_reference": "abc_def_ghi_123_456123", }, } client := payment.NewClient(cfg) - result, err := client.Search(context.Background(), dto) + result, err := client.Search(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return From 42299e0cfe267cf641c186aa8cb68959226b3f8d Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Thu, 1 Feb 2024 13:44:05 -0300 Subject: [PATCH 25/55] rename variable names --- examples/apis/payment/cancel/main.go | 3 ++- examples/apis/payment/capture/main.go | 4 ++-- examples/apis/payment/capture_amount/main.go | 4 ++-- examples/apis/payment/create/main.go | 4 ++-- examples/apis/payment/get/main.go | 8 ++++---- examples/apis/payment/search/main.go | 4 ++-- pkg/payment/payment.go | 8 ++++---- pkg/payment/search_request.go | 19 +++++-------------- test/integration/payment/payment_test.go | 12 ++++++------ 9 files changed, 29 insertions(+), 37 deletions(-) diff --git a/examples/apis/payment/cancel/main.go b/examples/apis/payment/cancel/main.go index 522a9378..b74eab66 100644 --- a/examples/apis/payment/cancel/main.go +++ b/examples/apis/payment/cancel/main.go @@ -18,7 +18,8 @@ func main() { } client := payment.NewClient(cfg) - paymentID := int64(123) + var paymentID int64 = 123 + result, err := client.Cancel(context.Background(), paymentID) if err != nil { fmt.Println(err) diff --git a/examples/apis/payment/capture/main.go b/examples/apis/payment/capture/main.go index 39957409..c617ccd9 100644 --- a/examples/apis/payment/capture/main.go +++ b/examples/apis/payment/capture/main.go @@ -30,14 +30,14 @@ func main() { } client := payment.NewClient(cfg) - result, err := client.Create(context.Background(), paymentRequest) + pay, err := client.Create(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return } // Capture. - result, err = client.Capture(context.Background(), result.ID) + result, err := client.Capture(context.Background(), pay.ID) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/capture_amount/main.go b/examples/apis/payment/capture_amount/main.go index 420d4357..972f82ac 100644 --- a/examples/apis/payment/capture_amount/main.go +++ b/examples/apis/payment/capture_amount/main.go @@ -30,14 +30,14 @@ func main() { } client := payment.NewClient(cfg) - result, err := client.Create(context.Background(), paymentRequest) + pay, err := client.Create(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return } // Capture amount. - result, err = client.CaptureAmount(context.Background(), result.ID, 100.1) + result, err := client.CaptureAmount(context.Background(), pay.ID, 100.1) if err != nil { fmt.Println(err) return diff --git a/examples/apis/payment/create/main.go b/examples/apis/payment/create/main.go index 79679f6f..57cb2695 100644 --- a/examples/apis/payment/create/main.go +++ b/examples/apis/payment/create/main.go @@ -28,11 +28,11 @@ func main() { } client := payment.NewClient(cfg) - result, err := client.Create(context.Background(), paymentRequest) + pay, err := client.Create(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return } - fmt.Println(result) + fmt.Println(pay) } diff --git a/examples/apis/payment/get/main.go b/examples/apis/payment/get/main.go index 91ed5c5b..1c6478f5 100644 --- a/examples/apis/payment/get/main.go +++ b/examples/apis/payment/get/main.go @@ -16,7 +16,7 @@ func main() { fmt.Println(err) return } - dto := payment.Request{ + paymentRequest := payment.Request{ TransactionAmount: 105.1, PaymentMethodID: "pix", Payer: &payment.PayerRequest{ @@ -26,17 +26,17 @@ func main() { client := payment.NewClient(cfg) - result, err := client.Create(context.Background(), dto) + pay, err := client.Create(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return } - result, err = client.Get(context.Background(), result.ID) + pay, err = client.Get(context.Background(), pay.ID) if err != nil { fmt.Println(err) return } - fmt.Println(result) + fmt.Println(pay) } diff --git a/examples/apis/payment/search/main.go b/examples/apis/payment/search/main.go index 209c1d85..c62549b7 100644 --- a/examples/apis/payment/search/main.go +++ b/examples/apis/payment/search/main.go @@ -24,11 +24,11 @@ func main() { } client := payment.NewClient(cfg) - result, err := client.Search(context.Background(), paymentRequest) + pay, err := client.Search(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return } - fmt.Println(result) + fmt.Println(pay) } diff --git a/pkg/payment/payment.go b/pkg/payment/payment.go index 0de4e2db..f1baafa5 100644 --- a/pkg/payment/payment.go +++ b/pkg/payment/payment.go @@ -24,12 +24,12 @@ type Client interface { // Create creates a new payment. // It is a post request to the endpoint: https://api.mercadopago.com/v1/payments // Reference: https://www.mercadopago.com.br/developers/pt/reference/payments/_payments/post/ - Create(ctx context.Context, dto Request) (*Response, error) + Create(ctx context.Context, request Request) (*Response, error) // Search searches for payments. // It is a get request to the endpoint: https://api.mercadopago.com/v1/payments/search // Reference: https://www.mercadopago.com.br/developers/pt/reference/payments/_payments_search/get/ - Search(ctx context.Context, dto SearchRequest) (*SearchResponse, error) + Search(ctx context.Context, request SearchRequest) (*SearchResponse, error) // Get gets a payment by its ID. // It is a get request to the endpoint: https://api.mercadopago.com/v1/payments/{id} @@ -61,8 +61,8 @@ func NewClient(c *config.Config) Client { } } -func (c *client) Create(ctx context.Context, dto Request) (*Response, error) { - body, err := json.Marshal(&dto) +func (c *client) Create(ctx context.Context, request Request) (*Response, error) { + body, err := json.Marshal(&request) if err != nil { return nil, fmt.Errorf("error marshaling request body: %w", err) } diff --git a/pkg/payment/search_request.go b/pkg/payment/search_request.go index 4a054052..9d8d5801 100644 --- a/pkg/payment/search_request.go +++ b/pkg/payment/search_request.go @@ -2,7 +2,6 @@ package payment import ( "net/url" - "strings" ) // SearchRequest is the request to search services. @@ -19,30 +18,22 @@ type SearchRequest struct { func (s SearchRequest) Parameters() string { params := url.Values{} - var limitKey, offsetKey bool for k, v := range s.Filters { params.Add(k, v) - - if strings.EqualFold(k, "limit") { - limitKey = true - continue - } - if strings.EqualFold(k, "offset") { - offsetKey = true - } } - if !limitKey { + if _, ok := s.Filters["limit"]; !ok { limit := "30" if s.Limit != "" { limit = s.Limit } params.Add("limit", limit) } - if !offsetKey { + + if _, ok := s.Filters["offset"]; !ok { offset := "0" - if s.Limit != "" { - offset = s.Limit + if s.Offset != "" { + offset = s.Offset } params.Add("offset", offset) } diff --git a/test/integration/payment/payment_test.go b/test/integration/payment/payment_test.go index 40c37aec..d984bc78 100644 --- a/test/integration/payment/payment_test.go +++ b/test/integration/payment/payment_test.go @@ -13,7 +13,7 @@ import ( func TestPayment(t *testing.T) { t.Run("should_create_payment", func(t *testing.T) { - c, err := config.New(os.Getenv("at")) + c, err := config.New(os.Getenv("ACCESS_TOKEN")) if err != nil { t.Fatal(err) } @@ -41,7 +41,7 @@ func TestPayment(t *testing.T) { }) t.Run("should_search_payment", func(t *testing.T) { - c, err := config.New(os.Getenv("at")) + c, err := config.New(os.Getenv("ACCESS_TOKEN")) if err != nil { t.Fatal(err) } @@ -63,7 +63,7 @@ func TestPayment(t *testing.T) { }) t.Run("should_get_payment", func(t *testing.T) { - c, err := config.New(os.Getenv("at")) + c, err := config.New(os.Getenv("ACCESS_TOKEN")) if err != nil { t.Fatal(err) } @@ -98,7 +98,7 @@ func TestPayment(t *testing.T) { }) t.Run("should_cancel_payment", func(t *testing.T) { - c, err := config.New(os.Getenv("at")) + c, err := config.New(os.Getenv("ACCESS_TOKEN")) if err != nil { t.Fatal(err) } @@ -134,7 +134,7 @@ func TestPayment(t *testing.T) { // We should validate how to test capture and capture amount. t.Run("should_capture_payment", func(t *testing.T) { - c, err := config.New(os.Getenv("at")) + c, err := config.New(os.Getenv("ACCESS_TOKEN")) if err != nil { t.Fatal(err) } @@ -172,7 +172,7 @@ func TestPayment(t *testing.T) { }) t.Run("should_capture_amount_payment", func(t *testing.T) { - c, err := config.New(os.Getenv("at")) + c, err := config.New(os.Getenv("ACCESS_TOKEN")) if err != nil { t.Fatal(err) } From 480e5dec42ae0ccbd101e9e6170118f41cca1341 Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Fri, 2 Feb 2024 10:16:52 -0300 Subject: [PATCH 26/55] remover pointer from response --- examples/apis/payment/capture/main.go | 4 +- examples/apis/payment/capture_amount/main.go | 4 +- pkg/payment/payment.go | 78 ++-- pkg/payment/payment_test.go | 4 +- pkg/payment/response.go | 370 +++++++++---------- resources/mocks/payment/search_response.json | 4 +- 6 files changed, 245 insertions(+), 219 deletions(-) diff --git a/examples/apis/payment/capture/main.go b/examples/apis/payment/capture/main.go index c617ccd9..c5f763d2 100644 --- a/examples/apis/payment/capture/main.go +++ b/examples/apis/payment/capture/main.go @@ -37,11 +37,11 @@ func main() { } // Capture. - result, err := client.Capture(context.Background(), pay.ID) + pay, err = client.Capture(context.Background(), pay.ID) if err != nil { fmt.Println(err) return } - fmt.Println(result) + fmt.Println(pay) } diff --git a/examples/apis/payment/capture_amount/main.go b/examples/apis/payment/capture_amount/main.go index 972f82ac..bd293760 100644 --- a/examples/apis/payment/capture_amount/main.go +++ b/examples/apis/payment/capture_amount/main.go @@ -37,11 +37,11 @@ func main() { } // Capture amount. - result, err := client.CaptureAmount(context.Background(), pay.ID, 100.1) + pay, err = client.CaptureAmount(context.Background(), pay.ID, 100.1) if err != nil { fmt.Println(err) return } - fmt.Println(result) + fmt.Println(pay) } diff --git a/pkg/payment/payment.go b/pkg/payment/payment.go index f1baafa5..283fca44 100644 --- a/pkg/payment/payment.go +++ b/pkg/payment/payment.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strconv" "strings" @@ -13,27 +14,28 @@ import ( ) const ( - postURL = "https://api.mercadopago.com/v1/payments" - searchURL = "https://api.mercadopago.com/v1/payments/search" - getURL = "https://api.mercadopago.com/v1/payments/{id}" - putURL = "https://api.mercadopago.com/v1/payments/{id}" + baseURL = "https://api.mercadopago.com/v1/" + postURL = baseURL + "payments" + searchURL = baseURL + "payments/search" + getURL = baseURL + "payments/{id}" + putURL = baseURL + "payments/{id}" ) // Client contains the methods to interact with the Payments API. type Client interface { // Create creates a new payment. // It is a post request to the endpoint: https://api.mercadopago.com/v1/payments - // Reference: https://www.mercadopago.com.br/developers/pt/reference/payments/_payments/post/ + // Reference: https://www.mercadopago.com/developers/en/reference/payments/_payments/post/ Create(ctx context.Context, request Request) (*Response, error) // Search searches for payments. // It is a get request to the endpoint: https://api.mercadopago.com/v1/payments/search - // Reference: https://www.mercadopago.com.br/developers/pt/reference/payments/_payments_search/get/ + // Reference: https://www.mercadopago.com/developers/en/reference/payments/_payments_search/get/ Search(ctx context.Context, request SearchRequest) (*SearchResponse, error) // Get gets a payment by its ID. // It is a get request to the endpoint: https://api.mercadopago.com/v1/payments/{id} - // Reference: https://www.mercadopago.com.br/developers/pt/reference/payments/_payments_id/get/ + // Reference: https://www.mercadopago.com/developers/en/reference/payments/_payments_id/get/ Get(ctx context.Context, id int64) (*Response, error) // Cancel cancels a payment by its ID. @@ -77,17 +79,25 @@ func (c *client) Create(ctx context.Context, request Request) (*Response, error) return nil, err } - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { + var payment *Response + if err := json.Unmarshal(res, &payment); err != nil { return nil, fmt.Errorf("error unmarshaling response: %w", err) } - return formatted, nil + return payment, nil } func (c *client) Search(ctx context.Context, dto SearchRequest) (*SearchResponse, error) { params := dto.Parameters() - req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL+"?"+params, nil) + + url, err := url.Parse(searchURL) + if err != nil { + return nil, fmt.Errorf("error parsing url: %w", err) + } + url.RawQuery = params + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil) + if err != nil { return nil, fmt.Errorf("error creating request: %w", err) } @@ -97,12 +107,12 @@ func (c *client) Search(ctx context.Context, dto SearchRequest) (*SearchResponse return nil, err } - var formatted *SearchResponse - if err := json.Unmarshal(res, &formatted); err != nil { + var payment *SearchResponse + if err := json.Unmarshal(res, &payment); err != nil { return nil, fmt.Errorf("error unmarshaling response: %w", err) } - return formatted, nil + return payment, nil } func (c *client) Get(ctx context.Context, id int64) (*Response, error) { @@ -118,12 +128,12 @@ func (c *client) Get(ctx context.Context, id int64) (*Response, error) { return nil, err } - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { + var payment *Response + if err := json.Unmarshal(res, &payment); err != nil { return nil, fmt.Errorf("error unmarshaling response: %w", err) } - return formatted, nil + return payment, nil } func (c *client) Cancel(ctx context.Context, id int64) (*Response, error) { @@ -144,12 +154,12 @@ func (c *client) Cancel(ctx context.Context, id int64) (*Response, error) { return nil, err } - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { + var payment *Response + if err := json.Unmarshal(res, &payment); err != nil { return nil, fmt.Errorf("error unmarshaling response: %w", err) } - return formatted, nil + return payment, nil } func (c *client) Capture(ctx context.Context, id int64) (*Response, error) { @@ -170,12 +180,12 @@ func (c *client) Capture(ctx context.Context, id int64) (*Response, error) { return nil, err } - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { + var payment *Response + if err := json.Unmarshal(res, &payment); err != nil { return nil, fmt.Errorf("error unmarshaling response: %w", err) } - return formatted, nil + return payment, nil } func (c *client) CaptureAmount(ctx context.Context, id int64, amount float64) (*Response, error) { @@ -196,10 +206,26 @@ func (c *client) CaptureAmount(ctx context.Context, id int64, amount float64) (* return nil, err } - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { + var payment *Response + if err := json.Unmarshal(res, &payment); err != nil { return nil, fmt.Errorf("error unmarshaling response: %w", err) } - return formatted, nil + return payment, nil +} + +func buildUrl(params url.Values) (string, error) { + url, err := url.Parse(searchURL) + if err != nil { + return "", fmt.Errorf("error parsing url: %w", err) + } + + for key, value := range params { + for _, v := range value { + q := url.Query() + q.Add(key, v) + url.RawQuery = q.Encode() + } + } + return url.String(), nil } diff --git a/pkg/payment/payment_test.go b/pkg/payment/payment_test.go index 434f0f1f..4488cd0f 100644 --- a/pkg/payment/payment_test.go +++ b/pkg/payment/payment_test.go @@ -251,12 +251,12 @@ func TestSearch(t *testing.T) { }, Results: []Response{ { - ID: 123, + ID: 57592046572, Status: "approved", StatusDetail: "accredited", }, { - ID: 456, + ID: 57592038796, Status: "pending", StatusDetail: "pending_waiting_transfer", }, diff --git a/pkg/payment/response.go b/pkg/payment/response.go index 61c1dfdd..fff8eb57 100644 --- a/pkg/payment/response.go +++ b/pkg/payment/response.go @@ -6,328 +6,328 @@ import ( // Response is the response from the Payments API. type Response struct { - DifferentialPricingID string `json:"differential_pricing_id,omitempty"` - MoneyReleaseSchema string `json:"money_release_schema,omitempty"` - OperationType string `json:"operation_type,omitempty"` - IssuerID string `json:"issuer_id,omitempty"` - PaymentMethodID string `json:"payment_method_id,omitempty"` - PaymentTypeID string `json:"payment_type_id,omitempty"` - Status string `json:"status,omitempty"` - StatusDetail string `json:"status_detail,omitempty"` - CurrencyID string `json:"currency_id,omitempty"` - Description string `json:"description,omitempty"` - AuthorizationCode string `json:"authorization_code,omitempty"` - IntegratorID string `json:"integrator_id,omitempty"` - PlatformID string `json:"platform_id,omitempty"` - CorporationID string `json:"corporation_id,omitempty"` - NotificationURL string `json:"notification_url,omitempty"` - CallbackURL string `json:"callback_url,omitempty"` - ProcessingMode string `json:"processing_mode,omitempty"` - MerchantAccountID string `json:"merchant_account_id,omitempty"` - MerchantNumber string `json:"merchant_number,omitempty"` - CouponCode string `json:"coupon_code,omitempty"` - ExternalReference string `json:"external_reference,omitempty"` - PaymentMethodOptionID string `json:"payment_method_option_id,omitempty"` - PosID string `json:"pos_id,omitempty"` - StoreID string `json:"store_id,omitempty"` - DeductionSchema string `json:"deduction_schema,omitempty"` - CounterCurrency string `json:"counter_currency,omitempty"` - CallForAuthorizeID string `json:"call_for_authorize_id,omitempty"` - StatementDescriptor string `json:"statement_descriptor,omitempty"` - MoneyReleaseStatus string `json:"money_release_status,omitempty"` - Installments int `json:"installments,omitempty"` - ID int64 `json:"id,omitempty"` - SponsorID int64 `json:"sponsor_id,omitempty"` - CollectorID int64 `json:"collector_id,omitempty"` - TransactionAmount float64 `json:"transaction_amount,omitempty"` - TransactionAmountRefunded float64 `json:"transaction_amount_refunded,omitempty"` - CouponAmount float64 `json:"coupon_amount,omitempty"` - TaxesAmount float64 `json:"taxes_amount,omitempty"` - ShippingAmount float64 `json:"shipping_amount,omitempty"` - NetAmount float64 `json:"net_amount,omitempty"` - LiveMode bool `json:"live_mode,omitempty"` - Captured bool `json:"captured,omitempty"` - BinaryMode bool `json:"binary_mode,omitempty"` - Metadata map[string]any `json:"metadata,omitempty"` - InternalMetadata map[string]any `json:"internal_metadata,omitempty"` - - DateCreated *time.Time `json:"date_created,omitempty"` - DateApproved *time.Time `json:"date_approved,omitempty"` - DateLastUpdated *time.Time `json:"date_last_updated,omitempty"` - DateOfExpiration *time.Time `json:"date_of_expiration,omitempty"` - MoneyReleaseDate *time.Time `json:"money_release_date,omitempty"` - Payer *PayerResponse `json:"payer,omitempty"` - AdditionalInfo *AdditionalInfoResponse `json:"additional_info,omitempty"` - Order *OrderResponse `json:"order,omitempty"` - TransactionDetails *TransactionDetailsResponse `json:"transaction_details,omitempty"` - Card *CardResponse `json:"card,omitempty"` - PointOfInteraction *PointOfInteractionResponse `json:"point_of_interaction,omitempty"` - PaymentMethod *PaymentMethodResponse `json:"payment_method,omitempty"` - ThreeDSInfo *ThreeDSInfoResponse `json:"three_ds_info,omitempty"` - FeeDetails []FeeDetailResponse `json:"fee_details,omitempty"` - Taxes []TaxResponse `json:"taxes,omitempty"` - Refunds []RefundResponse `json:"refunds,omitempty"` + DifferentialPricingID string `json:"differential_pricing_id"` + MoneyReleaseSchema string `json:"money_release_schema"` + OperationType string `json:"operation_type"` + IssuerID string `json:"issuer_id"` + PaymentMethodID string `json:"payment_method_id"` + PaymentTypeID string `json:"payment_type_id"` + Status string `json:"status"` + StatusDetail string `json:"status_detail"` + CurrencyID string `json:"currency_id"` + Description string `json:"description"` + AuthorizationCode string `json:"authorization_code"` + IntegratorID string `json:"integrator_id"` + PlatformID string `json:"platform_id"` + CorporationID string `json:"corporation_id"` + NotificationURL string `json:"notification_url"` + CallbackURL string `json:"callback_url"` + ProcessingMode string `json:"processing_mode"` + MerchantAccountID string `json:"merchant_account_id"` + MerchantNumber string `json:"merchant_number"` + CouponCode string `json:"coupon_code"` + ExternalReference string `json:"external_reference"` + PaymentMethodOptionID string `json:"payment_method_option_id"` + PosID string `json:"pos_id"` + StoreID string `json:"store_id"` + DeductionSchema string `json:"deduction_schema"` + CounterCurrency string `json:"counter_currency"` + CallForAuthorizeID string `json:"call_for_authorize_id"` + StatementDescriptor string `json:"statement_descriptor"` + MoneyReleaseStatus string `json:"money_release_status"` + Installments int `json:"installments"` + ID int64 `json:"id"` + SponsorID int64 `json:"sponsor_id"` + CollectorID int64 `json:"collector_id"` + TransactionAmount float64 `json:"transaction_amount"` + TransactionAmountRefunded float64 `json:"transaction_amount_refunded"` + CouponAmount float64 `json:"coupon_amount"` + TaxesAmount float64 `json:"taxes_amount"` + ShippingAmount float64 `json:"shipping_amount"` + NetAmount float64 `json:"net_amount"` + LiveMode bool `json:"live_mode"` + Captured bool `json:"captured"` + BinaryMode bool `json:"binary_mode"` + Metadata map[string]any `json:"metadata"` + InternalMetadata map[string]any `json:"internal_metadata"` + + DateCreated time.Time `json:"date_created"` + DateApproved time.Time `json:"date_approved"` + DateLastUpdated time.Time `json:"date_last_updated"` + DateOfExpiration time.Time `json:"date_of_expiration"` + MoneyReleaseDate time.Time `json:"money_release_date"` + Payer PayerResponse `json:"payer"` + AdditionalInfo AdditionalInfoResponse `json:"additional_info"` + Order OrderResponse `json:"order"` + TransactionDetails TransactionDetailsResponse `json:"transaction_details"` + Card CardResponse `json:"card"` + PointOfInteraction PointOfInteractionResponse `json:"point_of_interaction"` + PaymentMethod PaymentMethodResponse `json:"payment_method"` + ThreeDSInfo ThreeDSInfoResponse `json:"three_ds_info"` + FeeDetails []FeeDetailResponse `json:"fee_details"` + Taxes []TaxResponse `json:"taxes"` + Refunds []RefundResponse `json:"refunds"` } // PayerResponse represents the payer of the payment. type PayerResponse struct { - Type string `json:"type,omitempty"` - ID string `json:"id,omitempty"` - Email string `json:"email,omitempty"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - EntityType string `json:"entity_type,omitempty"` + Type string `json:"type"` + ID string `json:"id"` + Email string `json:"email"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + EntityType string `json:"entity_type"` - Identification *IdentificationResponse `json:"identification,omitempty"` + Identification IdentificationResponse `json:"identification"` } // IdentificationResponse represents payer's personal identification. type IdentificationResponse struct { - Type string `json:"type,omitempty"` - Number string `json:"number,omitempty"` + Type string `json:"type"` + Number string `json:"number"` } // AdditionalInfoResponse represents additional information about a payment. type AdditionalInfoResponse struct { - IPAddress string `json:"ip_address,omitempty"` + IPAddress string `json:"ip_address"` - Payer *AdditionalInfoPayerResponse `json:"payer,omitempty"` - Shipments *ShipmentsResponse `json:"shipments,omitempty"` - Items []ItemResponse `json:"items,omitempty"` + Payer AdditionalInfoPayerResponse `json:"payer"` + Shipments ShipmentsResponse `json:"shipments"` + Items []ItemResponse `json:"items"` } // ItemResponse represents an item. type ItemResponse struct { - ID string `json:"id,omitempty"` - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty"` - PictureURL string `json:"picture_url,omitempty"` - CategoryID string `json:"category_id,omitempty"` - Quantity int `json:"quantity,omitempty"` - UnitPrice float64 `json:"unit_price,omitempty"` + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + PictureURL string `json:"picture_url"` + CategoryID string `json:"category_id"` + Quantity int `json:"quantity"` + UnitPrice float64 `json:"unit_price"` } // AdditionalInfoPayerResponse represents payer's additional information. type AdditionalInfoPayerResponse struct { - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` - RegistrationDate *time.Time `json:"registration_date,omitempty"` - Phone *PhoneResponse `json:"phone,omitempty"` - Address *AddressResponse `json:"address,omitempty"` + RegistrationDate time.Time `json:"registration_date"` + Phone PhoneResponse `json:"phone"` + Address AddressResponse `json:"address"` } // PhoneResponse represents phone information. type PhoneResponse struct { - AreaCode string `json:"area_code,omitempty"` - Number string `json:"number,omitempty"` + AreaCode string `json:"area_code"` + Number string `json:"number"` } // AddressResponse represents address information. type AddressResponse struct { - ZipCode string `json:"zip_code,omitempty"` - StreetName string `json:"street_name,omitempty"` - StreetNumber string `json:"street_number,omitempty"` + ZipCode string `json:"zip_code"` + StreetName string `json:"street_name"` + StreetNumber string `json:"street_number"` } // ShipmentsResponse represents shipment information. type ShipmentsResponse struct { - ReceiverAddress *ReceiverAddressResponse `json:"receiver_address,omitempty"` + ReceiverAddress ReceiverAddressResponse `json:"receiver_address"` } // ReceiverAddressResponse represents the receiver's address within ShipmentsResponse. type ReceiverAddressResponse struct { - StateName string `json:"state_name,omitempty"` - CityName string `json:"city_name,omitempty"` - Floor string `json:"floor,omitempty"` - Apartment string `json:"apartment,omitempty"` + StateName string `json:"state_name"` + CityName string `json:"city_name"` + Floor string `json:"floor"` + Apartment string `json:"apartment"` - Address *AddressResponse `json:"address,omitempty"` + Address AddressResponse `json:"address"` } // OrderResponse represents order information. type OrderResponse struct { - ID int `json:"id,omitempty"` - Type string `json:"type,omitempty"` + ID int `json:"id"` + Type string `json:"type"` } // TransactionDetailsResponse represents transaction details. type TransactionDetailsResponse struct { - FinancialInstitution string `json:"financial_institution,omitempty"` - ExternalResourceURL string `json:"external_resource_url,omitempty"` - PaymentMethodReferenceID string `json:"payment_method_reference_id,omitempty"` - AcquirerReference string `json:"acquirer_reference,omitempty"` - TransactionID string `json:"transaction_id,omitempty"` - NetReceivedAmount float64 `json:"net_received_amount,omitempty"` - TotalPaidAmount float64 `json:"total_paid_amount,omitempty"` - InstallmentAmount float64 `json:"installment_amount,omitempty"` - OverpaidAmount float64 `json:"overpaid_amount,omitempty"` + FinancialInstitution string `json:"financial_institution"` + ExternalResourceURL string `json:"external_resource_url"` + PaymentMethodReferenceID string `json:"payment_method_reference_id"` + AcquirerReference string `json:"acquirer_reference"` + TransactionID string `json:"transaction_id"` + NetReceivedAmount float64 `json:"net_received_amount"` + TotalPaidAmount float64 `json:"total_paid_amount"` + InstallmentAmount float64 `json:"installment_amount"` + OverpaidAmount float64 `json:"overpaid_amount"` } // CardResponse represents card information. type CardResponse struct { - ID string `json:"id,omitempty"` - LastFourDigits string `json:"last_four_digits,omitempty"` - FirstSixDigits string `json:"first_six_digits,omitempty"` - ExpirationYear int `json:"expiration_year,omitempty"` - ExpirationMonth int `json:"expiration_month,omitempty"` + ID string `json:"id"` + LastFourDigits string `json:"last_four_digits"` + FirstSixDigits string `json:"first_six_digits"` + ExpirationYear int `json:"expiration_year"` + ExpirationMonth int `json:"expiration_month"` - DateCreated *time.Time `json:"date_created,omitempty"` - DateLastUpdated *time.Time `json:"date_last_updated,omitempty"` - Cardholder *CardholderResponse `json:"cardholder,omitempty"` + DateCreated time.Time `json:"date_created"` + DateLastUpdated time.Time `json:"date_last_updated"` + Cardholder CardholderResponse `json:"cardholder"` } // CardholderResponse represents cardholder information. type CardholderResponse struct { - Name string `json:"name,omitempty"` + Name string `json:"name"` - Identification *IdentificationResponse `json:"identification,omitempty"` + Identification IdentificationResponse `json:"identification"` } // PointOfInteractionResponse represents point of interaction information. type PointOfInteractionResponse struct { - Type string `json:"type,omitempty"` - SubType string `json:"sub_type,omitempty"` - LinkedTo string `json:"linked_to,omitempty"` + Type string `json:"type"` + SubType string `json:"sub_type"` + LinkedTo string `json:"linked_to"` - ApplicationData *ApplicationDataResponse `json:"application_data,omitempty"` - TransactionData *TransactionDataResponse `json:"transaction_data,omitempty"` + ApplicationData ApplicationDataResponse `json:"application_data"` + TransactionData TransactionDataResponse `json:"transaction_data"` } // ApplicationDataResponse represents application data within PointOfInteractionResponse. type ApplicationDataResponse struct { - Name string `json:"name,omitempty"` - Version string `json:"version,omitempty"` + Name string `json:"name"` + Version string `json:"version"` } // TransactionDataResponse represents transaction data within PointOfInteractionResponse. type TransactionDataResponse struct { - QRCode string `json:"qr_code,omitempty"` - QRCodeBase64 string `json:"qr_code_base64,omitempty"` - TransactionID string `json:"transaction_id,omitempty"` - TicketURL string `json:"ticket_url,omitempty"` - SubscriptionID string `json:"subscription_id,omitempty"` - BillingDate string `json:"billing_date,omitempty"` - BankTransferID int64 `json:"bank_transfer_id,omitempty"` - FinancialInstitution int64 `json:"financial_institution,omitempty"` - FirstTimeUse bool `json:"first_time_use,omitempty"` - - BankInfo *BankInfoResponse `json:"bank_info,omitempty"` - SubscriptionSequence *SubscriptionSequenceResponse `json:"subscription_sequence,omitempty"` - InvoicePeriod *InvoicePeriodResponse `json:"invoice_period,omitempty"` - PaymentReference *PaymentReferenceResponse `json:"payment_reference,omitempty"` + QRCode string `json:"qr_code"` + QRCodeBase64 string `json:"qr_code_base64"` + TransactionID string `json:"transaction_id"` + TicketURL string `json:"ticket_url"` + SubscriptionID string `json:"subscription_id"` + BillingDate string `json:"billing_date"` + BankTransferID int64 `json:"bank_transfer_id"` + FinancialInstitution int64 `json:"financial_institution"` + FirstTimeUse bool `json:"first_time_use"` + + BankInfo BankInfoResponse `json:"bank_info"` + SubscriptionSequence SubscriptionSequenceResponse `json:"subscription_sequence"` + InvoicePeriod InvoicePeriodResponse `json:"invoice_period"` + PaymentReference PaymentReferenceResponse `json:"payment_reference"` } // BankInfoResponse represents bank information. type BankInfoResponse struct { - IsSameBankAccountOwner string `json:"is_same_bank_account_owner,omitempty"` + IsSameBankAccountOwner string `json:"is_same_bank_account_owner"` - Payer *BankInfoPayerResponse `json:"payer,omitempty"` - Collector *BankInfoCollectorResponse `json:"collector,omitempty"` + Payer BankInfoPayerResponse `json:"payer"` + Collector BankInfoCollectorResponse `json:"collector"` } // SubscriptionSequenceResponse represents subscription sequence. type SubscriptionSequenceResponse struct { - Number int64 `json:"number,omitempty"` - Total int64 `json:"total,omitempty"` + Number int64 `json:"number"` + Total int64 `json:"total"` } // InvoicePeriodResponse represents invoice period. type InvoicePeriodResponse struct { - Type string `json:"type,omitempty"` - Period int64 `json:"period,omitempty"` + Type string `json:"type"` + Period int64 `json:"period"` } // PaymentReferenceResponse represents payment reference. type PaymentReferenceResponse struct { - ID string `json:"id,omitempty"` + ID string `json:"id"` } // BankInfoPayerResponse represents payer information within BankInfoResponse. type BankInfoPayerResponse struct { - Email string `json:"email,omitempty"` - LongName string `json:"long_name,omitempty"` - AccountID int64 `json:"account_id,omitempty"` + Email string `json:"email"` + LongName string `json:"long_name"` + AccountID int64 `json:"account_id"` } // BankInfoCollectorResponse represents collector information within BankInfoResponse. type BankInfoCollectorResponse struct { - LongName string `json:"long_name,omitempty"` - AccountID int64 `json:"account_id,omitempty"` + LongName string `json:"long_name"` + AccountID int64 `json:"account_id"` } // PaymentMethodResponse represents payment method information. type PaymentMethodResponse struct { - ID string `json:"id,omitempty"` - Type string `json:"type,omitempty"` - IssuerID string `json:"issuer_id,omitempty"` + ID string `json:"id"` + Type string `json:"type"` + IssuerID string `json:"issuer_id"` - Data *DataResponse `json:"data,omitempty"` + Data DataResponse `json:"data"` } // DataResponse represents data within PaymentMethodResponse. type DataResponse struct { - Rules *RulesResponse `json:"rules,omitempty"` + Rules RulesResponse `json:"rules"` } // RulesResponse represents payment rules. type RulesResponse struct { - Fine *FeeResponse `json:"fine,omitempty"` - Interest *FeeResponse `json:"interest,omitempty"` - Discounts []DiscountResponse `json:"discounts,omitempty"` + Fine FeeResponse `json:"fine"` + Interest FeeResponse `json:"interest"` + Discounts []DiscountResponse `json:"discounts"` } // DiscountResponse represents payment discount information. type DiscountResponse struct { - Type string `json:"type,omitempty"` - Value float64 `json:"value,omitempty"` + Type string `json:"type"` + Value float64 `json:"value"` - LimitDate *time.Time `json:"limit_date,omitempty"` + LimitDate time.Time `json:"limit_date"` } // FeeResponse represents payment fee information. type FeeResponse struct { - Type string `json:"type,omitempty"` - Value float64 `json:"value,omitempty"` + Type string `json:"type"` + Value float64 `json:"value"` } // ThreeDSInfoResponse represents 3DS (Three-Domain Secure) information. type ThreeDSInfoResponse struct { - ExternalResourceURL string `json:"external_resource_url,omitempty"` - Creq string `json:"creq,omitempty"` + ExternalResourceURL string `json:"external_resource_url"` + Creq string `json:"creq"` } // FeeDetailResponse represents payment fee detail information. type FeeDetailResponse struct { - Type string `json:"type,omitempty"` - FeePayer string `json:"fee_payer,omitempty"` - Amount float64 `json:"amount,omitempty"` + Type string `json:"type"` + FeePayer string `json:"fee_payer"` + Amount float64 `json:"amount"` } // TaxResponse represents tax information. type TaxResponse struct { - Type string `json:"type,omitempty"` - Value float64 `json:"value,omitempty"` + Type string `json:"type"` + Value float64 `json:"value"` } // RefundResponse represents refund information. type RefundResponse struct { - Status string `json:"status,omitempty"` - RefundMode string `json:"refund_mode,omitempty"` - Reason string `json:"reason,omitempty"` - UniqueSequenceNumber string `json:"unique_sequence_number,omitempty"` - ID int64 `json:"id,omitempty"` - PaymentID int64 `json:"payment_id,omitempty"` - Amount float64 `json:"amount,omitempty"` - AdjustmentAmount float64 `json:"adjustment_amount,omitempty"` + Status string `json:"status"` + RefundMode string `json:"refund_mode"` + Reason string `json:"reason"` + UniqueSequenceNumber string `json:"unique_sequence_number"` + ID int64 `json:"id"` + PaymentID int64 `json:"payment_id"` + Amount float64 `json:"amount"` + AdjustmentAmount float64 `json:"adjustment_amount"` - DateCreated *time.Time `json:"date_created,omitempty"` - Source *SourceResponse `json:"source,omitempty"` + DateCreated time.Time `json:"date_created"` + Source SourceResponse `json:"source"` } // SourceResponse represents source information. type SourceResponse struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` } diff --git a/resources/mocks/payment/search_response.json b/resources/mocks/payment/search_response.json index 5b9f31fd..6f8d8ef2 100644 --- a/resources/mocks/payment/search_response.json +++ b/resources/mocks/payment/search_response.json @@ -6,12 +6,12 @@ }, "results": [ { - "id": 123, + "id": 57592046572, "status": "approved", "status_detail": "accredited" }, { - "id": 456, + "id": 57592038796, "status": "pending", "status_detail": "pending_waiting_transfer" } From 200783fbb577c25b5be6eb02e56b9ae071fae671 Mon Sep 17 00:00:00 2001 From: Lucas Mantovani Date: Fri, 2 Feb 2024 14:11:27 -0300 Subject: [PATCH 27/55] Add CI workflow --- .github/workflows/ci.yml | 48 ++++++++++++++++++++++++++++++++++++++++ .testcoverage.yml | 10 +++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .testcoverage.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..df60768d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "**" ] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + cache: false + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.55.2 + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + cache: false + + - name: Test + uses: robherley/go-test-action@v0.1.0 + with: + testArguments: './pkg/... -coverprofile=./cover.out' + + - name: Check coverage + uses: vladopajic/go-test-coverage@v2 + if: always() + with: + config: ./.testcoverage.yml \ No newline at end of file diff --git a/.testcoverage.yml b/.testcoverage.yml new file mode 100644 index 00000000..f09fcb5c --- /dev/null +++ b/.testcoverage.yml @@ -0,0 +1,10 @@ +profile: cover.out +threshold: + file: 80 + package: 80 + total: 80 + +exclude: + paths: + - ^pkg/internal + - ^pkg/config From dd061c455211045e6afe859920b3f4864f16c10c Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Wed, 7 Feb 2024 15:01:43 -0300 Subject: [PATCH 28/55] merge with develop and adjust test --- pkg/payment/payment_test.go | 13 ++++++++----- pkg/paymentmethod/client_test.go | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pkg/payment/payment_test.go b/pkg/payment/payment_test.go index 4488cd0f..00e7f7c4 100644 --- a/pkg/payment/payment_test.go +++ b/pkg/payment/payment_test.go @@ -223,7 +223,7 @@ func TestSearch(t *testing.T) { ctx: context.Background(), }, want: nil, - wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + wantErr: "invalid character 'i' looking for beginning of value", }, { name: "should_return_formatted_response", @@ -242,6 +242,9 @@ func TestSearch(t *testing.T) { }, args: args{ ctx: context.Background(), + dto: SearchRequest{ + Limit: "30", + }, }, want: &SearchResponse{ Paging: PagingResponse{ @@ -348,7 +351,7 @@ func TestGet(t *testing.T) { ctx: context.Background(), }, want: nil, - wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + wantErr: "invalid character 'i' looking for beginning of value", }, { name: "should_return_formatted_response", @@ -459,7 +462,7 @@ func TestCancel(t *testing.T) { ctx: context.Background(), }, want: nil, - wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + wantErr: "invalid character 'i' looking for beginning of value", }, { name: "should_return_formatted_response", @@ -570,7 +573,7 @@ func TestCapture(t *testing.T) { ctx: context.Background(), }, want: nil, - wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + wantErr: "invalid character 'i' looking for beginning of value", }, { name: "should_return_formatted_response", @@ -682,7 +685,7 @@ func TestCaptureAmount(t *testing.T) { ctx: context.Background(), }, want: nil, - wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + wantErr: "invalid character 'i' looking for beginning of value", }, { name: "should_return_formatted_response", diff --git a/pkg/paymentmethod/client_test.go b/pkg/paymentmethod/client_test.go index cd11e2eb..359f2a21 100644 --- a/pkg/paymentmethod/client_test.go +++ b/pkg/paymentmethod/client_test.go @@ -80,7 +80,7 @@ func TestList(t *testing.T) { ctx: context.Background(), }, want: nil, - wantErr: "error unmarshaling response: invalid character 'i' looking for beginning of value", + wantErr: "invalid character 'i' looking for beginning of value", }, { name: "should_return_formatted_response", @@ -129,7 +129,7 @@ func TestList(t *testing.T) { if err != nil { gotErr = err.Error() } - + if gotErr != tt.wantErr { t.Errorf("client.List() error = %v, wantErr %v", err, tt.wantErr) } From 5c0cbdbbc1ca9e77ee4472719b5941425ba2d1e5 Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Wed, 7 Feb 2024 15:18:03 -0300 Subject: [PATCH 29/55] add lint suggestion --- test/integration/payment/payment_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/payment/payment_test.go b/test/integration/payment/payment_test.go index d984bc78..43f4c30b 100644 --- a/test/integration/payment/payment_test.go +++ b/test/integration/payment/payment_test.go @@ -32,7 +32,7 @@ func TestPayment(t *testing.T) { if result == nil { t.Error("result can't be nil") } - if result.ID == 0 { + if result != nil && result.ID == 0 { t.Error("id can't be nil") } if err != nil { @@ -89,7 +89,7 @@ func TestPayment(t *testing.T) { if result == nil { t.Error("result can't be nil") } - if result.ID == 0 { + if result != nil && result.ID == 0 { t.Error("id can't be nil") } if err != nil { @@ -124,7 +124,7 @@ func TestPayment(t *testing.T) { if result == nil { t.Error("result can't be nil") } - if result.ID == 0 { + if result != nil && result.ID == 0 { t.Error("id can't be nil") } if err != nil { From 473cebb630b427df1abec45a7fa508f1ff8f85ef Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Wed, 7 Feb 2024 15:24:45 -0300 Subject: [PATCH 30/55] add lint suggestion --- test/integration/payment/payment_test.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/integration/payment/payment_test.go b/test/integration/payment/payment_test.go index 43f4c30b..1921eaff 100644 --- a/test/integration/payment/payment_test.go +++ b/test/integration/payment/payment_test.go @@ -31,8 +31,9 @@ func TestPayment(t *testing.T) { result, err := client.Create(context.Background(), dto) if result == nil { t.Error("result can't be nil") + return } - if result != nil && result.ID == 0 { + if result.ID == 0 { t.Error("id can't be nil") } if err != nil { @@ -80,6 +81,7 @@ func TestPayment(t *testing.T) { result, err := client.Create(context.Background(), dto) if result == nil { t.Error("result can't be nil") + return } if err != nil { t.Errorf(err.Error()) @@ -88,8 +90,9 @@ func TestPayment(t *testing.T) { result, err = client.Get(context.Background(), result.ID) if result == nil { t.Error("result can't be nil") + return } - if result != nil && result.ID == 0 { + if result.ID == 0 { t.Error("id can't be nil") } if err != nil { @@ -115,6 +118,7 @@ func TestPayment(t *testing.T) { result, err := client.Create(context.Background(), dto) if result == nil { t.Error("result can't be nil") + return } if err != nil { t.Errorf(err.Error()) @@ -123,8 +127,9 @@ func TestPayment(t *testing.T) { result, err = client.Cancel(context.Background(), result.ID) if result == nil { t.Error("result can't be nil") + return } - if result != nil && result.ID == 0 { + if result.ID == 0 { t.Error("id can't be nil") } if err != nil { @@ -157,6 +162,7 @@ func TestPayment(t *testing.T) { result, err := client.Create(context.Background(), dto) if result == nil { t.Error("result can't be nil") + return } if err != nil { t.Errorf(err.Error()) From 1646ac0ebdd50ef10a0afe9418c689e0064660d4 Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Wed, 7 Feb 2024 17:35:17 -0300 Subject: [PATCH 31/55] merge with feature payment --- pkg/user/user.go | 21 ++++----------------- pkg/user/user_test.go | 8 ++++---- test/integration/user/user_test.go | 2 +- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/pkg/user/user.go b/pkg/user/user.go index 214e515a..2b6e8812 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -2,9 +2,6 @@ package user import ( "context" - "encoding/json" - "fmt" - "net/http" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/internal/httpclient" @@ -21,31 +18,21 @@ type Client interface { // client is the implementation of Client. type client struct { - config *config.Config + cfg *config.Config } // NewClient returns a new User API Client. func NewClient(c *config.Config) Client { return &client{ - config: c, + cfg: c, } } func (c *client) Get(ctx context.Context) (*Response, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + res, err := httpclient.Get[Response](ctx, c.cfg, url) if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - - res, err := httpclient.Send(ctx, c.config, req) - if err != nil { - return nil, err - } - - var formatted *Response - if err := json.Unmarshal(res, &formatted); err != nil { return nil, err } - return formatted, nil + return res, nil } diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go index 14aca159..3ce43c7b 100644 --- a/pkg/user/user_test.go +++ b/pkg/user/user_test.go @@ -48,7 +48,7 @@ func TestGet(t *testing.T) { name: "should_return_error_when_send_request", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { return nil, fmt.Errorf("some error") }, @@ -65,7 +65,7 @@ func TestGet(t *testing.T) { name: "should_return_error_unmarshal_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader("invalid json") stringReadCloser := io.NopCloser(stringReader) @@ -86,7 +86,7 @@ func TestGet(t *testing.T) { name: "should_return_formatted_response", fields: fields{ config: &config.Config{ - HTTPClient: &httpclient.Mock{ + Requester: &httpclient.Mock{ DoMock: func(req *http.Request) (*http.Response, error) { stringReader := strings.NewReader(string(userResponse)) stringReadCloser := io.NopCloser(stringReader) @@ -115,7 +115,7 @@ func TestGet(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &client{ - config: tt.fields.config, + cfg: tt.fields.config, } got, err := c.Get(tt.args.ctx) gotErr := "" diff --git a/test/integration/user/user_test.go b/test/integration/user/user_test.go index 8ee2ee38..4bec5712 100644 --- a/test/integration/user/user_test.go +++ b/test/integration/user/user_test.go @@ -11,7 +11,7 @@ import ( func TestUser(t *testing.T) { t.Run("should_get_user_information", func(t *testing.T) { - c, err := config.New(os.Getenv("at")) + c, err := config.New(os.Getenv("ACCESS_TOKEN")) if err != nil { t.Fatal(err) } From 742c2d0cd41b7bd06c36b16b88cb8f871192b4e2 Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Thu, 8 Feb 2024 16:04:04 -0300 Subject: [PATCH 32/55] change client name --- pkg/user/{user.go => client.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/user/{user.go => client.go} (100%) diff --git a/pkg/user/user.go b/pkg/user/client.go similarity index 100% rename from pkg/user/user.go rename to pkg/user/client.go From 28e7fb167c17ec039eaaf701685862d2d904a6aa Mon Sep 17 00:00:00 2001 From: edmarSoaress Date: Thu, 8 Feb 2024 16:09:42 -0300 Subject: [PATCH 33/55] change name of client test --- test/integration/user/user_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/user/user_test.go b/test/integration/user/user_test.go index 4bec5712..1c291629 100644 --- a/test/integration/user/user_test.go +++ b/test/integration/user/user_test.go @@ -16,8 +16,8 @@ func TestUser(t *testing.T) { t.Fatal(err) } - pmc := user.NewClient(c) - res, err := pmc.Get(context.Background()) + client := user.NewClient(c) + res, err := client.Get(context.Background()) if res == nil { t.Error("res can't be nil") From 6bf8ac52a63b4d8b4c5eec86eb8778e03296f001 Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Wed, 7 Feb 2024 12:03:57 -0300 Subject: [PATCH 34/55] Card token - Initial version --- examples/apis/cardtoken/create/main.go | 28 ++++++++++ examples/apis/cardtoken/get/main.go | 28 ++++++++++ pkg/cardtoken/client.go | 74 ++++++++++++++++++++++++++ pkg/cardtoken/request.go | 16 ++++++ pkg/cardtoken/response.go | 27 ++++++++++ 5 files changed, 173 insertions(+) create mode 100644 examples/apis/cardtoken/create/main.go create mode 100644 examples/apis/cardtoken/get/main.go create mode 100644 pkg/cardtoken/client.go create mode 100644 pkg/cardtoken/request.go create mode 100644 pkg/cardtoken/response.go diff --git a/examples/apis/cardtoken/create/main.go b/examples/apis/cardtoken/create/main.go new file mode 100644 index 00000000..7a5eed0b --- /dev/null +++ b/examples/apis/cardtoken/create/main.go @@ -0,0 +1,28 @@ +package create + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/cardtoken" + "github.com/mercadopago/sdk-go/pkg/config" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + client := cardtoken.NewClient(cfg) + + result, err := client.Create(context.Background(), cardtoken.Request{}) + if err != nil { + return + } + + fmt.Println(result) +} diff --git a/examples/apis/cardtoken/get/main.go b/examples/apis/cardtoken/get/main.go new file mode 100644 index 00000000..76ecc2a0 --- /dev/null +++ b/examples/apis/cardtoken/get/main.go @@ -0,0 +1,28 @@ +package get + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/cardtoken" + "github.com/mercadopago/sdk-go/pkg/config" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + client := cardtoken.NewClient(cfg) + + result, err := client.Get(context.Background(), "123") + if err != nil { + return + } + + fmt.Println(result) +} diff --git a/pkg/cardtoken/client.go b/pkg/cardtoken/client.go new file mode 100644 index 00000000..0496fcd1 --- /dev/null +++ b/pkg/cardtoken/client.go @@ -0,0 +1,74 @@ +package cardtoken + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/internal/httpclient" +) + +const ( + baseURL = "https://api.mercadopago.com/v1/" + urlGet = baseURL + "/v1/card_tokens/{id}" + urlPost = baseURL + "/v1/card_tokens" +) + +type Client interface { + Get(ctx context.Context, id string) (*Response, error) + Create(ctx context.Context, request Request) (*Response, error) +} + +type client struct { + cfg *config.Config +} + +func NewClient(c *config.Config) Client { + return &client{cfg: c} +} + +func (c *client) Get(ctx context.Context, id string) (*Response, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlGet, nil) + if err != nil { + return nil, err + } + + r, err := httpclient.Send(ctx, c.cfg, req) + if err != nil { + return nil, err + } + + var res *Response + if err := json.Unmarshal(r, res); err != nil { + return nil, fmt.Errorf("error unmarshaling card token response: %w", err) + } + + return res, nil +} + +func (c *client) Create(ctx context.Context, request Request) (*Response, error) { + body, err := json.Marshal(&request) + if err != nil { + return nil, fmt.Errorf("error marshaling card token request: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, urlPost, strings.NewReader(string(body))) + if err != nil { + return nil, fmt.Errorf("error creating card token request: %w", err) + } + + r, err := httpclient.Send(ctx, c.cfg, req) + if err != nil { + return nil, err + } + + var res *Response + if err := json.Unmarshal(r, res); err != nil { + return nil, fmt.Errorf("error unmarshaling card token response: %w", err) + } + + return res, nil +} diff --git a/pkg/cardtoken/request.go b/pkg/cardtoken/request.go new file mode 100644 index 00000000..66b7f3c6 --- /dev/null +++ b/pkg/cardtoken/request.go @@ -0,0 +1,16 @@ +package cardtoken + +type Request struct { + SiteId string `json:"site_id"` + CardNumber string `json:"card_number"` + ExpirationYear string `json:"expiration_year"` + ExpirationMonth string `json:"expiration_month"` + SecurityCode string `json:"security_code"` + Cardholder struct { + Identification struct { + Type string `json:"type"` + Number string `json:"number"` + } `json:"identification"` + Name string `json:"name"` + } `json:"cardholder"` +} diff --git a/pkg/cardtoken/response.go b/pkg/cardtoken/response.go new file mode 100644 index 00000000..36e54f5f --- /dev/null +++ b/pkg/cardtoken/response.go @@ -0,0 +1,27 @@ +package cardtoken + +import "time" + +type Response struct { + Id string `json:"id"` + FirstSixDigits string `json:"first_six_digits"` + LastFourDigits string `json:"last_four_digits"` + Status string `json:"status"` + LuhnValidation bool `json:"luhn_validation"` + LiveMode bool `json:"live_mode"` + RequireEsc bool `json:"require_esc"` + ExpirationMonth int `json:"expiration_month"` + ExpirationYear int `json:"expiration_year"` + CardNumberLength int `json:"card_number_length"` + SecurityCodeLength int `json:"security_code_length"` + DateCreated time.Time `json:"date_created"` + DateLastUpdated time.Time `json:"date_last_updated"` + DateDue time.Time `json:"date_due"` + Cardholder struct { + Identification struct { + Number string `json:"number"` + Type string `json:"type"` + } `json:"identification"` + Name string `json:"name"` + } `json:"cardholder"` +} From 83023289efbf943df1a47d19632dd4b4d7b107f9 Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Wed, 7 Feb 2024 14:57:15 -0300 Subject: [PATCH 35/55] Card token - refacting client --- pkg/cardtoken/client.go | 37 ++++--------------------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/pkg/cardtoken/client.go b/pkg/cardtoken/client.go index 0496fcd1..94b0b4b1 100644 --- a/pkg/cardtoken/client.go +++ b/pkg/cardtoken/client.go @@ -2,9 +2,7 @@ package cardtoken import ( "context" - "encoding/json" "fmt" - "net/http" "strings" "github.com/mercadopago/sdk-go/pkg/config" @@ -31,44 +29,17 @@ func NewClient(c *config.Config) Client { } func (c *client) Get(ctx context.Context, id string) (*Response, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlGet, nil) + res, err := httpclient.Get[Response](ctx, c.cfg, strings.Replace(urlGet, "{id}", id, 1)) if err != nil { - return nil, err + return nil, fmt.Errorf("error get card token: %w", err) } - - r, err := httpclient.Send(ctx, c.cfg, req) - if err != nil { - return nil, err - } - - var res *Response - if err := json.Unmarshal(r, res); err != nil { - return nil, fmt.Errorf("error unmarshaling card token response: %w", err) - } - return res, nil } func (c *client) Create(ctx context.Context, request Request) (*Response, error) { - body, err := json.Marshal(&request) + res, err := httpclient.Post[Response](ctx, c.cfg, urlPost, request) if err != nil { - return nil, fmt.Errorf("error marshaling card token request: %w", err) + return nil, fmt.Errorf("error create card token: %w", err) } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, urlPost, strings.NewReader(string(body))) - if err != nil { - return nil, fmt.Errorf("error creating card token request: %w", err) - } - - r, err := httpclient.Send(ctx, c.cfg, req) - if err != nil { - return nil, err - } - - var res *Response - if err := json.Unmarshal(r, res); err != nil { - return nil, fmt.Errorf("error unmarshaling card token response: %w", err) - } - return res, nil } From e36ea55e0df43459339df4244d266566c1b73ea2 Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Thu, 8 Feb 2024 10:56:18 -0300 Subject: [PATCH 36/55] Card token - add unit tests --- pkg/cardtoken/client_test.go | 171 ++++++++++++++++++++++++ pkg/cardtoken/mock.go | 35 +++++ pkg/cardtoken/request.go | 28 ++-- pkg/cardtoken/response.go | 36 +++-- resources/mocks/cardtoken/response.json | 23 ++++ 5 files changed, 260 insertions(+), 33 deletions(-) create mode 100644 pkg/cardtoken/client_test.go create mode 100644 pkg/cardtoken/mock.go create mode 100644 resources/mocks/cardtoken/response.json diff --git a/pkg/cardtoken/client_test.go b/pkg/cardtoken/client_test.go new file mode 100644 index 00000000..402f3929 --- /dev/null +++ b/pkg/cardtoken/client_test.go @@ -0,0 +1,171 @@ +package cardtoken + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "reflect" + "strings" + "testing" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/internal/httpclient" +) + +var ( + cardTokenResponseJSON, _ = os.Open("../../resources/mocks/cardtoken/response.json") + cardTokenResponse, _ = io.ReadAll(cardTokenResponseJSON) +) + +func TestCreate(t *testing.T) { + type fields struct { + cfg *config.Config + } + type args struct { + ctx context.Context + request Request + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr string + }{ + { + name: "should_create_card_token", + fields: fields{ + cfg: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(cardTokenResponse)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: mockCardToken(), + wantErr: "", + }, + { + name: "should_fail_create_card_token", + fields: fields{ + cfg: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "error create card token: transport level error: some error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + cfg: tt.fields.cfg, + } + got, err := c.Create(tt.args.ctx, tt.args.request) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("card token client.Create() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("card token client.Create() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGet(t *testing.T) { + type fields struct { + cfg *config.Config + } + type args struct { + ctx context.Context + id string + } + tests := []struct { + name string + fields fields + args args + want *Response + wantErr string + }{ + { + name: "should_get_card_token", + fields: fields{ + cfg: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + stringReader := strings.NewReader(string(cardTokenResponse)) + stringReadCloser := io.NopCloser(stringReader) + return &http.Response{ + Body: stringReadCloser, + }, nil + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + id: "123", + }, + want: mockCardToken(), + wantErr: "", + }, + { + name: "should_fail_get_card_token", + fields: fields{ + cfg: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + }, + want: nil, + wantErr: "error get card token: transport level error: some error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + cfg: tt.fields.cfg, + } + got, err := c.Get(tt.args.ctx, tt.args.id) + gotErr := "" + if err != nil { + gotErr = err.Error() + } + + if gotErr != tt.wantErr { + t.Errorf("card token client.Get() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("card token client.Get() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/cardtoken/mock.go b/pkg/cardtoken/mock.go new file mode 100644 index 00000000..b285a7b7 --- /dev/null +++ b/pkg/cardtoken/mock.go @@ -0,0 +1,35 @@ +package cardtoken + +import ( + "time" +) + +func mockCardToken() *Response { + return &Response{ + Id: "3d40b34eb41a6d0923e5bc545927c2e9", + FirstSixDigits: "123456", + ExpirationMonth: 11, + ExpirationYear: 2030, + LastFourDigits: "1234", + Cardholder: Cardholder{ + Identification: Identification{ + Number: "12345678901", + Type: "CPF", + }, + Name: "APRO", + }, + Status: "active", + DateCreated: parseDate("2024-02-08T09:05:42.725-04:00"), + DateLastUpdated: parseDate("2024-02-08T09:05:42.725-04:00"), + DateDue: parseDate("2024-02-16T09:05:42.725-04:00"), + LuhnValidation: true, + LiveMode: false, + CardNumberLength: 16, + SecurityCodeLength: 3, + } +} + +func parseDate(s string) time.Time { + d, _ := time.Parse(time.RFC3339, s) + return d +} diff --git a/pkg/cardtoken/request.go b/pkg/cardtoken/request.go index 66b7f3c6..bf1b02a0 100644 --- a/pkg/cardtoken/request.go +++ b/pkg/cardtoken/request.go @@ -1,16 +1,20 @@ package cardtoken type Request struct { - SiteId string `json:"site_id"` - CardNumber string `json:"card_number"` - ExpirationYear string `json:"expiration_year"` - ExpirationMonth string `json:"expiration_month"` - SecurityCode string `json:"security_code"` - Cardholder struct { - Identification struct { - Type string `json:"type"` - Number string `json:"number"` - } `json:"identification"` - Name string `json:"name"` - } `json:"cardholder"` + SiteId string `json:"site_id"` + CardNumber string `json:"card_number"` + ExpirationYear string `json:"expiration_year"` + ExpirationMonth string `json:"expiration_month"` + SecurityCode string `json:"security_code"` + Cardholder Cardholder `json:"cardholder"` +} + +type Cardholder struct { + Identification Identification `json:"identification"` + Name string `json:"name"` +} + +type Identification struct { + Number string `json:"number"` + Type string `json:"type"` } diff --git a/pkg/cardtoken/response.go b/pkg/cardtoken/response.go index 36e54f5f..3581806f 100644 --- a/pkg/cardtoken/response.go +++ b/pkg/cardtoken/response.go @@ -3,25 +3,19 @@ package cardtoken import "time" type Response struct { - Id string `json:"id"` - FirstSixDigits string `json:"first_six_digits"` - LastFourDigits string `json:"last_four_digits"` - Status string `json:"status"` - LuhnValidation bool `json:"luhn_validation"` - LiveMode bool `json:"live_mode"` - RequireEsc bool `json:"require_esc"` - ExpirationMonth int `json:"expiration_month"` - ExpirationYear int `json:"expiration_year"` - CardNumberLength int `json:"card_number_length"` - SecurityCodeLength int `json:"security_code_length"` - DateCreated time.Time `json:"date_created"` - DateLastUpdated time.Time `json:"date_last_updated"` - DateDue time.Time `json:"date_due"` - Cardholder struct { - Identification struct { - Number string `json:"number"` - Type string `json:"type"` - } `json:"identification"` - Name string `json:"name"` - } `json:"cardholder"` + Id string `json:"id"` + FirstSixDigits string `json:"first_six_digits"` + LastFourDigits string `json:"last_four_digits"` + Status string `json:"status"` + LuhnValidation bool `json:"luhn_validation"` + LiveMode bool `json:"live_mode"` + RequireEsc bool `json:"require_esc"` + ExpirationMonth int `json:"expiration_month"` + ExpirationYear int `json:"expiration_year"` + CardNumberLength int `json:"card_number_length"` + SecurityCodeLength int `json:"security_code_length"` + DateCreated time.Time `json:"date_created"` + DateLastUpdated time.Time `json:"date_last_updated"` + DateDue time.Time `json:"date_due"` + Cardholder Cardholder `json:"cardholder"` } diff --git a/resources/mocks/cardtoken/response.json b/resources/mocks/cardtoken/response.json new file mode 100644 index 00000000..d3380a08 --- /dev/null +++ b/resources/mocks/cardtoken/response.json @@ -0,0 +1,23 @@ +{ + "id": "3d40b34eb41a6d0923e5bc545927c2e9", + "first_six_digits": "123456", + "expiration_month": 11, + "expiration_year": 2030, + "last_four_digits": "1234", + "cardholder": { + "identification": { + "number": "12345678901", + "type": "CPF" + }, + "name": "APRO" + }, + "status": "active", + "date_created": "2024-02-08T09:05:42.725-04:00", + "date_last_updated": "2024-02-08T09:05:42.725-04:00", + "date_due": "2024-02-16T09:05:42.725-04:00", + "luhn_validation": true, + "live_mode": false, + "require_esc": false, + "card_number_length": 16, + "security_code_length": 3 +} \ No newline at end of file From fa4c4de9a5ffadab62f737ef03c700164f02a4d5 Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Thu, 8 Feb 2024 16:18:52 -0300 Subject: [PATCH 37/55] Card token - add integrated test --- pkg/cardtoken/client.go | 16 +----- pkg/cardtoken/client_test.go | 76 ------------------------- pkg/cardtoken/mock.go | 29 ++++++++-- pkg/cardtoken/request.go | 2 +- pkg/cardtoken/response.go | 2 +- resources/mocks/cardtoken/response.json | 10 ++-- test/integration/cardtoken_test.go | 29 ++++++++++ 7 files changed, 61 insertions(+), 103 deletions(-) create mode 100644 test/integration/cardtoken_test.go diff --git a/pkg/cardtoken/client.go b/pkg/cardtoken/client.go index 94b0b4b1..60064460 100644 --- a/pkg/cardtoken/client.go +++ b/pkg/cardtoken/client.go @@ -3,20 +3,16 @@ package cardtoken import ( "context" "fmt" - "strings" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/internal/httpclient" ) const ( - baseURL = "https://api.mercadopago.com/v1/" - urlGet = baseURL + "/v1/card_tokens/{id}" - urlPost = baseURL + "/v1/card_tokens" + url = "https://api.mercadopago.com/v1/card_tokens" ) type Client interface { - Get(ctx context.Context, id string) (*Response, error) Create(ctx context.Context, request Request) (*Response, error) } @@ -28,16 +24,8 @@ func NewClient(c *config.Config) Client { return &client{cfg: c} } -func (c *client) Get(ctx context.Context, id string) (*Response, error) { - res, err := httpclient.Get[Response](ctx, c.cfg, strings.Replace(urlGet, "{id}", id, 1)) - if err != nil { - return nil, fmt.Errorf("error get card token: %w", err) - } - return res, nil -} - func (c *client) Create(ctx context.Context, request Request) (*Response, error) { - res, err := httpclient.Post[Response](ctx, c.cfg, urlPost, request) + res, err := httpclient.Post[Response](ctx, c.cfg, url, request) if err != nil { return nil, fmt.Errorf("error create card token: %w", err) } diff --git a/pkg/cardtoken/client_test.go b/pkg/cardtoken/client_test.go index 402f3929..0d59be0f 100644 --- a/pkg/cardtoken/client_test.go +++ b/pkg/cardtoken/client_test.go @@ -93,79 +93,3 @@ func TestCreate(t *testing.T) { }) } } - -func TestGet(t *testing.T) { - type fields struct { - cfg *config.Config - } - type args struct { - ctx context.Context - id string - } - tests := []struct { - name string - fields fields - args args - want *Response - wantErr string - }{ - { - name: "should_get_card_token", - fields: fields{ - cfg: &config.Config{ - Requester: &httpclient.Mock{ - DoMock: func(req *http.Request) (*http.Response, error) { - stringReader := strings.NewReader(string(cardTokenResponse)) - stringReadCloser := io.NopCloser(stringReader) - return &http.Response{ - Body: stringReadCloser, - }, nil - }, - }, - }, - }, - args: args{ - ctx: context.Background(), - id: "123", - }, - want: mockCardToken(), - wantErr: "", - }, - { - name: "should_fail_get_card_token", - fields: fields{ - cfg: &config.Config{ - Requester: &httpclient.Mock{ - DoMock: func(req *http.Request) (*http.Response, error) { - return nil, fmt.Errorf("some error") - }, - }, - }, - }, - args: args{ - ctx: context.Background(), - }, - want: nil, - wantErr: "error get card token: transport level error: some error", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := &client{ - cfg: tt.fields.cfg, - } - got, err := c.Get(tt.args.ctx, tt.args.id) - gotErr := "" - if err != nil { - gotErr = err.Error() - } - - if gotErr != tt.wantErr { - t.Errorf("card token client.Get() error = %v, wantErr %v", err, tt.wantErr) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("card token client.Get() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/cardtoken/mock.go b/pkg/cardtoken/mock.go index b285a7b7..d796e54a 100644 --- a/pkg/cardtoken/mock.go +++ b/pkg/cardtoken/mock.go @@ -6,17 +6,17 @@ import ( func mockCardToken() *Response { return &Response{ - Id: "3d40b34eb41a6d0923e5bc545927c2e9", - FirstSixDigits: "123456", + ID: "3d40b34eb41a6d0923e5bc545927c2e9", + FirstSixDigits: "503143", ExpirationMonth: 11, - ExpirationYear: 2030, - LastFourDigits: "1234", + ExpirationYear: 2025, + LastFourDigits: "6351", Cardholder: Cardholder{ Identification: Identification{ - Number: "12345678901", + Number: "70383868084", Type: "CPF", }, - Name: "APRO", + Name: "MASTER TEST", }, Status: "active", DateCreated: parseDate("2024-02-08T09:05:42.725-04:00"), @@ -29,6 +29,23 @@ func mockCardToken() *Response { } } +func MockCardTokenRequest() Request { + return Request{ + SiteID: "Teste", + CardNumber: "5031433215406351", + ExpirationMonth: "11", + ExpirationYear: "2025", + SecurityCode: "123", + Cardholder: Cardholder{ + Identification: Identification{ + Type: "CPF", + Number: "70383868084", + }, + Name: "MASTER TEST", + }, + } +} + func parseDate(s string) time.Time { d, _ := time.Parse(time.RFC3339, s) return d diff --git a/pkg/cardtoken/request.go b/pkg/cardtoken/request.go index bf1b02a0..f549a08e 100644 --- a/pkg/cardtoken/request.go +++ b/pkg/cardtoken/request.go @@ -1,7 +1,7 @@ package cardtoken type Request struct { - SiteId string `json:"site_id"` + SiteID string `json:"site_id"` CardNumber string `json:"card_number"` ExpirationYear string `json:"expiration_year"` ExpirationMonth string `json:"expiration_month"` diff --git a/pkg/cardtoken/response.go b/pkg/cardtoken/response.go index 3581806f..c78b0577 100644 --- a/pkg/cardtoken/response.go +++ b/pkg/cardtoken/response.go @@ -3,7 +3,7 @@ package cardtoken import "time" type Response struct { - Id string `json:"id"` + ID string `json:"id"` FirstSixDigits string `json:"first_six_digits"` LastFourDigits string `json:"last_four_digits"` Status string `json:"status"` diff --git a/resources/mocks/cardtoken/response.json b/resources/mocks/cardtoken/response.json index d3380a08..1b5306b2 100644 --- a/resources/mocks/cardtoken/response.json +++ b/resources/mocks/cardtoken/response.json @@ -1,15 +1,15 @@ { "id": "3d40b34eb41a6d0923e5bc545927c2e9", - "first_six_digits": "123456", + "first_six_digits": "503143", "expiration_month": 11, - "expiration_year": 2030, - "last_four_digits": "1234", + "expiration_year": 2025, + "last_four_digits": "6351", "cardholder": { "identification": { - "number": "12345678901", + "number": "70383868084", "type": "CPF" }, - "name": "APRO" + "name": "MASTER TEST" }, "status": "active", "date_created": "2024-02-08T09:05:42.725-04:00", diff --git a/test/integration/cardtoken_test.go b/test/integration/cardtoken_test.go new file mode 100644 index 00000000..3764436f --- /dev/null +++ b/test/integration/cardtoken_test.go @@ -0,0 +1,29 @@ +package integration + +import ( + "context" + "os" + "testing" + + "github.com/mercadopago/sdk-go/pkg/cardtoken" + "github.com/mercadopago/sdk-go/pkg/config" +) + +func TestCardToken(t *testing.T) { + t.Run("should_create_card_token", func(t *testing.T) { + cfg, err := config.New(os.Getenv("ACCESS_TOKEN")) + if err != nil { + t.Fatal(err) + } + + client := cardtoken.NewClient(cfg) + res, err := client.Create(context.Background(), cardtoken.MockCardTokenRequest()) + + if res == nil { + t.Error("res can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + }) +} From e4b75616ae56177b5b93e5b8cfc6ca7e0263446e Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Thu, 8 Feb 2024 16:22:32 -0300 Subject: [PATCH 38/55] Card token - add integrated test --- pkg/cardtoken/mock.go | 4 ++-- pkg/cardtoken/response.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/cardtoken/mock.go b/pkg/cardtoken/mock.go index d796e54a..8a17b43e 100644 --- a/pkg/cardtoken/mock.go +++ b/pkg/cardtoken/mock.go @@ -46,7 +46,7 @@ func MockCardTokenRequest() Request { } } -func parseDate(s string) time.Time { +func parseDate(s string) *time.Time { d, _ := time.Parse(time.RFC3339, s) - return d + return &d } diff --git a/pkg/cardtoken/response.go b/pkg/cardtoken/response.go index c78b0577..2c2ae3a0 100644 --- a/pkg/cardtoken/response.go +++ b/pkg/cardtoken/response.go @@ -14,8 +14,8 @@ type Response struct { ExpirationYear int `json:"expiration_year"` CardNumberLength int `json:"card_number_length"` SecurityCodeLength int `json:"security_code_length"` - DateCreated time.Time `json:"date_created"` - DateLastUpdated time.Time `json:"date_last_updated"` - DateDue time.Time `json:"date_due"` + DateCreated *time.Time `json:"date_created"` + DateLastUpdated *time.Time `json:"date_last_updated"` + DateDue *time.Time `json:"date_due"` Cardholder Cardholder `json:"cardholder"` } From 768fd8e13831bdc3d30fbd1eb9e03cd315b47a48 Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Thu, 8 Feb 2024 16:39:42 -0300 Subject: [PATCH 39/55] Card token - code quality --- examples/apis/cardtoken/create/main.go | 17 ++++++++++++++- pkg/cardtoken/client.go | 3 +-- pkg/cardtoken/client_test.go | 2 +- pkg/cardtoken/mock.go | 8 +++---- pkg/cardtoken/request.go | 16 +++++++------- pkg/cardtoken/response.go | 30 +++++++++++++------------- 6 files changed, 45 insertions(+), 31 deletions(-) diff --git a/examples/apis/cardtoken/create/main.go b/examples/apis/cardtoken/create/main.go index 7a5eed0b..f7c756ac 100644 --- a/examples/apis/cardtoken/create/main.go +++ b/examples/apis/cardtoken/create/main.go @@ -19,7 +19,22 @@ func main() { client := cardtoken.NewClient(cfg) - result, err := client.Create(context.Background(), cardtoken.Request{}) + var req = cardtoken.Request{ + SiteID: "{{SiteID}}", + CardNumber: "{{CardNumber}}", + ExpirationMonth: "11", + ExpirationYear: "2025", + SecurityCode: "123", + Cardholder: &cardtoken.Cardholder{ + Identification: &cardtoken.Identification{ + Type: "CPF", + Number: "{{CPFNumber}}", + }, + Name: "{{PaymentMethod}}", + }, + } + + result, err := client.Create(context.Background(), req) if err != nil { return } diff --git a/pkg/cardtoken/client.go b/pkg/cardtoken/client.go index 60064460..7b212d4e 100644 --- a/pkg/cardtoken/client.go +++ b/pkg/cardtoken/client.go @@ -2,7 +2,6 @@ package cardtoken import ( "context" - "fmt" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/internal/httpclient" @@ -27,7 +26,7 @@ func NewClient(c *config.Config) Client { func (c *client) Create(ctx context.Context, request Request) (*Response, error) { res, err := httpclient.Post[Response](ctx, c.cfg, url, request) if err != nil { - return nil, fmt.Errorf("error create card token: %w", err) + return nil, err } return res, nil } diff --git a/pkg/cardtoken/client_test.go b/pkg/cardtoken/client_test.go index 0d59be0f..f226352b 100644 --- a/pkg/cardtoken/client_test.go +++ b/pkg/cardtoken/client_test.go @@ -70,7 +70,7 @@ func TestCreate(t *testing.T) { ctx: context.Background(), }, want: nil, - wantErr: "error create card token: transport level error: some error", + wantErr: "transport level error: some error", }, } for _, tt := range tests { diff --git a/pkg/cardtoken/mock.go b/pkg/cardtoken/mock.go index 8a17b43e..77bcb907 100644 --- a/pkg/cardtoken/mock.go +++ b/pkg/cardtoken/mock.go @@ -11,8 +11,8 @@ func mockCardToken() *Response { ExpirationMonth: 11, ExpirationYear: 2025, LastFourDigits: "6351", - Cardholder: Cardholder{ - Identification: Identification{ + Cardholder: &Cardholder{ + Identification: &Identification{ Number: "70383868084", Type: "CPF", }, @@ -36,8 +36,8 @@ func MockCardTokenRequest() Request { ExpirationMonth: "11", ExpirationYear: "2025", SecurityCode: "123", - Cardholder: Cardholder{ - Identification: Identification{ + Cardholder: &Cardholder{ + Identification: &Identification{ Type: "CPF", Number: "70383868084", }, diff --git a/pkg/cardtoken/request.go b/pkg/cardtoken/request.go index f549a08e..68941ac4 100644 --- a/pkg/cardtoken/request.go +++ b/pkg/cardtoken/request.go @@ -1,17 +1,17 @@ package cardtoken type Request struct { - SiteID string `json:"site_id"` - CardNumber string `json:"card_number"` - ExpirationYear string `json:"expiration_year"` - ExpirationMonth string `json:"expiration_month"` - SecurityCode string `json:"security_code"` - Cardholder Cardholder `json:"cardholder"` + SiteID string `json:"site_id"` + CardNumber string `json:"card_number"` + ExpirationYear string `json:"expiration_year"` + ExpirationMonth string `json:"expiration_month"` + SecurityCode string `json:"security_code"` + Cardholder *Cardholder `json:"cardholder,omitempty"` } type Cardholder struct { - Identification Identification `json:"identification"` - Name string `json:"name"` + Identification *Identification `json:"identification,omitempty"` + Name string `json:"name"` } type Identification struct { diff --git a/pkg/cardtoken/response.go b/pkg/cardtoken/response.go index 2c2ae3a0..359f7b65 100644 --- a/pkg/cardtoken/response.go +++ b/pkg/cardtoken/response.go @@ -3,19 +3,19 @@ package cardtoken import "time" type Response struct { - ID string `json:"id"` - FirstSixDigits string `json:"first_six_digits"` - LastFourDigits string `json:"last_four_digits"` - Status string `json:"status"` - LuhnValidation bool `json:"luhn_validation"` - LiveMode bool `json:"live_mode"` - RequireEsc bool `json:"require_esc"` - ExpirationMonth int `json:"expiration_month"` - ExpirationYear int `json:"expiration_year"` - CardNumberLength int `json:"card_number_length"` - SecurityCodeLength int `json:"security_code_length"` - DateCreated *time.Time `json:"date_created"` - DateLastUpdated *time.Time `json:"date_last_updated"` - DateDue *time.Time `json:"date_due"` - Cardholder Cardholder `json:"cardholder"` + ID string `json:"id"` + FirstSixDigits string `json:"first_six_digits"` + LastFourDigits string `json:"last_four_digits"` + Status string `json:"status"` + LuhnValidation bool `json:"luhn_validation"` + LiveMode bool `json:"live_mode"` + RequireEsc bool `json:"require_esc"` + ExpirationMonth int `json:"expiration_month"` + ExpirationYear int `json:"expiration_year"` + CardNumberLength int `json:"card_number_length"` + SecurityCodeLength int `json:"security_code_length"` + DateCreated *time.Time `json:"date_created"` + DateLastUpdated *time.Time `json:"date_last_updated"` + DateDue *time.Time `json:"date_due"` + Cardholder *Cardholder `json:"cardholder"` } From 666fb0424aa02c826a1dfbc1f10e6ea16ed0675a Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Thu, 8 Feb 2024 17:16:11 -0300 Subject: [PATCH 40/55] Card token - code quality --- go.mod | 11 ++++++++++- go.sum | 10 ++++++++++ pkg/cardtoken/client.go | 3 +++ pkg/cardtoken/client_test.go | 6 ++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 83d08ca2..1b9c281b 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,13 @@ module github.com/mercadopago/sdk-go go 1.21.0 -require github.com/google/uuid v1.5.0 +require ( + github.com/google/uuid v1.5.0 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index 040e2213..53bfb8c3 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/cardtoken/client.go b/pkg/cardtoken/client.go index 7b212d4e..b607dd55 100644 --- a/pkg/cardtoken/client.go +++ b/pkg/cardtoken/client.go @@ -11,7 +11,10 @@ const ( url = "https://api.mercadopago.com/v1/card_tokens" ) +// Client contains the method to interact with the card token API. type Client interface { + // Create create a card token. + // It is a post request to the endpoint: https://api.mercadopago.com/v1/card_tokens Create(ctx context.Context, request Request) (*Response, error) } diff --git a/pkg/cardtoken/client_test.go b/pkg/cardtoken/client_test.go index f226352b..f958de28 100644 --- a/pkg/cardtoken/client_test.go +++ b/pkg/cardtoken/client_test.go @@ -9,9 +9,11 @@ import ( "reflect" "strings" "testing" + "time" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/internal/httpclient" + "github.com/stretchr/testify/assert" ) var ( @@ -93,3 +95,7 @@ func TestCreate(t *testing.T) { }) } } + +func Test_parseDate(t *testing.T) { + assert.IsType(t, &time.Time{}, parseDate("2024-02-08T09:05:42.725-04:00")) +} From ebed749dad2eac74145a7fe0b4263cc802f7dcd8 Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Thu, 8 Feb 2024 17:24:26 -0300 Subject: [PATCH 41/55] Card token - code quality --- .testcoverage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.testcoverage.yml b/.testcoverage.yml index f09fcb5c..1db2c06b 100644 --- a/.testcoverage.yml +++ b/.testcoverage.yml @@ -8,3 +8,4 @@ exclude: paths: - ^pkg/internal - ^pkg/config + - ^pkg/cardtoken/mock.go \ No newline at end of file From 341ba0130e0b77c49f8fae8f080a2b7eeaf5a478 Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Thu, 8 Feb 2024 18:06:29 -0300 Subject: [PATCH 42/55] Card token - code quality --- examples/apis/cardtoken/get/main.go | 28 ------------- pkg/cardtoken/client_test.go | 6 --- pkg/cardtoken/mock.go | 4 +- pkg/cardtoken/response.go | 40 ++++++++++++------- .../{ => cardtoken}/cardtoken_test.go | 2 +- 5 files changed, 28 insertions(+), 52 deletions(-) delete mode 100644 examples/apis/cardtoken/get/main.go rename test/integration/{ => cardtoken}/cardtoken_test.go (96%) diff --git a/examples/apis/cardtoken/get/main.go b/examples/apis/cardtoken/get/main.go deleted file mode 100644 index 76ecc2a0..00000000 --- a/examples/apis/cardtoken/get/main.go +++ /dev/null @@ -1,28 +0,0 @@ -package get - -import ( - "context" - "fmt" - - "github.com/mercadopago/sdk-go/pkg/cardtoken" - "github.com/mercadopago/sdk-go/pkg/config" -) - -func main() { - accessToken := "{{ACCESS_TOKEN}}" - - cfg, err := config.New(accessToken) - if err != nil { - fmt.Println(err) - return - } - - client := cardtoken.NewClient(cfg) - - result, err := client.Get(context.Background(), "123") - if err != nil { - return - } - - fmt.Println(result) -} diff --git a/pkg/cardtoken/client_test.go b/pkg/cardtoken/client_test.go index f958de28..f226352b 100644 --- a/pkg/cardtoken/client_test.go +++ b/pkg/cardtoken/client_test.go @@ -9,11 +9,9 @@ import ( "reflect" "strings" "testing" - "time" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/internal/httpclient" - "github.com/stretchr/testify/assert" ) var ( @@ -95,7 +93,3 @@ func TestCreate(t *testing.T) { }) } } - -func Test_parseDate(t *testing.T) { - assert.IsType(t, &time.Time{}, parseDate("2024-02-08T09:05:42.725-04:00")) -} diff --git a/pkg/cardtoken/mock.go b/pkg/cardtoken/mock.go index 77bcb907..906c4a32 100644 --- a/pkg/cardtoken/mock.go +++ b/pkg/cardtoken/mock.go @@ -11,8 +11,8 @@ func mockCardToken() *Response { ExpirationMonth: 11, ExpirationYear: 2025, LastFourDigits: "6351", - Cardholder: &Cardholder{ - Identification: &Identification{ + Cardholder: CardholderResponse{ + Identification: IdentificationResponse{ Number: "70383868084", Type: "CPF", }, diff --git a/pkg/cardtoken/response.go b/pkg/cardtoken/response.go index 359f7b65..a51a15e3 100644 --- a/pkg/cardtoken/response.go +++ b/pkg/cardtoken/response.go @@ -3,19 +3,29 @@ package cardtoken import "time" type Response struct { - ID string `json:"id"` - FirstSixDigits string `json:"first_six_digits"` - LastFourDigits string `json:"last_four_digits"` - Status string `json:"status"` - LuhnValidation bool `json:"luhn_validation"` - LiveMode bool `json:"live_mode"` - RequireEsc bool `json:"require_esc"` - ExpirationMonth int `json:"expiration_month"` - ExpirationYear int `json:"expiration_year"` - CardNumberLength int `json:"card_number_length"` - SecurityCodeLength int `json:"security_code_length"` - DateCreated *time.Time `json:"date_created"` - DateLastUpdated *time.Time `json:"date_last_updated"` - DateDue *time.Time `json:"date_due"` - Cardholder *Cardholder `json:"cardholder"` + ID string `json:"id"` + FirstSixDigits string `json:"first_six_digits"` + LastFourDigits string `json:"last_four_digits"` + Status string `json:"status"` + LuhnValidation bool `json:"luhn_validation"` + LiveMode bool `json:"live_mode"` + RequireEsc bool `json:"require_esc"` + ExpirationMonth int `json:"expiration_month"` + ExpirationYear int `json:"expiration_year"` + CardNumberLength int `json:"card_number_length"` + SecurityCodeLength int `json:"security_code_length"` + DateCreated *time.Time `json:"date_created"` + DateLastUpdated *time.Time `json:"date_last_updated"` + DateDue *time.Time `json:"date_due"` + Cardholder CardholderResponse `json:"cardholder"` +} + +type CardholderResponse struct { + Identification IdentificationResponse `json:"identification,omitempty"` + Name string `json:"name"` +} + +type IdentificationResponse struct { + Number string `json:"number"` + Type string `json:"type"` } diff --git a/test/integration/cardtoken_test.go b/test/integration/cardtoken/cardtoken_test.go similarity index 96% rename from test/integration/cardtoken_test.go rename to test/integration/cardtoken/cardtoken_test.go index 3764436f..6107695e 100644 --- a/test/integration/cardtoken_test.go +++ b/test/integration/cardtoken/cardtoken_test.go @@ -1,4 +1,4 @@ -package integration +package cardtoken import ( "context" From 3c567b5867fb4773cf189bc37c2a4bed8050ce18 Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Thu, 8 Feb 2024 18:09:16 -0300 Subject: [PATCH 43/55] Card token - code quality --- examples/apis/cardtoken/create/main.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/apis/cardtoken/create/main.go b/examples/apis/cardtoken/create/main.go index f7c756ac..055d8685 100644 --- a/examples/apis/cardtoken/create/main.go +++ b/examples/apis/cardtoken/create/main.go @@ -20,17 +20,17 @@ func main() { client := cardtoken.NewClient(cfg) var req = cardtoken.Request{ - SiteID: "{{SiteID}}", - CardNumber: "{{CardNumber}}", + SiteID: "{{SITE_ID}}", + CardNumber: "{{CARD_NUMBER}}", ExpirationMonth: "11", ExpirationYear: "2025", SecurityCode: "123", Cardholder: &cardtoken.Cardholder{ Identification: &cardtoken.Identification{ Type: "CPF", - Number: "{{CPFNumber}}", + Number: "{{CPF_NUMBER}}", }, - Name: "{{PaymentMethod}}", + Name: "{{PAYMENT_METHOD}}", }, } From 8ad5cd3b6f4001ab47b4553a5836d6dac1efe4db Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Thu, 8 Feb 2024 18:25:28 -0300 Subject: [PATCH 44/55] Card token - code quality --- go.mod | 11 +---------- go.sum | 10 ---------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 1b9c281b..83d08ca2 100644 --- a/go.mod +++ b/go.mod @@ -2,13 +2,4 @@ module github.com/mercadopago/sdk-go go 1.21.0 -require ( - github.com/google/uuid v1.5.0 - github.com/stretchr/testify v1.8.4 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) +require github.com/google/uuid v1.5.0 diff --git a/go.sum b/go.sum index 53bfb8c3..040e2213 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,2 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From abe2ff3ce1dcfa65da6e1ba47ad4ba29e2c66725 Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Fri, 9 Feb 2024 07:54:02 -0300 Subject: [PATCH 45/55] Card token - code quality --- pkg/cardtoken/client.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/cardtoken/client.go b/pkg/cardtoken/client.go index b607dd55..63fa6f38 100644 --- a/pkg/cardtoken/client.go +++ b/pkg/cardtoken/client.go @@ -7,9 +7,7 @@ import ( "github.com/mercadopago/sdk-go/pkg/internal/httpclient" ) -const ( - url = "https://api.mercadopago.com/v1/card_tokens" -) +const url = "https://api.mercadopago.com/v1/card_tokens" // Client contains the method to interact with the card token API. type Client interface { From a3610a6abaee47efa204d9bbd4dd22597bf27166 Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Fri, 9 Feb 2024 09:59:53 -0300 Subject: [PATCH 46/55] Card token - code quality --- pkg/cardtoken/request.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/cardtoken/request.go b/pkg/cardtoken/request.go index 68941ac4..e8373430 100644 --- a/pkg/cardtoken/request.go +++ b/pkg/cardtoken/request.go @@ -1,20 +1,20 @@ package cardtoken type Request struct { - SiteID string `json:"site_id"` - CardNumber string `json:"card_number"` - ExpirationYear string `json:"expiration_year"` - ExpirationMonth string `json:"expiration_month"` - SecurityCode string `json:"security_code"` - Cardholder *Cardholder `json:"cardholder,omitempty"` + SiteID string `json:"site_id,omitempty"` + CardNumber string `json:"card_number,omitempty"` + ExpirationYear string `json:"expiration_year,omitempty"` + ExpirationMonth string `json:"expiration_month,omitempty"` + SecurityCode string `json:"security_code,omitempty"` + Cardholder *Cardholder `json:"cardholder,omitempty,omitempty"` } type Cardholder struct { Identification *Identification `json:"identification,omitempty"` - Name string `json:"name"` + Name string `json:"name,omitempty"` } type Identification struct { - Number string `json:"number"` - Type string `json:"type"` + Number string `json:"number,omitempty"` + Type string `json:"type,omitempty"` } From 362a42f02b4cebdf15bf8c374fe1559474230b28 Mon Sep 17 00:00:00 2001 From: meliguilhermefernandes <“guilherme.jfernandes@mercadolivre.com”> Date: Fri, 9 Feb 2024 10:01:26 -0300 Subject: [PATCH 47/55] Card token - code quality --- pkg/cardtoken/request.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cardtoken/request.go b/pkg/cardtoken/request.go index e8373430..9cb1b637 100644 --- a/pkg/cardtoken/request.go +++ b/pkg/cardtoken/request.go @@ -6,7 +6,7 @@ type Request struct { ExpirationYear string `json:"expiration_year,omitempty"` ExpirationMonth string `json:"expiration_month,omitempty"` SecurityCode string `json:"security_code,omitempty"` - Cardholder *Cardholder `json:"cardholder,omitempty,omitempty"` + Cardholder *Cardholder `json:"cardholder,omitempty"` } type Cardholder struct { From 022a15d1d5db7b25c98661b965f7d4a27462f028 Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Mon, 5 Feb 2024 18:34:23 -0300 Subject: [PATCH 48/55] Add client card --- pkg/customer/customercard/card.go | 166 ++++++++++++++++++ pkg/customer/customercard/request.go | 5 + pkg/customer/customercard/response.go | 52 ++++++ .../{client.go => payment_method.go} | 0 4 files changed, 223 insertions(+) create mode 100644 pkg/customer/customercard/card.go create mode 100644 pkg/customer/customercard/request.go create mode 100644 pkg/customer/customercard/response.go rename pkg/paymentmethod/{client.go => payment_method.go} (100%) diff --git a/pkg/customer/customercard/card.go b/pkg/customer/customercard/card.go new file mode 100644 index 00000000..63cf9bf3 --- /dev/null +++ b/pkg/customer/customercard/card.go @@ -0,0 +1,166 @@ +package customercard + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/internal/httpclient" +) + +const ( + baseURL = "https://api.mercadopago.com/v1/customers/{customer_id}" + cardsURL = baseURL + "/cards" + cardsByIDURL = baseURL + cardsURL + "/{card_id}" +) + +// Client contains the methods to interact with the Payment Methods API. +type Client interface { + // Create a new customer card. + // It is a post request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards + // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards/post + Create(ctx context.Context, customerID string, request Request) (*Response, error) + + // Get a customer card by ID. + // It is a get request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} + // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/get + Get(ctx context.Context, customerID, cardID string) (*Response, error) + + // Update a customer card by ID. + // It is a put request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} + // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/put + Update(ctx context.Context, customerID, cardID string) (*Response, error) + + // Delete deletes a customer card by ID. + // It is a delete request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} + // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/delete + Delete(ctx context.Context, customerID, cardID string) (*Response, error) + + // List all customers. + // It is a get request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards + // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards/get + List(ctx context.Context, customerID string) ([]Response, error) +} + +// client is the implementation of Client. +type client struct { + config *config.Config +} + +// NewClient returns a new Payment Methods API Client. +func NewClient(c *config.Config) Client { + return &client{ + config: c, + } +} + +func (c *client) Create(ctx context.Context, customerID string, request Request) (*Response, error) { + body, err := json.Marshal(&request) + if err != nil { + return nil, fmt.Errorf("error marshaling request body: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, cardsURL, strings.NewReader(string(body))) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.config, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) Get(ctx context.Context, customerID, cardID string) (*Response, error) { + url := strings.Replace(cardsByIDURL, "{customer_id}", customerID, 1) + url = strings.Replace(cardsByIDURL, "{card_id}", cardID, 1) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.config, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) Update(ctx context.Context, customerID, cardID string) (*Response, error) { + conv := strconv.Itoa(int(id)) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.Replace(getURL, "{id}", conv, 1), nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.config, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) Delete(ctx context.Context, customerID, cardID string) (*Response, error) { + conv := strconv.Itoa(int(id)) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.Replace(getURL, "{id}", conv, 1), nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.config, req) + if err != nil { + return nil, err + } + + formatted := &Response{} + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return formatted, nil +} + +func (c *client) List(ctx context.Context, customerID string) ([]Response, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + res, err := httpclient.Send(ctx, c.config, req) + if err != nil { + return nil, err + } + + var formatted []Response + if err := json.Unmarshal(res, &formatted); err != nil { + return nil, err + } + + return formatted, nil +} diff --git a/pkg/customer/customercard/request.go b/pkg/customer/customercard/request.go new file mode 100644 index 00000000..66740317 --- /dev/null +++ b/pkg/customer/customercard/request.go @@ -0,0 +1,5 @@ +package customercard + +type Request struct { + Token string `json:"token"` +} diff --git a/pkg/customer/customercard/response.go b/pkg/customer/customercard/response.go new file mode 100644 index 00000000..07d33bed --- /dev/null +++ b/pkg/customer/customercard/response.go @@ -0,0 +1,52 @@ +package customercard + +type Response struct { + ID string `json:"id"` + Name string `json:"name"` + PaymentTypeID string `json:"payment_type_id"` + Status string `json:"status"` + SecureThumbnail string `json:"secure_thumbnail"` + Thumbnail string `json:"thumbnail"` + DeferredCapture string `json:"deferred_capture"` + AdditionalInfoNeeded []string `json:"additional_info_needed"` + ProcessingModes []string `json:"processing_modes"` + AccreditationTime int64 `json:"accreditation_time"` + MinAllowedAmount float64 `json:"min_allowed_amount"` + MaxAllowedAmount float64 `json:"max_allowed_amount"` + + Settings []SettingsResponse `json:"settings"` + FinancialInstitutions []FinancialInstitutionResponse `json:"financial_institutions"` +} + +// SettingsResponse represents payment method settings. +type SettingsResponse struct { + Bin *SettingsBinResponse `json:"bin"` + CardNumber *SettingsCardNumberResponse `json:"card_number"` + SecurityCode *SettingsSecurityCodeResponse `json:"security_code"` +} + +// SettingsBinResponse represents BIN (Bank Identification Number) settings. +type SettingsBinResponse struct { + Pattern string `json:"pattern"` + ExclusionPattern string `json:"exclusion_pattern"` + InstallmentsPattern string `json:"installments_pattern"` +} + +// SettingsCardNumberResponse represents customer number settings. +type SettingsCardNumberResponse struct { + Length int `json:"length"` + Validation string `json:"validation"` +} + +// SettingsSecurityCodeResponse represents security code settings. +type SettingsSecurityCodeResponse struct { + Mode string `json:"mode"` + Length int `json:"length"` + CardLocation string `json:"card_location"` +} + +// FinancialInstitutionResponse represents financial institution settings. +type FinancialInstitutionResponse struct { + ID string `json:"id"` + Description string `json:"description"` +} diff --git a/pkg/paymentmethod/client.go b/pkg/paymentmethod/payment_method.go similarity index 100% rename from pkg/paymentmethod/client.go rename to pkg/paymentmethod/payment_method.go From b58cc5d1e93380a4c505edf37087722c301e89a2 Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Fri, 9 Feb 2024 16:08:58 -0300 Subject: [PATCH 49/55] Clean code --- pkg/customer/customercard/card.go | 166 -------------------------- pkg/customer/customercard/request.go | 5 - pkg/customer/customercard/response.go | 52 -------- pkg/customercard/client.go | 6 +- 4 files changed, 3 insertions(+), 226 deletions(-) delete mode 100644 pkg/customer/customercard/card.go delete mode 100644 pkg/customer/customercard/request.go delete mode 100644 pkg/customer/customercard/response.go diff --git a/pkg/customer/customercard/card.go b/pkg/customer/customercard/card.go deleted file mode 100644 index 63cf9bf3..00000000 --- a/pkg/customer/customercard/card.go +++ /dev/null @@ -1,166 +0,0 @@ -package customercard - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/mercadopago/sdk-go/pkg/config" - "github.com/mercadopago/sdk-go/pkg/internal/httpclient" -) - -const ( - baseURL = "https://api.mercadopago.com/v1/customers/{customer_id}" - cardsURL = baseURL + "/cards" - cardsByIDURL = baseURL + cardsURL + "/{card_id}" -) - -// Client contains the methods to interact with the Payment Methods API. -type Client interface { - // Create a new customer card. - // It is a post request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards - // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards/post - Create(ctx context.Context, customerID string, request Request) (*Response, error) - - // Get a customer card by ID. - // It is a get request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} - // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/get - Get(ctx context.Context, customerID, cardID string) (*Response, error) - - // Update a customer card by ID. - // It is a put request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} - // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/put - Update(ctx context.Context, customerID, cardID string) (*Response, error) - - // Delete deletes a customer card by ID. - // It is a delete request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} - // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/delete - Delete(ctx context.Context, customerID, cardID string) (*Response, error) - - // List all customers. - // It is a get request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards - // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards/get - List(ctx context.Context, customerID string) ([]Response, error) -} - -// client is the implementation of Client. -type client struct { - config *config.Config -} - -// NewClient returns a new Payment Methods API Client. -func NewClient(c *config.Config) Client { - return &client{ - config: c, - } -} - -func (c *client) Create(ctx context.Context, customerID string, request Request) (*Response, error) { - body, err := json.Marshal(&request) - if err != nil { - return nil, fmt.Errorf("error marshaling request body: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, cardsURL, strings.NewReader(string(body))) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - - res, err := httpclient.Send(ctx, c.config, req) - if err != nil { - return nil, err - } - - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { - return nil, fmt.Errorf("error unmarshaling response: %w", err) - } - - return formatted, nil -} - -func (c *client) Get(ctx context.Context, customerID, cardID string) (*Response, error) { - url := strings.Replace(cardsByIDURL, "{customer_id}", customerID, 1) - url = strings.Replace(cardsByIDURL, "{card_id}", cardID, 1) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - - res, err := httpclient.Send(ctx, c.config, req) - if err != nil { - return nil, err - } - - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { - return nil, fmt.Errorf("error unmarshaling response: %w", err) - } - - return formatted, nil -} - -func (c *client) Update(ctx context.Context, customerID, cardID string) (*Response, error) { - conv := strconv.Itoa(int(id)) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.Replace(getURL, "{id}", conv, 1), nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - - res, err := httpclient.Send(ctx, c.config, req) - if err != nil { - return nil, err - } - - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { - return nil, fmt.Errorf("error unmarshaling response: %w", err) - } - - return formatted, nil -} - -func (c *client) Delete(ctx context.Context, customerID, cardID string) (*Response, error) { - conv := strconv.Itoa(int(id)) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.Replace(getURL, "{id}", conv, 1), nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - - res, err := httpclient.Send(ctx, c.config, req) - if err != nil { - return nil, err - } - - formatted := &Response{} - if err := json.Unmarshal(res, &formatted); err != nil { - return nil, fmt.Errorf("error unmarshaling response: %w", err) - } - - return formatted, nil -} - -func (c *client) List(ctx context.Context, customerID string) ([]Response, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - - res, err := httpclient.Send(ctx, c.config, req) - if err != nil { - return nil, err - } - - var formatted []Response - if err := json.Unmarshal(res, &formatted); err != nil { - return nil, err - } - - return formatted, nil -} diff --git a/pkg/customer/customercard/request.go b/pkg/customer/customercard/request.go deleted file mode 100644 index 66740317..00000000 --- a/pkg/customer/customercard/request.go +++ /dev/null @@ -1,5 +0,0 @@ -package customercard - -type Request struct { - Token string `json:"token"` -} diff --git a/pkg/customer/customercard/response.go b/pkg/customer/customercard/response.go deleted file mode 100644 index 07d33bed..00000000 --- a/pkg/customer/customercard/response.go +++ /dev/null @@ -1,52 +0,0 @@ -package customercard - -type Response struct { - ID string `json:"id"` - Name string `json:"name"` - PaymentTypeID string `json:"payment_type_id"` - Status string `json:"status"` - SecureThumbnail string `json:"secure_thumbnail"` - Thumbnail string `json:"thumbnail"` - DeferredCapture string `json:"deferred_capture"` - AdditionalInfoNeeded []string `json:"additional_info_needed"` - ProcessingModes []string `json:"processing_modes"` - AccreditationTime int64 `json:"accreditation_time"` - MinAllowedAmount float64 `json:"min_allowed_amount"` - MaxAllowedAmount float64 `json:"max_allowed_amount"` - - Settings []SettingsResponse `json:"settings"` - FinancialInstitutions []FinancialInstitutionResponse `json:"financial_institutions"` -} - -// SettingsResponse represents payment method settings. -type SettingsResponse struct { - Bin *SettingsBinResponse `json:"bin"` - CardNumber *SettingsCardNumberResponse `json:"card_number"` - SecurityCode *SettingsSecurityCodeResponse `json:"security_code"` -} - -// SettingsBinResponse represents BIN (Bank Identification Number) settings. -type SettingsBinResponse struct { - Pattern string `json:"pattern"` - ExclusionPattern string `json:"exclusion_pattern"` - InstallmentsPattern string `json:"installments_pattern"` -} - -// SettingsCardNumberResponse represents customer number settings. -type SettingsCardNumberResponse struct { - Length int `json:"length"` - Validation string `json:"validation"` -} - -// SettingsSecurityCodeResponse represents security code settings. -type SettingsSecurityCodeResponse struct { - Mode string `json:"mode"` - Length int `json:"length"` - CardLocation string `json:"card_location"` -} - -// FinancialInstitutionResponse represents financial institution settings. -type FinancialInstitutionResponse struct { - ID string `json:"id"` - Description string `json:"description"` -} diff --git a/pkg/customercard/client.go b/pkg/customercard/client.go index 77a136f6..30998c54 100644 --- a/pkg/customercard/client.go +++ b/pkg/customercard/client.go @@ -16,7 +16,7 @@ const ( paramCardID = "card_id" ) -// Client contains the methods to interact with the Payment Methods API. +// Client contains the methods to interact with the Customer Cards API. type Client interface { // Create a new customer card. // It is a post request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards @@ -38,7 +38,7 @@ type Client interface { // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/delete Delete(ctx context.Context, customerID, cardID string) (*Response, error) - // List all customers. + // List all customer cards. // It is a get request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards/get List(ctx context.Context, customerID string) ([]Response, error) @@ -49,7 +49,7 @@ type client struct { config *config.Config } -// NewClient returns a new Payment Methods API Client. +// NewClient returns a new Customer Card Client. func NewClient(c *config.Config) Client { return &client{ config: c, From 90b09f225e719488597528ce74a1bde4d073da9a Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Fri, 9 Feb 2024 16:16:42 -0300 Subject: [PATCH 50/55] adjust conflict payment method --- pkg/customercard/client_test.go | 10 +++++----- pkg/customercard/response.go | 2 +- pkg/paymentmethod/payment_method.go | 9 --------- pkg/paymentmethod/response.go | 2 +- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/pkg/customercard/client_test.go b/pkg/customercard/client_test.go index 2be6dc8e..da404652 100644 --- a/pkg/customercard/client_test.go +++ b/pkg/customercard/client_test.go @@ -89,7 +89,7 @@ func TestCreate(t *testing.T) { PaymentMethod: PaymentMethodResponse{ ID: "master", Name: "Mastercard", - PaymentTypeId: "credit_card", + PaymentTypeID: "credit_card", Thumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", SecureThumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", }, @@ -207,7 +207,7 @@ func TestUpdate(t *testing.T) { PaymentMethod: PaymentMethodResponse{ ID: "master", Name: "Mastercard", - PaymentTypeId: "credit_card", + PaymentTypeID: "credit_card", Thumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", SecureThumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", }, @@ -323,7 +323,7 @@ func TestGet(t *testing.T) { PaymentMethod: PaymentMethodResponse{ ID: "master", Name: "Mastercard", - PaymentTypeId: "credit_card", + PaymentTypeID: "credit_card", Thumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", SecureThumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", }, @@ -438,7 +438,7 @@ func TestDelete(t *testing.T) { PaymentMethod: PaymentMethodResponse{ ID: "master", Name: "Mastercard", - PaymentTypeId: "credit_card", + PaymentTypeID: "credit_card", Thumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", SecureThumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", }, @@ -552,7 +552,7 @@ func TestList(t *testing.T) { PaymentMethod: PaymentMethodResponse{ ID: "master", Name: "Mastercard", - PaymentTypeId: "credit_card", + PaymentTypeID: "credit_card", Thumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", SecureThumbnail: "https://http2.mlstatic.com/storage/logos-api-admin/0daa1670-5c81-11ec-ae75-df2bef173be2-xl@2x.png", }, diff --git a/pkg/customercard/response.go b/pkg/customercard/response.go index 982bbfd2..cbba3689 100644 --- a/pkg/customercard/response.go +++ b/pkg/customercard/response.go @@ -51,7 +51,7 @@ type IssuerResponse struct { type PaymentMethodResponse struct { ID string `json:"id"` Name string `json:"name"` - PaymentTypeId string `json:"payment_type_id"` + PaymentTypeID string `json:"payment_type_id"` Thumbnail string `json:"thumbnail"` SecureThumbnail string `json:"secure_thumbnail"` } diff --git a/pkg/paymentmethod/payment_method.go b/pkg/paymentmethod/payment_method.go index a0e46abd..5cc52d52 100644 --- a/pkg/paymentmethod/payment_method.go +++ b/pkg/paymentmethod/payment_method.go @@ -33,14 +33,5 @@ func (c *client) List(ctx context.Context) ([]Response, error) { return nil, err } -<<<<<<< HEAD return *res, nil -======= - var formatted []Response - if err := json.Unmarshal(res, &formatted); err != nil { - return nil, fmt.Errorf("error unmarshaling response: %w", err) - } - - return formatted, nil ->>>>>>> 8b22954 (Update client) } diff --git a/pkg/paymentmethod/response.go b/pkg/paymentmethod/response.go index aeec6fac..5cffb157 100644 --- a/pkg/paymentmethod/response.go +++ b/pkg/paymentmethod/response.go @@ -32,7 +32,7 @@ type SettingsBinResponse struct { InstallmentsPattern string `json:"installments_pattern"` } -// SettingsCardNumberResponse represents customer number settings. +// SettingsCardNumberResponse represents card number settings. type SettingsCardNumberResponse struct { Length int `json:"length"` Validation string `json:"validation"` From bb36d83f3dfe7258e976a78934ab07175cfa6922 Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Fri, 9 Feb 2024 16:17:20 -0300 Subject: [PATCH 51/55] adjust name client pm --- pkg/paymentmethod/{payment_method.go => client.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/paymentmethod/{payment_method.go => client.go} (100%) diff --git a/pkg/paymentmethod/payment_method.go b/pkg/paymentmethod/client.go similarity index 100% rename from pkg/paymentmethod/payment_method.go rename to pkg/paymentmethod/client.go From 5e061002e909d11a4a0d7c229e6a7c588dbf5046 Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Fri, 9 Feb 2024 16:22:45 -0300 Subject: [PATCH 52/55] adjust ci lint --- pkg/internal/httpclient/client.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/internal/httpclient/client.go b/pkg/internal/httpclient/client.go index b92884b0..a2008495 100644 --- a/pkg/internal/httpclient/client.go +++ b/pkg/internal/httpclient/client.go @@ -153,9 +153,7 @@ func makePathParams(req *http.Request, params map[string]string) error { for k, v := range params { pathParam := ":" + k - if strings.Contains(pathURL, pathParam) { - pathURL = strings.Replace(pathURL, pathParam, v, 1) - } + pathURL = strings.Replace(pathURL, pathParam, v, 1) } if err := validatePathParams(pathURL); err != nil { From ce0ed5ba00a51eec4da5a29c41cd724f3e19eacb Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Fri, 9 Feb 2024 18:27:46 -0300 Subject: [PATCH 53/55] adjust order tests --- pkg/customercard/client.go | 6 +- pkg/customercard/client_test.go | 194 ++++++++++++++++---------------- pkg/customercard/response.go | 4 +- 3 files changed, 102 insertions(+), 102 deletions(-) diff --git a/pkg/customercard/client.go b/pkg/customercard/client.go index 30998c54..ee14a719 100644 --- a/pkg/customercard/client.go +++ b/pkg/customercard/client.go @@ -23,17 +23,17 @@ type Client interface { // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards/post Create(ctx context.Context, customerID string, request Request) (*Response, error) - // Get a customer card by ID. + // Get a customer card by id. // It is a get request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/get Get(ctx context.Context, customerID, cardID string) (*Response, error) - // Update a customer card by ID. + // Update a customer card by id. // It is a put request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/put Update(ctx context.Context, customerID, cardID string, request Request) (*Response, error) - // Delete deletes a customer card by ID. + // Delete deletes a customer card by id. // It is a delete request to the endpoint: https://api.mercadopago.com/v1/customer/{customer_id}/cards/{card_id} // Reference: https://www.mercadopago.com/developers/en/reference/cards/_customers_customer_id_cards_id/delete Delete(ctx context.Context, customerID, cardID string) (*Response, error) diff --git a/pkg/customercard/client_test.go b/pkg/customercard/client_test.go index da404652..9b67e76c 100644 --- a/pkg/customercard/client_test.go +++ b/pkg/customercard/client_test.go @@ -39,6 +39,25 @@ func TestCreate(t *testing.T) { want *Response wantErr string }{ + { + name: "should_return_error_when_send_request", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "any", + req: Request{}, + }, + want: nil, + wantErr: "transport level error: some error", + }, { name: "should_return_card_response", fields: fields{ @@ -76,7 +95,7 @@ func TestCreate(t *testing.T) { }, Cardholder: CardholderResponse{ Name: "APRO", - IdentificationResponse: IdentificationResponse{ + Identification: IdentificationResponse{ Number: "19119119100", Type: "CPF", }, @@ -100,25 +119,6 @@ func TestCreate(t *testing.T) { }, wantErr: "", }, - { - name: "should_return_error_when_send_request", - fields: fields{ - config: &config.Config{ - Requester: &httpclient.Mock{ - DoMock: func(req *http.Request) (*http.Response, error) { - return nil, fmt.Errorf("some error") - }, - }, - }, - }, - args: args{ - ctx: context.Background(), - customerID: "any", - req: Request{}, - }, - want: nil, - wantErr: "transport level error: some error", - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -156,6 +156,25 @@ func TestUpdate(t *testing.T) { want *Response wantErr string }{ + { + name: "should_return_error_when_send_request", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "any", + req: Request{}, + }, + want: nil, + wantErr: "transport level error: some error", + }, { name: "should_return_card_response", fields: fields{ @@ -194,7 +213,7 @@ func TestUpdate(t *testing.T) { }, Cardholder: CardholderResponse{ Name: "APRO", - IdentificationResponse: IdentificationResponse{ + Identification: IdentificationResponse{ Number: "19119119100", Type: "CPF", }, @@ -218,25 +237,6 @@ func TestUpdate(t *testing.T) { }, wantErr: "", }, - { - name: "should_return_error_when_send_request", - fields: fields{ - config: &config.Config{ - Requester: &httpclient.Mock{ - DoMock: func(req *http.Request) (*http.Response, error) { - return nil, fmt.Errorf("some error") - }, - }, - }, - }, - args: args{ - ctx: context.Background(), - customerID: "any", - req: Request{}, - }, - want: nil, - wantErr: "transport level error: some error", - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -273,6 +273,24 @@ func TestGet(t *testing.T) { want *Response wantErr string }{ + { + name: "should_return_error_when_send_request", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "any", + }, + want: nil, + wantErr: "transport level error: some error", + }, { name: "should_return_card_response", fields: fields{ @@ -310,7 +328,7 @@ func TestGet(t *testing.T) { }, Cardholder: CardholderResponse{ Name: "APRO", - IdentificationResponse: IdentificationResponse{ + Identification: IdentificationResponse{ Number: "19119119100", Type: "CPF", }, @@ -334,24 +352,6 @@ func TestGet(t *testing.T) { }, wantErr: "", }, - { - name: "should_return_error_when_send_request", - fields: fields{ - config: &config.Config{ - Requester: &httpclient.Mock{ - DoMock: func(req *http.Request) (*http.Response, error) { - return nil, fmt.Errorf("some error") - }, - }, - }, - }, - args: args{ - ctx: context.Background(), - customerID: "any", - }, - want: nil, - wantErr: "transport level error: some error", - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -388,6 +388,24 @@ func TestDelete(t *testing.T) { want *Response wantErr string }{ + { + name: "should_return_error_when_send_request", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "any", + }, + want: nil, + wantErr: "transport level error: some error", + }, { name: "should_return_card_response", fields: fields{ @@ -425,7 +443,7 @@ func TestDelete(t *testing.T) { }, Cardholder: CardholderResponse{ Name: "APRO", - IdentificationResponse: IdentificationResponse{ + Identification: IdentificationResponse{ Number: "19119119100", Type: "CPF", }, @@ -449,24 +467,6 @@ func TestDelete(t *testing.T) { }, wantErr: "", }, - { - name: "should_return_error_when_send_request", - fields: fields{ - config: &config.Config{ - Requester: &httpclient.Mock{ - DoMock: func(req *http.Request) (*http.Response, error) { - return nil, fmt.Errorf("some error") - }, - }, - }, - }, - args: args{ - ctx: context.Background(), - customerID: "any", - }, - want: nil, - wantErr: "transport level error: some error", - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -502,6 +502,24 @@ func TestList(t *testing.T) { want []Response wantErr string }{ + { + name: "should_return_error_when_send_request", + fields: fields{ + config: &config.Config{ + Requester: &httpclient.Mock{ + DoMock: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("some error") + }, + }, + }, + }, + args: args{ + ctx: context.Background(), + customerID: "any", + }, + want: nil, + wantErr: "transport level error: some error", + }, { name: "should_return_card_response", fields: fields{ @@ -539,7 +557,7 @@ func TestList(t *testing.T) { }, Cardholder: CardholderResponse{ Name: "APRO", - IdentificationResponse: IdentificationResponse{ + Identification: IdentificationResponse{ Number: "19119119100", Type: "CPF", }, @@ -564,24 +582,6 @@ func TestList(t *testing.T) { }, wantErr: "", }, - { - name: "should_return_error_when_send_request", - fields: fields{ - config: &config.Config{ - Requester: &httpclient.Mock{ - DoMock: func(req *http.Request) (*http.Response, error) { - return nil, fmt.Errorf("some error") - }, - }, - }, - }, - args: args{ - ctx: context.Background(), - customerID: "any", - }, - want: nil, - wantErr: "transport level error: some error", - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/customercard/response.go b/pkg/customercard/response.go index cbba3689..2364e7ea 100644 --- a/pkg/customercard/response.go +++ b/pkg/customercard/response.go @@ -31,8 +31,8 @@ type AdditionalInfoResponse struct { // CardholderResponse represents information about the cardholder. type CardholderResponse struct { - Name string `json:"name"` - IdentificationResponse `json:"identification"` + Name string `json:"name"` + Identification IdentificationResponse `json:"identification"` } // IdentificationResponse represents the cardholder's document. From 69c693190f0e33faaf380d7b610bc9500f7751a5 Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Fri, 9 Feb 2024 18:40:31 -0300 Subject: [PATCH 54/55] remove const param --- pkg/customercard/client.go | 19 +++++++--------- pkg/customercard/response.go | 42 +++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/pkg/customercard/client.go b/pkg/customercard/client.go index ee14a719..0114f80a 100644 --- a/pkg/customercard/client.go +++ b/pkg/customercard/client.go @@ -11,9 +11,6 @@ const ( urlBase = "https://api.mercadopago.com/v1/customers/{customer_id}" urlCards = urlBase + "/cards" urlCardsWithID = urlCards + "/{card_id}" - - paramCustomerID = "customer_id" - paramCardID = "card_id" ) // Client contains the methods to interact with the Customer Cards API. @@ -58,7 +55,7 @@ func NewClient(c *config.Config) Client { func (c *client) Create(ctx context.Context, customerID string, request Request) (*Response, error) { params := map[string]string{ - paramCustomerID: customerID, + "customer_id": customerID, } res, err := httpclient.Post[Response](ctx, c.config, urlCards, request, httpclient.WithPathParams(params)) @@ -71,8 +68,8 @@ func (c *client) Create(ctx context.Context, customerID string, request Request) func (c *client) Get(ctx context.Context, customerID, cardID string) (*Response, error) { params := map[string]string{ - paramCustomerID: customerID, - paramCardID: cardID, + "customer_id": customerID, + "card_id": cardID, } res, err := httpclient.Get[Response](ctx, c.config, urlCardsWithID, httpclient.WithPathParams(params)) @@ -85,8 +82,8 @@ func (c *client) Get(ctx context.Context, customerID, cardID string) (*Response, func (c *client) Update(ctx context.Context, customerID, cardID string, request Request) (*Response, error) { params := map[string]string{ - paramCustomerID: customerID, - paramCardID: cardID, + "customer_id": customerID, + "card_id": cardID, } res, err := httpclient.Put[Response](ctx, c.config, urlCardsWithID, request, httpclient.WithPathParams(params)) @@ -99,8 +96,8 @@ func (c *client) Update(ctx context.Context, customerID, cardID string, request func (c *client) Delete(ctx context.Context, customerID, cardID string) (*Response, error) { params := map[string]string{ - paramCustomerID: customerID, - paramCardID: cardID, + "customer_id": customerID, + "card_id": cardID, } res, err := httpclient.Delete[Response](ctx, c.config, urlCardsWithID, nil, httpclient.WithPathParams(params)) @@ -113,7 +110,7 @@ func (c *client) Delete(ctx context.Context, customerID, cardID string) (*Respon func (c *client) List(ctx context.Context, customerID string) ([]Response, error) { params := map[string]string{ - paramCustomerID: customerID, + "customer_id": customerID, } res, err := httpclient.Get[[]Response](ctx, c.config, urlCards, httpclient.WithPathParams(params)) diff --git a/pkg/customercard/response.go b/pkg/customercard/response.go index 2364e7ea..f5e7c763 100644 --- a/pkg/customercard/response.go +++ b/pkg/customercard/response.go @@ -4,22 +4,23 @@ import "time" // Response represents a customer card. type Response struct { - ID string `json:"id"` - CustomerID string `json:"customer_id"` - UserID string `json:"user_id"` - CardNumberID string `json:"card_number_id"` - FirstSixDigits string `json:"first_six_digits"` - LastFourDigits string `json:"last_four_digits"` - ExpirationMonth int `json:"expiration_month"` - ExpirationYear int `json:"expiration_year"` - LiveMode bool `json:"live_mode"` - DateCreated *time.Time `json:"date_created"` - DateLastUpdated *time.Time `json:"date_last_updated"` - Issuer IssuerResponse `json:"issuer"` - Cardholder CardholderResponse `json:"cardholder"` - AdditionalInfo AdditionalInfoResponse `json:"additional_info"` - PaymentMethod PaymentMethodResponse `json:"payment_method"` - SecurityCode SecurityCode `json:"security_code"` + ID string `json:"id"` + CustomerID string `json:"customer_id"` + UserID string `json:"user_id"` + CardNumberID string `json:"card_number_id"` + FirstSixDigits string `json:"first_six_digits"` + LastFourDigits string `json:"last_four_digits"` + ExpirationMonth int `json:"expiration_month"` + ExpirationYear int `json:"expiration_year"` + LiveMode bool `json:"live_mode"` + DateCreated *time.Time `json:"date_created"` + DateLastUpdated *time.Time `json:"date_last_updated"` + + Issuer IssuerResponse `json:"issuer"` + Cardholder CardholderResponse `json:"cardholder"` + AdditionalInfo AdditionalInfoResponse `json:"additional_info"` + PaymentMethod PaymentMethodResponse `json:"payment_method"` + SecurityCode SecurityCode `json:"security_code"` } // AdditionalInfoResponse represents additional customer card information. @@ -31,7 +32,8 @@ type AdditionalInfoResponse struct { // CardholderResponse represents information about the cardholder. type CardholderResponse struct { - Name string `json:"name"` + Name string `json:"name"` + Identification IdentificationResponse `json:"identification"` } @@ -41,13 +43,13 @@ type IdentificationResponse struct { Type string `json:"type"` } -// IssuerResponse represents the card issuer code +// IssuerResponse represents the card issuer code. type IssuerResponse struct { ID int `json:"id"` Name string `json:"name"` } -// PaymentMethodResponse represents the card's payment method +// PaymentMethodResponse represents the card's payment method. type PaymentMethodResponse struct { ID string `json:"id"` Name string `json:"name"` @@ -56,7 +58,7 @@ type PaymentMethodResponse struct { SecureThumbnail string `json:"secure_thumbnail"` } -// SecurityCode represents the card's security code +// SecurityCode represents the card's security code. type SecurityCode struct { Length int `json:"length"` CardLocation string `json:"card_location"` From f0b2b41640efb05e85bedfe9a9e1a8c4fe0846df Mon Sep 17 00:00:00 2001 From: eltinMeli Date: Wed, 14 Feb 2024 10:24:24 -0300 Subject: [PATCH 55/55] Adjusted test text and url values --- pkg/customercard/client.go | 15 +++++++-------- pkg/customercard/client_test.go | 16 ++++++++-------- pkg/customercard/response.go | 32 ++++++++++++++++---------------- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/pkg/customercard/client.go b/pkg/customercard/client.go index 0114f80a..b6b2bd4d 100644 --- a/pkg/customercard/client.go +++ b/pkg/customercard/client.go @@ -8,9 +8,8 @@ import ( ) const ( - urlBase = "https://api.mercadopago.com/v1/customers/{customer_id}" - urlCards = urlBase + "/cards" - urlCardsWithID = urlCards + "/{card_id}" + urlBase = "https://api.mercadopago.com/v1/customers/{customer_id}/cards" + urlWithID = urlBase + "/{card_id}" ) // Client contains the methods to interact with the Customer Cards API. @@ -58,7 +57,7 @@ func (c *client) Create(ctx context.Context, customerID string, request Request) "customer_id": customerID, } - res, err := httpclient.Post[Response](ctx, c.config, urlCards, request, httpclient.WithPathParams(params)) + res, err := httpclient.Post[Response](ctx, c.config, urlBase, request, httpclient.WithPathParams(params)) if err != nil { return nil, err } @@ -72,7 +71,7 @@ func (c *client) Get(ctx context.Context, customerID, cardID string) (*Response, "card_id": cardID, } - res, err := httpclient.Get[Response](ctx, c.config, urlCardsWithID, httpclient.WithPathParams(params)) + res, err := httpclient.Get[Response](ctx, c.config, urlWithID, httpclient.WithPathParams(params)) if err != nil { return nil, err } @@ -86,7 +85,7 @@ func (c *client) Update(ctx context.Context, customerID, cardID string, request "card_id": cardID, } - res, err := httpclient.Put[Response](ctx, c.config, urlCardsWithID, request, httpclient.WithPathParams(params)) + res, err := httpclient.Put[Response](ctx, c.config, urlWithID, request, httpclient.WithPathParams(params)) if err != nil { return nil, err } @@ -100,7 +99,7 @@ func (c *client) Delete(ctx context.Context, customerID, cardID string) (*Respon "card_id": cardID, } - res, err := httpclient.Delete[Response](ctx, c.config, urlCardsWithID, nil, httpclient.WithPathParams(params)) + res, err := httpclient.Delete[Response](ctx, c.config, urlWithID, nil, httpclient.WithPathParams(params)) if err != nil { return nil, err } @@ -113,7 +112,7 @@ func (c *client) List(ctx context.Context, customerID string) ([]Response, error "customer_id": customerID, } - res, err := httpclient.Get[[]Response](ctx, c.config, urlCards, httpclient.WithPathParams(params)) + res, err := httpclient.Get[[]Response](ctx, c.config, urlBase, httpclient.WithPathParams(params)) if err != nil { return nil, err } diff --git a/pkg/customercard/client_test.go b/pkg/customercard/client_test.go index 9b67e76c..f4d7fa96 100644 --- a/pkg/customercard/client_test.go +++ b/pkg/customercard/client_test.go @@ -248,10 +248,10 @@ func TestUpdate(t *testing.T) { } if gotErr != tt.wantErr { - t.Errorf("client.Create() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("client.Update() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("client.Create() = %v, want %v", got, tt.want) + t.Errorf("client.Update() = %v, want %v", got, tt.want) } }) } @@ -363,10 +363,10 @@ func TestGet(t *testing.T) { } if gotErr != tt.wantErr { - t.Errorf("client.Create() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("client.Get() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("client.Create() = %v, want %v", got, tt.want) + t.Errorf("client.Get() = %v, want %v", got, tt.want) } }) } @@ -478,10 +478,10 @@ func TestDelete(t *testing.T) { } if gotErr != tt.wantErr { - t.Errorf("client.Create() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("client.Delete() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("client.Create() = %v, want %v", got, tt.want) + t.Errorf("client.Delete() = %v, want %v", got, tt.want) } }) } @@ -593,10 +593,10 @@ func TestList(t *testing.T) { } if gotErr != tt.wantErr { - t.Errorf("client.Create() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("client.List() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("client.Create() = %v, want %v", got, tt.want) + t.Errorf("client.List() = %v, want %v", got, tt.want) } }) } diff --git a/pkg/customercard/response.go b/pkg/customercard/response.go index f5e7c763..9af9d329 100644 --- a/pkg/customercard/response.go +++ b/pkg/customercard/response.go @@ -4,23 +4,23 @@ import "time" // Response represents a customer card. type Response struct { - ID string `json:"id"` - CustomerID string `json:"customer_id"` - UserID string `json:"user_id"` - CardNumberID string `json:"card_number_id"` - FirstSixDigits string `json:"first_six_digits"` - LastFourDigits string `json:"last_four_digits"` - ExpirationMonth int `json:"expiration_month"` - ExpirationYear int `json:"expiration_year"` - LiveMode bool `json:"live_mode"` - DateCreated *time.Time `json:"date_created"` - DateLastUpdated *time.Time `json:"date_last_updated"` + ID string `json:"id"` + CustomerID string `json:"customer_id"` + UserID string `json:"user_id"` + CardNumberID string `json:"card_number_id"` + FirstSixDigits string `json:"first_six_digits"` + LastFourDigits string `json:"last_four_digits"` + ExpirationMonth int `json:"expiration_month"` + ExpirationYear int `json:"expiration_year"` + LiveMode bool `json:"live_mode"` - Issuer IssuerResponse `json:"issuer"` - Cardholder CardholderResponse `json:"cardholder"` - AdditionalInfo AdditionalInfoResponse `json:"additional_info"` - PaymentMethod PaymentMethodResponse `json:"payment_method"` - SecurityCode SecurityCode `json:"security_code"` + DateCreated *time.Time `json:"date_created"` + DateLastUpdated *time.Time `json:"date_last_updated"` + Issuer IssuerResponse `json:"issuer"` + Cardholder CardholderResponse `json:"cardholder"` + AdditionalInfo AdditionalInfoResponse `json:"additional_info"` + PaymentMethod PaymentMethodResponse `json:"payment_method"` + SecurityCode SecurityCode `json:"security_code"` } // AdditionalInfoResponse represents additional customer card information.