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

Commit

Permalink
Update Authorize Net to support Google Pay Refunds (#257)
Browse files Browse the repository at this point in the history
* Update Authorize Net to support Google Pay Refunds

Update Authorize Net to support Google Pay Refunds. Authorize Net requires last 4 digits of credit card number for all refunds. We don't currently store this for auth.net Google Pay transactions, however we have the ability to fetch this from auth net via api(getTransactionDetails). This PR fetches the last4 from getTransactionDetails and populates the CC number in the refund request

Related bug: https://app.asana.com/0/1200616417881435/1204243318709223/f

Thread: https://boltpay.slack.com/archives/C03U3T92Y8Z/p1680433093558869

* Update authorizenet.go

* Update authorizenet.go

* Update types.go

* Update types.go

* Update types.go

* Fix bugs

* Update types.go

* Update request_builders_test.go

* Update authorizenet.go

* Add unit tests

* Add err

* Why is this not working?

* Add integration test

* Update authorizenet.go

* Update authorizenet.go

* Update authorizenet_test.go

* Update authorizenet_test.go

* Update authorizenet_test.go
  • Loading branch information
anooj-bolt authored Apr 11, 2023
1 parent ac0e98c commit cce88d1
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 15 deletions.
44 changes: 44 additions & 0 deletions gateways/authorizenet/authorizenet.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,19 @@ func (client *AuthorizeNetClient) Refund(request *sleet.RefundRequest) (*sleet.R

// RefundWithContext refunds a captured transaction with amount and captured transaction reference
func (client *AuthorizeNetClient) RefundWithContext(ctx context.Context, request *sleet.RefundRequest) (*sleet.RefundResponse, error) {

if request.Options != nil && request.Options[sleet.GooglePayTokenOption] != nil {
transactionDetailsResponse, err := client.GetTransactionDetails(&sleet.TransactionDetailsRequest{
TransactionReference: request.TransactionReference,
})
if err != nil {
return nil, err
}
creditCardNumber := transactionDetailsResponse.CardNumber
last4 := creditCardNumber[len(creditCardNumber)-4:]
request.Last4 = last4
}

authorizeNetRefundRequest, err := buildRefundRequest(client.merchantName, client.transactionKey, request)
if err != nil {
return nil, err
Expand All @@ -150,6 +163,37 @@ func (client *AuthorizeNetClient) RefundWithContext(ctx context.Context, request
}, nil
}

// GetTransactionDetails Use this function to get detailed information about a specific transaction.
// Used to get the last 4 digits of a card to support Google Pay
func (client *AuthorizeNetClient) GetTransactionDetails(request *sleet.TransactionDetailsRequest) (*sleet.TransactionDetailsResponse, error) {
return client.GetTransactionDetailsWithContext(context.TODO(), request)
}

// GetTransactionDetails Use this function to get detailed information about a specific transaction.
// Used to get the last 4 digits of a card to support Google Pay
func (client *AuthorizeNetClient) GetTransactionDetailsWithContext(ctx context.Context, request *sleet.TransactionDetailsRequest) (*sleet.TransactionDetailsResponse, error) {
authorizeNetTransactionDetailsRequest, err := BuildTransactionDetailsRequest(client.merchantName, client.transactionKey, request)
if err != nil {
return nil, err
}

authorizeNetResponse, _, err := client.sendRequest(ctx, *authorizeNetTransactionDetailsRequest)
if err != nil {
return nil, err
}

if authorizeNetResponse.Messsages.ResultCode != ResultCodeOK {
return &sleet.TransactionDetailsResponse{
ResultCode: string(authorizeNetResponse.Messsages.ResultCode),
}, nil
}

return &sleet.TransactionDetailsResponse{
ResultCode: string(authorizeNetResponse.Messsages.ResultCode),
CardNumber: authorizeNetResponse.Transaction.Payment.CreditCard.CardNumber,
}, nil
}

func (client *AuthorizeNetClient) sendRequest(ctx context.Context, data Request) (*Response, *http.Response, error) {
bodyJSON, err := json.Marshal(data)
if err != nil {
Expand Down
74 changes: 74 additions & 0 deletions gateways/authorizenet/authorizenet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,12 @@ func TestRefund(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

httpmock.RegisterResponder("POST", url, func(req *http.Request) (*http.Response, error) {
transactionDetailsResponseRaw := helper.ReadFile("test_data/transactionDetailsSuccessResponse.json")
resp := httpmock.NewBytesResponse(http.StatusOK, transactionDetailsResponseRaw)
return resp, nil
})

request := sleet_t.BaseRefundRequest()
httpmock.RegisterResponder("POST", url, func(req *http.Request) (*http.Response, error) {
refundResponseRaw := helper.ReadFile("test_data/refundSuccessResponse.json")
Expand Down Expand Up @@ -395,6 +401,74 @@ func TestRefund(t *testing.T) {
})
}

func TestGetTransactionDetails(t *testing.T) {
helper := sleet_t.NewTestHelper(t)
url := "https://apitest.authorize.net/xml/v1/request.api"

t.Run("With Success Response", func(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

request := &sleet.TransactionDetailsRequest{
TransactionReference: "1234569999",
}
httpmock.RegisterResponder("POST", url, func(req *http.Request) (*http.Response, error) {
transactionDetailsResponseRaw := helper.ReadFile("test_data/transactionDetailsSuccessResponse.json")
resp := httpmock.NewBytesResponse(http.StatusOK, transactionDetailsResponseRaw)
return resp, nil
})

want := &sleet.TransactionDetailsResponse{
ResultCode: "Ok",
CardNumber: "XXXX1111",
}

client := NewClient("MerchantName", "Key", common.Sandbox)

got, err := client.GetTransactionDetails(request)

if err != nil {
t.Fatalf("Error thrown after sending request %q", err)
}

if !cmp.Equal(*got, *want, sleet_t.CompareUnexported) {
t.Error("Response body does not match expected")
t.Error(cmp.Diff(*want, *got, sleet_t.CompareUnexported))
}
})

t.Run("With Error Response", func(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

request := &sleet.TransactionDetailsRequest{
TransactionReference: "1234569999",
}
httpmock.RegisterResponder("POST", url, func(req *http.Request) (*http.Response, error) {
transactionDetailsResponseRaw := helper.ReadFile("test_data/transactionDetailsErrorResponse.json")
resp := httpmock.NewBytesResponse(http.StatusOK, transactionDetailsResponseRaw)
return resp, nil
})

want := &sleet.TransactionDetailsResponse{
ResultCode: string(ResultCodeError),
}

client := NewClient("MerchantName", "Key", common.Sandbox)

got, err := client.GetTransactionDetails(request)

if err != nil {
t.Fatalf("Error thrown after sending request %q", err)
}

if !cmp.Equal(*got, *want, sleet_t.CompareUnexported) {
t.Error("Response body does not match expected")
t.Error(cmp.Diff(*want, *got, sleet_t.CompareUnexported))
}
})
}

func TestAlreadyCaptured(t *testing.T) {
helper := sleet_t.NewTestHelper(t)

Expand Down
21 changes: 17 additions & 4 deletions gateways/authorizenet/request_builders.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ func buildAuthRequest(merchantName string, transactionKey string, authRequest *s
}
}

return &Request{CreateTransactionRequest: authorizeRequest}
return &Request{CreateTransactionRequest: &authorizeRequest}
}

func buildVoidRequest(merchantName string, transactionKey string, voidRequest *sleet.VoidRequest) *Request {
return &Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: authentication(merchantName, transactionKey),
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypeVoid,
Expand All @@ -119,7 +119,7 @@ func buildVoidRequest(merchantName string, transactionKey string, voidRequest *s
func buildCaptureRequest(merchantName string, transactionKey string, captureRequest *sleet.CaptureRequest) *Request {
amountStr := sleet.AmountToDecimalString(captureRequest.Amount)
request := &Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: authentication(merchantName, transactionKey),
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypePriorAuthCapture,
Expand All @@ -137,7 +137,7 @@ func buildRefundRequest(merchantName string, transactionKey string, refundReques
) {
amountStr := sleet.AmountToDecimalString(refundRequest.Amount)
request := &Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: authentication(merchantName, transactionKey),
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypeRefund,
Expand Down Expand Up @@ -260,3 +260,16 @@ func buildLineItemsString(authRequest *sleet.AuthorizationRequest) *string {
}
return nil
}

func BuildTransactionDetailsRequest(merchantName string, transactionKey string, transactionDetailsRequest *sleet.TransactionDetailsRequest) (
*Request,
error,
) {
request := &Request{
GetTransactionDetailsRequest: &GetTransactionDetailsRequest{
MerchantAuthentication: authentication(merchantName, transactionKey),
TransID: transactionDetailsRequest.TransactionReference,
},
}
return request, nil
}
20 changes: 10 additions & 10 deletions gateways/authorizenet/request_builders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestBuildAuthRequest(t *testing.T) {
"Basic Auth Request",
base,
&Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: MerchantAuthentication{Name: "MerchantName", TransactionKey: "Key"},
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypeAuthOnly,
Expand Down Expand Up @@ -90,7 +90,7 @@ func TestBuildAuthRequest(t *testing.T) {
Cryptogram: "cryptogram",
},
&Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: MerchantAuthentication{Name: "MerchantName", TransactionKey: "Key"},
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypeAuthOnly,
Expand Down Expand Up @@ -131,7 +131,7 @@ func TestBuildAuthRequest(t *testing.T) {
Cryptogram: "testGooglePayToken",
},
&Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: MerchantAuthentication{Name: "MerchantName", TransactionKey: "Key"},
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypeAuthOnly,
Expand Down Expand Up @@ -163,7 +163,7 @@ func TestBuildAuthRequest(t *testing.T) {
"L2L3 Data",
baseL2L3,
&Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: MerchantAuthentication{Name: "MerchantName", TransactionKey: "Key"},
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypeAuthOnly,
Expand Down Expand Up @@ -218,7 +218,7 @@ func TestBuildAuthRequest(t *testing.T) {
"L2L3 Data Multiple items",
baseL2L3MultipleItems,
&Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: MerchantAuthentication{Name: "MerchantName", TransactionKey: "Key"},
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypeAuthOnly,
Expand Down Expand Up @@ -273,7 +273,7 @@ func TestBuildAuthRequest(t *testing.T) {
"Basic Auth Request with customer IP",
withCustomerIP,
&Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: MerchantAuthentication{Name: "MerchantName", TransactionKey: "Key"},
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypeAuthOnly,
Expand Down Expand Up @@ -316,7 +316,7 @@ func TestBuildAuthRequest(t *testing.T) {
MerchantOrderReference: base.MerchantOrderReference,
},
&Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: MerchantAuthentication{Name: "MerchantName", TransactionKey: "Key"},
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypeAuthOnly,
Expand Down Expand Up @@ -364,7 +364,7 @@ func TestBuildCaptureRequest(t *testing.T) {
"Basic Capture Request",
base,
&Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: MerchantAuthentication{Name: "MerchantName", TransactionKey: "Key"},
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypePriorAuthCapture,
Expand Down Expand Up @@ -398,7 +398,7 @@ func TestBuildVoidRequest(t *testing.T) {
"Basic Void Request",
base,
&Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: MerchantAuthentication{Name: "MerchantName", TransactionKey: "Key"},
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypeVoid,
Expand Down Expand Up @@ -438,7 +438,7 @@ func TestBuildRefundRequest(t *testing.T) {
"Basic Refund Request",
base,
&Request{
CreateTransactionRequest: CreateTransactionRequest{
CreateTransactionRequest: &CreateTransactionRequest{
MerchantAuthentication: MerchantAuthentication{Name: "MerchantName", TransactionKey: "Key"},
TransactionRequest: TransactionRequest{
TransactionType: TransactionTypeRefund,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"messages": {
"resultCode": "Error",
"message": [
{
"code": "E00040",
"text": "The record cannot be found."
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"transaction": {
"transId": "40116993894",
"submitTimeUTC": "2023-03-21T20:01:31.243Z",
"submitTimeLocal": "2023-03-21T13:01:31.243",
"transactionType": "authOnlyTransaction",
"transactionStatus": "settledSuccessfully",
"responseCode": 1,
"responseReasonCode": 1,
"responseReasonDescription": "Approval",
"authCode": "6UBJKU",
"AVSResponse": "Y",
"cardCodeResponse": "P",
"batch": {
"batchId": "13855989",
"settlementTimeUTC": "2023-03-22T03:40:34.663Z",
"settlementTimeLocal": "2023-03-21T20:40:34.663",
"settlementState": "settledSuccessfully"
},
"order": {
"invoiceNumber": "9006052827063014",
"discountAmount": 0,
"taxIsAfterDiscount": false
},
"authAmount": 100.5,
"settleAmount": 100.5,
"taxExempt": false,
"payment": {
"creditCard": {
"cardNumber": "XXXX1111",
"expirationDate": "XXXX",
"cardType": "Visa"
}
},
"customer": {
"email": "[email protected]"
},
"billTo": {
"phoneNumber": "13126470579",
"firstName": "Abhishek",
"lastName": "Tidke",
"address": "21150 N Tatum Blvd Apt 2088",
"city": "Phoenix",
"state": "Arizona",
"zip": "85050",
"country": "US"
},
"shipTo": {
"firstName": "Abhishek",
"lastName": "Tidke",
"address": "21150 N Tatum Blvd Apt 2088",
"city": "Phoenix",
"state": "Arizona",
"zip": "85050",
"country": "US"
},
"recurringBilling": false,
"customerIP": "70.176.143.138",
"product": "Card Not Present",
"marketType": "eCommerce",
"networkTransId": "L49LOS1DVBGDNPKEVZG3NOG",
"authorizationIndicator": "pre"
},
"messages": {
"resultCode": "Ok",
"message": [
{
"code": "I00001",
"text": "Successful."
}
]
}
}

Loading

0 comments on commit cce88d1

Please sign in to comment.