Skip to content

Commit

Permalink
Release v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
KadoBOT authored May 13, 2020
2 parents 27f2e61 + 4e48de3 commit 380f77e
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 31 deletions.
12 changes: 8 additions & 4 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ go get github.com/adyen/adyen-go-api-library
```go
import (
"github.com/adyen/adyen-go-api-library/src/checkout"
"github.com/adyen/adyen-go-api-library/src/common"
"github.com/adyen/adyen-go-api-library/src/adyen"
)

Expand All @@ -58,7 +59,8 @@ res, httpRes, err := client.Checkout.PaymentMethods(&checkout.PaymentMethodsRequ

```go
import (
"github.com/adyen/adyen-go-api-library/src/checkout"
"github.com/adyen/adyen-go-api-library/src/checkout"
"github.com/adyen/adyen-go-api-library/src/common"
"github.com/adyen/adyen-go-api-library/src/adyen"
)

Expand All @@ -77,7 +79,8 @@ res, httpRes, err := client.Checkout.PaymentMethods(&checkout.PaymentMethodsRequ

```go
import (
"github.com/adyen/adyen-go-api-library/src/recurring"
"github.com/adyen/adyen-go-api-library/src/recurring"
"github.com/adyen/adyen-go-api-library/src/common"
"github.com/adyen/adyen-go-api-library/src/adyen"
)

Expand All @@ -101,7 +104,8 @@ res, httpRes, err := client.Recurring.ListRecurringDetails(&recurring.RecurringD

```go
import (
"github.com/adyen/adyen-go-api-library/src/adyen"
"github.com/adyen/adyen-go-api-library/src/adyen"
"github.com/adyen/adyen-go-api-library/src/common"
)

client := adyen.NewClient(&common.Config{
Expand Down Expand Up @@ -189,7 +193,7 @@ client = adyen.NewClient(&common.Config{

## Support

If you have any problems, questions or suggestions, create an issue here or send your inquiry to support@adyen.com.
If you have a feature request, or spotted a bug or a technical problem, create a github issue. For other questions, contact our [support team](https://support.adyen.com/hc/en-us/requests/new?ticket_form_id=360000705420m).

## Contributing

Expand Down
6 changes: 1 addition & 5 deletions src/adyen/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ const (
EndpointTest = "https://pal-test.adyen.com"
EndpointLive = "https://pal-live.adyen.com"
EndpointLiveSuffix = "-pal-live.adyenpayments.com"
HppTest = "https://test.adyen.com/hpp"
HppLive = "https://live.adyen.com/hpp"
MarketpayEndpointTest = "https://cal-test.adyen.com/cal/services"
MarketpayEndpointLive = "https://cal-live.adyen.com/cal/services"
MarketpayAccountAPIVersion = "v5"
Expand Down Expand Up @@ -130,7 +128,7 @@ func NewClient(cfg *common.Config) *APIClient {
cfg.DefaultHeader = make(map[string]string)
}
if cfg.UserAgent == "" {
cfg.UserAgent = fmt.Sprintf("%s %s/%s", cfg.ApplicationName, common.LibName, common.LibVersion)
cfg.UserAgent = fmt.Sprintf("%s/%s", common.LibName, common.LibVersion)
}

c := &APIClient{}
Expand Down Expand Up @@ -195,7 +193,6 @@ func (c *APIClient) SetEnvironment(env common.Environment, liveEndpointURLPrefix
if env == common.LiveEnv {
c.client.Cfg.Environment = env
c.client.Cfg.MarketPayEndpoint = MarketpayEndpointLive
c.client.Cfg.HppEndpoint = HppLive
if liveEndpointURLPrefix != "" {
c.client.Cfg.Endpoint = EndpointProtocol + liveEndpointURLPrefix + EndpointLiveSuffix
c.client.Cfg.CheckoutEndpoint = EndpointProtocol + liveEndpointURLPrefix + CheckoutEndpointLiveSuffix
Expand All @@ -208,7 +205,6 @@ func (c *APIClient) SetEnvironment(env common.Environment, liveEndpointURLPrefix
c.client.Cfg.Environment = env
c.client.Cfg.Endpoint = EndpointTest
c.client.Cfg.MarketPayEndpoint = MarketpayEndpointTest
c.client.Cfg.HppEndpoint = HppTest
c.client.Cfg.CheckoutEndpoint = CheckoutEndpointTest
c.client.Cfg.TerminalApiCloudEndpoint = TerminalAPIEndpointTest
}
Expand Down
7 changes: 3 additions & 4 deletions src/adyen/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,9 @@ func Test_api(t *testing.T) {
})

client = NewClient(&common.Config{
Username: USER,
Password: PASS,
Environment: "TEST",
ApplicationName: "adyen-api-go-library",
Username: USER,
Password: PASS,
Environment: "TEST",
})

t.Run("Create a API request that uses basic auth and should pass", func(t *testing.T) {
Expand Down
22 changes: 7 additions & 15 deletions src/common/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,29 +61,21 @@ const (

const (
LibName = "adyen-go-api-library"
LibVersion = "0.0.1"
LibVersion = "1.0.0"
)

// Config stores the configuration of the API client
type Config struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
MerchantAccount string `json:"merchantAccount,omitempty"`
Environment Environment `json:"environment,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
MarketPayEndpoint string `json:"marketPayEndpoint,omitempty"`
// Application name: used as HTTP client User-Agent
ApplicationName string `json:"applicationName,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
MerchantAccount string `json:"merchantAccount,omitempty"`
Environment Environment `json:"environment,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
MarketPayEndpoint string `json:"marketPayEndpoint,omitempty"`
ApiKey string `json:"apiKey,omitempty"`
ConnectionTimeoutMillis time.Duration `json:"connectionTimeoutMillis,omitempty"`
ReadTimeoutMillis time.Duration `json:"readTimeoutMillis,omitempty"`
CertificatePath string `json:"certificatePath,omitempty"`

//HPP specific
HppEndpoint string `json:"hppEndpoint,omitempty"`
SkinCode string `json:"skinCode,omitempty"`
HmacKey string `json:"hmacKey,omitempty"`

//Checkout Specific
CheckoutEndpoint string `json:"checkoutEndpoint,omitempty"`

Expand Down
87 changes: 87 additions & 0 deletions src/hmacvalidator/hmacvalidator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package hmacvalidator

import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"regexp"
"sort"
"strconv"
"strings"

"github.com/adyen/adyen-go-api-library/src/notification"
)

// CalculateHmac calculates the SHA-256 HMAC for the given data and key
func CalculateHmac(data interface{}, secret string) (string, error) {
switch val := data.(type) {
case string:
return encode(val, secret)
default:
src := GetDataToSign(data)
return encode(src, secret)
}
}

// ValidateHmac calculates the HMAC of the notification request item and checks if it matches with the given key
func ValidateHmac(notificationRequestItem notification.NotificationRequestItem, key string) bool {
expectedSign, err := CalculateHmac(notificationRequestItem, key)
if err != nil {
return false
}
merchantSign := (*notificationRequestItem.AdditionalData)["HmacSignature"]
return expectedSign == merchantSign
}

// GetDataToSign converts a notification request item to string, which later on can be used for calculating a HMAC
func GetDataToSign(notificationRequestItem interface{}) string {
switch item := notificationRequestItem.(type) {
case notification.NotificationRequestItem:
signedDataList := []string{
item.PspReference,
item.OriginalReference,
item.MerchantAccountCode,
item.MerchantReference,
strconv.Itoa(int(item.Amount.Value)),
item.Amount.Currency,
item.EventCode,
item.Success,
}
return strings.Join(signedDataList, ":")
case map[string]string:
keys := make([]string, 0)
values := make([]string, 0)

for k := range item {
keys = append(keys, replacer(k))
}
sort.Strings(keys)
for _, k := range keys {
values = append(values, replacer(item[k]))
}

return strings.Join(keys, ":") + ":" + strings.Join(values, ":")
default:
return ""
}
}

func encode(data string, secret string) (string, error) {
key, err := hex.DecodeString(secret)
if err != nil {
return "", fmt.Errorf("failed to generate HMAC: %s", err.Error())
}
h := hmac.New(sha256.New, key)
h.Write([]byte(data))
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
}

func replacer(s string) string {
re1 := regexp.MustCompile(`\\`)
re2 := regexp.MustCompile(`:`)
str := re1.ReplaceAllString(s, "\\\\")
str = re2.ReplaceAllString(str, "\\:")
return str
}
71 changes: 71 additions & 0 deletions src/hmacvalidator/hmacvalidator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package hmacvalidator

import (
"testing"
"time"

"github.com/adyen/adyen-go-api-library/src/notification"
"github.com/stretchr/testify/assert"
)

const key = "DFB1EB5485895CFA84146406857104ABB4CBCABDC8AAF103A624C8F6A3EAAB00"
const expectedSign = "ipnxGCaUZ4l8TUW75a71/ghd2Fe5ffvX0pV4TLTntIc="

var eventDate = time.Date(1970, time.January, 01, 0, 0, 0, 0, time.UTC)
var notificationRequestItem = notification.NotificationRequestItem{
AdditionalData: &map[string]interface{}{"HmacSignature": expectedSign},
Amount: notification.Amount{
Currency: "EUR",
Value: 1000,
},
EventCode: "EVENT",
EventDate: &eventDate,
MerchantAccountCode: "merchantAccount",
MerchantReference: "reference",
OriginalReference: "originalReference",
PaymentMethod: "VISA",
PspReference: "pspReference",
Reason: "reason",
Success: "true",
}

func Test_Hmacvalidator(t *testing.T) {
t.Run("GetDataToSign", func(t *testing.T) {
t.Run("Get correct data", func(t *testing.T) {
data := map[string]string{"MerchantAccount": "ACC", "CurrencyCode": "EUR"}
dataToSign := GetDataToSign(data)
assert.Equal(t, "CurrencyCode:MerchantAccount:EUR:ACC", dataToSign)
})
t.Run("Get correct data with escaped characters", func(t *testing.T) {
data := map[string]string{"CurrencyCode": "EUR", "MerchantAccount": "ACC:\\", "SessionValidity": "2019-09-21T11:45:24.637Z"}
dataToSign := GetDataToSign(data)
assert.Equal(t, "CurrencyCode:MerchantAccount:SessionValidity:EUR:ACC\\:\\\\:2019-09-21T11\\:45\\:24.637Z", dataToSign)
})
t.Run("Get correct data to sign", func(t *testing.T) {
data := GetDataToSign(notificationRequestItem)
assert.Equal(t, "pspReference:originalReference:merchantAccount:reference:1000:EUR:EVENT:true", data)
})
})
t.Run("CalculateHmac", func(t *testing.T) {
t.Run("Encrypt correctly", func(t *testing.T) {
data := "countryCode:currencyCode:merchantAccount:merchantReference:paymentAmount:sessionValidity:skinCode:NL:EUR:MagentoMerchantTest2:TEST-PAYMENT-2017-02-01-14\\:02\\:05:199:2017-02-02T14\\:02\\:05+01\\:00:PKz2KML1"
encrypted, err := CalculateHmac(data, key)
assert.Nil(t, err)
assert.Equal(t, "34oR8T1whkQWTv9P+SzKyp8zhusf9n0dpqrm9nsqSJs=", encrypted)
})
t.Run("Get Valid HMAC", func(t *testing.T) {
enc, err := CalculateHmac(notificationRequestItem, key)
assert.Nil(t, err)
assert.Equal(t, expectedSign, enc)
})
})
t.Run("ValidateHmac", func(t *testing.T) {
t.Run("Validate HMAC", func(t *testing.T) {
assert.True(t, ValidateHmac(notificationRequestItem, key))
})
t.Run("Get Invalid HMAC", func(t *testing.T) {
notificationRequestItem.AdditionalData = &map[string]interface{}{"HmacSignature": "InvalidSignature"}
assert.False(t, ValidateHmac(notificationRequestItem, key))
})
})
}
5 changes: 2 additions & 3 deletions tests/checkout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package tests

import (
"os"
"regexp"
"testing"

"github.com/adyen/adyen-go-api-library/src/adyen"
Expand Down Expand Up @@ -46,10 +47,8 @@ func Test_Checkout(t *testing.T) {
})

require.NotNil(t, err)
assert.Equal(t, "422 Unprocessable Entity: Required field countryCode not specified (validation: 158)", err.Error())
assert.Regexp(t, regexp.MustCompile("422 Unprocessable Entity"), err.Error())
require.NotNil(t, httpRes)
assert.Equal(t, 422, httpRes.StatusCode)
assert.Equal(t, "422 Unprocessable Entity", httpRes.Status)
require.NotNil(t, res)
})
t.Run("Create an API request that should pass", func(t *testing.T) {
Expand Down

0 comments on commit 380f77e

Please sign in to comment.