mirror of
https://github.com/bestbytes/datatrans.git
synced 2025-10-16 12:05:36 +00:00
finish client for transactions, add tests and workflows
This commit is contained in:
parent
3049559f76
commit
5a3adfa458
25
.github/workflows/go.yml
vendored
Normal file
25
.github/workflows/go.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: Go
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
test-build:
|
||||||
|
name: Test & Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Set up Go 1.12
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.12
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
go mod tidy -v
|
||||||
|
go test -race ./...
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build ./...
|
||||||
33
README.md
33
README.md
@ -6,6 +6,8 @@ This Go package implements the new JSON based Datatrans API #golang #paymentprov
|
|||||||
|
|
||||||
https://api-reference.datatrans.ch
|
https://api-reference.datatrans.ch
|
||||||
|
|
||||||
|
https://docs.datatrans.ch/docs
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -15,14 +17,14 @@ https://api-reference.datatrans.ch
|
|||||||
Timeout: 30*time.Second,
|
Timeout: 30*time.Second,
|
||||||
}).Do),
|
}).Do),
|
||||||
datatrans.OptionMerchant{
|
datatrans.OptionMerchant{
|
||||||
InternalID: "",
|
InternalID: "",
|
||||||
Server: "https://api.sandbox.datatrans.com",
|
Server: "https://api.sandbox.datatrans.com",
|
||||||
MerchantID: "32234323242",
|
MerchantID: "32234323242",
|
||||||
Password: "dbce0e6cfc012e475c843c1bbb0ca439a048fe8e",
|
Password: "dbce0e6cfc012e475c843c1bbb0ca439a048fe8e",
|
||||||
},
|
},
|
||||||
// add more merchants if you like
|
// add more merchants if you like
|
||||||
datatrans.OptionMerchant{
|
datatrans.OptionMerchant{
|
||||||
InternalID: "B",
|
InternalID: "B",
|
||||||
Server: "https://api.sandbox.datatrans.com",
|
Server: "https://api.sandbox.datatrans.com",
|
||||||
MerchantID: "78967896789",
|
MerchantID: "78967896789",
|
||||||
Password: "e249002bc8e0c36dd89c393bfc7f7aa369c5842f",
|
Password: "e249002bc8e0c36dd89c393bfc7f7aa369c5842f",
|
||||||
@ -36,6 +38,33 @@ https://api-reference.datatrans.ch
|
|||||||
c.Status("65784567")
|
c.Status("65784567")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### My request needs additional fields which aren't in the struct!
|
||||||
|
|
||||||
|
How can I extend the JSON data posted to datatrans?
|
||||||
|
|
||||||
|
```go
|
||||||
|
ri := &datatrans.RequestInitialize{
|
||||||
|
Currency: "CHF",
|
||||||
|
RefNo: "234234",
|
||||||
|
AutoSettle: true,
|
||||||
|
Amount: 10023,
|
||||||
|
Language: "DE",
|
||||||
|
CustomFields: map[string]interface{}{
|
||||||
|
"TWI": map[string]interface{}{
|
||||||
|
"alias": "ZGZhc2RmYXNkZmFzZGZhc2Q=",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, err := datatrans.MarshalJSON(ri)
|
||||||
|
// handle error
|
||||||
|
// using TWI for Twint specific parameters
|
||||||
|
const wantJSON = `{"TWI":{"alias":"ZGZhc2RmYXNkZmFzZGZhc2Q="},"amount":10023,"autoSettle":true,"currency":"CHF","language":"DE","refno":"234234"}`
|
||||||
|
if string(data) != wantJSON {
|
||||||
|
t.Errorf("\nWant: %s\nHave: %s", wantJSON, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
Mozilla Public License Version 2.0
|
Mozilla Public License Version 2.0
|
||||||
|
|||||||
161
client.go
161
client.go
@ -10,14 +10,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
pathBase = "/v1/transactions"
|
pathBase = "/v1/transactions"
|
||||||
pathStatus = pathBase + "/%s"
|
pathStatus = pathBase + "/%s"
|
||||||
pathCredit = pathBase + "/%s/credit"
|
pathCredit = pathBase + "/%s/credit"
|
||||||
pathCreditAuthorize = pathBase + "/credit"
|
pathCreditAuthorize = pathBase + "/credit"
|
||||||
pathCancel = pathBase + "/%s/cancel"
|
pathCancel = pathBase + "/%s/cancel"
|
||||||
pathSettle = pathBase + "/%s/settle"
|
pathSettle = pathBase + "/%s/settle"
|
||||||
pathValidate = pathBase + "/validate"
|
pathValidate = pathBase + "/validate"
|
||||||
pathAuthorize = pathBase + "/%s/authorize"
|
pathAuthorizeTransaction = pathBase + "/%s/authorize"
|
||||||
|
pathAuthorize = pathBase + "/authorize"
|
||||||
|
pathInitialize = pathBase
|
||||||
)
|
)
|
||||||
|
|
||||||
type OptionMerchant struct {
|
type OptionMerchant struct {
|
||||||
@ -43,9 +45,10 @@ func (fn OptionHTTPRequestFn) apply(c *Client) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
doFn OptionHTTPRequestFn
|
copyRawResponseBody bool
|
||||||
merchants map[string]OptionMerchant // string = your custom merchant ID
|
doFn OptionHTTPRequestFn
|
||||||
currentInternalID string
|
merchants map[string]OptionMerchant // string = your custom merchant ID
|
||||||
|
currentInternalID string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option interface {
|
type Option interface {
|
||||||
@ -84,7 +87,9 @@ func (c *Client) do(req *http.Request, v interface{}) error {
|
|||||||
return fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", internalID, err)
|
return fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", internalID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
var buf bytes.Buffer
|
||||||
|
body := io.TeeReader(resp.Body, &buf)
|
||||||
|
dec := json.NewDecoder(body)
|
||||||
|
|
||||||
if !c.isSuccess(resp.StatusCode) {
|
if !c.isSuccess(resp.StatusCode) {
|
||||||
var errResp ErrorResponse
|
var errResp ErrorResponse
|
||||||
@ -99,6 +104,14 @@ func (c *Client) do(req *http.Request, v interface{}) error {
|
|||||||
return fmt.Errorf("ClientID:%q: failed to unmarshal HTTP error response: %w", internalID, err)
|
return fmt.Errorf("ClientID:%q: failed to unmarshal HTTP error response: %w", internalID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ri, ok := v.(*ResponseInitialize); ok {
|
||||||
|
if loc := resp.Header.Get("Location"); loc != "" {
|
||||||
|
ri.Location = loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if set, ok := v.(rawJSONBodySetter); ok {
|
||||||
|
set.setJSONRawBody(buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -115,6 +128,55 @@ func closeResponse(r *http.Response) {
|
|||||||
_ = r.Body.Close()
|
_ = r.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes the postData struct to json but also can merge custom
|
||||||
|
// settings into the final JSON. This function is called before sending the
|
||||||
|
// request to datatrans. Function exported for debug reasons.
|
||||||
|
func MarshalJSON(postData interface{}) ([]byte, error) {
|
||||||
|
jsonBytes, err := json.Marshal(postData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal postData: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this steps merges two different Go types into one JS object.
|
||||||
|
if cfg, ok := postData.(customFieldsGetter); ok {
|
||||||
|
custFields := cfg.getCustomFields()
|
||||||
|
if len(custFields) == 0 {
|
||||||
|
return jsonBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
postDataMap := map[string]interface{}{}
|
||||||
|
if err := json.Unmarshal(jsonBytes, &postDataMap); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to Unmarshal postData raw bytes: %w", err)
|
||||||
|
}
|
||||||
|
for k, v := range custFields {
|
||||||
|
postDataMap[k] = v // overwrites existing data from postData struct
|
||||||
|
}
|
||||||
|
jsonBytes, err = json.Marshal(postDataMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal postDataMap: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) preparePostJSONReq(path string, postData interface{}) (*http.Request, error) {
|
||||||
|
internalID := c.currentInternalID
|
||||||
|
|
||||||
|
jsonBytes, err := MarshalJSON(postData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ClientID:%q: failed to json marshal HTTP request: %w", internalID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, c.merchants[internalID].Server+path, bytes.NewReader(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ClientID:%q: failed to create HTTP request: %w", internalID, err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Status allows once a transactionId has been received the status can be checked
|
// Status allows once a transactionId has been received the status can be checked
|
||||||
// with the Status API.
|
// with the Status API.
|
||||||
func (c *Client) Status(transactionID string) (*ResponseStatus, error) {
|
func (c *Client) Status(transactionID string) (*ResponseStatus, error) {
|
||||||
@ -135,21 +197,6 @@ func (c *Client) Status(transactionID string) (*ResponseStatus, error) {
|
|||||||
return &respStatus, nil
|
return &respStatus, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) preparePostJSONReq(path string, postData interface{}) (*http.Request, error) {
|
|
||||||
internalID := c.currentInternalID
|
|
||||||
jsonBytes, err := json.Marshal(postData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("ClientID:%q: failed to json marshal HTTP request: %w", internalID, err)
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest(http.MethodPost, c.merchants[internalID].Server+path, bytes.NewReader(jsonBytes))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("ClientID:%q: failed to create HTTP request: %w", internalID, err)
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Credit uses the credit API to credit a transaction which is in status settled.
|
// Credit uses the credit API to credit a transaction which is in status settled.
|
||||||
// The previously settled amount must not be exceeded.
|
// The previously settled amount must not be exceeded.
|
||||||
func (c *Client) Credit(transactionID string, rc RequestCredit) (*ResponseCardMasked, error) {
|
func (c *Client) Credit(transactionID string, rc RequestCredit) (*ResponseCardMasked, error) {
|
||||||
@ -217,6 +264,7 @@ func (c *Client) Cancel(transactionID string, refno string) error {
|
|||||||
// used for the settlement of previously authorized transactions. The
|
// used for the settlement of previously authorized transactions. The
|
||||||
// transactionId is needed to settle an authorization. Note: This API call is not
|
// transactionId is needed to settle an authorization. Note: This API call is not
|
||||||
// needed if "autoSettle": true was used when initializing a transaction.
|
// needed if "autoSettle": true was used when initializing a transaction.
|
||||||
|
// https://api-reference.datatrans.ch/#operation/settle
|
||||||
func (c *Client) Settle(transactionID string, rs RequestSettle) error {
|
func (c *Client) Settle(transactionID string, rs RequestSettle) error {
|
||||||
if transactionID == "" || rs.Amount == 0 || rs.Currency == "" || rs.RefNo == "" {
|
if transactionID == "" || rs.Amount == 0 || rs.Currency == "" || rs.RefNo == "" {
|
||||||
return fmt.Errorf("neither transactionID nor refno nor amount nor currency can be empty")
|
return fmt.Errorf("neither transactionID nor refno nor amount nor currency can be empty")
|
||||||
@ -236,6 +284,7 @@ func (c *Client) Settle(transactionID string, rs RequestSettle) error {
|
|||||||
// transaction validate API. No amount will be blocked on the customers account.
|
// transaction validate API. No amount will be blocked on the customers account.
|
||||||
// Only credit cards (including Apple Pay and Google Pay), PFC, KLN and PAP
|
// Only credit cards (including Apple Pay and Google Pay), PFC, KLN and PAP
|
||||||
// support validation of an existing alias.
|
// support validation of an existing alias.
|
||||||
|
// https://api-reference.datatrans.ch/#operation/validate
|
||||||
func (c *Client) ValidateAlias(rva RequestValidateAlias) (*ResponseCardMasked, error) {
|
func (c *Client) ValidateAlias(rva RequestValidateAlias) (*ResponseCardMasked, error) {
|
||||||
if rva.Currency == "" || rva.RefNo == "" {
|
if rva.Currency == "" || rva.RefNo == "" {
|
||||||
return nil, fmt.Errorf("neither currency nor refno can be empty")
|
return nil, fmt.Errorf("neither currency nor refno can be empty")
|
||||||
@ -252,14 +301,15 @@ func (c *Client) ValidateAlias(rva RequestValidateAlias) (*ResponseCardMasked, e
|
|||||||
return &rcm, nil
|
return &rcm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authorize an authenticated transaction. If during the initialization of a
|
// AuthorizeTransaction an authenticated transaction. If during the initialization of a
|
||||||
// transaction the parameter option.authenticationOnly was set to true, this API
|
// transaction the parameter option.authenticationOnly was set to true, this API
|
||||||
// can be used to authorize an already authenticated (3D) transaction.
|
// can be used to authorize an already authenticated (3D) transaction.
|
||||||
func (c *Client) Authorize(transactionID string, rva RequestValidateAlias) (*ResponseAuthorize, error) {
|
// https://api-reference.datatrans.ch/#operation/authorize-split
|
||||||
if transactionID == "" || rva.Currency == "" || rva.RefNo == "" {
|
func (c *Client) AuthorizeTransaction(transactionID string, rva RequestAuthorizeTransaction) (*ResponseAuthorize, error) {
|
||||||
return nil, fmt.Errorf("neither transactionID nor currency nor refno can be empty")
|
if transactionID == "" || rva.RefNo == "" {
|
||||||
|
return nil, fmt.Errorf("neither transactionID nor refno can be empty")
|
||||||
}
|
}
|
||||||
req, err := c.preparePostJSONReq(fmt.Sprintf(pathAuthorize, transactionID), rva)
|
req, err := c.preparePostJSONReq(fmt.Sprintf(pathAuthorizeTransaction, transactionID), rva)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -270,3 +320,50 @@ func (c *Client) Authorize(transactionID string, rva RequestValidateAlias) (*Res
|
|||||||
}
|
}
|
||||||
return &rcm, nil
|
return &rcm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Authorize a transaction. Use this API to make an authorization without user
|
||||||
|
// interaction. (For example merchant initiated transactions with an alias)
|
||||||
|
// Depending on the payment method, different parameters are mandatory. Refer to
|
||||||
|
// the payment method specific objects (for example PAP) to see which parameters
|
||||||
|
// so send. For credit cards, the card object can be used.
|
||||||
|
// https://api-reference.datatrans.ch/#operation/authorize
|
||||||
|
func (c *Client) Authorize(rva RequestAuthorize) (*ResponseCardMasked, error) {
|
||||||
|
if rva.Amount == 0 || rva.Currency == "" || rva.RefNo == "" {
|
||||||
|
return nil, fmt.Errorf("neither transactionID nor amount nor currency nor refno can be empty")
|
||||||
|
}
|
||||||
|
req, err := c.preparePostJSONReq(pathAuthorize, rva)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rcm ResponseCardMasked
|
||||||
|
if err := c.do(req, &rcm); err != nil {
|
||||||
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
||||||
|
}
|
||||||
|
return &rcm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a transaction. Securely send all the needed parameters to the
|
||||||
|
// transaction initialization API. The result of this API call is a HTTP 201
|
||||||
|
// status code with a transactionId in the response body and the Location header
|
||||||
|
// set. If you want to use the payment page redirect mode to collect the payment
|
||||||
|
// details, the browser needs to be redirected to this URL to continue with the
|
||||||
|
// transaction. Following the link provided in the Location header will raise the
|
||||||
|
// Datatrans Payment Page with all the payment methods available for the given
|
||||||
|
// merchantId. If you want to limit the number of payment methods, the
|
||||||
|
// paymentMethod array can be used.
|
||||||
|
func (c *Client) Initialize(rva RequestInitialize) (*ResponseInitialize, error) {
|
||||||
|
if rva.Amount == 0 || rva.Currency == "" || rva.RefNo == "" {
|
||||||
|
return nil, fmt.Errorf("neither transactionID nor amount nor currency nor refno can be empty")
|
||||||
|
}
|
||||||
|
req, err := c.preparePostJSONReq(pathInitialize, rva)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ri ResponseInitialize
|
||||||
|
if err := c.do(req, &ri); err != nil {
|
||||||
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
||||||
|
}
|
||||||
|
return &ri, nil
|
||||||
|
}
|
||||||
|
|||||||
134
client_test.go
134
client_test.go
@ -1,44 +1,55 @@
|
|||||||
package datatrans
|
package datatrans_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
"github.com/globusdigital/datatrans"
|
||||||
)
|
)
|
||||||
|
|
||||||
func must(t *testing.T, err error) {
|
func must(t *testing.T, err error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%#v", err)
|
t.Fatalf("%s\n%#v", err, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mockResponse(status int, body string) func(req *http.Request) (*http.Response, error) {
|
func mockResponse(
|
||||||
|
t *testing.T,
|
||||||
|
status int,
|
||||||
|
body string,
|
||||||
|
testReq func(t *testing.T, req *http.Request),
|
||||||
|
) func(req *http.Request) (*http.Response, error) {
|
||||||
|
rc := ioutil.NopCloser(strings.NewReader(body))
|
||||||
|
if strings.HasSuffix(body, ".json") {
|
||||||
|
fp, err := os.Open(body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rc = fp
|
||||||
|
}
|
||||||
|
if testReq == nil {
|
||||||
|
testReq = func(t *testing.T, req *http.Request) {}
|
||||||
|
}
|
||||||
|
|
||||||
return func(req *http.Request) (*http.Response, error) {
|
return func(req *http.Request) (*http.Response, error) {
|
||||||
|
testReq(t, req)
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: status,
|
StatusCode: status,
|
||||||
Body: ioutil.NopCloser(strings.NewReader(body)),
|
Body: rc,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_Status(t *testing.T) {
|
func TestClient_Status(t *testing.T) {
|
||||||
_,_ = MakeClient(
|
c, err := datatrans.MakeClient(
|
||||||
OptionHTTPRequestFn((&http.Client{
|
datatrans.OptionHTTPRequestFn(mockResponse(t, 200, "testdata/status_response.json", nil)),
|
||||||
Timeout: 30*time.Second,
|
datatrans.OptionMerchant{
|
||||||
}).Do),
|
|
||||||
OptionMerchant{
|
|
||||||
Server: "https://api.sandbox.datatrans.com",
|
|
||||||
MerchantID: "32234323242",
|
|
||||||
Password: "dbce0e6cfc012e475c843c1bbb0ca439a048fe8e",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
c, err := MakeClient(
|
|
||||||
OptionHTTPRequestFn(mockResponse(200, "{}")),
|
|
||||||
OptionMerchant{
|
|
||||||
Server: "http://localhost",
|
Server: "http://localhost",
|
||||||
MerchantID: "322342",
|
MerchantID: "322342",
|
||||||
Password: "32168",
|
Password: "32168",
|
||||||
@ -46,10 +57,87 @@ func TestClient_Status(t *testing.T) {
|
|||||||
)
|
)
|
||||||
must(t, err)
|
must(t, err)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
rs, err := c.Status("3423423423")
|
rs, err := c.Status("3423423423")
|
||||||
must(t, err)
|
must(t, err)
|
||||||
// TODO continue here
|
if rs.TransactionID != "210215103042148501" {
|
||||||
t.Logf("%#v", rs)
|
t.Errorf("incorrect TransactionID:%q", rs.TransactionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_Initialize(t *testing.T) {
|
||||||
|
c, err := datatrans.MakeClient(
|
||||||
|
datatrans.OptionHTTPRequestFn(mockResponse(t, 200, `{"transactionId": "210215103033478409"}`, func(t *testing.T, req *http.Request) {
|
||||||
|
if req.Method != http.MethodPost {
|
||||||
|
t.Error("not a post request")
|
||||||
|
}
|
||||||
|
if req.Header.Get("Content-Type") != "application/json" {
|
||||||
|
t.Error("invalid content type")
|
||||||
|
}
|
||||||
|
|
||||||
|
u, p, _ := req.BasicAuth()
|
||||||
|
if u != "322342" {
|
||||||
|
t.Error("invalid basic username")
|
||||||
|
}
|
||||||
|
if p != "sfdgsdfg" {
|
||||||
|
t.Error("invalid basic password")
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.ReadFrom(req.Body)
|
||||||
|
|
||||||
|
const wantBody = `{"alp":true,"amount":1337,"currency":"CHF","paymentMethods":["VIS","PFC"],"redirect":{"cancelUrl":"https://.../cancelPage.jsp","errorUrl":"https://.../errorPage.jsp","successUrl":"https://.../successPage.jsp"},"refno":"872732"}`
|
||||||
|
if buf.String() != wantBody {
|
||||||
|
t.Errorf("invalid body: %q", buf.String())
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
datatrans.OptionMerchant{
|
||||||
|
Server: "http://localhost",
|
||||||
|
MerchantID: "322342",
|
||||||
|
Password: "sfdgsdfg",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
must(t, err)
|
||||||
|
|
||||||
|
rs, err := c.Initialize(datatrans.RequestInitialize{
|
||||||
|
Currency: "CHF",
|
||||||
|
RefNo: "872732",
|
||||||
|
Amount: 1337,
|
||||||
|
Language: "",
|
||||||
|
PaymentMethods: []string{"VIS", "PFC"},
|
||||||
|
Redirect: &datatrans.Redirect{
|
||||||
|
SuccessUrl: "https://.../successPage.jsp",
|
||||||
|
CancelUrl: "https://.../cancelPage.jsp",
|
||||||
|
ErrorUrl: "https://.../errorPage.jsp",
|
||||||
|
},
|
||||||
|
CustomFields: map[string]interface{}{
|
||||||
|
"alp": true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
must(t, err)
|
||||||
|
|
||||||
|
want := &datatrans.ResponseInitialize{TransactionId: "210215103033478409", RawJSONBody: datatrans.RawJSONBody{0x7b, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x32, 0x31, 0x30, 0x32, 0x31, 0x35, 0x31, 0x30, 0x33, 0x30, 0x33, 0x33, 0x34, 0x37, 0x38, 0x34, 0x30, 0x39, 0x22, 0x7d}}
|
||||||
|
if !reflect.DeepEqual(rs, want) {
|
||||||
|
t.Error("invalid response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalJSON(t *testing.T) {
|
||||||
|
ri := datatrans.RequestInitialize{
|
||||||
|
Currency: "CHF",
|
||||||
|
RefNo: "234234",
|
||||||
|
RefNo2: "",
|
||||||
|
AutoSettle: true,
|
||||||
|
Amount: 123,
|
||||||
|
Language: "DE",
|
||||||
|
CustomFields: map[string]interface{}{
|
||||||
|
"twi": map[string]interface{}{
|
||||||
|
"alias": "ZGZhc2RmYXNkZmFzZGZhc2Q=",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, err := datatrans.MarshalJSON(ri)
|
||||||
|
must(t, err)
|
||||||
|
const wantJSON = `{"amount":123,"autoSettle":true,"currency":"CHF","language":"DE","refno":"234234","twi":{"alias":"ZGZhc2RmYXNkZmFzZGZhc2Q="}}`
|
||||||
|
if string(data) != wantJSON {
|
||||||
|
t.Errorf("\nWant: %s\nHave: %s", wantJSON, data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
294
dto.go
294
dto.go
@ -1,108 +1,246 @@
|
|||||||
package datatrans
|
package datatrans
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// More fields can be added to any of the structs if needed. Just send a PR.
|
||||||
|
|
||||||
|
type customFieldsGetter interface {
|
||||||
|
getCustomFields() map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomFields allows to extend any input with merchant specific settings.
|
||||||
|
type CustomFields map[string]interface{}
|
||||||
|
|
||||||
|
func (cf CustomFields) getCustomFields() map[string]interface{} { return cf }
|
||||||
|
|
||||||
|
type rawJSONBodySetter interface {
|
||||||
|
setJSONRawBody([]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawJSONBody includes the original response from the datatrans server. There
|
||||||
|
// might be custom fields in the response which are not included in the structs
|
||||||
|
// in this package. This type allows for unmarshaling into custom structs.
|
||||||
|
type RawJSONBody []byte
|
||||||
|
|
||||||
|
func (b *RawJSONBody) setJSONRawBody(p []byte) {
|
||||||
|
*b = p
|
||||||
|
}
|
||||||
|
|
||||||
type ResponseAuthorize struct {
|
type ResponseAuthorize struct {
|
||||||
AcquirerAuthorizationCode string `json:"acquirerAuthorizationCode"`
|
AcquirerAuthorizationCode string `json:"acquirerAuthorizationCode"`
|
||||||
|
RawJSONBody `json:"raw,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://api-reference.datatrans.ch/#operation/init
|
||||||
|
type RequestInitialize struct {
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
RefNo string `json:"refno"`
|
||||||
|
RefNo2 string `json:"refno2,omitempty"`
|
||||||
|
AutoSettle bool `json:"autoSettle,omitempty"`
|
||||||
|
Customer *Customer `json:"customer,omitempty"`
|
||||||
|
Card *CardAlias `json:"card,omitempty"`
|
||||||
|
Amount int `json:"amount,omitempty"`
|
||||||
|
Language string `json:"language,omitempty"` // Enum: "en" "de" "fr" "it" "es" "el" "no" "da" "pl" "pt" "ru" "ja"
|
||||||
|
PaymentMethods []string `json:"paymentMethods,omitempty"`
|
||||||
|
Theme *Theme `json:"theme,omitempty"`
|
||||||
|
Redirect *Redirect `json:"redirect,omitempty"`
|
||||||
|
Option *InitializeOption `json:"option,omitempty"`
|
||||||
|
CustomFields `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseInitialize struct {
|
||||||
|
Location string `json:"location,omitempty"` // A URL where the users browser needs to be redirect to complete the payment. This redirect is only needed when using Redirect Mode. For Lightbox Mode the returned transactionId can be used to start the payment page.
|
||||||
|
TransactionId string `json:"transactionId,omitempty"`
|
||||||
|
MobileToken string `json:"mobileToken,omitempty"`
|
||||||
|
RawJSONBody `json:"raw,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestAuthorize struct {
|
||||||
|
Amount int `json:"amount,omitempty"`
|
||||||
|
Currency string `json:"currency,omitempty"`
|
||||||
|
RefNo string `json:"refno,omitempty"`
|
||||||
|
RefNo2 string `json:"refno2,omitempty"`
|
||||||
|
AutoSettle bool `json:"autoSettle,omitempty"`
|
||||||
|
// The card object to be submitted when authorizing with an existing credit
|
||||||
|
// card alias.
|
||||||
|
Card *CardAlias `json:"card,omitempty"`
|
||||||
|
CustomFields `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestAuthorizeTransaction struct {
|
||||||
|
RefNo string `json:"refno,omitempty"`
|
||||||
|
Amount int `json:"amount,omitempty"`
|
||||||
|
AutoSettle bool `json:"autoSettle,omitempty"`
|
||||||
|
RefNo2 string `json:"refno2,omitempty"`
|
||||||
|
CustomFields `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestValidateAlias struct {
|
type RequestValidateAlias struct {
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency,omitempty"`
|
||||||
RefNo string `json:"refno"`
|
RefNo string `json:"refno,omitempty"`
|
||||||
CardSimple *CardAlias `json:"card,omitempty"`
|
RefNo2 string `json:"refno2,omitempty"`
|
||||||
//autoSettle
|
Card *CardAlias `json:"card,omitempty"`
|
||||||
RefNo2 string `json:"refno2,omitempty"`
|
CustomFields `json:"-"`
|
||||||
// TODO add more fields
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestSettle struct {
|
type RequestSettle struct {
|
||||||
Amount int `json:"amount"`
|
Amount int `json:"amount,omitempty"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency,omitempty"`
|
||||||
RefNo string `json:"refno"`
|
RefNo string `json:"refno,omitempty"`
|
||||||
RefNo2 string `json:"refno2,omitempty"`
|
RefNo2 string `json:"refno2,omitempty"`
|
||||||
Extensions map[string]interface{} `json:"extensions,omitempty"`
|
CustomFields `json:"-"`
|
||||||
// TODO add more fields
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestCredit struct {
|
type RequestCredit struct {
|
||||||
Amount int `json:"amount"`
|
Amount int `json:"amount,omitempty"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency,omitempty"`
|
||||||
RefNo string `json:"refno"`
|
RefNo string `json:"refno,omitempty"`
|
||||||
RefNo2 string `json:"refno2,omitempty"`
|
RefNo2 string `json:"refno2,omitempty"`
|
||||||
Extensions map[string]interface{} `json:"extensions,omitempty"`
|
CustomFields `json:"-"`
|
||||||
// TODO add more fields
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponseCardMasked struct {
|
|
||||||
TransactionId string `json:"transactionId"`
|
|
||||||
AcquirerAuthorizationCode string `json:"acquirerAuthorizationCode"`
|
|
||||||
Card *CardMaskedSimple `json:"card,omitempty"` // only set in case of CreditAuthorize
|
|
||||||
}
|
|
||||||
|
|
||||||
type CardMaskedSimple struct {
|
|
||||||
Masked string `json:"masked"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestCreditAuthorize struct {
|
type RequestCreditAuthorize struct {
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency,omitempty"`
|
||||||
RefNo string `json:"refno"`
|
RefNo string `json:"refno,omitempty"`
|
||||||
Card *CardAlias `json:"card,omitempty"`
|
Card *CardAlias `json:"card,omitempty"`
|
||||||
Amount int `json:"amount"`
|
Amount int `json:"amount,omitempty"`
|
||||||
AutoSettle bool `json:"autoSettle,omitempty"`
|
AutoSettle bool `json:"autoSettle,omitempty"`
|
||||||
Refno2 string `json:"refno2,omitempty"`
|
Refno2 string `json:"refno2,omitempty"`
|
||||||
// TODO add more fields
|
CustomFields `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseCardMasked struct {
|
||||||
|
TransactionId string `json:"transactionId,omitempty"`
|
||||||
|
AcquirerAuthorizationCode string `json:"acquirerAuthorizationCode,omitempty"`
|
||||||
|
Card *CardMaskedSimple `json:"card,omitempty"` // only set in case of CreditAuthorize
|
||||||
|
RawJSONBody `json:"raw,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CardMaskedSimple struct {
|
||||||
|
Masked string `json:"masked,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CardAlias struct {
|
type CardAlias struct {
|
||||||
Alias string `json:"alias"`
|
Alias string `json:"alias,omitempty"`
|
||||||
ExpiryMonth string `json:"expiryMonth"`
|
ExpiryMonth string `json:"expiryMonth,omitempty"`
|
||||||
ExpiryYear string `json:"expiryYear"`
|
ExpiryYear string `json:"expiryYear,omitempty"`
|
||||||
|
CreateAliasCVV bool `json:"createAliasCVV,omitempty"` // only used when initializing a transaction
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseStatus struct {
|
type ResponseStatus struct {
|
||||||
TransactionID string `json:"transactionId"`
|
TransactionID string `json:"transactionId,omitempty"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type,omitempty"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status,omitempty"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency,omitempty"`
|
||||||
RefNo string `json:"refno"`
|
RefNo string `json:"refno,omitempty"`
|
||||||
PaymentMethod string `json:"paymentMethod"`
|
PaymentMethod string `json:"paymentMethod,omitempty"`
|
||||||
Detail struct {
|
Detail map[string]interface{} `json:"detail,omitempty"`
|
||||||
Init struct {
|
Customer *Customer `json:"customer,omitempty"`
|
||||||
Expires time.Time `json:"expires"`
|
Card *CardExtended `json:"card,omitempty"`
|
||||||
} `json:"init"`
|
Language string `json:"language,omitempty"`
|
||||||
Authorize struct {
|
History []History `json:"history,omitempty"`
|
||||||
Amount int `json:"amount"`
|
RawJSONBody `json:"raw,omitempty"`
|
||||||
AcquirerAuthorizationCode string `json:"acquirerAuthorizationCode"`
|
|
||||||
} `json:"authorize"`
|
|
||||||
// TODO add more fields https://api-reference.datatrans.ch/#operation/status
|
|
||||||
} `json:"detail"`
|
|
||||||
Card *CardExtended `json:"card"`
|
|
||||||
Twint *struct {
|
|
||||||
Alias string `json:"alias"`
|
|
||||||
} `json:"twi"`
|
|
||||||
// TODO add more fields https://api-reference.datatrans.ch/#operation/status
|
|
||||||
|
|
||||||
History []History `json:"history"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CardExtended struct {
|
type CardExtended struct {
|
||||||
Masked string `json:"masked"`
|
Alias string `json:"alias,omitempty"`
|
||||||
ExpiryMonth string `json:"expiryMonth"`
|
AliasCVV string `json:"aliasCVV,omitempty"`
|
||||||
ExpiryYear string `json:"expiryYear"`
|
Masked string `json:"masked,omitempty"`
|
||||||
Info struct {
|
ExpiryMonth string `json:"expiryMonth,omitempty"`
|
||||||
Brand string `json:"brand"`
|
ExpiryYear string `json:"expiryYear,omitempty"`
|
||||||
Type string `json:"type"`
|
Info *CardExtendedInfo `json:"info,omitempty"`
|
||||||
Usage string `json:"usage"`
|
WalletIndicator string `json:"walletIndicator,omitempty"`
|
||||||
Country string `json:"country"`
|
}
|
||||||
Issuer string `json:"issuer"`
|
|
||||||
} `json:"info"`
|
type CardExtendedInfo struct {
|
||||||
|
Brand string `json:"brand,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Usage string `json:"usage,omitempty"`
|
||||||
|
Country string `json:"country,omitempty"`
|
||||||
|
Issuer string `json:"issuer,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type History struct {
|
type History struct {
|
||||||
Action string `json:"action"`
|
Action string `json:"action,omitempty"`
|
||||||
Amount int `json:"amount"`
|
Amount int `json:"amount,omitempty"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source,omitempty"`
|
||||||
Date time.Time `json:"date"`
|
Date time.Time `json:"date,omitempty"`
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success,omitempty"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Customer struct {
|
||||||
|
ID string `json:"id,omitempty"` // Unique customer identifier
|
||||||
|
Title string `json:"title,omitempty"` // Something like Ms or Mrs
|
||||||
|
FirstName string `json:"firstName,omitempty"` // The first name of the customer.
|
||||||
|
LastName string `json:"lastName,omitempty"` // The last name of the customer.
|
||||||
|
Street string `json:"street,omitempty"` // The street of the customer.
|
||||||
|
Street2 string `json:"street2,omitempty"` // Additional street information. For example: '3rd floor'
|
||||||
|
City string `json:"city,omitempty"` // The city of the customer.
|
||||||
|
Country string `json:"country,omitempty"` // 2 letter ISO 3166-1 alpha-2 country code
|
||||||
|
ZipCode string `json:"zipCode,omitempty"` // Zip code of the customer.
|
||||||
|
Phone string `json:"phone,omitempty"` // Phone number of the customer.
|
||||||
|
CellPhone string `json:"cellPhone,omitempty"` // Cell Phone number of the customer.
|
||||||
|
Email string `json:"email,omitempty"` // The email address of the customer.
|
||||||
|
Gender string `json:"gender,omitempty"` // Gender of the customer. female or male.
|
||||||
|
BirthDate string `json:"birthDate,omitempty"` // The birth date of the customer. Must be in ISO-8601 format (YYYY-MM-DD).
|
||||||
|
Language string `json:"language,omitempty"` // The language of the customer.
|
||||||
|
Type string `json:"type,omitempty"` // P or C depending on whether the customer is private or a company. If C, the fields name and companyRegisterNumber are required
|
||||||
|
Name string `json:"name,omitempty"` // The name of the company. Only applicable if type=C
|
||||||
|
CompanyLegalForm string `json:"companyLegalForm,omitempty"` // The legal form of the company (AG, GmbH, ...)
|
||||||
|
CompanyRegisterNumber string `json:"companyRegisterNumber,omitempty"` // The register number of the company. Only applicable if type=C
|
||||||
|
IpAddress string `json:"ipAddress,omitempty"` // The ip address of the customer.
|
||||||
|
}
|
||||||
|
|
||||||
|
type Theme struct {
|
||||||
|
// Theme configuration options when using the default DT2015 theme
|
||||||
|
Configuration ThemeConfiguration `json:"configuration,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThemeConfiguration struct {
|
||||||
|
BrandColor string `json:"brandColor,omitempty"` // Hex notation of a color
|
||||||
|
TextColor string `json:"textColor,omitempty"` // Enum: "white" "black" The color of the text in the header bar if no logo is given
|
||||||
|
LogoType string `json:"logoType,omitempty"` // Enum: "circle" "rectangle" "none" The header logo's display style
|
||||||
|
LogoBorderColor string `json:"logoBorderColor,omitempty"` // Decides whether the logo shall be styled with a border around it, if the value is true the default background color is chosen, else the provided string is used as color value
|
||||||
|
BrandButton string `json:"brandButton,omitempty"` // Decides if the pay button should have the same color as the brandColor. If set to false the hex color #01669F will be used as a default
|
||||||
|
PayButtonTextColor string `json:"payButtonTextColor,omitempty"` // The color (hex) of the pay button
|
||||||
|
LogoSrc string `json:"logoSrc,omitempty"` // An SVG image provided by the merchant. The image needs to be uploaded by using the Datatrans Web Administration Tool
|
||||||
|
InitialView string `json:"initialView,omitempty"` // Enum: "list" "grid" Wheter the payment page shows the payment method selection as list (default) or as a grid
|
||||||
|
BrandTitle bool `json:"brandTitle,omitempty"` // If set to false and no logo is used (see logoSrc), the payment page header will be empty
|
||||||
|
}
|
||||||
|
|
||||||
|
type Redirect struct {
|
||||||
|
SuccessUrl string `json:"successUrl,omitempty"` // The URL where the customer gets redirected to if the transaction was successful.
|
||||||
|
CancelUrl string `json:"cancelUrl,omitempty"` // The URL where the customer gets redirected to if the transaction was canceled.
|
||||||
|
ErrorUrl string `json:"errorUrl,omitempty"` // The URL where the customer gets redirected to if an error occurred.
|
||||||
|
// If the payment is started within an iframe or when using the Lightbox
|
||||||
|
// Mode, use value _top. This ensures a proper browser flow for payment
|
||||||
|
// methods who need a redirect.
|
||||||
|
StartTarget string `json:"startTarget,omitempty"`
|
||||||
|
// If the payment is started within an iframe or when using the Lightbox
|
||||||
|
// Mode, use _top if the redirect URLs should be opened full screen when
|
||||||
|
// payment returns from a 3rd party (for example 3D).
|
||||||
|
ReturnTarget string `json:"returnTarget,omitempty"`
|
||||||
|
// The preferred HTTP method for the redirect request (GET or POST). When
|
||||||
|
// using GET as a method, the query string parameter datatransTrxId will be
|
||||||
|
// added to the corresponding return url upon redirection. In case of POST,
|
||||||
|
// all the query parameters from the corresponding return url will be moved
|
||||||
|
// to the application/x-www-form-urlencoded body of the redirection request
|
||||||
|
// along with the added datatransTrxId parameter.
|
||||||
|
Method string `json:"method,omitempty"` // Default: "GET" Enum: "GET" "POST"
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitializeOption struct {
|
||||||
|
// Whether an alias should be created for this transaction or not. If set to
|
||||||
|
// true an alias will be created. This alias can then be used to initialize
|
||||||
|
// or authorize a transaction. One possible use case is to charge the card of
|
||||||
|
// an existing (registered) cardholder.
|
||||||
|
CreateAlias bool `json:"createAlias"`
|
||||||
|
ReturnMaskedCardNumber bool `json:"returnMaskedCardNumber"` // Whether to return the masked card number. Format: 520000xxxxxx0080
|
||||||
|
ReturnCustomerCountry bool `json:"returnCustomerCountry"` // If set to true, the country of the customers issuer will be returned.
|
||||||
|
AuthenticationOnly bool `json:"authenticationOnly"` // Whether to only authenticate the transaction (3D process only). If set to true, the actual authorization will not take place.
|
||||||
|
RememberMe string `json:"rememberMe"` // Enum: "true" "checked" Whether to show a checkbox on the payment page to let the customer choose if they want to save their card information.
|
||||||
|
ReturnMobileToken bool `json:"returnMobileToken"` // Indicates that a mobile token should be created. This is needed when using our Mobile SDKs.
|
||||||
}
|
}
|
||||||
|
|||||||
1
error.go
1
error.go
@ -7,6 +7,7 @@ type ErrorResponse struct {
|
|||||||
ErrorDetail ErrorDetail `json:"error"`
|
ErrorDetail ErrorDetail `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// see https://docs.datatrans.ch/docs/error-messages
|
||||||
type ErrorDetail struct {
|
type ErrorDetail struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -1,5 +1,3 @@
|
|||||||
module github.com/globusdigital/datatrans
|
module github.com/globusdigital/datatrans
|
||||||
|
|
||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
require github.com/shopspring/decimal v1.2.0
|
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -1,2 +0,0 @@
|
|||||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
|
||||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
|
||||||
39
testdata/status_response.json
vendored
Normal file
39
testdata/status_response.json
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"transactionId": "210215103042148501",
|
||||||
|
"type": "payment",
|
||||||
|
"status": "authorized",
|
||||||
|
"currency": "CHF",
|
||||||
|
"refno": "0coWYw9kL",
|
||||||
|
"paymentMethod": "VIS",
|
||||||
|
"detail": {
|
||||||
|
"authorize": {
|
||||||
|
"amount": 1000,
|
||||||
|
"acquirerAuthorizationCode": "103042"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"twi": {
|
||||||
|
"alias": "abcdef"
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"masked": "424242xxxxxx4242",
|
||||||
|
"expiryMonth": "12",
|
||||||
|
"expiryYear": "21",
|
||||||
|
"info": {
|
||||||
|
"brand": "VISA CREDIT",
|
||||||
|
"type": "credit",
|
||||||
|
"usage": "consumer",
|
||||||
|
"country": "GB",
|
||||||
|
"issuer": "DATATRANS"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"action": "authorize",
|
||||||
|
"amount": 1000,
|
||||||
|
"source": "api",
|
||||||
|
"date": "2021-02-15T09:30:42Z",
|
||||||
|
"success": true,
|
||||||
|
"ip": "77.109.165.195"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user