finish client for transactions, add tests and workflows

This commit is contained in:
Cyrill Schumacher 2021-03-03 15:23:20 +01:00
parent 3049559f76
commit 5a3adfa458
9 changed files with 552 additions and 139 deletions

25
.github/workflows/go.yml vendored Normal file
View 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 ./...

View File

@ -6,6 +6,8 @@ This Go package implements the new JSON based Datatrans API #golang #paymentprov
https://api-reference.datatrans.ch
https://docs.datatrans.ch/docs
## Usage
```go
@ -36,6 +38,33 @@ https://api-reference.datatrans.ch
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
Mozilla Public License Version 2.0

141
client.go
View File

@ -17,7 +17,9 @@ const (
pathCancel = pathBase + "/%s/cancel"
pathSettle = pathBase + "/%s/settle"
pathValidate = pathBase + "/validate"
pathAuthorize = pathBase + "/%s/authorize"
pathAuthorizeTransaction = pathBase + "/%s/authorize"
pathAuthorize = pathBase + "/authorize"
pathInitialize = pathBase
)
type OptionMerchant struct {
@ -43,6 +45,7 @@ func (fn OptionHTTPRequestFn) apply(c *Client) error {
}
type Client struct {
copyRawResponseBody bool
doFn OptionHTTPRequestFn
merchants map[string]OptionMerchant // string = your custom merchant ID
currentInternalID string
@ -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)
}
dec := json.NewDecoder(resp.Body)
var buf bytes.Buffer
body := io.TeeReader(resp.Body, &buf)
dec := json.NewDecoder(body)
if !c.isSuccess(resp.StatusCode) {
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)
}
}
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
}
@ -115,6 +128,55 @@ func closeResponse(r *http.Response) {
_ = 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
// with the Status API.
func (c *Client) Status(transactionID string) (*ResponseStatus, error) {
@ -135,21 +197,6 @@ func (c *Client) Status(transactionID string) (*ResponseStatus, error) {
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.
// The previously settled amount must not be exceeded.
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
// transactionId is needed to settle an authorization. Note: This API call is not
// 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 {
if transactionID == "" || rs.Amount == 0 || rs.Currency == "" || rs.RefNo == "" {
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.
// Only credit cards (including Apple Pay and Google Pay), PFC, KLN and PAP
// support validation of an existing alias.
// https://api-reference.datatrans.ch/#operation/validate
func (c *Client) ValidateAlias(rva RequestValidateAlias) (*ResponseCardMasked, error) {
if rva.Currency == "" || rva.RefNo == "" {
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
}
// 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
// can be used to authorize an already authenticated (3D) transaction.
func (c *Client) Authorize(transactionID string, rva RequestValidateAlias) (*ResponseAuthorize, error) {
if transactionID == "" || rva.Currency == "" || rva.RefNo == "" {
return nil, fmt.Errorf("neither transactionID nor currency nor refno can be empty")
// https://api-reference.datatrans.ch/#operation/authorize-split
func (c *Client) AuthorizeTransaction(transactionID string, rva RequestAuthorizeTransaction) (*ResponseAuthorize, error) {
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 {
return nil, err
}
@ -270,3 +320,50 @@ func (c *Client) Authorize(transactionID string, rva RequestValidateAlias) (*Res
}
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
}

View File

@ -1,44 +1,55 @@
package datatrans
package datatrans_test
import (
"bytes"
"io/ioutil"
"net/http"
"os"
"reflect"
"strings"
"testing"
"time"
"github.com/globusdigital/datatrans"
)
func must(t *testing.T, err error) {
t.Helper()
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) {
testReq(t, req)
return &http.Response{
StatusCode: status,
Body: ioutil.NopCloser(strings.NewReader(body)),
Body: rc,
}, nil
}
}
func TestClient_Status(t *testing.T) {
_,_ = MakeClient(
OptionHTTPRequestFn((&http.Client{
Timeout: 30*time.Second,
}).Do),
OptionMerchant{
Server: "https://api.sandbox.datatrans.com",
MerchantID: "32234323242",
Password: "dbce0e6cfc012e475c843c1bbb0ca439a048fe8e",
},
)
c, err := MakeClient(
OptionHTTPRequestFn(mockResponse(200, "{}")),
OptionMerchant{
c, err := datatrans.MakeClient(
datatrans.OptionHTTPRequestFn(mockResponse(t, 200, "testdata/status_response.json", nil)),
datatrans.OptionMerchant{
Server: "http://localhost",
MerchantID: "322342",
Password: "32168",
@ -46,10 +57,87 @@ func TestClient_Status(t *testing.T) {
)
must(t, err)
rs, err := c.Status("3423423423")
must(t, err)
// TODO continue here
t.Logf("%#v", rs)
if rs.TransactionID != "210215103042148501" {
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)
}
}

282
dto.go
View File

@ -1,108 +1,246 @@
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 {
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 {
Currency string `json:"currency"`
RefNo string `json:"refno"`
CardSimple *CardAlias `json:"card,omitempty"`
//autoSettle
Currency string `json:"currency,omitempty"`
RefNo string `json:"refno,omitempty"`
RefNo2 string `json:"refno2,omitempty"`
// TODO add more fields
Card *CardAlias `json:"card,omitempty"`
CustomFields `json:"-"`
}
type RequestSettle struct {
Amount int `json:"amount"`
Currency string `json:"currency"`
RefNo string `json:"refno"`
Amount int `json:"amount,omitempty"`
Currency string `json:"currency,omitempty"`
RefNo string `json:"refno,omitempty"`
RefNo2 string `json:"refno2,omitempty"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
// TODO add more fields
CustomFields `json:"-"`
}
type RequestCredit struct {
Amount int `json:"amount"`
Currency string `json:"currency"`
RefNo string `json:"refno"`
Amount int `json:"amount,omitempty"`
Currency string `json:"currency,omitempty"`
RefNo string `json:"refno,omitempty"`
RefNo2 string `json:"refno2,omitempty"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
// 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"`
CustomFields `json:"-"`
}
type RequestCreditAuthorize struct {
Currency string `json:"currency"`
RefNo string `json:"refno"`
Currency string `json:"currency,omitempty"`
RefNo string `json:"refno,omitempty"`
Card *CardAlias `json:"card,omitempty"`
Amount int `json:"amount"`
Amount int `json:"amount,omitempty"`
AutoSettle bool `json:"autoSettle,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 {
Alias string `json:"alias"`
ExpiryMonth string `json:"expiryMonth"`
ExpiryYear string `json:"expiryYear"`
Alias string `json:"alias,omitempty"`
ExpiryMonth string `json:"expiryMonth,omitempty"`
ExpiryYear string `json:"expiryYear,omitempty"`
CreateAliasCVV bool `json:"createAliasCVV,omitempty"` // only used when initializing a transaction
}
type ResponseStatus struct {
TransactionID string `json:"transactionId"`
Type string `json:"type"`
Status string `json:"status"`
Currency string `json:"currency"`
RefNo string `json:"refno"`
PaymentMethod string `json:"paymentMethod"`
Detail struct {
Init struct {
Expires time.Time `json:"expires"`
} `json:"init"`
Authorize struct {
Amount int `json:"amount"`
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"`
TransactionID string `json:"transactionId,omitempty"`
Type string `json:"type,omitempty"`
Status string `json:"status,omitempty"`
Currency string `json:"currency,omitempty"`
RefNo string `json:"refno,omitempty"`
PaymentMethod string `json:"paymentMethod,omitempty"`
Detail map[string]interface{} `json:"detail,omitempty"`
Customer *Customer `json:"customer,omitempty"`
Card *CardExtended `json:"card,omitempty"`
Language string `json:"language,omitempty"`
History []History `json:"history,omitempty"`
RawJSONBody `json:"raw,omitempty"`
}
type CardExtended struct {
Masked string `json:"masked"`
ExpiryMonth string `json:"expiryMonth"`
ExpiryYear string `json:"expiryYear"`
Info struct {
Brand string `json:"brand"`
Type string `json:"type"`
Usage string `json:"usage"`
Country string `json:"country"`
Issuer string `json:"issuer"`
} `json:"info"`
Alias string `json:"alias,omitempty"`
AliasCVV string `json:"aliasCVV,omitempty"`
Masked string `json:"masked,omitempty"`
ExpiryMonth string `json:"expiryMonth,omitempty"`
ExpiryYear string `json:"expiryYear,omitempty"`
Info *CardExtendedInfo `json:"info,omitempty"`
WalletIndicator string `json:"walletIndicator,omitempty"`
}
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 {
Action string `json:"action"`
Amount int `json:"amount"`
Source string `json:"source"`
Date time.Time `json:"date"`
Success bool `json:"success"`
IP string `json:"ip"`
Action string `json:"action,omitempty"`
Amount int `json:"amount,omitempty"`
Source string `json:"source,omitempty"`
Date time.Time `json:"date,omitempty"`
Success bool `json:"success,omitempty"`
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.
}

View File

@ -7,6 +7,7 @@ type ErrorResponse struct {
ErrorDetail ErrorDetail `json:"error"`
}
// see https://docs.datatrans.ch/docs/error-messages
type ErrorDetail struct {
Code string `json:"code"`
Message string `json:"message"`

2
go.mod
View File

@ -1,5 +1,3 @@
module github.com/globusdigital/datatrans
go 1.15
require github.com/shopspring/decimal v1.2.0

2
go.sum
View File

@ -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
View 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"
}
]
}