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 diff --git a/examples/apis/cardtoken/create/main.go b/examples/apis/cardtoken/create/main.go new file mode 100644 index 00000000..055d8685 --- /dev/null +++ b/examples/apis/cardtoken/create/main.go @@ -0,0 +1,43 @@ +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) + + var req = cardtoken.Request{ + SiteID: "{{SITE_ID}}", + CardNumber: "{{CARD_NUMBER}}", + ExpirationMonth: "11", + ExpirationYear: "2025", + SecurityCode: "123", + Cardholder: &cardtoken.Cardholder{ + Identification: &cardtoken.Identification{ + Type: "CPF", + Number: "{{CPF_NUMBER}}", + }, + Name: "{{PAYMENT_METHOD}}", + }, + } + + result, err := client.Create(context.Background(), req) + 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..63fa6f38 --- /dev/null +++ b/pkg/cardtoken/client.go @@ -0,0 +1,33 @@ +package cardtoken + +import ( + "context" + + "github.com/mercadopago/sdk-go/pkg/config" + "github.com/mercadopago/sdk-go/pkg/internal/httpclient" +) + +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) +} + +type client struct { + cfg *config.Config +} + +func NewClient(c *config.Config) Client { + return &client{cfg: c} +} + +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, err + } + return res, nil +} diff --git a/pkg/cardtoken/client_test.go b/pkg/cardtoken/client_test.go new file mode 100644 index 00000000..f226352b --- /dev/null +++ b/pkg/cardtoken/client_test.go @@ -0,0 +1,95 @@ +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: "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) + } + }) + } +} diff --git a/pkg/cardtoken/mock.go b/pkg/cardtoken/mock.go new file mode 100644 index 00000000..906c4a32 --- /dev/null +++ b/pkg/cardtoken/mock.go @@ -0,0 +1,52 @@ +package cardtoken + +import ( + "time" +) + +func mockCardToken() *Response { + return &Response{ + ID: "3d40b34eb41a6d0923e5bc545927c2e9", + FirstSixDigits: "503143", + ExpirationMonth: 11, + ExpirationYear: 2025, + LastFourDigits: "6351", + Cardholder: CardholderResponse{ + Identification: IdentificationResponse{ + Number: "70383868084", + Type: "CPF", + }, + Name: "MASTER TEST", + }, + 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 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 new file mode 100644 index 00000000..9cb1b637 --- /dev/null +++ b/pkg/cardtoken/request.go @@ -0,0 +1,20 @@ +package cardtoken + +type Request struct { + 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"` +} + +type Cardholder struct { + Identification *Identification `json:"identification,omitempty"` + Name string `json:"name,omitempty"` +} + +type Identification struct { + Number string `json:"number,omitempty"` + Type string `json:"type,omitempty"` +} diff --git a/pkg/cardtoken/response.go b/pkg/cardtoken/response.go new file mode 100644 index 00000000..a51a15e3 --- /dev/null +++ b/pkg/cardtoken/response.go @@ -0,0 +1,31 @@ +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 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/resources/mocks/cardtoken/response.json b/resources/mocks/cardtoken/response.json new file mode 100644 index 00000000..1b5306b2 --- /dev/null +++ b/resources/mocks/cardtoken/response.json @@ -0,0 +1,23 @@ +{ + "id": "3d40b34eb41a6d0923e5bc545927c2e9", + "first_six_digits": "503143", + "expiration_month": 11, + "expiration_year": 2025, + "last_four_digits": "6351", + "cardholder": { + "identification": { + "number": "70383868084", + "type": "CPF" + }, + "name": "MASTER TEST" + }, + "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 diff --git a/test/integration/cardtoken/cardtoken_test.go b/test/integration/cardtoken/cardtoken_test.go new file mode 100644 index 00000000..6107695e --- /dev/null +++ b/test/integration/cardtoken/cardtoken_test.go @@ -0,0 +1,29 @@ +package cardtoken + +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()) + } + }) +}