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

Commit

Permalink
Fix response fields used by CyberSource tokenization (#261)
Browse files Browse the repository at this point in the history
  • Loading branch information
Wolfizen authored Aug 25, 2023
1 parent d12187b commit 4c384fe
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 35 deletions.
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("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},
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 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"),
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
}

0 comments on commit 4c384fe

Please sign in to comment.