diff --git a/examples/apis/payment/cancel/main.go b/examples/apis/payment/cancel/main.go index b74eab66..6b784ad1 100644 --- a/examples/apis/payment/cancel/main.go +++ b/examples/apis/payment/cancel/main.go @@ -20,11 +20,11 @@ func main() { client := payment.NewClient(cfg) var paymentID int64 = 123 - result, err := client.Cancel(context.Background(), paymentID) + payment, err := client.Cancel(context.Background(), paymentID) if err != nil { fmt.Println(err) return } - fmt.Println(result) + fmt.Println(payment) } diff --git a/examples/apis/payment/capture/main.go b/examples/apis/payment/capture/main.go index c5f763d2..9b28a17b 100644 --- a/examples/apis/payment/capture/main.go +++ b/examples/apis/payment/capture/main.go @@ -30,18 +30,18 @@ func main() { } client := payment.NewClient(cfg) - pay, err := client.Create(context.Background(), paymentRequest) + payment, err := client.Create(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return } // Capture. - pay, err = client.Capture(context.Background(), pay.ID) + payment, err = client.Capture(context.Background(), payment.ID) if err != nil { fmt.Println(err) return } - fmt.Println(pay) + fmt.Println(payment) } diff --git a/examples/apis/payment/capture_amount/main.go b/examples/apis/payment/capture_amount/main.go index bd293760..8e96fa71 100644 --- a/examples/apis/payment/capture_amount/main.go +++ b/examples/apis/payment/capture_amount/main.go @@ -30,18 +30,18 @@ func main() { } client := payment.NewClient(cfg) - pay, err := client.Create(context.Background(), paymentRequest) + payment, err := client.Create(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return } // Capture amount. - pay, err = client.CaptureAmount(context.Background(), pay.ID, 100.1) + payment, err = client.CaptureAmount(context.Background(), payment.ID, 100.1) if err != nil { fmt.Println(err) return } - fmt.Println(pay) + fmt.Println(payment) } diff --git a/examples/apis/payment/create/main.go b/examples/apis/payment/create/main.go index 57cb2695..d3f4028f 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) - pay, err := client.Create(context.Background(), paymentRequest) + payment, err := client.Create(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return } - fmt.Println(pay) + fmt.Println(payment) } diff --git a/examples/apis/payment/get/main.go b/examples/apis/payment/get/main.go index 1c6478f5..c3afd712 100644 --- a/examples/apis/payment/get/main.go +++ b/examples/apis/payment/get/main.go @@ -23,20 +23,19 @@ func main() { Email: "{{EMAIL}}", }, } - client := payment.NewClient(cfg) - pay, err := client.Create(context.Background(), paymentRequest) + payment, err := client.Create(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return } - pay, err = client.Get(context.Background(), pay.ID) + payment, err = client.Get(context.Background(), payment.ID) if err != nil { fmt.Println(err) return } - fmt.Println(pay) + fmt.Println(payment) } diff --git a/examples/apis/payment/search/main.go b/examples/apis/payment/search/main.go index c62549b7..7525d039 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) - pay, err := client.Search(context.Background(), paymentRequest) + payment, err := client.Search(context.Background(), paymentRequest) if err != nil { fmt.Println(err) return } - fmt.Println(pay) + fmt.Println(payment) } diff --git a/examples/apis/paymentmethod/list/main.go b/examples/apis/paymentmethod/list/main.go index 46530a2f..e9df7526 100644 --- a/examples/apis/paymentmethod/list/main.go +++ b/examples/apis/paymentmethod/list/main.go @@ -17,8 +17,8 @@ func main() { return } - client := paymentmethod.NewClient(cfg) - paymentMethods, err := client.List(context.Background()) + paymentMethodClient := paymentmethod.NewClient(cfg) + paymentMethods, err := paymentMethodClient.List(context.Background()) if err != nil { fmt.Println(err) return diff --git a/examples/apis/refund/get/main.go b/examples/apis/refund/get/main.go new file mode 100644 index 00000000..f35a438d --- /dev/null +++ b/examples/apis/refund/get/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/refund" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + refundClient := refund.NewClient(cfg) + + var paymentID int64 = 12344555 + var refundID int64 = 12344555 + + refund, err := refundClient.Get(context.Background(), paymentID, refundID) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(refund) +} diff --git a/examples/apis/refund/list/main.go b/examples/apis/refund/list/main.go new file mode 100644 index 00000000..365fd886 --- /dev/null +++ b/examples/apis/refund/list/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/refund" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + refundClient := refund.NewClient(cfg) + + var paymentID int64 = 12233344 + + refunds, err := refundClient.List(context.Background(), paymentID) + if err != nil { + fmt.Println(err) + return + } + for _, v := range refunds { + fmt.Println(v) + } +} diff --git a/examples/apis/refund/partial_refund/main.go b/examples/apis/refund/partial_refund/main.go new file mode 100644 index 00000000..38dc30d5 --- /dev/null +++ b/examples/apis/refund/partial_refund/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/payment" + "github.com/mercadopago/sdk-go/pkg/refund" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + // Create payment. + paymentRequest := payment.Request{ + TransactionAmount: 105.1, + PaymentMethodID: "master", + Payer: &payment.PayerRequest{ + Email: "{{EMAIL}}", + }, + Token: "{{TOKEN}}", + Installments: 1, + Capture: false, + } + + paymentClient := payment.NewClient(cfg) + payment, err := paymentClient.Create(context.Background(), paymentRequest) + if err != nil { + fmt.Println(err) + return + } + + partialAmount := payment.TransactionAmount - 10.0 + + refundClient := refund.NewClient(cfg) + refund, err := refundClient.CreatePartialRefund(context.Background(), partialAmount, payment.ID) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(refund) +} diff --git a/examples/apis/refund/total_refund/main.go b/examples/apis/refund/total_refund/main.go new file mode 100644 index 00000000..682d1c7e --- /dev/null +++ b/examples/apis/refund/total_refund/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "context" + "fmt" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/payment" + "github.com/mercadopago/sdk-go/pkg/refund" +) + +func main() { + accessToken := "{{ACCESS_TOKEN}}" + + cfg, err := config.New(accessToken) + if err != nil { + fmt.Println(err) + return + } + + // Create payment. + paymentRequest := payment.Request{ + TransactionAmount: 105.1, + PaymentMethodID: "master", + Payer: &payment.PayerRequest{ + Email: "{{EMAIL}}", + }, + Token: "{{TOKEN}}", + Installments: 1, + Capture: false, + } + + paymentClient := payment.NewClient(cfg) + payment, err := paymentClient.Create(context.Background(), paymentRequest) + if err != nil { + fmt.Println(err) + return + } + + refundClient := refund.NewClient(cfg) + refund, err := refundClient.Create(context.Background(), payment.ID) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(refund) +} diff --git a/examples/apis/user/get/main.go b/examples/apis/user/get/main.go index fec0b150..85b7aaed 100644 --- a/examples/apis/user/get/main.go +++ b/examples/apis/user/get/main.go @@ -17,8 +17,8 @@ func main() { return } - client := user.NewClient(cfg) - user, err := client.Get(context.Background()) + userClient := user.NewClient(cfg) + user, err := userClient.Get(context.Background()) if err != nil { fmt.Println(err) return diff --git a/pkg/payment/payment.go b/pkg/payment/client.go similarity index 89% rename from pkg/payment/payment.go rename to pkg/payment/client.go index 6f21f97f..a89c9650 100644 --- a/pkg/payment/payment.go +++ b/pkg/payment/client.go @@ -12,11 +12,9 @@ import ( ) const ( - baseURL = "https://api.mercadopago.com/v1/" - postURL = baseURL + "payments" - searchURL = baseURL + "payments/search" - getURL = baseURL + "payments/{id}" - putURL = baseURL + "payments/{id}" + urlBase = "https://api.mercadopago.com/v1/payments" + urlSearch = urlBase + "/search" + urlWithID = urlBase + "/{id}" ) // Client contains the methods to interact with the Payments API. @@ -62,7 +60,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, postURL, request) + res, err := httpclient.Post[Response](ctx, c.cfg, urlBase, request) if err != nil { return nil, err } @@ -73,7 +71,7 @@ func (c *client) Create(ctx context.Context, request Request) (*Response, error) func (c *client) Search(ctx context.Context, dto SearchRequest) (*SearchResponse, error) { params := dto.Parameters() - url, err := url.Parse(searchURL) + url, err := url.Parse(urlSearch) if err != nil { return nil, fmt.Errorf("error parsing url: %w", err) } @@ -90,7 +88,7 @@ func (c *client) Search(ctx context.Context, dto SearchRequest) (*SearchResponse func (c *client) Get(ctx context.Context, id int64) (*Response, error) { conv := strconv.Itoa(int(id)) - res, err := httpclient.Get[Response](ctx, c.cfg, strings.Replace(getURL, "{id}", conv, 1)) + res, err := httpclient.Get[Response](ctx, c.cfg, strings.Replace(urlWithID, "{id}", conv, 1)) if err != nil { return nil, err } @@ -102,7 +100,7 @@ func (c *client) Cancel(ctx context.Context, id int64) (*Response, error) { dto := &CancelRequest{Status: "cancelled"} conv := strconv.Itoa(int(id)) - res, err := httpclient.Put[Response](ctx, c.cfg, strings.Replace(putURL, "{id}", conv, 1), dto) + res, err := httpclient.Put[Response](ctx, c.cfg, strings.Replace(urlWithID, "{id}", conv, 1), dto) if err != nil { return nil, err } @@ -114,7 +112,7 @@ func (c *client) Capture(ctx context.Context, id int64) (*Response, error) { dto := &CaptureRequest{Capture: true} conv := strconv.Itoa(int(id)) - res, err := httpclient.Put[Response](ctx, c.cfg, strings.Replace(putURL, "{id}", conv, 1), dto) + res, err := httpclient.Put[Response](ctx, c.cfg, strings.Replace(urlWithID, "{id}", conv, 1), dto) if err != nil { return nil, err } @@ -126,7 +124,7 @@ func (c *client) CaptureAmount(ctx context.Context, id int64, amount float64) (* dto := &CaptureRequest{TransactionAmount: amount, Capture: true} conv := strconv.Itoa(int(id)) - res, err := httpclient.Put[Response](ctx, c.cfg, strings.Replace(putURL, "{id}", conv, 1), dto) + res, err := httpclient.Put[Response](ctx, c.cfg, strings.Replace(urlWithID, "{id}", conv, 1), dto) if err != nil { return nil, err } diff --git a/pkg/payment/payment_test.go b/pkg/payment/client_test.go similarity index 98% rename from pkg/payment/payment_test.go rename to pkg/payment/client_test.go index 4022660c..1379aa84 100644 --- a/pkg/payment/payment_test.go +++ b/pkg/payment/client_test.go @@ -115,7 +115,7 @@ func TestCreate(t *testing.T) { wantErr: "invalid character 'i' looking for beginning of value", }, { - name: "should_return_formatted_response", + name: "should_return_response", fields: fields{ config: &config.Config{ Requester: &httpclient.Mock{ @@ -226,7 +226,7 @@ func TestSearch(t *testing.T) { wantErr: "invalid character 'i' looking for beginning of value", }, { - name: "should_return_formatted_response", + name: "should_return_response", fields: fields{ config: &config.Config{ Requester: &httpclient.Mock{ @@ -354,7 +354,7 @@ func TestGet(t *testing.T) { wantErr: "invalid character 'i' looking for beginning of value", }, { - name: "should_return_formatted_response", + name: "should_return_response", fields: fields{ config: &config.Config{ Requester: &httpclient.Mock{ @@ -465,7 +465,7 @@ func TestCancel(t *testing.T) { wantErr: "invalid character 'i' looking for beginning of value", }, { - name: "should_return_formatted_response", + name: "should_return_response", fields: fields{ config: &config.Config{ Requester: &httpclient.Mock{ @@ -576,7 +576,7 @@ func TestCapture(t *testing.T) { wantErr: "invalid character 'i' looking for beginning of value", }, { - name: "should_return_formatted_response", + name: "should_return_response", fields: fields{ config: &config.Config{ Requester: &httpclient.Mock{ @@ -688,7 +688,7 @@ func TestCaptureAmount(t *testing.T) { wantErr: "invalid character 'i' looking for beginning of value", }, { - name: "should_return_formatted_response", + name: "should_return_response", fields: fields{ config: &config.Config{ Requester: &httpclient.Mock{ diff --git a/pkg/payment/response.go b/pkg/payment/response.go index fff8eb57..506264aa 100644 --- a/pkg/payment/response.go +++ b/pkg/payment/response.go @@ -51,11 +51,11 @@ type Response struct { 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"` + 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"` @@ -112,7 +112,7 @@ type AdditionalInfoPayerResponse struct { FirstName string `json:"first_name"` LastName string `json:"last_name"` - RegistrationDate time.Time `json:"registration_date"` + RegistrationDate *time.Time `json:"registration_date"` Phone PhoneResponse `json:"phone"` Address AddressResponse `json:"address"` } @@ -172,8 +172,8 @@ type CardResponse struct { ExpirationYear int `json:"expiration_year"` ExpirationMonth int `json:"expiration_month"` - DateCreated time.Time `json:"date_created"` - DateLastUpdated time.Time `json:"date_last_updated"` + DateCreated *time.Time `json:"date_created"` + DateLastUpdated *time.Time `json:"date_last_updated"` Cardholder CardholderResponse `json:"cardholder"` } @@ -282,7 +282,7 @@ type DiscountResponse struct { Type string `json:"type"` Value float64 `json:"value"` - LimitDate time.Time `json:"limit_date"` + LimitDate *time.Time `json:"limit_date"` } // FeeResponse represents payment fee information. @@ -321,7 +321,7 @@ type RefundResponse struct { Amount float64 `json:"amount"` AdjustmentAmount float64 `json:"adjustment_amount"` - DateCreated time.Time `json:"date_created"` + DateCreated *time.Time `json:"date_created"` Source SourceResponse `json:"source"` } diff --git a/pkg/refund/client.go b/pkg/refund/client.go new file mode 100644 index 00000000..b0862d78 --- /dev/null +++ b/pkg/refund/client.go @@ -0,0 +1,99 @@ +package refund + +import ( + "context" + "strconv" + "strings" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/internal/httpclient" +) + +const ( + urlBase = "https://api.mercadopago.com/v1/payments/{id}/refunds" + urlWithID = urlBase + "/{refund_id}" +) + +// Client contains the methods to interact with the Payment's refund API. +type Client interface { + // Get gets a specific refund by payment id and refund id. + // It is a get request to the endpoint: https://api.mercadopago.com/v1/payments/{id}/refunds/{refund_id} + // Reference: https://www.mercadopago.com/developers/en/reference/chargebacks/_payments_id_refunds_refund_id/get + Get(ctx context.Context, paymentID, refundID int64) (*Response, error) + + // List gets a refund list by payment id. + // It is a get request to the endpoint: https://api.mercadopago.com/v1/payments/{id}/refunds + // Reference: https://www.mercadopago.com/developers/en/reference/chargebacks/_payments_id_refunds/get + List(ctx context.Context, paymentID int64) ([]Response, error) + + // Create create a refund by payment id. + // It is a post request to the endpoint: https://api.mercadopago.com/v1/payments/{id}/refunds + // Reference: https://www.mercadopago.com/developers/en/reference/chargebacks/_payments_id_refunds/post + Create(ctx context.Context, paymentID int64) (*Response, error) + + // CreatePartialRefund create a partial refund by payment id. + // It is a post request to the endpoint: https://api.mercadopago.com/v1/payments/{id}/refunds + // Reference: https://www.mercadopago.com/developers/en/reference/chargebacks/_payments_id_refunds/post + CreatePartialRefund(ctx context.Context, amount float64, paymentID int64) (*Response, error) +} + +// client is the implementation of Client. +type client struct { + cfg *config.Config +} + +// Client contains the methods to interact with the Refund's API. +func NewClient(c *config.Config) Client { + return &client{ + cfg: c, + } +} + +func (c *client) Get(ctx context.Context, paymentID, refundID int64) (*Response, error) { + convertedPaymentID := strconv.Itoa(int(paymentID)) + convertedRefundID := strconv.Itoa(int(refundID)) + + url := strings.NewReplacer("{id}", convertedPaymentID, "{refund_id}", convertedRefundID).Replace(urlWithID) + + res, err := httpclient.Get[Response](ctx, c.cfg, url) + if err != nil { + return nil, err + } + + return res, nil +} + +func (c *client) List(ctx context.Context, paymentID int64) ([]Response, error) { + convertedRefundID := strconv.Itoa(int(paymentID)) + + res, err := httpclient.Get[[]Response](ctx, c.cfg, strings.Replace(urlBase, "{id}", convertedRefundID, 1)) + if err != nil { + return nil, err + } + + return *res, nil +} + +func (c *client) Create(ctx context.Context, paymentID int64) (*Response, error) { + convertedPaymentID := strconv.Itoa(int(paymentID)) + + res, err := httpclient.Post[Response](ctx, c.cfg, strings.Replace(urlBase, "{id}", convertedPaymentID, 1), nil) + if err != nil { + return nil, err + } + + return res, nil +} + +func (c *client) CreatePartialRefund(ctx context.Context, amount float64, paymentID int64) (*Response, error) { + request := &Request{Amount: amount} + + convertedPaymentID := strconv.Itoa(int(paymentID)) + + res, err := httpclient.Post[Response](ctx, c.cfg, strings.Replace(urlBase, "{id}", convertedPaymentID, 1), request) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/pkg/refund/client_test.go b/pkg/refund/client_test.go new file mode 100644 index 00000000..3a23a2d1 --- /dev/null +++ b/pkg/refund/client_test.go @@ -0,0 +1,469 @@ +package refund + +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 ( + responseJSON, _ = os.Open("../../resources/mocks/refund/response.json") + response, _ = io.ReadAll(responseJSON) + + listResponseJSON, _ = os.Open("../../resources/mocks/refund/list_response.json") + listResponse, _ = io.ReadAll(listResponseJSON) +) + +func TestCreate(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_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{ + 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_fail_to_unmarshaling_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_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(), + }, + want: buildResponseMock(), + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + cfg: tt.fields.config, + } + got, err := c.Create(tt.args.ctx, 1622029222) + 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 TestCreatePartialRefund(t *testing.T) { + type fields struct { + config *config.Config + } + type args struct { + ctx context.Context + 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{ + 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_fail_to_unmarshaling_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_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(), + }, + want: buildResponseMock(), + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + cfg: tt.fields.config, + } + got, err := c.CreatePartialRefund(tt.args.ctx, tt.args.amount, 1622029222) + 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 + } + 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{ + 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_fail_to_unmarshaling_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_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(), + }, + want: buildResponseMock(), + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + cfg: tt.fields.config, + } + got, err := c.Get(tt.args.ctx, 1622029222, 12355555) + 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 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_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{ + 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_fail_to_unmarshaling_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_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{*buildResponseMock()}, + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &client{ + cfg: tt.fields.config, + } + got, err := c.List(tt.args.ctx, 1622029222) + 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 buildResponseMock() *Response { + d, _ := time.Parse(time.RFC3339, "2024-02-05T15:35:49.000-04:00") + res := &Response{ + ID: 1622029222, + PaymentID: 7186040733, + Status: "approved", + Amount: 1.5, + Source: Source{ + Name: "Test Test", + ID: "7186040733", + Type: "collector", + }, + DateCreated: &d, + RefundMode: "standard", + AdjustmentAmount: 0, + } + + return res +} diff --git a/pkg/refund/request.go b/pkg/refund/request.go new file mode 100644 index 00000000..6fa35e42 --- /dev/null +++ b/pkg/refund/request.go @@ -0,0 +1,6 @@ +package refund + +// Request represents a request for creating a refund. +type Request struct { + Amount float64 `json:"amount,omitempty"` +} diff --git a/pkg/refund/response.go b/pkg/refund/response.go new file mode 100644 index 00000000..42c3b55c --- /dev/null +++ b/pkg/refund/response.go @@ -0,0 +1,27 @@ +package refund + +import ( + "time" +) + +// Response is the response from the Refund's API. +type Response struct { + 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"` + Source Source `json:"source"` +} + +// Source represents the data to identify who originated the refund +type Source struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` +} diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go index 3ce43c7b..6230f71a 100644 --- a/pkg/user/user_test.go +++ b/pkg/user/user_test.go @@ -83,7 +83,7 @@ func TestGet(t *testing.T) { wantErr: "invalid character 'i' looking for beginning of value", }, { - name: "should_return_formatted_response", + name: "should_return_response", fields: fields{ config: &config.Config{ Requester: &httpclient.Mock{ diff --git a/resources/mocks/refund/list_response.json b/resources/mocks/refund/list_response.json new file mode 100644 index 00000000..61e92a3b --- /dev/null +++ b/resources/mocks/refund/list_response.json @@ -0,0 +1,18 @@ +[ + { + "id": 1622029222, + "payment_id": 7186040733, + "amount": 1.5, + "source": { + "name": "Test Test", + "id": "7186040733", + "type": "collector" + }, + "date_created": "2024-02-05T15:35:49.000-04:00", + "unique_sequence_number": null, + "refund_mode": "standard", + "adjustment_amount": 0, + "status": "approved", + "reason": null + } +] \ No newline at end of file diff --git a/resources/mocks/refund/response.json b/resources/mocks/refund/response.json new file mode 100644 index 00000000..2f271351 --- /dev/null +++ b/resources/mocks/refund/response.json @@ -0,0 +1,16 @@ +{ + "id": 1622029222, + "payment_id": 7186040733, + "amount": 1.5, + "source": { + "name": "Test Test", + "id": "7186040733", + "type": "collector" + }, + "date_created": "2024-02-05T15:35:49.000-04:00", + "unique_sequence_number": null, + "refund_mode": "standard", + "adjustment_amount": 0, + "status": "approved", + "reason": null +} \ No newline at end of file diff --git a/test/integration/cardtoken/cardtoken_test.go b/test/integration/cardtoken/cardtoken_test.go index 6107695e..59c16621 100644 --- a/test/integration/cardtoken/cardtoken_test.go +++ b/test/integration/cardtoken/cardtoken_test.go @@ -16,11 +16,11 @@ func TestCardToken(t *testing.T) { t.Fatal(err) } - client := cardtoken.NewClient(cfg) - res, err := client.Create(context.Background(), cardtoken.MockCardTokenRequest()) + cardTokenClient := cardtoken.NewClient(cfg) + cardToken, err := cardTokenClient.Create(context.Background(), cardtoken.MockCardTokenRequest()) - if res == nil { - t.Error("res can't be nil") + if cardToken == nil { + t.Error("cardToken can't be nil") } if err != nil { t.Errorf(err.Error()) diff --git a/test/integration/payment/payment_test.go b/test/integration/payment/payment_test.go index 1921eaff..1d612281 100644 --- a/test/integration/payment/payment_test.go +++ b/test/integration/payment/payment_test.go @@ -18,9 +18,9 @@ func TestPayment(t *testing.T) { t.Fatal(err) } - client := payment.NewClient(c) + paymentClient := payment.NewClient(c) - dto := payment.Request{ + paymentRequest := payment.Request{ TransactionAmount: 105.1, PaymentMethodID: "pix", Payer: &payment.PayerRequest{ @@ -28,12 +28,12 @@ func TestPayment(t *testing.T) { }, } - result, err := client.Create(context.Background(), dto) - if result == nil { - t.Error("result can't be nil") + payment, err := paymentClient.Create(context.Background(), paymentRequest) + if payment == nil { + t.Error("payment can't be nil") return } - if result.ID == 0 { + if payment.ID == 0 { t.Error("id can't be nil") } if err != nil { @@ -47,16 +47,16 @@ func TestPayment(t *testing.T) { t.Fatal(err) } - dto := payment.SearchRequest{ + paymentRequest := 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") + paymentClient := payment.NewClient(c) + paymentSearch, err := paymentClient.Search(context.Background(), paymentRequest) + if paymentSearch == nil { + t.Error("paymentSearch can't be nil") } if err != nil { t.Errorf(err.Error()) @@ -69,8 +69,8 @@ func TestPayment(t *testing.T) { t.Fatal(err) } - client := payment.NewClient(c) - dto := payment.Request{ + paymentClient := payment.NewClient(c) + paymentRequest := payment.Request{ TransactionAmount: 105.1, PaymentMethodID: "pix", Payer: &payment.PayerRequest{ @@ -78,21 +78,21 @@ func TestPayment(t *testing.T) { }, } - result, err := client.Create(context.Background(), dto) - if result == nil { - t.Error("result can't be nil") + payment, err := paymentClient.Create(context.Background(), paymentRequest) + if payment == nil { + t.Error("payment can't be nil") return } 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") + payment, err = paymentClient.Get(context.Background(), payment.ID) + if payment == nil { + t.Error("payment can't be nil") return } - if result.ID == 0 { + if payment.ID == 0 { t.Error("id can't be nil") } if err != nil { @@ -106,8 +106,8 @@ func TestPayment(t *testing.T) { t.Fatal(err) } - client := payment.NewClient(c) - dto := payment.Request{ + paymentClient := payment.NewClient(c) + paymentRequest := payment.Request{ TransactionAmount: 105.1, PaymentMethodID: "pix", Payer: &payment.PayerRequest{ @@ -115,21 +115,21 @@ func TestPayment(t *testing.T) { }, } - result, err := client.Create(context.Background(), dto) - if result == nil { - t.Error("result can't be nil") + payment, err := paymentClient.Create(context.Background(), paymentRequest) + if payment == nil { + t.Error("payment can't be nil") return } 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") + payment, err = paymentClient.Cancel(context.Background(), payment.ID) + if payment == nil { + t.Error("payment can't be nil") return } - if result.ID == 0 { + if payment.ID == 0 { t.Error("id can't be nil") } if err != nil { @@ -144,10 +144,10 @@ func TestPayment(t *testing.T) { t.Fatal(err) } - client := payment.NewClient(c) + paymentClient := payment.NewClient(c) // Create payment. - dto := payment.Request{ + paymentRequest := payment.Request{ TransactionAmount: 105.1, PaymentMethodID: "visa", Payer: &payment.PayerRequest{ @@ -159,18 +159,18 @@ func TestPayment(t *testing.T) { Capture: false, } - result, err := client.Create(context.Background(), dto) - if result == nil { - t.Error("result can't be nil") + payment, err := paymentClient.Create(context.Background(), paymentRequest) + if payment == nil { + t.Error("payment can't be nil") return } 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") + payment, err = paymentClient.Capture(context.Background(), payment.ID) + if payment == nil { + t.Error("payment can't be nil") } if err != nil { t.Errorf(err.Error()) @@ -183,10 +183,10 @@ func TestPayment(t *testing.T) { 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") + paymentClient := payment.NewClient(c) + payment, err := paymentClient.CaptureAmount(context.Background(), 123, 100.1) + if payment == nil { + t.Error("payment can't be nil") } if err != nil { t.Errorf(err.Error()) diff --git a/test/integration/refund/refund_test.go b/test/integration/refund/refund_test.go new file mode 100644 index 00000000..95875c90 --- /dev/null +++ b/test/integration/refund/refund_test.go @@ -0,0 +1,207 @@ +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" + "github.com/mercadopago/sdk-go/pkg/refund" +) + +func TestRefund(t *testing.T) { + t.Run("should_create_refund", func(t *testing.T) { + c, err := config.New(os.Getenv("ACCESS_TOKEN")) + if err != nil { + t.Fatal(err) + } + + paymentClient := payment.NewClient(c) + + // Create payment. + paymentRequest := payment.Request{ + TransactionAmount: 105.1, + 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, + } + + payment, err := paymentClient.Create(context.Background(), paymentRequest) + if payment == nil { + t.Error("payment can't be nil") + return + } + if err != nil { + t.Errorf(err.Error()) + } + + refundClient := refund.NewClient(c) + refund, err := refundClient.Create(context.Background(), payment.ID) + if refund == nil { + t.Error("refund can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + }) + + t.Run("should_create_partial_refund", func(t *testing.T) { + c, err := config.New(os.Getenv("ACCESS_TOKEN")) + if err != nil { + t.Fatal(err) + } + + paymentClient := payment.NewClient(c) + + // Create payment. + paymentRequest := payment.Request{ + TransactionAmount: 105.1, + 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, + } + + payment, err := paymentClient.Create(context.Background(), paymentRequest) + if payment == nil { + t.Error("payment can't be nil") + return + } + if err != nil { + t.Errorf(err.Error()) + } + partialAmount := paymentRequest.TransactionAmount - 5.0 + + refundClient := refund.NewClient(c) + refund, err := refundClient.CreatePartialRefund(context.Background(), partialAmount, payment.ID) + if refund == nil { + t.Error("refund can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + }) + + t.Run("should_get_refund", func(t *testing.T) { + c, err := config.New(os.Getenv("ACCESS_TOKEN")) + if err != nil { + t.Fatal(err) + } + + paymentClient := payment.NewClient(c) + + // Create payment. + paymentRequest := payment.Request{ + TransactionAmount: 105.1, + 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, + } + + payment, err := paymentClient.Create(context.Background(), paymentRequest) + if payment == nil { + t.Error("payment can't be nil") + return + } + if err != nil { + t.Errorf(err.Error()) + } + + refundClient := refund.NewClient(c) + refund, err := refundClient.Create(context.Background(), payment.ID) + if refund == nil { + t.Error("refund can't be nil") + return + } + if err != nil { + t.Errorf(err.Error()) + } + + refund, err = refundClient.Get(context.Background(), payment.ID, refund.ID) + if err != nil { + t.Errorf(err.Error()) + } + + if refund.ID == 0 { + t.Error("id can't be nil") + } + }) + + t.Run("should_list_refund", func(t *testing.T) { + c, err := config.New(os.Getenv("ACCESS_TOKEN")) + if err != nil { + t.Fatal(err) + } + + paymentClient := payment.NewClient(c) + + // Create payment. + paymentRequest := payment.Request{ + TransactionAmount: 105.1, + 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, + } + + payment, err := paymentClient.Create(context.Background(), paymentRequest) + if payment == nil { + t.Error("payment can't be nil") + return + } + if err != nil { + t.Errorf(err.Error()) + } + + refundClient := refund.NewClient(c) + partialAmount := paymentRequest.TransactionAmount - 5.0 + + // Partial refund + refund, err := refundClient.CreatePartialRefund(context.Background(), partialAmount, payment.ID) + if refund == nil { + t.Error("refund can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + + // Total refund + refund, err = refundClient.Create(context.Background(), payment.ID) + if refund == nil { + t.Error("refund can't be nil") + } + if err != nil { + t.Errorf(err.Error()) + } + + refunds, err := refundClient.List(context.Background(), payment.ID) + if err != nil { + t.Errorf(err.Error()) + } + + if len(refunds) != 2 { + t.Error("size can't be different of 2") + } + }) +}