This repository has been archived by the owner on Mar 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 630bea0
Showing
11 changed files
with
428 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
} |
Oops, something went wrong.