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

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
rlcooper46 committed Feb 2, 2022
2 parents 9c9ea24 + a0035c7 commit 4f00b32
Show file tree
Hide file tree
Showing 10 changed files with 511 additions and 8 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ jobs:
- run:
name: Unit Test
command: |
go test -v -cover \
go test -v \
-coverpkg $(go list ./... | grep -v integration-tests | grep -v testing | tr '\n' ',' | sed -e 's/.$//') \
-coverprofile=unit_coverage.profile -tags=unit \
$(go list ./... | grep -v integration-tests)
- run:
name: Integration Test
command: |
go test -v -cover \
go test -v \
-coverpkg $(go list ./... | grep -v integration-tests | grep -v testing | tr '\n' ',' | sed -e 's/.$//') \
-coverprofile=integration_coverage.profile ./integration-tests/*.go
- save_cache:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ $ export CYBERSOURCE_ACCOUNT="YOUR_CYBS_ACCOUNT"
$ export CYBERSOURCE_API_KEY="YOUR_CYBS_KEY"
$ export CYBERSOURCE_SHARED_SECRET="YOUR_CYBS_SECRET"
$ export NMI_SECURITY_KEY="YOUR_NMI_PRIVATE_KEY"
$ export CHECKOUTCOM_TEST_KEY="YOUR_CHECKOUTCOM_PRIVATE_KEY"
```

Then run tests with: `go test ./integration-tests/`
Expand Down
178 changes: 178 additions & 0 deletions gateways/checkoutcom/checkoutcom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package checkoutcom

import (
"encoding/json"
"fmt"
"github.com/BoltApp/sleet"
"github.com/BoltApp/sleet/common"
"github.com/checkout/checkout-sdk-go"
"github.com/checkout/checkout-sdk-go/payments"
"net/http"
"strconv"
)

// checkout.com documentation here: https://www.checkout.com/docs/four/payments/accept-payments, SDK here: https://github.com/checkout/checkout-sdk-go

// checkoutomClient uses API-Key and custom http client to make http calls
type CheckoutComClient struct {
apiKey string
httpClient *http.Client
}

const AcceptedStatusCode = 202

// NewClient creates a CheckoutComClient
// Note: the environment is indicated by the apiKey. See "isSandbox" assignment in checkout.Create.
func NewClient(apiKey string) *CheckoutComClient {
return NewWithHTTPClient(apiKey, common.DefaultHttpClient())
}

// NewWithHTTPClient uses a custom http client for requests
func NewWithHTTPClient(apiKey string, httpClient *http.Client) *CheckoutComClient {
return &CheckoutComClient{
apiKey: apiKey,
httpClient: httpClient,
}
}

func (client *CheckoutComClient) generateCheckoutDCClient() (*payments.Client, error) {
config, err := checkout.Create(client.apiKey, nil)
if err != nil {
return nil, err
}
config.HTTPClient = client.httpClient

return payments.NewClient(*config), nil
}

// Authorize a transaction for specified amount
func (client *CheckoutComClient) Authorize(request *sleet.AuthorizationRequest) (*sleet.AuthorizationResponse, error) {
checkoutComClient, err := client.generateCheckoutDCClient()
if err != nil {
return nil, err
}

input, err := buildChargeParams(request)
if err != nil {
return nil, err
}

response, err := checkoutComClient.Request(input, nil)

if err != nil {
return &sleet.AuthorizationResponse{Success: false, TransactionReference: "", AvsResult: sleet.AVSResponseUnknown, CvvResult: sleet.CVVResponseUnknown, ErrorCode: err.Error()}, err
}

out, _ := json.Marshal(response.StatusResponse.ResponseBody)
fmt.Printf(string(out))

if *response.Processed.Approved {
return &sleet.AuthorizationResponse{
Success: true,
TransactionReference: response.Processed.ID,
AvsResult: sleet.AVSresponseZipMatchAddressMatch, // TODO: Use translateAvs(AVSResponseCode(response.Processed.Source.AVSCheck)) to enable avs code handling
CvvResult: sleet.CVVResponseMatch, // TODO: use translateCvv(CVVResponseCode(response.Processed.Source.CVVCheck)) to enable cvv code handling
AvsResultRaw: response.Processed.Source.AVSCheck,
CvvResultRaw: response.Processed.Source.CVVCheck,
Response: response.Processed.ResponseCode,
}, nil
} else {
return &sleet.AuthorizationResponse{
Success: false,
TransactionReference: "",
AvsResult: sleet.AVSResponseUnknown,
CvvResult: sleet.CVVResponseUnknown,
Response: response.Processed.ResponseCode,
ErrorCode: response.Processed.ResponseCode,
}, nil
}
}

// Capture an authorized transaction by charge ID
func (client *CheckoutComClient) Capture(request *sleet.CaptureRequest) (*sleet.CaptureResponse, error) {
checkoutComClient, err := client.generateCheckoutDCClient()
if err != nil {
return nil, err
}

input, err := buildCaptureParams(request)
if err != nil {
return nil, err
}

response, err := checkoutComClient.Captures(request.TransactionReference, input, nil)

if err != nil {
return &sleet.CaptureResponse{Success: false, ErrorCode: common.SPtr(err.Error())}, err
}

if response.StatusResponse.StatusCode == AcceptedStatusCode {
return &sleet.CaptureResponse{Success: true, TransactionReference: request.TransactionReference}, nil
} else {
return &sleet.CaptureResponse{
Success: false,
ErrorCode: common.SPtr(strconv.Itoa(response.StatusResponse.StatusCode)),
TransactionReference: request.TransactionReference,
}, nil
}
}

// Refund a captured transaction with amount and charge ID
func (client *CheckoutComClient) Refund(request *sleet.RefundRequest) (*sleet.RefundResponse, error) {
config, err := checkout.Create(client.apiKey, nil)
if err != nil {
return nil, err
}
config.HTTPClient = client.httpClient

checkoutComClient := payments.NewClient(*config)

input, err := buildRefundParams(request)
if err != nil {
return nil, err
}

response, err := checkoutComClient.Refunds(request.TransactionReference, input, nil)
if err != nil {
return &sleet.RefundResponse{Success: false, ErrorCode: common.SPtr(err.Error())}, err
}

if response.StatusResponse.StatusCode == AcceptedStatusCode {
return &sleet.RefundResponse{Success: true, TransactionReference: response.Accepted.Reference}, nil
} else {
return &sleet.RefundResponse{
Success: false,
ErrorCode: common.SPtr(strconv.Itoa(response.StatusResponse.StatusCode)),
TransactionReference: request.TransactionReference,
}, nil
}
}

// Void an authorized transaction with charge ID
func (client *CheckoutComClient) Void(request *sleet.VoidRequest) (*sleet.VoidResponse, error) {
checkoutComClient, err := client.generateCheckoutDCClient()
if err != nil {
return nil, err
}

input, err := buildVoidParams(request)
if err != nil {
return nil, err
}

response, err := checkoutComClient.Voids(request.TransactionReference, input, nil)

if err != nil {
return &sleet.VoidResponse{Success: false, ErrorCode: common.SPtr(err.Error())}, err
}

if response.StatusResponse.StatusCode == AcceptedStatusCode {
return &sleet.VoidResponse{Success: true, TransactionReference: response.Accepted.Reference}, nil
} else {
return &sleet.VoidResponse{
Success: false,
ErrorCode: common.SPtr(strconv.Itoa(response.StatusResponse.StatusCode)),
TransactionReference: request.TransactionReference,
}, nil
}
}
61 changes: 61 additions & 0 deletions gateways/checkoutcom/request_builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package checkoutcom

import (
"github.com/BoltApp/sleet"
"github.com/BoltApp/sleet/common"
checkout_com_common "github.com/checkout/checkout-sdk-go/common"
"github.com/checkout/checkout-sdk-go/payments"
)

func buildChargeParams(authRequest *sleet.AuthorizationRequest) (*payments.Request, error) {
var source = payments.CardSource{
Type: "card",
Number: authRequest.CreditCard.Number,
ExpiryMonth: uint64(authRequest.CreditCard.ExpirationMonth),
ExpiryYear: uint64(authRequest.CreditCard.ExpirationYear),
Name: authRequest.CreditCard.FirstName + " " + authRequest.CreditCard.LastName,
CVV: authRequest.CreditCard.CVV,
BillingAddress: &checkout_com_common.Address{
AddressLine1: common.SafeStr(authRequest.BillingAddress.StreetAddress1),
AddressLine2: common.SafeStr(authRequest.BillingAddress.StreetAddress2),
City: common.SafeStr(authRequest.BillingAddress.Locality),
State: common.SafeStr(authRequest.BillingAddress.RegionCode),
ZIP: common.SafeStr(authRequest.BillingAddress.PostalCode),
Country: common.SafeStr(authRequest.BillingAddress.CountryCode),
},
}

return &payments.Request{
Source: source,
Amount: uint64(authRequest.Amount.Amount),
Capture: common.BPtr(false),
Currency: authRequest.Amount.Currency,
Reference: authRequest.MerchantOrderReference,
Customer: &payments.Customer{
Email: common.SafeStr(authRequest.BillingAddress.Email),
Name: authRequest.CreditCard.FirstName + " " + authRequest.CreditCard.LastName,
},
}, nil
}

func buildRefundParams(refundRequest *sleet.RefundRequest) (*payments.RefundsRequest, error) {
return &payments.RefundsRequest{
Amount: uint64(refundRequest.Amount.Amount),
Reference: *refundRequest.MerchantOrderReference,
}, nil
}

func buildCaptureParams(captureRequest *sleet.CaptureRequest) (*payments.CapturesRequest, error) {
return &payments.CapturesRequest{
Amount: uint64(captureRequest.Amount.Amount),
Reference: *captureRequest.MerchantOrderReference,
}, nil
}

func buildVoidParams(voidRequest *sleet.VoidRequest) (*payments.VoidsRequest, error) {
return &payments.VoidsRequest{
Reference: *voidRequest.MerchantOrderReference,
}, nil
}


57 changes: 57 additions & 0 deletions gateways/checkoutcom/translator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package checkoutcom

import "github.com/BoltApp/sleet"

var cvvMap = map[CVVResponseCode]sleet.CVVResponse{
CVVResponseMatched: sleet.CVVResponseMatch,
CVVResponseNotConfigured: sleet.CVVResponseError,
CVVResponseCVDMissing: sleet.CVVResponseError,
CVVResponseNotPresent: sleet.CVVResponseRequiredButMissing,
CVVResponseNotValid: sleet.CVVResponseSkipped,
CVVResponseFailed: sleet.CVVResponseError,
}

func translateCvv(code CVVResponseCode) sleet.CVVResponse {
sleetCode, ok := cvvMap[code]
if !ok {
return sleet.CVVResponseUnknown
}
return sleetCode
}

var avsMap = map[AVSResponseCode]sleet.AVSResponse{
AVSResponseStreetMatch: sleet.AVSResponseMatch,
AVSResponseStreetMatchPostalUnverified: sleet.AVSResponseNonUsZipUnverifiedAddressMatch,
AVSResponseStreetAndPostalUnverified: sleet.AVSResponseNonUsZipNoMatchAddressNoMatch,
AVSResponseStreetAndPostalMatch: sleet.AVSResponseNameMatchZipMatchAddressMatch,
AVSResponseAddressMatchError: sleet.AVSResponseError,
AVSResponseStreetAndPostalMatchUK: sleet.AVSResponseNonUsZipMatchAddressMatch,
AVSResponseNotVerifiedOrNotSupported: sleet.AVSResponseUnsupported,
AVSResponseAddressUnverified: sleet.AVSResponseNonUsZipNoMatchAddressNoMatch,
AVSResponseStreetAndPostalMatchMIntl: sleet.AVSResponseNonUsZipMatchAddressMatch,
AVSResponseNoAddressMatch: sleet.AVSResponseNoMatch,
AVSResponseAVSNotRequested: sleet.AVSResponseSkipped,
AVSResponseStreetUnverifiedPostalMatch: sleet.AVSResponseZipMatchAddressUnverified,
AVSResponseAVSUnavailable: sleet.AVSResponseError,
AVSResponseAVSUnsupported: sleet.AVSResponseUnsupported,
AVSResponseMatchNotCapable: sleet.AVSResponseError,
AVSResponseNineDigitPostalMatch: sleet.AVSResponseZip9MatchAddressNoMatch,
AVSResponseStreetAndNineDigitPostalMatch: sleet.AVSResponseZip9MatchAddressMatch,
AVSResponseStreetAndFiveDigitPostalMatch: sleet.AVSResponseZip5MatchAddressMatch,
AVSResponseFiveDigitPostalMatch: sleet.AVSResponseZip5MatchAddressNoMatch,
AVSResponseCardholderNameIncorrectPostalMatch: sleet.AVSResponseNameNoMatchZipMatch,
AVSResponseCardholderNameIncorrectStreetAndPostalMatch: sleet.AVSResponseNameMatchZipMatchAddressMatch,
AVSResponseCardholderNameIncorrectStreetMatch: sleet.AVSResponseNameMatchZipNoMatchAddressMatch,
AVSResponseCardholderNameMatch: sleet.AVSResponseNameMatchZipNoMatchAddressNoMatch,
AVSResponseCardholderNameAndPostalMatch: sleet.AVSResponseNameMatchZipMatchAddressNoMatch,
AVSResponseCardholderNameAndStreetAndPostalMatch: sleet.AVSResponseNameMatchZipMatchAddressMatch,
AVSResponseCardholderNameAndStreetMatch: sleet.AVSResponseNameMatchZipNoMatchAddressMatch,
}

func translateAvs(avs AVSResponseCode) sleet.AVSResponse {
sleetCode, ok := avsMap[avs]
if !ok {
return sleet.AVSResponseUnknown
}
return sleetCode
}
45 changes: 45 additions & 0 deletions gateways/checkoutcom/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package checkoutcom

type CVVResponseCode string

// See https://www.checkout.com/docs/resources/codes/cvv-response-codes
const (
CVVResponseNotPresent CVVResponseCode = "X"
CVVResponseNotConfigured CVVResponseCode = "U"
CVVResponseCVDMissing CVVResponseCode = "P"
CVVResponseMatched CVVResponseCode = "Y"
CVVResponseNotValid CVVResponseCode = "D"
CVVResponseFailed CVVResponseCode = "N"
)

type AVSResponseCode string

// See https://www.checkout.com/docs/resources/codes/avs-codes
const (
AVSResponseStreetMatch AVSResponseCode = "A"
AVSResponseStreetMatchPostalUnverified AVSResponseCode = "B"
AVSResponseStreetAndPostalUnverified AVSResponseCode = "C"
AVSResponseStreetAndPostalMatch AVSResponseCode = "D"
AVSResponseAddressMatchError AVSResponseCode = "E"
AVSResponseStreetAndPostalMatchUK AVSResponseCode = "F"
AVSResponseNotVerifiedOrNotSupported AVSResponseCode = "G"
AVSResponseAddressUnverified AVSResponseCode = "I"
AVSResponseStreetAndPostalMatchMIntl AVSResponseCode = "M"
AVSResponseNoAddressMatch AVSResponseCode = "N"
AVSResponseAVSNotRequested AVSResponseCode = "O"
AVSResponseStreetUnverifiedPostalMatch AVSResponseCode = "P"
AVSResponseAVSUnavailable AVSResponseCode = "R"
AVSResponseAVSUnsupported AVSResponseCode = "S"
AVSResponseMatchNotCapable AVSResponseCode = "U"
AVSResponseNineDigitPostalMatch AVSResponseCode = "W"
AVSResponseStreetAndNineDigitPostalMatch AVSResponseCode = "X"
AVSResponseStreetAndFiveDigitPostalMatch AVSResponseCode = "Y"
AVSResponseFiveDigitPostalMatch AVSResponseCode = "Z"
AVSResponseCardholderNameIncorrectPostalMatch AVSResponseCode = "AE1"
AVSResponseCardholderNameIncorrectStreetAndPostalMatch AVSResponseCode = "AE2"
AVSResponseCardholderNameIncorrectStreetMatch AVSResponseCode = "AE3"
AVSResponseCardholderNameMatch AVSResponseCode = "AE4"
AVSResponseCardholderNameAndPostalMatch AVSResponseCode = "AE5"
AVSResponseCardholderNameAndStreetAndPostalMatch AVSResponseCode = "AE6"
AVSResponseCardholderNameAndStreetMatch AVSResponseCode = "AE7"
)
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/BoltApp/braintree-go v0.26.0
github.com/Pallinder/go-randomdata v1.2.0
github.com/adyen/adyen-go-api-library/v4 v4.0.0
github.com/checkout/checkout-sdk-go v0.0.19
github.com/go-playground/form v3.1.4+incompatible
github.com/go-test/deep v1.0.7
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqC
github.com/adyen/adyen-go-api-library/v4 v4.0.0 h1:zNXA984f8PW/ygW+MwzXIp1A0mAFwdJ5qCAsFxiganU=
github.com/adyen/adyen-go-api-library/v4 v4.0.0/go.mod h1:OpD0jxx7ObTThFu4Xg7PZID/Gr8LB7FaX+jutrJfUYw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/checkout/checkout-sdk-go v0.0.19 h1:T9HVkUCyrpTkC5DGu5ZmmdjuQs1JDpseJvdYxb1NNpk=
github.com/checkout/checkout-sdk-go v0.0.19/go.mod h1:lPJ9QLwgdZZPdT7UJgfSRQSG6ygSVBFbU/xVb6QLI30=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
Expand Down Expand Up @@ -96,6 +98,7 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
Expand Down
Loading

0 comments on commit 4f00b32

Please sign in to comment.