diff --git a/.circleci/config.yml b/.circleci/config.yml index 30a8c091..680a7e21 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,9 +27,8 @@ jobs: - run: name: Integration Test command: | - 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 + cd integration-tests + go test -v - save_cache: key: v1-pkg-cache paths: diff --git a/README.md b/README.md index de25d746..1d619444 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ We support abstracting PsP Webhook notifications into a common interface. | [Authorize.Net](https://developer.authorize.net/api/reference/index.html#payment-transactions) | ✅ | ❌ | | [Braintree](https://www.braintreepayments.com/) | ✅ | ❌ | | [CyberSource](https://developer.cybersource.com/api-reference-assets/index.html#payments) | ✅ | ❌ | +| [Checkout.com](https://api-reference.checkout.com/) | ✅ | ❌ | | [FirstData](https://docs.firstdata.com/org/gateway/docs/api) | ✅ | ❌ | | [NMI](https://secure.networkmerchants.com/gw/merchants/resources/integration/integration_portal.php#methodology) | ✅ | ❌ | | [Orbital](https://developer.jpmorgan.com/products/orbital-api) | ✅ | ❌ | diff --git a/coverage.txt b/coverage.txt deleted file mode 100644 index 141dd639..00000000 --- a/coverage.txt +++ /dev/null @@ -1,14 +0,0 @@ - -mode: atomic -mode: atomic -mode: atomic -mode: atomic -mode: atomic -mode: atomic -mode: atomic -mode: atomic -github.com/BoltApp/sleet/integration-tests/env_util.go:8.32,10.17 2 65 -github.com/BoltApp/sleet/integration-tests/env_util.go:13.2,13.10 1 65 -github.com/BoltApp/sleet/integration-tests/env_util.go:10.17,11.64 1 0 -github.com/BoltApp/sleet/integration-tests/pointers.go:3.29,5.2 1 22 -mode: atomic diff --git a/gateways/checkoutcom/checkoutcom.go b/gateways/checkoutcom/checkoutcom.go index 3c179688..7302cf36 100644 --- a/gateways/checkoutcom/checkoutcom.go +++ b/gateways/checkoutcom/checkoutcom.go @@ -1,8 +1,6 @@ package checkoutcom import ( - "encoding/json" - "fmt" "github.com/BoltApp/sleet" "github.com/BoltApp/sleet/common" "github.com/checkout/checkout-sdk-go" @@ -63,9 +61,6 @@ func (client *CheckoutComClient) Authorize(request *sleet.AuthorizationRequest) 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, diff --git a/gateways/checkoutcom/request_builder.go b/gateways/checkoutcom/request_builder.go index 0852d4b1..f6b54a39 100644 --- a/gateways/checkoutcom/request_builder.go +++ b/gateways/checkoutcom/request_builder.go @@ -7,6 +7,9 @@ import ( "github.com/checkout/checkout-sdk-go/payments" ) +// Cof specifies the transaction type under the Credential-on-File framework +const recurringPaymentType = "Recurring" + func buildChargeParams(authRequest *sleet.AuthorizationRequest) (*payments.Request, error) { var source = payments.CardSource{ Type: "card", @@ -25,7 +28,7 @@ func buildChargeParams(authRequest *sleet.AuthorizationRequest) (*payments.Reque }, } - return &payments.Request{ + request := &payments.Request{ Source: source, Amount: uint64(authRequest.Amount.Amount), Capture: common.BPtr(false), @@ -35,21 +38,63 @@ func buildChargeParams(authRequest *sleet.AuthorizationRequest) (*payments.Reque Email: common.SafeStr(authRequest.BillingAddress.Email), Name: authRequest.CreditCard.FirstName + " " + authRequest.CreditCard.LastName, }, - }, nil + } + + if authRequest.ProcessingInitiator != nil { + initializeProcessingInitiator(authRequest, request, &source) + } + + return request, nil +} + +func initializeProcessingInitiator(authRequest *sleet.AuthorizationRequest, request *payments.Request, source *payments.CardSource) { + // see documentation for instructions on stored credentials, merchant-initiated transactions, and subscriptions: + // https://www.checkout.com/docs/four/payments/accept-payments/use-saved-details/about-stored-card-details + switch *authRequest.ProcessingInitiator { + // initiated by merchant or cardholder, stored card, recurring, first payment + case sleet.ProcessingInitiatorTypeInitialRecurring: + if authRequest.CreditCard.Network == sleet.CreditCardNetworkVisa { + request.PaymentType = recurringPaymentType // visa only + } + request.MerchantInitiated = common.BPtr(false) + // initiated by merchant, stored card, recurring/single transaction, follow-on payment + case sleet.ProcessingInitiatorTypeFollowingRecurring, + sleet.ProcessingInitiatorTypeStoredMerchantInitiated: + request.MerchantInitiated = common.BPtr(true) + source.Stored = common.BPtr(true) + request.PaymentType = recurringPaymentType + request.PreviousPaymentID = *authRequest.PreviousExternalTransactionID + // initiated by cardholder, stored card, single transaction, follow-on payment + case sleet.ProcessingInitiatorTypeStoredCardholderInitiated: + source.Stored = common.BPtr(true) + // initiated by merchant or cardholder, stored card, single transaction, first payment + case sleet.ProcessingInitiatorTypeInitialCardOnFile: + request.MerchantInitiated = common.BPtr(false) + } } func buildRefundParams(refundRequest *sleet.RefundRequest) (*payments.RefundsRequest, error) { - return &payments.RefundsRequest{ + request := &payments.RefundsRequest{ Amount: uint64(refundRequest.Amount.Amount), - Reference: *refundRequest.MerchantOrderReference, - }, nil + } + + if refundRequest.MerchantOrderReference != nil { + request.Reference = *refundRequest.MerchantOrderReference + } + + return request, nil } func buildCaptureParams(captureRequest *sleet.CaptureRequest) (*payments.CapturesRequest, error) { - return &payments.CapturesRequest{ + request := &payments.CapturesRequest{ Amount: uint64(captureRequest.Amount.Amount), - Reference: *captureRequest.MerchantOrderReference, - }, nil + } + + if captureRequest.MerchantOrderReference != nil { + request.Reference = *captureRequest.MerchantOrderReference + } + + return request, nil } func buildVoidParams(voidRequest *sleet.VoidRequest) (*payments.VoidsRequest, error) { diff --git a/gateways/nmi/request_builders.go b/gateways/nmi/request_builders.go index 22a28312..507a8b68 100644 --- a/gateways/nmi/request_builders.go +++ b/gateways/nmi/request_builders.go @@ -2,8 +2,10 @@ package nmi import ( "fmt" - "github.com/BoltApp/sleet" "strconv" + + "github.com/BoltApp/sleet" + "github.com/shopspring/decimal" ) // NMI transaction types @@ -85,7 +87,6 @@ func enableTestMode(testMode bool) *string { } func formatAmount(amountInt int64) *string { - amountString := strconv.FormatInt(amountInt, 10) - formattedAmount := fmt.Sprintf("%s.%s", amountString[:len(amountString)-2], amountString[len(amountString)-2:]) - return &formattedAmount + formattatedAmount := decimal.NewFromInt(amountInt).Div(decimal.NewFromInt(int64(100))).StringFixed(2) + return &formattatedAmount } diff --git a/go.mod b/go.mod index 0ff93297..e74e0799 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/google/go-cmp v0.5.1 github.com/jarcoal/httpmock v1.0.5 github.com/rocketgate/rocketgate-go-sdk v0.0.0-20220106233346-17d98d87a0ff + github.com/shopspring/decimal v1.3.1 // indirect github.com/stripe/stripe-go v70.11.0+incompatible golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 // indirect diff --git a/go.sum b/go.sum index 22b31666..ba805da3 100644 --- a/go.sum +++ b/go.sum @@ -133,6 +133,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/rocketgate/rocketgate-go-sdk v0.0.0-20220106233346-17d98d87a0ff h1:KcXYk9nBcMz35HTctRxrTziAjSJUk1Lid5WVaEMjU6w= github.com/rocketgate/rocketgate-go-sdk v0.0.0-20220106233346-17d98d87a0ff/go.mod h1:5zfoDg5zWlwcB1SlaOLYa2Hm9YJaQlB7fqccBb6tosg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/test.sh b/test.sh deleted file mode 100755 index b074ca57..00000000 --- a/test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -e -echo "" > coverage.txt - -for d in $(go list ./...); do - go test -race -coverprofile=profile.out -covermode=atomic $d - if [ -f profile.out ]; then - cat profile.out >> coverage.txt - rm profile.out - fi -done