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

Fix response fields used by CyberSource tokenization #261

Merged
merged 5 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions gateways/cybersource/cybersource.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ func (client *CybersourceClient) AuthorizeWithContext(ctx context.Context, reque
Header: responseHeader,
}
return &response, nil
// Status 401 - during a cybersource outage, most fields were empty and ID was nil
} else if cybersourceResponse.ID == nil {
}
// Status 401 - during a cybersource outage, most fields were empty and ID was nil
if cybersourceResponse.ID == nil {
return &sleet.AuthorizationResponse{Success: false}, nil
}

Expand Down Expand Up @@ -116,8 +117,8 @@ func (client *CybersourceClient) AuthorizeWithContext(ctx context.Context, reque
response.ExternalTransactionID = cybersourceResponse.ProcessorInformation.TransactionID
response.Metadata = buildResponseMetadata(*cybersourceResponse.ProcessorInformation)
}
if cybersourceResponse.PaymentInformation != nil {
response.CreatedTokens = buildCreatedTokens(*cybersourceResponse.PaymentInformation)
if cybersourceResponse.TokenInformation != nil {
response.CreatedTokens = buildCreatedTokens(*cybersourceResponse.TokenInformation)
}
return response, nil
}
Expand All @@ -129,19 +130,19 @@ func buildResponseMetadata(processorInformation ProcessorInformation) map[string
return metadata
}

func buildCreatedTokens(paymentInformation PaymentInformation) map[sleet.TokenType]string {
func buildCreatedTokens(tokenInformation TokenInformation) map[sleet.TokenType]string {
createdTokens := map[sleet.TokenType]string{}
if paymentInformation.Customer != nil {
createdTokens[sleet.TokenTypeCustomer] = paymentInformation.Customer.ID
if tokenInformation.Customer != nil {
createdTokens[sleet.TokenTypeCustomer] = tokenInformation.Customer.ID
}
if paymentInformation.PaymentInstrument != nil {
createdTokens[sleet.TokenTypePayment] = paymentInformation.PaymentInstrument.ID
if tokenInformation.PaymentInstrument != nil {
createdTokens[sleet.TokenTypePayment] = tokenInformation.PaymentInstrument.ID
}
if paymentInformation.InstrumentIdentifier != nil {
createdTokens[sleet.TokenTypePaymentIdentifier] = paymentInformation.InstrumentIdentifier.ID
if tokenInformation.InstrumentIdentifier != nil {
createdTokens[sleet.TokenTypePaymentIdentifier] = tokenInformation.InstrumentIdentifier.ID
}
if paymentInformation.ShippingAddress != nil {
createdTokens[sleet.TokenTypeShippingAddress] = paymentInformation.ShippingAddress.ID
if tokenInformation.ShippingAddress != nil {
createdTokens[sleet.TokenTypeShippingAddress] = tokenInformation.ShippingAddress.ID
}
if len(createdTokens) == 0 {
return nil
Expand Down Expand Up @@ -282,7 +283,6 @@ func (client *CybersourceClient) sendRequest(ctx context.Context, path string, d
}
}()

fmt.Printf("status %s\n", resp.Status) // debug
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
Expand Down
18 changes: 9 additions & 9 deletions gateways/cybersource/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type Response struct {
ErrorInformation *ErrorInformation `json:"errorInformation,omitempty"`
ClientReferenceInformation *ClientReferenceInformation `json:"clientReferenceInformation,omitempty"`
ProcessorInformation *ProcessorInformation `json:"processorInformation,omitempty"`
PaymentInformation *PaymentInformation `json:"paymentInformation,omitempty"`
TokenInformation *TokenInformation `json:"tokenInformation,omitempty"`
OrderInformation *OrderInformation `json:"orderInformation,omitempty"`
ErrorReason *string `json:"reason,omitempty"`
ErrorMessage *string `json:"message,omitempty"`
Expand Down Expand Up @@ -161,8 +161,12 @@ type ShippingDetails struct {

// PaymentInformation stores Card or TokenizedCard information (but can be extended to other payment types)
type PaymentInformation struct {
Card *CardInformation `json:"card,omitempty"`
TokenizedCard *TokenizedCard `json:"tokenizedCard,omitempty"`
Card *CardInformation `json:"card,omitempty"`
TokenizedCard *TokenizedCard `json:"tokenizedCard,omitempty"`
}

// TokenInformation stores tokens that were created as a side-effect of a transaction
type TokenInformation struct {
Customer *Customer `json:"customer,omitempty"`
PaymentInstrument *PaymentInstrument `json:"paymentInstrument,omitempty"`
InstrumentIdentifier *InstrumentIdentifier `json:"instrumentIdentifier,omitempty"`
Expand Down Expand Up @@ -236,12 +240,8 @@ type AuthorizationOptions struct {
type ProcessingAction string

const (
ProcessingActionDecisionSkip ProcessingAction = "DECISION_SKIP"
ProcessingActionTokenCreate ProcessingAction = "TOKEN_CREATE"
ProcessingActionConsumerAuthentication ProcessingAction = "CONSUMER_AUTHENTICATION"
ProcessingActionValidateConsumerAuthentication ProcessingAction = "VALIDATE_CONSUMER_AUTHENTICATION"
ProcessingActionAlternatePaymentInitiate ProcessingAction = "AP_INITIATE"
ProcessingActionWatchlistScreening ProcessingAction = "WATCHLIST_SCREENING"
ProcessingActionTokenCreate ProcessingAction = "TOKEN_CREATE"
// There are other actions that we don't use
)

// ProcessingActionTokenType defines token types that can be created when using ProcessingActionTokenCreate.
Expand Down
43 changes: 31 additions & 12 deletions integration-tests/cybersource_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package test

import (
"net/http"
"testing"

"github.com/BoltApp/sleet"
Expand All @@ -11,9 +12,8 @@ import (

func TestAuthorizeAndCaptureAndRefund(t *testing.T) {
testCurrency := "USD"
client := cybersource.NewClient(common.Sandbox, getEnv("CYBERSOURCE_ACCOUNT"), getEnv("CYBERSOURCE_API_KEY"), getEnv("CYBERSOURCE_SHARED_SECRET"))
client := getCybersourceClientForTest(t)
authRequest := sleet_testing.BaseAuthorizationRequest()
authRequest.ClientTransactionReference = sPtr("[auth]-CUSTOMER-REFERENCE-CODE") // This will be overridden by the level 3 CustomerReference
authRequest.BillingAddress = &sleet.Address{
StreetAddress1: sPtr("77 Geary St"),
StreetAddress2: sPtr("Floor 4"),
Expand All @@ -25,7 +25,8 @@ func TestAuthorizeAndCaptureAndRefund(t *testing.T) {
Email: sPtr("[email protected]"),
}
authRequest.Level3Data = &sleet.Level3Data{
CustomerReference: "[auth][l3]-CUSTOMER-REFERENCE-CODE",
// ClientTransactionReference will be overridden by the level 3 CustomerReference
CustomerReference: "l3-" + *authRequest.ClientTransactionReference,
TaxAmount: sleet.Amount{Amount: 10, Currency: testCurrency},
DiscountAmount: sleet.Amount{Amount: 0, Currency: testCurrency},
ShippingAmount: sleet.Amount{Amount: 0, Currency: testCurrency},
Expand Down Expand Up @@ -62,7 +63,7 @@ func TestAuthorizeAndCaptureAndRefund(t *testing.T) {
capResp, err := client.Capture(&sleet.CaptureRequest{
Amount: &authRequest.Amount,
TransactionReference: resp.TransactionReference,
ClientTransactionReference: sPtr("[capture]-CUSTOMER-REFERENCE-CODE"),
ClientTransactionReference: sPtr("capture-" + *authRequest.ClientTransactionReference),
})
if err != nil {
t.Errorf("Expected no error: received: %s", err)
Expand All @@ -78,7 +79,7 @@ func TestAuthorizeAndCaptureAndRefund(t *testing.T) {
refundResp, err := client.Refund(&sleet.RefundRequest{
Amount: &authRequest.Amount,
TransactionReference: capResp.TransactionReference,
ClientTransactionReference: sPtr("[refund]-CUSTOMER-REFERENCE-CODE"),
ClientTransactionReference: sPtr("refund-" + *authRequest.ClientTransactionReference),
})
if err != nil {
t.Errorf("Expected no error: received: %s", err)
Expand All @@ -92,9 +93,8 @@ func TestAuthorizeAndCaptureWithTokenCreation(t *testing.T) {
// Not all CyberSource accounts have this feature.
// If this test fails but you are not planning on using tokenization, you can safely ignore the result of this test.
t.Skip("Skipping temporarily. TODO [email protected]")
client := cybersource.NewClient(common.Sandbox, getEnv("CYBERSOURCE_ACCOUNT"), getEnv("CYBERSOURCE_API_KEY"), getEnv("CYBERSOURCE_SHARED_SECRET"))
client := getCybersourceClientForTest(t)
authRequest := sleet_testing.BaseAuthorizationRequest()
authRequest.ClientTransactionReference = sPtr("[auth]-CUSTOMER-REFERENCE-CODE")
authRequest.BillingAddress = &sleet.Address{
StreetAddress1: sPtr("77 Geary St"),
StreetAddress2: sPtr("Floor 4"),
Expand Down Expand Up @@ -145,7 +145,7 @@ func TestAuthorizeAndCaptureWithTokenCreation(t *testing.T) {
capResp, err := client.Capture(&sleet.CaptureRequest{
Amount: &authRequest.Amount,
TransactionReference: resp.TransactionReference,
ClientTransactionReference: sPtr("[capture]-CUSTOMER-REFERENCE-CODE"),
ClientTransactionReference: sPtr("capture-" + *authRequest.ClientTransactionReference),
})
if err != nil {
t.Errorf("Expected no error: received: %s", err)
Expand All @@ -156,9 +156,8 @@ func TestAuthorizeAndCaptureWithTokenCreation(t *testing.T) {
}

func TestVoid(t *testing.T) {
client := cybersource.NewClient(common.Sandbox, getEnv("CYBERSOURCE_ACCOUNT"), getEnv("CYBERSOURCE_API_KEY"), getEnv("CYBERSOURCE_SHARED_SECRET"))
client := getCybersourceClientForTest(t)
authRequest := sleet_testing.BaseAuthorizationRequest()
authRequest.ClientTransactionReference = sPtr("[auth]-CUSTOMER-REFERENCE-CODE")
authRequest.BillingAddress = &sleet.Address{
StreetAddress1: sPtr("77 Geary St"),
StreetAddress2: sPtr("Floor 4"),
Expand All @@ -177,10 +176,14 @@ func TestVoid(t *testing.T) {
t.Errorf("Expected Success: received: %s", resp.ErrorCode)
}

if t.Failed() {
return
}

// void
voidResp, err := client.Void(&sleet.VoidRequest{
TransactionReference: resp.TransactionReference,
ClientTransactionReference: sPtr("[void]-CUSTOMER-REFERENCE-CODE"),
ClientTransactionReference: sPtr("void-" + *authRequest.ClientTransactionReference),
})
if err != nil {
t.Errorf("Expected no error: received: %s", err)
Expand All @@ -191,7 +194,7 @@ func TestVoid(t *testing.T) {
}

func TestMissingReference(t *testing.T) {
client := cybersource.NewClient(common.Sandbox, getEnv("CYBERSOURCE_ACCOUNT"), getEnv("CYBERSOURCE_API_KEY"), getEnv("CYBERSOURCE_SHARED_SECRET"))
client := getCybersourceClientForTest(t)
request := sleet_testing.BaseRefundRequest()
request.TransactionReference = ""
resp, err := client.Refund(request)
Expand All @@ -202,3 +205,19 @@ func TestMissingReference(t *testing.T) {
t.Errorf("Expected no response, received %v", resp)
}
}

func getCybersourceClientForTest(t *testing.T) *cybersource.CybersourceClient {
helper := sleet_testing.NewTestHelper(t)

httpClient := &http.Client{
Transport: helper,
Timeout: common.DefaultTimeout,
}
return cybersource.NewWithHttpClient(
common.Sandbox,
getEnv("CYBERSOURCE_ACCOUNT"),
getEnv("CYBERSOURCE_API_KEY"),
getEnv("CYBERSOURCE_SHARED_SECRET"),
httpClient,
)
}
38 changes: 38 additions & 0 deletions testing/utils.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package testing

import (
"bytes"
"encoding/json"
"encoding/xml"
"io/ioutil"
"net/http"
"reflect"
"testing"

Expand Down Expand Up @@ -51,3 +53,39 @@ func (h TestHelper) XmlUnmarshal(data []byte, destination interface{}) {
h.t.Fatalf("Error unmarshaling json %q \n", err)
}
}

// RoundTrip allows TestHelper to act as a HTTP RoundTripper that logs requests and responses.
// This can be used by overriding the HTTP client used by a PSP client to be the TestHelper instance.
//
// Example:
//
// helper := sleet_testing.NewTestHelper(t)
// httpClient := &http.Client{
// Transport: helper,
// Timeout: common.DefaultTimeout,
// }
func (h TestHelper) RoundTrip(req *http.Request) (*http.Response, error) {
h.t.Helper()

resp, err := http.DefaultTransport.RoundTrip(req)

reqBodyStream, _ := req.GetBody()
defer reqBodyStream.Close()
reqBody, _ := ioutil.ReadAll(reqBodyStream)

respBodyStream := resp.Body
defer respBodyStream.Close()
respBody, _ := ioutil.ReadAll(respBodyStream)
// we need to replace the resp body to be read again by the actual handler
resp.Body = ioutil.NopCloser(bytes.NewBuffer(respBody))

h.t.Logf(
"logTransport HTTP request\n"+
"-> status %s\n"+
"-v request\n"+
string(reqBody)+"\n"+
"-v response\n"+
string(respBody)+"\n\n",
resp.Status)
return resp, err
}