From bd203455705d32f14a24e909f80145360bea6d9e Mon Sep 17 00:00:00 2001 From: Eduardo Bernardino Date: Fri, 30 Jun 2023 09:55:14 -0300 Subject: [PATCH] iniciador-sdk-go --- README.md | 222 +++++++++++++++++++++++++ go.mod | 5 + go.sum | 4 + iniciador/auth/auth.go | 95 +++++++++++ iniciador/participants/participants.go | 98 +++++++++++ iniciador/payments/payments.go | 208 +++++++++++++++++++++++ iniciador/utils/utils.go | 151 +++++++++++++++++ tests/sdk/auth_test.go | 149 +++++++++++++++++ tests/sdk/helpers/helpers.go | 25 +++ tests/sdk/participants_test.go | 132 +++++++++++++++ tests/sdk/payments_test.go | 202 ++++++++++++++++++++++ 11 files changed, 1291 insertions(+) create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 iniciador/auth/auth.go create mode 100644 iniciador/participants/participants.go create mode 100644 iniciador/payments/payments.go create mode 100644 iniciador/utils/utils.go create mode 100644 tests/sdk/auth_test.go create mode 100644 tests/sdk/helpers/helpers.go create mode 100644 tests/sdk/participants_test.go create mode 100644 tests/sdk/payments_test.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f6e93c --- /dev/null +++ b/README.md @@ -0,0 +1,222 @@ +# Iniciador Go SDK + +Welcome to the Iniciador Go SDK! This tool is made for Golang developers who want to easily integrate with our API. + +If you have no idea what Iniciador is, check out our [website](https://www.iniciador.com.br/) + +## 1. Description + +The Iniciador SDK is a Golang library that provides a convenient way to interact with the Iniciador API. + +## 2. Installation + +To install the Iniciador SDK, run the following command: + +```bash +go get github.com/iniciador-de-pagamentos/iniciador-sdk-go +``` + +## 3. Usage + +To use the Iniciador SDK, import the necessary modules and create an instance of the `NewAuthClient`: + +```go + import ( + "iniciador-sdk/iniciador/auth" + ) + + func main() { + clientID := "clientId" + clientSecret := "clientSecret" + environment := "dev" + + authClient := auth.NewAuthClient(clientID, clientSecret, environment) + } +``` + +### 3.1 Whitelabel + +#### 3.1.1 Authentication + +To authenticate with the Iniciador Whitelabel, use the `AuthInterface` method: + +```go + import ( + "iniciador-sdk/iniciador/auth" + ) + + func main() { + authOutput, err := authClient.AuthInterface() + if err != nil { + fmt.Println("Authentication failed:", err) + return + } + + accessToken := authOutput.AccessToken + interfaceURL := authOutput.InterfaceURL + paymentId := authOutput.PaymentID + } +``` + +- Use interfaceURL to complete the payment flow +- Use the accessToken and paymentId to verify the payment data + +#### 3.1.2 Payments + +To use payments services with the Iniciador Whitelabel, use the `payments` method: + +##### 3.1.2.1 `get` + +to get the payment details use `Get` method + +```go + import ( + "iniciador-sdk/iniciador/payments" + ) + + func main() { + payment, err := payments.Get(accessToken, authClient) + if err != nil { + fmt.Println("Get Payment failed:", err) + return + } + } +``` + +##### 3.1.2.2 `status` + +to get the payment status details use `Status` method + +```go + import ( + "iniciador-sdk/iniciador/payments" + ) + + func main() { + paymentStatus, err := payments.Status(accessToken, authClient) + if err != nil { + fmt.Println("Get Payment Status failed:", err) + return + } + } +``` + +### 3.2 API Only + +#### 3.2.1 Authentication + +To authenticate with the Iniciador API, use the `Auth` method: + +```go + import ( + "iniciador-sdk/iniciador/auth" + ) + + func main() { + authOutput, err := authClient.Auth() + if err != nil { + fmt.Println("Authentication failed:", err) + return + } + + accessToken := authOutput.AccessToken + } +``` + +#### 3.2.2 Participants + +To get participants with the Iniciador API, use the `GetParticipants` method: + +```go + import ( + "iniciador-sdk/iniciador/participants" + ) + + func main() { + filters := &participants.ParticipantsFilter{} + + participants, err := participants.GetParticipants(accessToken, filters, authClient) + if err != nil { + fmt.Println("Get Participants failed:", err) + return + } + } +``` + +#### 3.2.3 Payments + +To use payments services with the Iniciador API, use the `payments` method: + +##### 3.2.3.1 `send` + +to send the payment use `Send` method + +```go + import ( + "iniciador-sdk/iniciador/payments" + ) + + func main() { + paymentPayload := &payments.PaymentInitiationPayload{ + ExternalID: "externalId", + ParticipantID: "c8f0bf49-4744-4933-8960-7add6e590841", + RedirectURL: "https://app.sandbox.inic.dev/pag-receipt", + User: payments.User{ + Name: "John Doe", + TaxID: "taxId", + }, + Amount: 133300, + Method: "PIX_MANU_AUTO", + } + + paymentInitiation, err := payments.Send(accessToken, paymentPayload, authClient) + if err != nil { + fmt.Println("Send Payments failed:", err) + return + } + } +``` + +##### 3.2.3.2 `get` + +to get the payment details use `Get` method + +```go + import ( + "iniciador-sdk/iniciador/payments" + ) + + func main() { + payment, err := payments.Get(accessToken, authClient) + if err != nil { + fmt.Println("Get Payment failed:", err) + return + } + } +``` + +##### 3.2.3.3 `status` + +to get the payment status details use `Status` method + +```go + import ( + "iniciador-sdk/iniciador/payments" + ) + + func main() { + paymentStatus, err := payments.Get(accessToken, authClient) + if err != nil { + fmt.Println("Get Payment Status failed:", err) + return + } + } +``` + +## Help and Feedback + +If you have any questions or need assistance regarding our SDK, please don't hesitate to reach out to us. Our dedicated support team is here to help you integrate with us as quickly as possible. We strive to provide prompt responses and excellent support. + +We also highly appreciate any feedback you may have. Your thoughts and suggestions are valuable to us as we continuously improve our SDK and services. We welcome your input and encourage you to share your thoughts with us. + +Feel free to contact us by sending an email to suporte@iniciador.com.br. We look forward to hearing from you and assisting you with your integration. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2ae760d --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module iniciador-sdk + +go 1.13 + +require github.com/jarcoal/httpmock v1.3.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e2692f5 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= +github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= +github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= diff --git a/iniciador/auth/auth.go b/iniciador/auth/auth.go new file mode 100644 index 0000000..ba4fbb0 --- /dev/null +++ b/iniciador/auth/auth.go @@ -0,0 +1,95 @@ +package auth + +import ( + "bytes" + "encoding/json" + "net/http" + + "iniciador-sdk/iniciador/utils" +) + +type AuthOutput struct { + AccessToken string `json:"accessToken"` +} + +type AuthInterfaceOutput struct { + AccessToken string `json:"accessToken"` + InterfaceURL string `json:"interfaceURL"` + PaymentID string `json:"paymentId"` +} + +type AuthClient struct { + ClientID string + ClientSecret string + Environment string +} + +func NewAuthClient(clientID, clientSecret, environment string) *AuthClient { + return &AuthClient{ + ClientID: clientID, + ClientSecret: clientSecret, + Environment: utils.SetEnvironment(environment), + } +} + +func (c *AuthClient) GetEnvironment() string { + return c.Environment +} + +func (c *AuthClient) Auth() (*AuthOutput, error) { + requestBody := map[string]interface{}{ + "clientId": c.ClientID, + "clientSecret": c.ClientSecret, + } + requestBodyBytes, err := json.Marshal(requestBody) + if err != nil { + return nil, err + } + + response, err := http.Post( + c.Environment+"/auth", + "application/json", + bytes.NewBuffer(requestBodyBytes), + ) + if err != nil { + return nil, err + } + defer response.Body.Close() + + var authOutput AuthOutput + err = utils.HandleResponse(response, &authOutput) + if err != nil { + return nil, err + } + + return &authOutput, nil +} + +func (c *AuthClient) AuthInterface() (*AuthInterfaceOutput, error) { + requestBody := map[string]interface{}{ + "clientId": c.ClientID, + "clientSecret": c.ClientSecret, + } + requestBodyBytes, err := json.Marshal(requestBody) + if err != nil { + return nil, err + } + + response, err := http.Post( + c.Environment+"/auth/interface", + "application/json", + bytes.NewBuffer(requestBodyBytes), + ) + if err != nil { + return nil, err + } + defer response.Body.Close() + + var authInterfaceOutput AuthInterfaceOutput + err = utils.HandleResponse(response, &authInterfaceOutput) + if err != nil { + return nil, err + } + + return &authInterfaceOutput, nil +} diff --git a/iniciador/participants/participants.go b/iniciador/participants/participants.go new file mode 100644 index 0000000..ee76699 --- /dev/null +++ b/iniciador/participants/participants.go @@ -0,0 +1,98 @@ +package participants + +import ( + "fmt" + "iniciador-sdk/iniciador/auth" + "iniciador-sdk/iniciador/utils" + "net/http" + "net/url" +) + +type ParticipantsPayload struct { + ID string `json:"id"` + Slug string `json:"slug"` + Name string `json:"name"` + Avatar string `json:"avatar"` +} + +type Cursor struct { + AfterCursor string `json:"afterCursor"` + BeforeCursor string `json:"beforeCursor"` +} + +type ParticipantFilterOutput struct { + Data []ParticipantsPayload `json:"data"` + Cursor Cursor `json:"cursor"` +} + +type ParticipantsFilter struct { + ID string + Name string + Slug string + Status string + FirstParticipants string + Limit string + AfterCursor string + BeforeCursor string +} + +func GetParticipants(accessToken string, filters *ParticipantsFilter, authClient *auth.AuthClient) (*ParticipantFilterOutput, error) { + environment := authClient.GetEnvironment() + filterParams := make(url.Values) + + if filters != nil { + if filters.ID != "" { + filterParams.Set("id", filters.ID) + } + if filters.Name != "" { + filterParams.Set("name", filters.Name) + } + if filters.Slug != "" { + filterParams.Set("slug", filters.Slug) + } + if filters.Status != "" { + filterParams.Set("status", filters.Status) + } + if filters.FirstParticipants != "" { + filterParams.Set("firstParticipants", filters.FirstParticipants) + } + if filters.Limit != "" { + filterParams.Set("limit", filters.Limit) + } + if filters.AfterCursor != "" { + filterParams.Set("afterCursor", filters.AfterCursor) + } + if filters.BeforeCursor != "" { + filterParams.Set("beforeCursor", filters.BeforeCursor) + } + } + + var queryString string + if len(filterParams) > 0 { + queryString = filterParams.Encode() + } + + url := fmt.Sprintf("%s/participants?%s", environment, queryString) + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", "Bearer "+accessToken) + + client := http.DefaultClient + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var output ParticipantFilterOutput + err = utils.HandleResponse(resp, &output) + if err != nil { + return nil, err + } + + return &output, nil +} diff --git a/iniciador/payments/payments.go b/iniciador/payments/payments.go new file mode 100644 index 0000000..aa639f8 --- /dev/null +++ b/iniciador/payments/payments.go @@ -0,0 +1,208 @@ +package payments + +import ( + "bytes" + "fmt" + "iniciador-sdk/iniciador/auth" + "iniciador-sdk/iniciador/utils" + "net/http" +) + +type User struct { + TaxID string `json:"taxId"` + Name string `json:"name,omitempty"` +} + +type Bank struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Avatar string `json:"avatar,omitempty"` +} + +type OFAccountsType string + +const ( + CheckingAccount OFAccountsType = "CACC" + SalaryAccount OFAccountsType = "SLRY" + SavingsAccount OFAccountsType = "SVGS" + PrePaidAccount OFAccountsType = "TRAN" +) + +type BankAccount struct { + TaxID string `json:"taxId,omitempty"` + Name string `json:"name,omitempty"` + Number string `json:"number"` + AccountType OFAccountsType `json:"accountType"` + ISPB string `json:"ispb"` + Issuer string `json:"issuer"` + Bank *Bank `json:"bank,omitempty"` +} + +type PaymentInitiationStatus string + +const ( + Started PaymentInitiationStatus = "STARTED" + Enqueued PaymentInitiationStatus = "ENQUEUED" + ConsentAwaitingAuthorization PaymentInitiationStatus = "CONSENT_AWAITING_AUTHORIZATION" + ConsentAuthorized PaymentInitiationStatus = "CONSENT_AUTHORIZED" + ConsentRejected PaymentInitiationStatus = "CONSENT_REJECTED" + PaymentPending PaymentInitiationStatus = "PAYMENT_PENDING" + PaymentPartiallyAccepted PaymentInitiationStatus = "PAYMENT_PARTIALLY_ACCEPTED" + PaymentSettlementProcessing PaymentInitiationStatus = "PAYMENT_SETTLEMENT_PROCESSING" + PaymentSettlementDebtorAccount PaymentInitiationStatus = "PAYMENT_SETTLEMENT_DEBTOR_ACCOUNT" + PaymentCompleted PaymentInitiationStatus = "PAYMENT_COMPLETED" + PaymentRejected PaymentInitiationStatus = "PAYMENT_REJECTED" + Canceled PaymentInitiationStatus = "CANCELED" + Err PaymentInitiationStatus = "ERROR" + PaymentScheduled PaymentInitiationStatus = "PAYMENT_SCHEDULED" +) + +type Provider struct { + TradeName string `json:"tradeName"` + Avatar string `json:"avatar"` + MainColor string `json:"mainColor"` +} + +type Metadata map[string]interface{} + +type PaymentInitiationPayload struct { + ID string `json:"id"` + CreatedAt string `json:"createdAt"` + Error *Error `json:"error,omitempty"` + Status *PaymentInitiationStatus `json:"status,omitempty"` + ExternalID string `json:"externalId,omitempty"` + EndToEndID string `json:"endToEndId,omitempty"` + TransactionIdentification string `json:"transactionIdentification,omitempty"` + ClientID string `json:"clientId"` + CustomerID string `json:"customerId"` + Provider *Provider `json:"provider,omitempty"` + ConsentID string `json:"consentId,omitempty"` + PaymentID string `json:"paymentId,omitempty"` + ParticipantID string `json:"participantId,omitempty"` + User User `json:"user"` + BusinessEntity *User `json:"businessEntity,omitempty"` + Method string `json:"method"` + PixKey string `json:"pixKey,omitempty"` + QRCode string `json:"qrCode,omitempty"` + Amount float64 `json:"amount"` + Date string `json:"date"` + Description string `json:"description,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` + RedirectURL string `json:"redirectURL,omitempty"` + RedirectOnErrorURL string `json:"redirectOnErrorURL,omitempty"` + IBGE string `json:"ibge,omitempty"` + Debtor *BankAccount `json:"debtor,omitempty"` + Creditor *BankAccount `json:"creditor,omitempty"` + Fee float64 `json:"fee,omitempty"` +} + +type Error struct { + Code string `json:"code"` + Description string `json:"description"` +} + +type PaymentStatusPayload struct { + ID string `json:"id"` + Date string `json:"date"` + ConsentID string `json:"consentId,omitempty"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` + TransactionIdentification string `json:"transactionIdentification,omitempty"` + EndToEndID string `json:"endToEndId,omitempty"` + Amount float64 `json:"amount"` + Status string `json:"status"` + Error *Error `json:"error,omitempty"` + RedirectConsentURL string `json:"redirectConsentURL,omitempty"` + ExternalID string `json:"externalId"` +} + +func Send(accessToken string, payment *PaymentInitiationPayload, authClient *auth.AuthClient) (*PaymentInitiationPayload, error) { + payload, err := utils.MarshalWithoutEmptyFields(payment) + if err != nil { + return nil, err + } + + fmt.Println(bytes.NewBuffer(payload)) + + req, err := http.NewRequest("POST", authClient.Environment+"/payments", bytes.NewBuffer(payload)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+accessToken) + + client := &http.Client{} + response, err := client.Do(req) + if err != nil { + return nil, err + } + defer response.Body.Close() + + var paymentInitiationPayload PaymentInitiationPayload + err = utils.HandleResponse(response, &paymentInitiationPayload) + if err != nil { + return nil, err + } + + return &paymentInitiationPayload, nil +} + +func Get(accessToken string, authClient *auth.AuthClient) (*PaymentInitiationPayload, error) { + paymentId, err := utils.ExtractPaymentIDFromJWTPayload(accessToken) + if err != nil { + fmt.Println("Something went wrong trying to get token data:", err) + return nil, err + } + + url := fmt.Sprintf("%s/payments/%s", authClient.Environment, paymentId) + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+accessToken) + + client := http.DefaultClient + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var payload PaymentInitiationPayload + err = utils.HandleResponse(resp, &payload) + if err != nil { + return nil, err + } + + return &payload, nil +} + +func Status(accessToken string, authClient *auth.AuthClient) (*PaymentStatusPayload, error) { + paymentId, err := utils.ExtractPaymentIDFromJWTPayload(accessToken) + if err != nil { + fmt.Println("Something went wrong trying to get token data:", err) + return nil, err + } + + url := fmt.Sprintf("%s/payments/%s/status", authClient.Environment, paymentId) + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+accessToken) + + client := http.DefaultClient + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var payload PaymentStatusPayload + err = utils.HandleResponse(resp, &payload) + if err != nil { + return nil, err + } + + return &payload, nil +} diff --git a/iniciador/utils/utils.go b/iniciador/utils/utils.go new file mode 100644 index 0000000..80fd61e --- /dev/null +++ b/iniciador/utils/utils.go @@ -0,0 +1,151 @@ +package utils + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" +) + +func SetEnvironment(environment string) string { + switch environment { + case "dev": + return "https://consumer.dev.inic.dev/v1" + case "sandbox": + return "https://consumer.sandbox.inic.dev/v1" + case "staging": + return "https://consumer.staging.inic.dev/v1" + case "prod": + return "https://consumer.u4c-iniciador.com.br/v1" + default: + panic(fmt.Errorf("Something went wrong, verify environment value.")) + } +} + +type Error struct { + ErrorCode string `json:"errorCode"` + Message []string `json:"message"` + Method string `json:"method"` + Path string `json:"path"` + StatusCode int `json:"statusCode"` + Timestamp string `json:"timestamp"` +} + +func HandleResponse(response *http.Response, output interface{}) error { + defer response.Body.Close() + + bodyBytes, err := ioutil.ReadAll(response.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %v", err) + } + + if response.StatusCode >= 200 && response.StatusCode <= 299 { + err = json.Unmarshal(bodyBytes, output) + if err != nil { + return fmt.Errorf("failed to decode response body: %v", err) + } + return nil + } + + var errResponse Error + err = json.Unmarshal(bodyBytes, &errResponse) + if err != nil { + return fmt.Errorf("failed to decode error response: %v", err) + } + + if len(errResponse.Message) > 0 { + return fmt.Errorf("request failed with status code %d: %s", errResponse.StatusCode, strings.Join(errResponse.Message, ", ")) + } + + return fmt.Errorf("request failed with status code %d", response.StatusCode) +} + +func MarshalWithoutEmptyFields(payload interface{}) ([]byte, error) { + data, err := json.Marshal(payload) + if err != nil { + return nil, err + } + + var m map[string]interface{} + err = json.Unmarshal(data, &m) + if err != nil { + return nil, err + } + + nonEmptyFields := make(map[string]interface{}) + for k, v := range m { + if v != nil { + switch value := v.(type) { + case string: + if value != "" { + nonEmptyFields[k] = v + } + default: + nonEmptyFields[k] = v + } + } + } + + return json.Marshal(nonEmptyFields) +} + +type TokenData struct { + Payload PayloadData `json:"payload"` + Iat int64 `json:"iat"` + Exp int64 `json:"exp"` + Aud string `json:"aud"` + Iss string `json:"iss"` + Sub string `json:"sub"` +} + +type PayloadData struct { + ID string `json:"id"` + CreatedAt string `json:"createdAt"` + Date string `json:"date"` + Status string `json:"status"` + ClientID string `json:"clientId"` + CustomerID string `json:"customerId"` + Fee int `json:"fee"` + Creditor Creditor `json:"creditor"` + PaymentMethods []string `json:"paymentMethods"` +} + +type Creditor struct { + TaxID string `json:"taxId"` + Name string `json:"name"` + ISPB string `json:"ispb"` + Issuer string `json:"issuer"` + Number string `json:"number"` + AccountType string `json:"accountType"` +} + +func ExtractPaymentIDFromJWTPayload(token string) (string, error) { + // Split the token into its parts: header, payload, and signature + parts := strings.Split(token, ".") + + if len(parts) != 3 { + return "", fmt.Errorf("Invalid JWT token.") + } + + // Decode the payload part from Base64 + payloadBytes, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return "", fmt.Errorf("Error decoding payload: %v", err) + } + + // Convert the decoded payload into a string + payload := string(payloadBytes) + + var payloadData TokenData + err = json.Unmarshal([]byte(payload), &payloadData) + if err != nil { + fmt.Println("Error decoding JSON payload:", err) + return "", err + } + + id := payloadData.Payload.ID + + return id, nil +} diff --git a/tests/sdk/auth_test.go b/tests/sdk/auth_test.go new file mode 100644 index 0000000..9ba2e11 --- /dev/null +++ b/tests/sdk/auth_test.go @@ -0,0 +1,149 @@ +package sdk + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "iniciador-sdk/iniciador/auth" + "iniciador-sdk/tests/sdk/helpers" +) + +func TestAuthClient_Auth(t *testing.T) { + // Create a test server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request method and path + if r.Method != http.MethodPost { + t.Errorf("expected a POST method, but got %s", r.Method) + } + if r.URL.Path != "/auth" { + t.Errorf("expected path to be /auth, but got %s", r.URL.Path) + } + + // Decode the request body + var requestBody map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&requestBody) + if err != nil { + t.Errorf("failed to decode the request body: %v", err) + } + + // Verify the fields in the request body + clientID, ok := requestBody["clientId"].(string) + if !ok || clientID != "testClientID" { + t.Errorf("invalid value for the clientId field") + } + + clientSecret, ok := requestBody["clientSecret"].(string) + if !ok || clientSecret != "testClientSecret" { + t.Errorf("invalid value for the clientSecret field") + } + + // Create a simulated response + authOutput := auth.AuthOutput{ + AccessToken: "testAccessToken", + } + responseBody, err := json.Marshal(authOutput) + if err != nil { + t.Errorf("failed to encode the response: %v", err) + } + + // Send the simulated response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(responseBody) + })) + defer server.Close() + + // Configure the authentication client for the test + authClient := auth.NewAuthClient("testClientID", "testClientSecret", "dev") + + // Override the environment URL with the test server's URL + authClient.Environment = server.URL + + // Execute the method to be tested + authOutput, err := authClient.Auth() + + // Verify the results + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + expectedOutput := &auth.AuthOutput{ + AccessToken: "testAccessToken", + } + if !helpers.IsEqual(authOutput, expectedOutput) { + t.Errorf("expected result: %+v, actual result: %+v", expectedOutput, authOutput) + } +} + +func TestAuthClient_AuthInterface(t *testing.T) { + // Create a test server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request method and path + if r.Method != http.MethodPost { + t.Errorf("expected a POST method, but got %s", r.Method) + } + if r.URL.Path != "/auth/interface" { + t.Errorf("expected path to be /auth/interface, but got %s", r.URL.Path) + } + + // Decode the request body + var requestBody map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&requestBody) + if err != nil { + t.Errorf("failed to decode the request body: %v", err) + } + + // Verify the fields in the request body + clientID, ok := requestBody["clientId"].(string) + if !ok || clientID != "testClientID" { + t.Errorf("invalid value for the clientId field") + } + + clientSecret, ok := requestBody["clientSecret"].(string) + if !ok || clientSecret != "testClientSecret" { + t.Errorf("invalid value for the clientSecret field") + } + + // Create a simulated response + authInterfaceOutput := auth.AuthInterfaceOutput{ + AccessToken: "testAccessToken", + InterfaceURL: "testInterfaceURL", + PaymentID: "testPaymentID", + } + responseBody, err := json.Marshal(authInterfaceOutput) + if err != nil { + t.Errorf("failed to encode the response: %v", err) + } + + // Send the simulated response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(responseBody) + })) + defer server.Close() + + // Configure the authentication client for the test + authClient := auth.NewAuthClient("testClientID", "testClientSecret", "dev") + + // Override the environment URL with the test server's URL + authClient.Environment = server.URL + + // Execute the method to be tested + authInterfaceOutput, err := authClient.AuthInterface() + + // Verify the results + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + expectedOutput := &auth.AuthInterfaceOutput{ + AccessToken: "testAccessToken", + InterfaceURL: "testInterfaceURL", + PaymentID: "testPaymentID", + } + if !helpers.IsEqual(authInterfaceOutput, expectedOutput) { + t.Errorf("expected result: %+v, actual result: %+v", expectedOutput, authInterfaceOutput) + } +} diff --git a/tests/sdk/helpers/helpers.go b/tests/sdk/helpers/helpers.go new file mode 100644 index 0000000..cc9efba --- /dev/null +++ b/tests/sdk/helpers/helpers.go @@ -0,0 +1,25 @@ +package helpers + +import ( + "encoding/json" + "net/url" + "reflect" +) + +// Helper function to compare two values properly +func IsEqual(a, b interface{}) bool { + aJSON, err := json.Marshal(a) + if err != nil { + return false + } + bJSON, err := json.Marshal(b) + if err != nil { + return false + } + return string(aJSON) == string(bJSON) +} + +// AreURLQueryParamsEqual checks if two URL query parameters are equal regardless of their order. +func AreURLQueryParamsEqual(params1, params2 url.Values) bool { + return reflect.DeepEqual(params1, params2) +} diff --git a/tests/sdk/participants_test.go b/tests/sdk/participants_test.go new file mode 100644 index 0000000..fd80598 --- /dev/null +++ b/tests/sdk/participants_test.go @@ -0,0 +1,132 @@ +package sdk + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "iniciador-sdk/iniciador/auth" + "iniciador-sdk/iniciador/participants" + "iniciador-sdk/tests/sdk/helpers" +) + +func TestGetParticipants(t *testing.T) { + // Create a test server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request method and path + if r.Method != http.MethodGet { + t.Errorf("expected a GET method, but got %s", r.Method) + } + expectedURL := "/participants" + if r.URL.Path != expectedURL { + t.Errorf("expected path to be %s, but got %s", expectedURL, r.URL.Path) + } + + // Verify the authorization header + authHeader := r.Header.Get("Authorization") + expectedAuthHeader := "Bearer testAccessToken" + if authHeader != expectedAuthHeader { + t.Errorf("expected authorization header to be %s, but got %s", expectedAuthHeader, authHeader) + } + + // Verify the query parameters + queryParams := r.URL.Query() + expectedQueryParams := url.Values{ + "id": []string{"testID"}, + "name": []string{"testName"}, + "slug": []string{"testSlug"}, + "status": []string{"testStatus"}, + "firstParticipants": []string{"testFirstParticipants"}, + "limit": []string{"testLimit"}, + "afterCursor": []string{"testAfterCursor"}, + "beforeCursor": []string{"testBeforeCursor"}, + } + if !helpers.AreURLQueryParamsEqual(queryParams, expectedQueryParams) { + t.Errorf("expected query parameters to be %v, but got %v", expectedQueryParams, queryParams) + } + + // Create a simulated response + participantsOutput := participants.ParticipantFilterOutput{ + Data: []participants.ParticipantsPayload{ + { + ID: "participant1", + Slug: "participant1", + Name: "Participant 1", + Avatar: "avatar1", + }, + { + ID: "participant2", + Slug: "participant2", + Name: "Participant 2", + Avatar: "avatar2", + }, + }, + Cursor: participants.Cursor{ + AfterCursor: "nextCursor", + BeforeCursor: "prevCursor", + }, + } + responseBody, err := json.Marshal(participantsOutput) + if err != nil { + t.Errorf("failed to encode the response: %v", err) + } + + // Send the simulated response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(responseBody) + })) + defer server.Close() + + // Configure the authentication client for the test + authClient := auth.NewAuthClient("testClientID", "testClientSecret", "dev") + + // Override the environment URL with the test server's URL + authClient.Environment = server.URL + + // Set up the filters + filters := &participants.ParticipantsFilter{ + ID: "testID", + Name: "testName", + Slug: "testSlug", + Status: "testStatus", + FirstParticipants: "testFirstParticipants", + Limit: "testLimit", + AfterCursor: "testAfterCursor", + BeforeCursor: "testBeforeCursor", + } + + // Execute the method to be tested + output, err := participants.GetParticipants("testAccessToken", filters, authClient) + + // Verify the results + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + expectedOutput := &participants.ParticipantFilterOutput{ + Data: []participants.ParticipantsPayload{ + { + ID: "participant1", + Slug: "participant1", + Name: "Participant 1", + Avatar: "avatar1", + }, + { + ID: "participant2", + Slug: "participant2", + Name: "Participant 2", + Avatar: "avatar2", + }, + }, + Cursor: participants.Cursor{ + AfterCursor: "nextCursor", + BeforeCursor: "prevCursor", + }, + } + if !helpers.IsEqual(output, expectedOutput) { + t.Errorf("expected result: %+v, actual result: %+v", expectedOutput, output) + } +} diff --git a/tests/sdk/payments_test.go b/tests/sdk/payments_test.go new file mode 100644 index 0000000..fd00cf9 --- /dev/null +++ b/tests/sdk/payments_test.go @@ -0,0 +1,202 @@ +package sdk + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "iniciador-sdk/iniciador/auth" + "iniciador-sdk/iniciador/payments" + "iniciador-sdk/tests/sdk/helpers" +) + +func TestSendGetStatus(t *testing.T) { + // Create a test server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request method and path + expectedURL := "/payments" + if r.URL.Path != expectedURL { + t.Errorf("expected path to be %s, but got %s", expectedURL, r.URL.Path) + } + + // Verify the authorization header + authHeader := r.Header.Get("Authorization") + expectedAuthHeader := "Bearer testAccessToken" + if authHeader != expectedAuthHeader { + t.Errorf("expected authorization header to be %s, but got %s", expectedAuthHeader, authHeader) + } + + // Verify the request body + var payment payments.PaymentInitiationPayload + err := json.NewDecoder(r.Body).Decode(&payment) + if err != nil { + t.Errorf("failed to decode the request body: %v", err) + } + + expectedPayment := payments.PaymentInitiationPayload{ + ID: "testID", + Amount: 100.0, + } + if !helpers.IsEqual(payment, expectedPayment) { + t.Errorf("expected request body: %+v, actual request body: %+v", expectedPayment, payment) + } + + // Create a simulated response + status := payments.PaymentInitiationStatus(payments.Started) + paymentInitiationPayload := payments.PaymentInitiationPayload{ + ID: "testID", + Status: &status, + } + responseBody, err := json.Marshal(paymentInitiationPayload) + if err != nil { + t.Errorf("failed to encode the response: %v", err) + } + + // Send the simulated response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(responseBody) + })) + defer server.Close() + + // Configure the authentication client for the test + authClient := auth.NewAuthClient("testClientID", "testClientSecret", "dev") + + // Override the environment URL with the test server's URL + authClient.Environment = server.URL + + // Set up the payment payload + payment := &payments.PaymentInitiationPayload{ + ID: "testID", + Amount: 100.0, + } + + // Execute the Send function + sentPayment, err := payments.Send("testAccessToken", payment, authClient) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Verify the sent payment + status := payments.PaymentInitiationStatus(payments.Started) + expectedSentPayment := &payments.PaymentInitiationPayload{ + ID: "testID", + Status: &status, + } + if !helpers.IsEqual(sentPayment, expectedSentPayment) { + t.Errorf("expected sent payment: %+v, actual sent payment: %+v", expectedSentPayment, sentPayment) + } + + // Create a test server for the Get and Status functions + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request method + if r.Method != http.MethodGet { + t.Errorf("expected a GET method, but got %s", r.Method) + } + + // Verify the authorization header + authHeader := r.Header.Get("Authorization") + expectedAuthHeader := "Bearer testAccessToken" + if authHeader != expectedAuthHeader { + t.Errorf("expected authorization header to be %s, but got %s", expectedAuthHeader, authHeader) + } + + // Verify the request URL + expectedURL := fmt.Sprintf("/payments/%s", payment.ID) + if r.URL.Path != expectedURL { + t.Errorf("expected path to be %s, but got %s", expectedURL, r.URL.Path) + } + + // Create a simulated response + status := payments.PaymentInitiationStatus(payments.Started) + paymentPayload := payments.PaymentInitiationPayload{ + ID: payment.ID, + Status: &status, + } + responseBody, err := json.Marshal(paymentPayload) + if err != nil { + t.Errorf("failed to encode the response: %v", err) + } + + // Send the simulated response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(responseBody) + })) + defer server.Close() + + // Override the environment URL with the test server's URL + authClient.Environment = server.URL + + // Execute the Get function + retrievedPayment, err := payments.Get("testAccessToken", authClient) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Verify the retrieved payment + expectedRetrievedPayment := &payments.PaymentInitiationPayload{ + ID: payment.ID, + Status: &status, + } + if !helpers.IsEqual(retrievedPayment, expectedRetrievedPayment) { + t.Errorf("expected retrieved payment: %+v, actual retrieved payment: %+v", expectedRetrievedPayment, retrievedPayment) + } + + // Create a test server for the Status function + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request method + if r.Method != http.MethodGet { + t.Errorf("expected a GET method, but got %s", r.Method) + } + + // Verify the authorization header + authHeader := r.Header.Get("Authorization") + expectedAuthHeader := "Bearer testAccessToken" + if authHeader != expectedAuthHeader { + t.Errorf("expected authorization header to be %s, but got %s", expectedAuthHeader, authHeader) + } + + // Verify the request URL + expectedURL := fmt.Sprintf("/payments/%s/status", payment.ID) + if r.URL.Path != expectedURL { + t.Errorf("expected path to be %s, but got %s", expectedURL, r.URL.Path) + } + + // Create a simulated response + statusPayload := payments.PaymentStatusPayload{ + ID: payment.ID, + Status: string(payments.Started), + } + responseBody, err := json.Marshal(statusPayload) + if err != nil { + t.Errorf("failed to encode the response: %v", err) + } + + // Send the simulated response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(responseBody) + })) + defer server.Close() + + // Override the environment URL with the test server's URL + authClient.Environment = server.URL + + // Execute the Status function + paymentStatus, err := payments.Status("testAccessToken", authClient) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Verify the payment status + expectedPaymentStatus := &payments.PaymentStatusPayload{ + ID: payment.ID, + Status: string(payments.Started), + } + if !helpers.IsEqual(paymentStatus, expectedPaymentStatus) { + t.Errorf("expected payment status: %+v, actual payment status: %+v", expectedPaymentStatus, paymentStatus) + } +}