Skip to content
This repository has been archived by the owner on Mar 1, 2023. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoAdamek committed May 21, 2019
0 parents commit 630bea0
Show file tree
Hide file tree
Showing 11 changed files with 428 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Sagepay Go
==========

An API client library for Sagepay and Go.


Features
--------

* Overridable HTTP Client
* Context support for tracibility
* Minimal Dependencies
* Pluggable credential sources
140 changes: 140 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package sagepay

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"

"golang.org/x/net/context/ctxhttp"
)

// Client represents a SagePay API client instance
type Client struct {
// HTTP Client
HTTP *http.Client

DebugWriter io.Writer

provider CredentialsProvider
testMode bool
sessionKey string
sessionKeyExpiresAt time.Time
}

const (
// TestHost is the address of the test API
TestHost = "https://pi-test.sagepay.com/api/v1"

// ProductionHost is the address of the production API
ProductionHost = "https://pi-live.sagepay.com/api/v1"
)

// New creates a new Sagepay API Client
func New(ctx context.Context, credentials CredentialsProvider) *Client {
hc := &http.Client{
Transport: http.DefaultTransport,
}

return &Client{
HTTP: hc,
provider: credentials,
testMode: false,
sessionKey: "",
sessionKeyExpiresAt: time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC),
}
}

// Do performs the given HTTP Request
func (c *Client) Do(ctx context.Context, req *http.Request) (*http.Response, error) {

req.Header.Set("User-Agent", "Sagepay-go +https://github.com/mrzen/go-sagepay")

credentials, err := c.provider.GetCredentials(ctx)

if err != nil {
return nil, err
}

req.SetBasicAuth(credentials.Username, credentials.Password)

if c.testMode && c.DebugWriter != nil {
if req.Body != nil {
fmt.Fprintln(c.DebugWriter, "--------- REQUEST --------")
cb := new(bytes.Buffer)
tr := io.TeeReader(req.Body, cb)
req.Body = ioutil.NopCloser(tr)
req.Write(os.Stdout)
req.Body = ioutil.NopCloser(bytes.NewReader(cb.Bytes()))
}
}

return ctxhttp.Do(ctx, c.HTTP, req)
}

func (c *Client) getEndpoint() string {
if c.testMode {
return TestHost
}

return ProductionHost
}

// JSON performs an HTTP request with a given request body value encoded as JSON
// and decodes the response as JSON into the given response pointer.
func (c *Client) JSON(ctx context.Context, method, path string, body, into interface{}) error {

buffer := new(bytes.Buffer)

if err := json.NewEncoder(buffer).Encode(body); err != nil {
return err
}

req, err := http.NewRequest(method, c.getEndpoint()+path, bytes.NewReader(buffer.Bytes()))

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")

if err != nil {
return err
}

res, err := c.Do(ctx, req)

if err != nil {
return err
}

defer res.Body.Close()

if res.StatusCode >= 400 {
errorBody := ErrorResponse{}

cb := new(bytes.Buffer)
tr := io.TeeReader(res.Body, cb)

if err := json.NewDecoder(tr).Decode(&errorBody); err != nil {
return err
}

if len(errorBody.Errors) == 0 {
return errors.New(cb.String())
}

return errorBody
}

return json.NewDecoder(res.Body).Decode(&into)
}

// SetTestMode determines if the API client will communite with a test
// or production endpoint
func (c *Client) SetTestMode(testMode bool) {
c.testMode = testMode
}
35 changes: 35 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package sagepay

import (
"context"
"net/http"
"testing"
)

var demoCredentials = StaticCredentials(
"dq9w6WkkdD2y8k3t4olqu8H6a0vtt3IY7VEsGhAtacbCZ2b5Ud",
"hno3JTEwDHy7hJckU4WuxfeTrjD0N92pIaituQBw5Mtj7RG3V8zOdHCSPKwJ02wAV",
)

func getTestClient() *Client {
c := New(context.TODO(), demoCredentials)

c.SetTestMode(true)
//c.DebugWriter = os.Stdout

return c
}

func TestClientDo(t *testing.T) {
c := getTestClient()

req, _ := http.NewRequest(http.MethodGet, TestHost, nil)

_, err := c.Do(context.TODO(), req)

if err != nil {
t.Error(err)
}

//res.Write(os.Stdout)
}
48 changes: 48 additions & 0 deletions credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package sagepay

import (
"context"
"os"
)

// CredentialsProvider is a source for API credentials.
type CredentialsProvider interface {
GetCredentials(ctx context.Context) (*Credentials, error)
}

// Credentials are the API credentials for an API client
type Credentials struct {
Username string
Password string
}

// StaticCredentialsProvider gets credentials statically from a struct.
type StaticCredentialsProvider struct {
Credentials
}

// EnvironmentCredentialsProvider gets credentials from the OS environment
type EnvironmentCredentialsProvider struct{}

// GetCredentials gets the current credentials from the environment
func (e EnvironmentCredentialsProvider) GetCredentials(ctx context.Context) (*Credentials, error) {
return &Credentials{
Username: os.Getenv("SAGE_USERNAME"),
Password: os.Getenv("SAGE_PASSWORD"),
}, nil
}

// StaticCredentials creates a new set of static credentials
func StaticCredentials(username, password string) StaticCredentialsProvider {
return StaticCredentialsProvider{
Credentials: Credentials{
Username: username,
Password: password,
},
}
}

// GetCredentials gets the current credentials
func (s StaticCredentialsProvider) GetCredentials(ctx context.Context) (*Credentials, error) {
return &s.Credentials, nil
}
33 changes: 33 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package sagepay

import (
"fmt"
"strings"
)

// Error represents an error from the sage API
type Error struct {
Code string `json:"code"`
Property string `json:"property"`
Description string `json:"description"`
UserMessage string `json:"clientMessage"`
}

// ErrorResponse represents the response given by an Error
type ErrorResponse struct {
Errors []Error `json:"errors"`
}

func (e ErrorResponse) Error() string {
msgs := make([]string, len(e.Errors))

for i, e := range e.Errors {
msgs[i] = e.Error()
}

return strings.Join(msgs, "\n")
}

func (e Error) Error() string {
return fmt.Sprintf("Error(%s): %s (%s)", e.Code, e.UserMessage, e.Description)
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/mrzen/go-sagepay

go 1.12

require golang.org/x/net v0.0.0-20190520210107-018c4d40a106
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190520210107-018c4d40a106 h1:EZofHp/BzEf3j39/+7CX1JvH0WaPG+ikBrqAdAPf+GM=
golang.org/x/net v0.0.0-20190520210107-018c4d40a106/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
3 changes: 3 additions & 0 deletions sagepay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Package sagepay provices a client library for interacting with Sagepay
// service APIs.
package sagepay
30 changes: 30 additions & 0 deletions session_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package sagepay

import (
"context"
"net/http"
"time"
)

// SessionKey represents a session key
type SessionKey struct {
Key string `json:"merchantSessionKey"`
Expiry time.Time `json:"expiry"`
}

// GetSessionKey gets a new merchant session Key
func (c Client) GetSessionKey(ctx context.Context, vendorName string) (*SessionKey, error) {
path := "/merchant-session-keys"

body := map[string]string{
"vendorName": vendorName,
}

res := SessionKey{}

if err := c.JSON(ctx, http.MethodPost, path, body, &res); err != nil {
return nil, err
}

return &res, nil
}
20 changes: 20 additions & 0 deletions session_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package sagepay

import (
"context"
"testing"
)

func TestClientGetSessionKey(t *testing.T) {

client := getTestClient()

sk, err := client.GetSessionKey(context.TODO(), "sandboxEC")

if err != nil {
t.Error(err)
}

t.Log(sk)

}
Loading

0 comments on commit 630bea0

Please sign in to comment.