Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
- Made logger an explicit dependency of Adyen.
- Implemented signature todos.
- Removed unused Java-like getter and setter methods.
- Moved test helpers into a TestMain function.
- Simplified bool parsing into boolean.go with tests in boolean_test.go
- Removed newEnvironment helper and replaced with simple initialisation.
- Added tests for URL formatting.
- Simplified some bool checks in tests.
- Added a todo section in README.
- Replaced HTTP verb magic strings with stdlib constants.
  • Loading branch information
codingconcepts committed Jan 30, 2018
1 parent 6c1e820 commit d8d302b
Show file tree
Hide file tree
Showing 17 changed files with 288 additions and 261 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,8 @@ https://github.com/zhutik/adyen-api-go-example
Open http://localhost:8080 in your browser and check implemented actions.

Test credit cards could be found https://docs.adyen.com/support/integration#testcardnumbers

## TODOs

* Move some constants into enum files.
* Parse URLs for environment's BaseURL, ClientURL and HppURL methods instead of string concatenation (needs to return an error as well)
89 changes: 22 additions & 67 deletions adyen.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ import (
"github.com/google/go-querystring/query"
)

// DefaultCurrency default currency for transactions
const DefaultCurrency = "EUR"

// Version of a current Adyen API
const (
// DefaultCurrency default currency for transactions
DefaultCurrency = "EUR"

// APIVersion of current Adyen API
APIVersion = "v25"
)

// Enpoint service to use
const (
PaymentService = "Payment"
// PaymentService is used to identify the standard payment workflow.
PaymentService = "Payment"

// RecurringService is used to identify the recurring payment workflow.
RecurringService = "Recurring"
)

Expand All @@ -33,10 +33,11 @@ const (
// - password - API password for authentication
//
// You can create new API user there: https://ca-test.adyen.com/ca/ca/config/users.shtml
func New(env environment, username, password string) *Adyen {
func New(env environment, username, password string, logger *log.Logger) *Adyen {
return &Adyen{
Credentials: newCredentials(env, username, password),
Currency: DefaultCurrency,
Logger: logger,
}
}

Expand All @@ -52,10 +53,11 @@ func New(env environment, username, password string) *Adyen {
// - hmac - is generated when new Skin is created in Adyen Customer Area
//
// New skin can be created there https://ca-test.adyen.com/ca/ca/skin/skins.shtml
func NewWithHMAC(env environment, username, password, hmac string) *Adyen {
func NewWithHMAC(env environment, username, password, hmac string, logger *log.Logger) *Adyen {
return &Adyen{
Credentials: newCredentialsWithHMAC(env, username, password, hmac),
Currency: DefaultCurrency,
Logger: logger,
}
}

Expand All @@ -64,7 +66,7 @@ func NewWithHMAC(env environment, username, password, hmac string) *Adyen {
// - Credentials instance of API creditials to connect to Adyen API
// - Currency is a default request currency. Request data overrides this setting
// - MerchantAccount is default merchant account to be used. Request data overrides this setting
// - Logger is an optional logger instance
// - Logger optionally logs to a configured io.Writer.
//
// Currency and MerchantAccount should be used only to store the data and be able to use it later.
// Requests won't be automatically populated with given values
Expand Down Expand Up @@ -92,37 +94,6 @@ func (a *Adyen) createHPPUrl(requestType string) string {
return a.Credentials.Env.HppURL(requestType)
}

// AttachLogger attach logger to API instance
//
// Example:
//
// instance := adyen.New(....)
// Logger = log.New(os.Stdout, "Adyen Playground: ", log.Ldate|log.Ltime|log.Lshortfile)
// instance.AttachLogger(Logger)
func (a *Adyen) AttachLogger(Logger *log.Logger) {
a.Logger = Logger
}

// GetCurrency get currency for current settion
func (a *Adyen) GetCurrency() string {
return a.Currency
}

// SetCurrency set default currency for transactions
func (a *Adyen) SetCurrency(currency string) {
a.Currency = currency
}

// GetMerchantAccount get default merchant account that is currency set
func (a *Adyen) GetMerchantAccount() string {
return a.MerchantAccount
}

// SetMerchantAccount set default merchant account for current instance
func (a *Adyen) SetMerchantAccount(account string) {
a.MerchantAccount = account
}

// execute request on Adyen side, transforms "requestEntity" into JSON representation
//
// internal method to do a request to Adyen API endpoint
Expand All @@ -134,48 +105,38 @@ func (a *Adyen) execute(service string, method string, requestEntity interface{}
}

url := a.adyenURL(service, method)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(body))
if err != nil {
return nil, err
}

if a.Logger != nil {
a.Logger.Printf("[Request]: %s %s\n%s", method, url, body)
}
a.Logger.Printf("[Request]: %s %s\n%s", method, url, body)

req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(a.Credentials.Username, a.Credentials.Password)

client := &http.Client{}
resp, err := client.Do(req)

if err != nil {
return nil, err
}

defer func() {
err = resp.Body.Close()
}()

buf := new(bytes.Buffer)
_, err = buf.ReadFrom(resp.Body)

if err != nil {
if _, err = buf.ReadFrom(resp.Body); err != nil {
return nil, err
}

if a.Logger != nil {
a.Logger.Printf("[Response]: %s %s\n%s", method, url, buf.String())
}
a.Logger.Printf("[Response]: %s %s\n%s", method, url, buf.String())

providerResponse := &Response{
Response: resp,
Body: buf.Bytes(),
}

err = providerResponse.handleHTTPError()

if err != nil {
if err = providerResponse.handleHTTPError(); err != nil {
return nil, err
}

Expand All @@ -191,14 +152,12 @@ func (a *Adyen) executeHpp(method string, requestEntity interface{}) (*Response,
v, _ := query.Values(requestEntity)
url = url + "?" + v.Encode()

req, err := http.NewRequest("GET", url, nil)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}

if a.Logger != nil {
a.Logger.Printf("[Request]: %s %s", method, url)
}
a.Logger.Printf("[Request]: %s %s", method, url)

client := &http.Client{}
resp, err := client.Do(req)
Expand All @@ -212,15 +171,11 @@ func (a *Adyen) executeHpp(method string, requestEntity interface{}) (*Response,
}()

buf := new(bytes.Buffer)
_, err = buf.ReadFrom(resp.Body)

if err != nil {
if _, err = buf.ReadFrom(resp.Body); err != nil {
return nil, err
}

if a.Logger != nil {
a.Logger.Printf("[Response]: %s %s\n%s", method, url, buf.String())
}
a.Logger.Printf("[Response]: %s %s\n%s", method, url, buf.String())

providerResponse := &Response{
Response: resp,
Expand Down
21 changes: 19 additions & 2 deletions test_case_hepler.go → adyen_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
package adyen

import (
"io/ioutil"
"log"
"math/rand"
"os"
"testing"
"time"

"github.com/joho/godotenv"
)

func TestMain(m *testing.M) {
// Set environment variables for subsequent tests.
if err := godotenv.Load(".default.env"); err != nil {
log.Fatalf("error loading .env file: %v", err)
}

os.Exit(m.Run())
}

var noopLogger = log.New(ioutil.Discard, "", log.LstdFlags)

// getTestInstance - instanciate adyen for tests
func getTestInstance() *Adyen {
instance := New(
Testing,
os.Getenv("ADYEN_USERNAME"),
os.Getenv("ADYEN_PASSWORD"),
)
noopLogger)

return instance
}
Expand All @@ -24,7 +40,7 @@ func getTestInstanceWithHPP() *Adyen {
os.Getenv("ADYEN_USERNAME"),
os.Getenv("ADYEN_PASSWORD"),
os.Getenv("ADYEN_HMAC"),
)
noopLogger)

return instance
}
Expand All @@ -35,6 +51,7 @@ func randInt(min int, max int) int {
}

// randomString - generate randorm string of given length
// note: not for use in live code
func randomString(l int) string {
rand.Seed(time.Now().UTC().UnixNano())

Expand Down
24 changes: 24 additions & 0 deletions boolean.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package adyen

import (
"strconv"
"strings"
)

// stringBool allows us to unmarhsal Adyen Boolean values
// which appear as strings instead of bools.
type stringBool bool

func (b *stringBool) UnmarshalJSON(data []byte) (err error) {
str := strings.TrimFunc(strings.ToLower(string(data)), func(c rune) bool {
return c == ' ' || c == '"'
})

parsed, err := strconv.ParseBool(str)
if err != nil {
return
}

*b = stringBool(parsed)
return
}
88 changes: 88 additions & 0 deletions boolean_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package adyen

import (
"encoding/json"
"testing"
)

type thing struct {
Bool stringBool `json:"value"`
}

func TestStringBool_Unmarshal(t *testing.T) {
cases := []struct {
name string
json string
exp bool
expErr bool
}{
{
name: "empty",
json: `{ "value": "" }`,
exp: false,
expErr: true,
},
{
name: "true",
json: `{ "value": "true" }`,
exp: true,
},
{
name: "TRUE",
json: `{ "value": "TRUE" }`,
exp: true,
},
{
name: "1",
json: `{ "value": "1" }`,
exp: true,
},
{
name: "spaces",
json: `{ "value": " true " }`,
exp: true,
},
{
name: "false",
json: `{ "value": "false" }`,
exp: false,
},
{
name: "FALSE",
json: `{ "value": "FALSE" }`,
exp: false,
},
{
name: "0",
json: `{ "value": "0" }`,
exp: false,
},
{
name: "spaces",
json: `{ "value": " false " }`,
exp: false,
},
{
name: "spaces",
json: `{ "value": " false " }`,
exp: false,
},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var th thing
err := json.Unmarshal([]byte(c.json), &th)
if !c.expErr && err != nil {
t.Fatalf("unexpected error: %v", err)
}
if c.expErr && err == nil {
t.Fatalf("expected error but didn't get one")
}

if stringBool(c.exp) != th.Bool {
t.Fatalf("my exp: %v but got %v", c.exp, th.Bool)
}
})
}
}
24 changes: 0 additions & 24 deletions convertible_boolean.go

This file was deleted.

Loading

0 comments on commit d8d302b

Please sign in to comment.