From b6995397287b86430b4329573e9c1b9136f777bc Mon Sep 17 00:00:00 2001 From: Winona Schroeer-Smith Date: Thu, 24 Aug 2023 20:02:56 -0700 Subject: [PATCH 1/5] Fix response fields to use the correct field names --- gateways/cybersource/cybersource.go | 22 +++++++++++----------- gateways/cybersource/types.go | 18 +++++++++--------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/gateways/cybersource/cybersource.go b/gateways/cybersource/cybersource.go index ba86f03d..9525f15d 100644 --- a/gateways/cybersource/cybersource.go +++ b/gateways/cybersource/cybersource.go @@ -116,8 +116,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 } @@ -129,19 +129,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 diff --git a/gateways/cybersource/types.go b/gateways/cybersource/types.go index ae35e1be..8b52435a 100644 --- a/gateways/cybersource/types.go +++ b/gateways/cybersource/types.go @@ -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"` @@ -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"` @@ -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. From ca2bc023430fbc3df34fb41b46b94e3b724cfcb2 Mon Sep 17 00:00:00 2001 From: Winona Schroeer-Smith Date: Fri, 25 Aug 2023 00:28:43 -0700 Subject: [PATCH 2/5] debug test --- gateways/cybersource/cybersource.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gateways/cybersource/cybersource.go b/gateways/cybersource/cybersource.go index 9525f15d..c2cb85e9 100644 --- a/gateways/cybersource/cybersource.go +++ b/gateways/cybersource/cybersource.go @@ -205,6 +205,10 @@ func (client *CybersourceClient) VoidWithContext(ctx context.Context, request *s } voidPath := authPath + request.TransactionReference + "/voids" cybersourceResponse, _, err := client.sendRequest(ctx, voidPath, cybersourceVoidRequest) + if cybersourceResponse != nil { + b, _ := json.Marshal(cybersourceResponse) + fmt.Printf("resp %s\n", b) // debug + } if err != nil { return nil, err } From 787e7e75dcadff8de58d57bf4fadf4a824fac520 Mon Sep 17 00:00:00 2001 From: Winona Schroeer-Smith Date: Fri, 25 Aug 2023 00:31:14 -0700 Subject: [PATCH 3/5] remove debug test --- gateways/cybersource/cybersource.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gateways/cybersource/cybersource.go b/gateways/cybersource/cybersource.go index c2cb85e9..9525f15d 100644 --- a/gateways/cybersource/cybersource.go +++ b/gateways/cybersource/cybersource.go @@ -205,10 +205,6 @@ func (client *CybersourceClient) VoidWithContext(ctx context.Context, request *s } voidPath := authPath + request.TransactionReference + "/voids" cybersourceResponse, _, err := client.sendRequest(ctx, voidPath, cybersourceVoidRequest) - if cybersourceResponse != nil { - b, _ := json.Marshal(cybersourceResponse) - fmt.Printf("resp %s\n", b) // debug - } if err != nil { return nil, err } From 37217bc7d495e911db81b86a06423991a97ceb97 Mon Sep 17 00:00:00 2001 From: Winona Schroeer-Smith Date: Fri, 25 Aug 2023 00:42:16 -0700 Subject: [PATCH 4/5] extra debug --- gateways/cybersource/cybersource.go | 4 ++++ integration-tests/cybersource_test.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/gateways/cybersource/cybersource.go b/gateways/cybersource/cybersource.go index 9525f15d..c2cb85e9 100644 --- a/gateways/cybersource/cybersource.go +++ b/gateways/cybersource/cybersource.go @@ -205,6 +205,10 @@ func (client *CybersourceClient) VoidWithContext(ctx context.Context, request *s } voidPath := authPath + request.TransactionReference + "/voids" cybersourceResponse, _, err := client.sendRequest(ctx, voidPath, cybersourceVoidRequest) + if cybersourceResponse != nil { + b, _ := json.Marshal(cybersourceResponse) + fmt.Printf("resp %s\n", b) // debug + } if err != nil { return nil, err } diff --git a/integration-tests/cybersource_test.go b/integration-tests/cybersource_test.go index f125b432..9890d4d0 100644 --- a/integration-tests/cybersource_test.go +++ b/integration-tests/cybersource_test.go @@ -177,6 +177,10 @@ 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, From 0da37eaa65dd527b350d544970baf57f81514b2e Mon Sep 17 00:00:00 2001 From: Winona Schroeer-Smith Date: Fri, 25 Aug 2023 02:19:29 -0700 Subject: [PATCH 5/5] overkill debugging --- gateways/cybersource/cybersource.go | 10 +++---- integration-tests/cybersource_test.go | 39 ++++++++++++++++++--------- testing/utils.go | 38 ++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/gateways/cybersource/cybersource.go b/gateways/cybersource/cybersource.go index c2cb85e9..3f67b668 100644 --- a/gateways/cybersource/cybersource.go +++ b/gateways/cybersource/cybersource.go @@ -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 } @@ -205,10 +206,6 @@ func (client *CybersourceClient) VoidWithContext(ctx context.Context, request *s } voidPath := authPath + request.TransactionReference + "/voids" cybersourceResponse, _, err := client.sendRequest(ctx, voidPath, cybersourceVoidRequest) - if cybersourceResponse != nil { - b, _ := json.Marshal(cybersourceResponse) - fmt.Printf("resp %s\n", b) // debug - } if err != nil { return nil, err } @@ -286,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 diff --git a/integration-tests/cybersource_test.go b/integration-tests/cybersource_test.go index 9890d4d0..ca30137f 100644 --- a/integration-tests/cybersource_test.go +++ b/integration-tests/cybersource_test.go @@ -1,6 +1,7 @@ package test import ( + "net/http" "testing" "github.com/BoltApp/sleet" @@ -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"), @@ -25,7 +25,8 @@ func TestAuthorizeAndCaptureAndRefund(t *testing.T) { Email: sPtr("test@bolt.com"), } 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}, @@ -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) @@ -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) @@ -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 winona@bolt.com") - 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"), @@ -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) @@ -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"), @@ -184,7 +183,7 @@ func TestVoid(t *testing.T) { // 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) @@ -195,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) @@ -206,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, + ) +} diff --git a/testing/utils.go b/testing/utils.go index 38b31074..183ca87f 100644 --- a/testing/utils.go +++ b/testing/utils.go @@ -1,9 +1,11 @@ package testing import ( + "bytes" "encoding/json" "encoding/xml" "io/ioutil" + "net/http" "reflect" "testing" @@ -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 +}