Skip to content
This repository was archived by the owner on Apr 29, 2024. It is now read-only.

Commit 4f00b32

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents 9c9ea24 + a0035c7 commit 4f00b32

File tree

10 files changed

+511
-8
lines changed

10 files changed

+511
-8
lines changed

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ jobs:
2020
- run:
2121
name: Unit Test
2222
command: |
23-
go test -v -cover \
23+
go test -v \
2424
-coverpkg $(go list ./... | grep -v integration-tests | grep -v testing | tr '\n' ',' | sed -e 's/.$//') \
2525
-coverprofile=unit_coverage.profile -tags=unit \
2626
$(go list ./... | grep -v integration-tests)
2727
- run:
2828
name: Integration Test
2929
command: |
30-
go test -v -cover \
30+
go test -v \
3131
-coverpkg $(go list ./... | grep -v integration-tests | grep -v testing | tr '\n' ',' | sed -e 's/.$//') \
3232
-coverprofile=integration_coverage.profile ./integration-tests/*.go
3333
- save_cache:

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ $ export CYBERSOURCE_ACCOUNT="YOUR_CYBS_ACCOUNT"
6363
$ export CYBERSOURCE_API_KEY="YOUR_CYBS_KEY"
6464
$ export CYBERSOURCE_SHARED_SECRET="YOUR_CYBS_SECRET"
6565
$ export NMI_SECURITY_KEY="YOUR_NMI_PRIVATE_KEY"
66+
$ export CHECKOUTCOM_TEST_KEY="YOUR_CHECKOUTCOM_PRIVATE_KEY"
6667
```
6768

6869
Then run tests with: `go test ./integration-tests/`

gateways/checkoutcom/checkoutcom.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package checkoutcom
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/BoltApp/sleet"
7+
"github.com/BoltApp/sleet/common"
8+
"github.com/checkout/checkout-sdk-go"
9+
"github.com/checkout/checkout-sdk-go/payments"
10+
"net/http"
11+
"strconv"
12+
)
13+
14+
// checkout.com documentation here: https://www.checkout.com/docs/four/payments/accept-payments, SDK here: https://github.com/checkout/checkout-sdk-go
15+
16+
// checkoutomClient uses API-Key and custom http client to make http calls
17+
type CheckoutComClient struct {
18+
apiKey string
19+
httpClient *http.Client
20+
}
21+
22+
const AcceptedStatusCode = 202
23+
24+
// NewClient creates a CheckoutComClient
25+
// Note: the environment is indicated by the apiKey. See "isSandbox" assignment in checkout.Create.
26+
func NewClient(apiKey string) *CheckoutComClient {
27+
return NewWithHTTPClient(apiKey, common.DefaultHttpClient())
28+
}
29+
30+
// NewWithHTTPClient uses a custom http client for requests
31+
func NewWithHTTPClient(apiKey string, httpClient *http.Client) *CheckoutComClient {
32+
return &CheckoutComClient{
33+
apiKey: apiKey,
34+
httpClient: httpClient,
35+
}
36+
}
37+
38+
func (client *CheckoutComClient) generateCheckoutDCClient() (*payments.Client, error) {
39+
config, err := checkout.Create(client.apiKey, nil)
40+
if err != nil {
41+
return nil, err
42+
}
43+
config.HTTPClient = client.httpClient
44+
45+
return payments.NewClient(*config), nil
46+
}
47+
48+
// Authorize a transaction for specified amount
49+
func (client *CheckoutComClient) Authorize(request *sleet.AuthorizationRequest) (*sleet.AuthorizationResponse, error) {
50+
checkoutComClient, err := client.generateCheckoutDCClient()
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
input, err := buildChargeParams(request)
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
response, err := checkoutComClient.Request(input, nil)
61+
62+
if err != nil {
63+
return &sleet.AuthorizationResponse{Success: false, TransactionReference: "", AvsResult: sleet.AVSResponseUnknown, CvvResult: sleet.CVVResponseUnknown, ErrorCode: err.Error()}, err
64+
}
65+
66+
out, _ := json.Marshal(response.StatusResponse.ResponseBody)
67+
fmt.Printf(string(out))
68+
69+
if *response.Processed.Approved {
70+
return &sleet.AuthorizationResponse{
71+
Success: true,
72+
TransactionReference: response.Processed.ID,
73+
AvsResult: sleet.AVSresponseZipMatchAddressMatch, // TODO: Use translateAvs(AVSResponseCode(response.Processed.Source.AVSCheck)) to enable avs code handling
74+
CvvResult: sleet.CVVResponseMatch, // TODO: use translateCvv(CVVResponseCode(response.Processed.Source.CVVCheck)) to enable cvv code handling
75+
AvsResultRaw: response.Processed.Source.AVSCheck,
76+
CvvResultRaw: response.Processed.Source.CVVCheck,
77+
Response: response.Processed.ResponseCode,
78+
}, nil
79+
} else {
80+
return &sleet.AuthorizationResponse{
81+
Success: false,
82+
TransactionReference: "",
83+
AvsResult: sleet.AVSResponseUnknown,
84+
CvvResult: sleet.CVVResponseUnknown,
85+
Response: response.Processed.ResponseCode,
86+
ErrorCode: response.Processed.ResponseCode,
87+
}, nil
88+
}
89+
}
90+
91+
// Capture an authorized transaction by charge ID
92+
func (client *CheckoutComClient) Capture(request *sleet.CaptureRequest) (*sleet.CaptureResponse, error) {
93+
checkoutComClient, err := client.generateCheckoutDCClient()
94+
if err != nil {
95+
return nil, err
96+
}
97+
98+
input, err := buildCaptureParams(request)
99+
if err != nil {
100+
return nil, err
101+
}
102+
103+
response, err := checkoutComClient.Captures(request.TransactionReference, input, nil)
104+
105+
if err != nil {
106+
return &sleet.CaptureResponse{Success: false, ErrorCode: common.SPtr(err.Error())}, err
107+
}
108+
109+
if response.StatusResponse.StatusCode == AcceptedStatusCode {
110+
return &sleet.CaptureResponse{Success: true, TransactionReference: request.TransactionReference}, nil
111+
} else {
112+
return &sleet.CaptureResponse{
113+
Success: false,
114+
ErrorCode: common.SPtr(strconv.Itoa(response.StatusResponse.StatusCode)),
115+
TransactionReference: request.TransactionReference,
116+
}, nil
117+
}
118+
}
119+
120+
// Refund a captured transaction with amount and charge ID
121+
func (client *CheckoutComClient) Refund(request *sleet.RefundRequest) (*sleet.RefundResponse, error) {
122+
config, err := checkout.Create(client.apiKey, nil)
123+
if err != nil {
124+
return nil, err
125+
}
126+
config.HTTPClient = client.httpClient
127+
128+
checkoutComClient := payments.NewClient(*config)
129+
130+
input, err := buildRefundParams(request)
131+
if err != nil {
132+
return nil, err
133+
}
134+
135+
response, err := checkoutComClient.Refunds(request.TransactionReference, input, nil)
136+
if err != nil {
137+
return &sleet.RefundResponse{Success: false, ErrorCode: common.SPtr(err.Error())}, err
138+
}
139+
140+
if response.StatusResponse.StatusCode == AcceptedStatusCode {
141+
return &sleet.RefundResponse{Success: true, TransactionReference: response.Accepted.Reference}, nil
142+
} else {
143+
return &sleet.RefundResponse{
144+
Success: false,
145+
ErrorCode: common.SPtr(strconv.Itoa(response.StatusResponse.StatusCode)),
146+
TransactionReference: request.TransactionReference,
147+
}, nil
148+
}
149+
}
150+
151+
// Void an authorized transaction with charge ID
152+
func (client *CheckoutComClient) Void(request *sleet.VoidRequest) (*sleet.VoidResponse, error) {
153+
checkoutComClient, err := client.generateCheckoutDCClient()
154+
if err != nil {
155+
return nil, err
156+
}
157+
158+
input, err := buildVoidParams(request)
159+
if err != nil {
160+
return nil, err
161+
}
162+
163+
response, err := checkoutComClient.Voids(request.TransactionReference, input, nil)
164+
165+
if err != nil {
166+
return &sleet.VoidResponse{Success: false, ErrorCode: common.SPtr(err.Error())}, err
167+
}
168+
169+
if response.StatusResponse.StatusCode == AcceptedStatusCode {
170+
return &sleet.VoidResponse{Success: true, TransactionReference: response.Accepted.Reference}, nil
171+
} else {
172+
return &sleet.VoidResponse{
173+
Success: false,
174+
ErrorCode: common.SPtr(strconv.Itoa(response.StatusResponse.StatusCode)),
175+
TransactionReference: request.TransactionReference,
176+
}, nil
177+
}
178+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package checkoutcom
2+
3+
import (
4+
"github.com/BoltApp/sleet"
5+
"github.com/BoltApp/sleet/common"
6+
checkout_com_common "github.com/checkout/checkout-sdk-go/common"
7+
"github.com/checkout/checkout-sdk-go/payments"
8+
)
9+
10+
func buildChargeParams(authRequest *sleet.AuthorizationRequest) (*payments.Request, error) {
11+
var source = payments.CardSource{
12+
Type: "card",
13+
Number: authRequest.CreditCard.Number,
14+
ExpiryMonth: uint64(authRequest.CreditCard.ExpirationMonth),
15+
ExpiryYear: uint64(authRequest.CreditCard.ExpirationYear),
16+
Name: authRequest.CreditCard.FirstName + " " + authRequest.CreditCard.LastName,
17+
CVV: authRequest.CreditCard.CVV,
18+
BillingAddress: &checkout_com_common.Address{
19+
AddressLine1: common.SafeStr(authRequest.BillingAddress.StreetAddress1),
20+
AddressLine2: common.SafeStr(authRequest.BillingAddress.StreetAddress2),
21+
City: common.SafeStr(authRequest.BillingAddress.Locality),
22+
State: common.SafeStr(authRequest.BillingAddress.RegionCode),
23+
ZIP: common.SafeStr(authRequest.BillingAddress.PostalCode),
24+
Country: common.SafeStr(authRequest.BillingAddress.CountryCode),
25+
},
26+
}
27+
28+
return &payments.Request{
29+
Source: source,
30+
Amount: uint64(authRequest.Amount.Amount),
31+
Capture: common.BPtr(false),
32+
Currency: authRequest.Amount.Currency,
33+
Reference: authRequest.MerchantOrderReference,
34+
Customer: &payments.Customer{
35+
Email: common.SafeStr(authRequest.BillingAddress.Email),
36+
Name: authRequest.CreditCard.FirstName + " " + authRequest.CreditCard.LastName,
37+
},
38+
}, nil
39+
}
40+
41+
func buildRefundParams(refundRequest *sleet.RefundRequest) (*payments.RefundsRequest, error) {
42+
return &payments.RefundsRequest{
43+
Amount: uint64(refundRequest.Amount.Amount),
44+
Reference: *refundRequest.MerchantOrderReference,
45+
}, nil
46+
}
47+
48+
func buildCaptureParams(captureRequest *sleet.CaptureRequest) (*payments.CapturesRequest, error) {
49+
return &payments.CapturesRequest{
50+
Amount: uint64(captureRequest.Amount.Amount),
51+
Reference: *captureRequest.MerchantOrderReference,
52+
}, nil
53+
}
54+
55+
func buildVoidParams(voidRequest *sleet.VoidRequest) (*payments.VoidsRequest, error) {
56+
return &payments.VoidsRequest{
57+
Reference: *voidRequest.MerchantOrderReference,
58+
}, nil
59+
}
60+
61+

gateways/checkoutcom/translator.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package checkoutcom
2+
3+
import "github.com/BoltApp/sleet"
4+
5+
var cvvMap = map[CVVResponseCode]sleet.CVVResponse{
6+
CVVResponseMatched: sleet.CVVResponseMatch,
7+
CVVResponseNotConfigured: sleet.CVVResponseError,
8+
CVVResponseCVDMissing: sleet.CVVResponseError,
9+
CVVResponseNotPresent: sleet.CVVResponseRequiredButMissing,
10+
CVVResponseNotValid: sleet.CVVResponseSkipped,
11+
CVVResponseFailed: sleet.CVVResponseError,
12+
}
13+
14+
func translateCvv(code CVVResponseCode) sleet.CVVResponse {
15+
sleetCode, ok := cvvMap[code]
16+
if !ok {
17+
return sleet.CVVResponseUnknown
18+
}
19+
return sleetCode
20+
}
21+
22+
var avsMap = map[AVSResponseCode]sleet.AVSResponse{
23+
AVSResponseStreetMatch: sleet.AVSResponseMatch,
24+
AVSResponseStreetMatchPostalUnverified: sleet.AVSResponseNonUsZipUnverifiedAddressMatch,
25+
AVSResponseStreetAndPostalUnverified: sleet.AVSResponseNonUsZipNoMatchAddressNoMatch,
26+
AVSResponseStreetAndPostalMatch: sleet.AVSResponseNameMatchZipMatchAddressMatch,
27+
AVSResponseAddressMatchError: sleet.AVSResponseError,
28+
AVSResponseStreetAndPostalMatchUK: sleet.AVSResponseNonUsZipMatchAddressMatch,
29+
AVSResponseNotVerifiedOrNotSupported: sleet.AVSResponseUnsupported,
30+
AVSResponseAddressUnverified: sleet.AVSResponseNonUsZipNoMatchAddressNoMatch,
31+
AVSResponseStreetAndPostalMatchMIntl: sleet.AVSResponseNonUsZipMatchAddressMatch,
32+
AVSResponseNoAddressMatch: sleet.AVSResponseNoMatch,
33+
AVSResponseAVSNotRequested: sleet.AVSResponseSkipped,
34+
AVSResponseStreetUnverifiedPostalMatch: sleet.AVSResponseZipMatchAddressUnverified,
35+
AVSResponseAVSUnavailable: sleet.AVSResponseError,
36+
AVSResponseAVSUnsupported: sleet.AVSResponseUnsupported,
37+
AVSResponseMatchNotCapable: sleet.AVSResponseError,
38+
AVSResponseNineDigitPostalMatch: sleet.AVSResponseZip9MatchAddressNoMatch,
39+
AVSResponseStreetAndNineDigitPostalMatch: sleet.AVSResponseZip9MatchAddressMatch,
40+
AVSResponseStreetAndFiveDigitPostalMatch: sleet.AVSResponseZip5MatchAddressMatch,
41+
AVSResponseFiveDigitPostalMatch: sleet.AVSResponseZip5MatchAddressNoMatch,
42+
AVSResponseCardholderNameIncorrectPostalMatch: sleet.AVSResponseNameNoMatchZipMatch,
43+
AVSResponseCardholderNameIncorrectStreetAndPostalMatch: sleet.AVSResponseNameMatchZipMatchAddressMatch,
44+
AVSResponseCardholderNameIncorrectStreetMatch: sleet.AVSResponseNameMatchZipNoMatchAddressMatch,
45+
AVSResponseCardholderNameMatch: sleet.AVSResponseNameMatchZipNoMatchAddressNoMatch,
46+
AVSResponseCardholderNameAndPostalMatch: sleet.AVSResponseNameMatchZipMatchAddressNoMatch,
47+
AVSResponseCardholderNameAndStreetAndPostalMatch: sleet.AVSResponseNameMatchZipMatchAddressMatch,
48+
AVSResponseCardholderNameAndStreetMatch: sleet.AVSResponseNameMatchZipNoMatchAddressMatch,
49+
}
50+
51+
func translateAvs(avs AVSResponseCode) sleet.AVSResponse {
52+
sleetCode, ok := avsMap[avs]
53+
if !ok {
54+
return sleet.AVSResponseUnknown
55+
}
56+
return sleetCode
57+
}

gateways/checkoutcom/types.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package checkoutcom
2+
3+
type CVVResponseCode string
4+
5+
// See https://www.checkout.com/docs/resources/codes/cvv-response-codes
6+
const (
7+
CVVResponseNotPresent CVVResponseCode = "X"
8+
CVVResponseNotConfigured CVVResponseCode = "U"
9+
CVVResponseCVDMissing CVVResponseCode = "P"
10+
CVVResponseMatched CVVResponseCode = "Y"
11+
CVVResponseNotValid CVVResponseCode = "D"
12+
CVVResponseFailed CVVResponseCode = "N"
13+
)
14+
15+
type AVSResponseCode string
16+
17+
// See https://www.checkout.com/docs/resources/codes/avs-codes
18+
const (
19+
AVSResponseStreetMatch AVSResponseCode = "A"
20+
AVSResponseStreetMatchPostalUnverified AVSResponseCode = "B"
21+
AVSResponseStreetAndPostalUnverified AVSResponseCode = "C"
22+
AVSResponseStreetAndPostalMatch AVSResponseCode = "D"
23+
AVSResponseAddressMatchError AVSResponseCode = "E"
24+
AVSResponseStreetAndPostalMatchUK AVSResponseCode = "F"
25+
AVSResponseNotVerifiedOrNotSupported AVSResponseCode = "G"
26+
AVSResponseAddressUnverified AVSResponseCode = "I"
27+
AVSResponseStreetAndPostalMatchMIntl AVSResponseCode = "M"
28+
AVSResponseNoAddressMatch AVSResponseCode = "N"
29+
AVSResponseAVSNotRequested AVSResponseCode = "O"
30+
AVSResponseStreetUnverifiedPostalMatch AVSResponseCode = "P"
31+
AVSResponseAVSUnavailable AVSResponseCode = "R"
32+
AVSResponseAVSUnsupported AVSResponseCode = "S"
33+
AVSResponseMatchNotCapable AVSResponseCode = "U"
34+
AVSResponseNineDigitPostalMatch AVSResponseCode = "W"
35+
AVSResponseStreetAndNineDigitPostalMatch AVSResponseCode = "X"
36+
AVSResponseStreetAndFiveDigitPostalMatch AVSResponseCode = "Y"
37+
AVSResponseFiveDigitPostalMatch AVSResponseCode = "Z"
38+
AVSResponseCardholderNameIncorrectPostalMatch AVSResponseCode = "AE1"
39+
AVSResponseCardholderNameIncorrectStreetAndPostalMatch AVSResponseCode = "AE2"
40+
AVSResponseCardholderNameIncorrectStreetMatch AVSResponseCode = "AE3"
41+
AVSResponseCardholderNameMatch AVSResponseCode = "AE4"
42+
AVSResponseCardholderNameAndPostalMatch AVSResponseCode = "AE5"
43+
AVSResponseCardholderNameAndStreetAndPostalMatch AVSResponseCode = "AE6"
44+
AVSResponseCardholderNameAndStreetMatch AVSResponseCode = "AE7"
45+
)

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/BoltApp/braintree-go v0.26.0
77
github.com/Pallinder/go-randomdata v1.2.0
88
github.com/adyen/adyen-go-api-library/v4 v4.0.0
9+
github.com/checkout/checkout-sdk-go v0.0.19
910
github.com/go-playground/form v3.1.4+incompatible
1011
github.com/go-test/deep v1.0.7
1112
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqC
4040
github.com/adyen/adyen-go-api-library/v4 v4.0.0 h1:zNXA984f8PW/ygW+MwzXIp1A0mAFwdJ5qCAsFxiganU=
4141
github.com/adyen/adyen-go-api-library/v4 v4.0.0/go.mod h1:OpD0jxx7ObTThFu4Xg7PZID/Gr8LB7FaX+jutrJfUYw=
4242
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
43+
github.com/checkout/checkout-sdk-go v0.0.19 h1:T9HVkUCyrpTkC5DGu5ZmmdjuQs1JDpseJvdYxb1NNpk=
44+
github.com/checkout/checkout-sdk-go v0.0.19/go.mod h1:lPJ9QLwgdZZPdT7UJgfSRQSG6ygSVBFbU/xVb6QLI30=
4345
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
4446
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
4547
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -96,6 +98,7 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
9698
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
9799
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
98100
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
101+
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
99102
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
100103
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
101104
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=

0 commit comments

Comments
 (0)