Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NumberFormatException when voiding a transaction made with Track Data #26

Closed
alfredaday opened this issue Oct 10, 2014 · 16 comments
Closed

Comments

@alfredaday
Copy link

Hi,

I tested the use case where a transaction is created using the card's track data (a card present transaction) and then followed up with voiding that transaction. When I void it, it fails with a NumberFormatException. I think I've traced it down to the response code being "1.0" instead of "1", which triggers the exception when trying to convert it to an Integer. It's important to note that if the transaction is created without using the track data, say by setting the card number manually, the void works fine (that's because the response code returns a "1" instead of a "1.0" allowing a successful integer conversion.

Also, note that I'm using a Card Not Present (CNP) Authorize.net account with CIM enabled, if that matters.

Let's start with the test case (substitute your credentials):

package com.thinkreservations.service.gateway.impl;

import java.math.BigDecimal;

import net.authorize.Environment;
import net.authorize.Merchant;
import net.authorize.Transaction;
import net.authorize.data.creditcard.CreditCard;

import org.junit.Test;

import com.thinkreservations.service.exception.restful.CreditCardProcessorFailedException;

public class AuthorizeNetTest {

    @Test
    public void test() {

        final String apiLoginId = "YOUR_API_LOGIN_ID_TO_A_CARD_NOT_PRESENT_ACCOUNT";
        final String transactionKey = "YOUR_TRANSACTION_KEY_TO_A_CARD_NOT_PRESENT_ACCOUNT";

        // Create the Card Present transaction
        String transactionId;
        {
            final Merchant merchant = Merchant.createMerchant(Environment.SANDBOX, apiLoginId, transactionKey);
            merchant.setDeviceType(net.authorize.DeviceType.VIRTUAL_TERMINAL);
            merchant.setMarketType(net.authorize.MarketType.RETAIL);

            final CreditCard creditCard = CreditCard.createCreditCard();
            creditCard.setTrack1("%B370000000000002^CARDUSER/JOHN^1803101000000000020000831000000?");

            final BigDecimal amount = BigDecimal.valueOf(1.99);

            final net.authorize.aim.Transaction transaction = merchant.createAIMTransaction(net.authorize.TransactionType.AUTH_CAPTURE, amount);
            transaction.setCreditCard(creditCard);

            final net.authorize.aim.cardpresent.Result<Transaction> result = (net.authorize.aim.cardpresent.Result<Transaction>) merchant.postTransaction(transaction);

            if (false == result.isApproved()) {
                throw new CreditCardProcessorFailedException("["+result.getResponseCode()+"] "+result.getResponseReasonCodes().get(0)+": "+result.getResponseReasonCodes().get(0).getReasonText());
            }

            transactionId = result.getTransId();
        }

        // Void the transaction
        {
            final Merchant merchant = Merchant.createMerchant(Environment.SANDBOX, apiLoginId, transactionKey);

            final net.authorize.aim.Transaction transaction = merchant.createAIMTransaction(net.authorize.TransactionType.VOID, null);
            transaction.setTransactionId(transactionId);

            final net.authorize.aim.Result<Transaction> result = (net.authorize.aim.Result<Transaction>) merchant.postTransaction(transaction);

            if (false == result.isApproved()) {
                throw new CreditCardProcessorFailedException("["+result.getResponseCode()+"] "+result.getResponseText());
            }
        }
    }

}

Wire log:

DEBUG: org.apache.http.wire -  >> "POST /gateway/transact.dll HTTP/1.1[\r][\n]"
DEBUG: org.apache.http.wire -  >> "Content-Type: application/x-www-form-urlencoded; charset=utf-8[\r][\n]"
DEBUG: org.apache.http.wire -  >> "Content-Length: 408[\r][\n]"
DEBUG: org.apache.http.wire -  >> "Host: test.authorize.net[\r][\n]"
DEBUG: org.apache.http.wire -  >> "Connection: Keep-Alive[\r][\n]"
DEBUG: org.apache.http.wire -  >> "User-Agent: Apache-HttpClient/4.3.1 (java 1.5)[\r][\n]"
DEBUG: org.apache.http.wire -  >> "[\r][\n]"
DEBUG: org.apache.http.wire -  >> "x_tran_key=9Ktc364dmk6T94Fz&x_allow_partial_Auth=FALSE&x_market_type=2&x_card_num=&x_method=CC&x_delim_data=TRUE&x_exp_date=&x_relay_response=FALSE&x_login=3uDJcL26PRN&x_version=3.1&x_device_type=10&x_amount=1.99&x_test_request=FALSE&x_type=AUTH_CAPTURE&x_delim_char=%7C&x_cpversion=1.0&x_response_format=0&x_track2=&x_encap_char=&x_track1=BMASKED**0002%5ECARDUSER%2FJOHN%5EMASKED**0000MASKED**0000"
DEBUG: org.apache.http.wire -  << "HTTP/1.1 200 OK[\r][\n]"
DEBUG: org.apache.http.wire -  << "Cache-Control: private, must-revalidate, max-age=0[\r][\n]"
DEBUG: org.apache.http.wire -  << "Content-Length: 516[\r][\n]"
DEBUG: org.apache.http.wire -  << "Content-Type: text/xml[\r][\n]"
DEBUG: org.apache.http.wire -  << "Expires: Tue, 01 Jan 1980 00:00:00 GMT[\r][\n]"
DEBUG: org.apache.http.wire -  << "Server: Microsoft-IIS/7.5[\r][\n]"
DEBUG: org.apache.http.wire -  << "X-Powered-By: ASP.NET[\r][\n]"
DEBUG: org.apache.http.wire -  << "Date: Fri, 10 Oct 2014 18:34:24 GMT[\r][\n]"
DEBUG: org.apache.http.wire -  << "Connection: close[\r][\n]"
DEBUG: org.apache.http.wire -  << "[\r][\n]"
DEBUG: org.apache.http.wire -  << "<?xml version="1.0" ?><response><ResponseCode>1</ResponseCode><Messages><Message><Code>1</Code><Description><![CDATA[This transaction has been approved.]]></Description></Message></Messages><AuthCode><![CDATA[AXOFVN]]></AuthCode><AVSResultCode>Y</AVSResultCode><CVVResultCode></CVVResultCode><TransID>2222211937</TransID><RefTransID></RefTransID><TransHash>305F339BA37F634C7B4FA0A7DA8894EC</TransHash><TestMode>0</TestMode><AccountNumber>XXXX0002</AccountNumber><AccountType>American Express</AccountType></response>"
DEBUG: org.apache.http.wire -  >> "POST /gateway/transact.dll HTTP/1.1[\r][\n]"
DEBUG: org.apache.http.wire -  >> "Content-Type: application/x-www-form-urlencoded; charset=utf-8[\r][\n]"
DEBUG: org.apache.http.wire -  >> "Content-Length: 229[\r][\n]"
DEBUG: org.apache.http.wire -  >> "Host: test.authorize.net[\r][\n]"
DEBUG: org.apache.http.wire -  >> "Connection: Keep-Alive[\r][\n]"
DEBUG: org.apache.http.wire -  >> "User-Agent: Apache-HttpClient/4.3.1 (java 1.5)[\r][\n]"
DEBUG: org.apache.http.wire -  >> "[\r][\n]"
DEBUG: org.apache.http.wire -  >> "x_tran_key=9Ktc364dmk6T94Fz&x_allow_partial_Auth=FALSE&x_trans_id=2222211937&x_version=3.1&x_amount=0.00&x_delim_data=TRUE&x_type=VOID&x_test_request=FALSE&x_delim_char=%7C&x_relay_response=FALSE&x_encap_char=&x_login=3uDJcL26PRN"
DEBUG: org.apache.http.wire -  << "HTTP/1.1 200 OK[\r][\n]"
DEBUG: org.apache.http.wire -  << "Cache-Control: private, must-revalidate, max-age=0[\r][\n]"
DEBUG: org.apache.http.wire -  << "Content-Length: 128[\r][\n]"
DEBUG: org.apache.http.wire -  << "Content-Type: text/html[\r][\n]"
DEBUG: org.apache.http.wire -  << "Expires: Tue, 01 Jan 1980 00:00:00 GMT[\r][\n]"
DEBUG: org.apache.http.wire -  << "Server: Microsoft-IIS/7.5[\r][\n]"
DEBUG: org.apache.http.wire -  << "X-Powered-By: ASP.NET[\r][\n]"
DEBUG: org.apache.http.wire -  << "Date: Fri, 10 Oct 2014 18:34:24 GMT[\r][\n]"
DEBUG: org.apache.http.wire -  << "Connection: close[\r][\n]"
DEBUG: org.apache.http.wire -  << "[\r][\n]"
DEBUG: org.apache.http.wire -  << "1.0|1|1|This transaction has been approved.||P||2222211937|12B1AA66FCCF02D998C3CD2623973A91||||||||||||XXXX0002|American Express"

Stack trace:

java.lang.NumberFormatException: For input string: "1.0"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
    at java.lang.Integer.parseInt(Integer.java:458)
    at java.lang.Integer.parseInt(Integer.java:499)
    at net.authorize.aim.Result.createResult(Result.java:33)
    at net.authorize.Merchant.postTransaction(Merchant.java:287)
    at com.thinkreservations.service.gateway.impl.AuthorizeNetTest.test(AuthorizeNetTest.java:53)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

If you look at the last line of the wire log, you'll notice that the response code (the first number in the string) is set to "1.0". That causes line 33 in Result.java to throw an exception. Here's the relevant code (the Integer.parseInt method throws the exception):

String responseCodeStr = responseMap.get(ResponseField.RESPONSE_CODE);
result.responseCode = responseCodeStr!=null && !"".equals(responseCodeStr)?
        ResponseCode.findByResponseCode(Integer.parseInt(responseCodeStr)):
            ResponseCode.ERROR;

Thanks for looking into this.

Alfred

@alfredaday
Copy link
Author

Also, I wonder why the response code is "1.0" for a transaction with track data versus "1" for a transaction without track data?

@alfredaday
Copy link
Author

Have you had a chance to look at this? Were you able to reproduce the issue given my test case?

This issue is correctly blocking several of my customers from being able to utilize Card Present transactions in Authorize.net's new Blended Account functionality.

Thanks!
Alfred

@ramittal
Copy link
Contributor

Thanks for reporting the issue Alfred. I was able to repro, thanks for providing the complete test case. While I look for the root cause of this, in the mean time you can try voids (and infact all other apis) using the new controller model.
I have promoted the test case in my branch at: https://github.com/ramittal/sdk-java/blob/master/src/test/java/net/authorize/api/controller/test/CreateTransactionTest.java # testCardPresentTransaction(). Hope it works for you.

@alfredaday
Copy link
Author

Hey Rajeev,

Thanks for the help! I switched to the new controller model and I can now void card present transactions. Is there any documentation I can reference for the new controller model (besides the test cases)?

Thanks!

@brianmc
Copy link
Contributor

brianmc commented Oct 15, 2014

Hi Alfred,

We wanted to get some more feedback and usage before we go all out to promote the new SDK model but we do hope to do that soon. We'd love to hear any feedback you have and thanks again for raising this issue.

Cheers,

Brian

@ramittal
Copy link
Contributor

We are still working on documentation on using the controllers. Basically, all the controllers are defined at in the name space net.authorize.api.controller under the source tree src/java/main and all the respective tests including the sample code is under src/test/java under same namespace. Mostly the pattern is:

  • create request
  • populate necessary data elements, including endpoint and authentication
  • create controller
  • send request to the controller for processing
  • validate and use response
    We will be adding more sample code over time.

@alfredaday
Copy link
Author

Great job guys, the new API is significantly easier to use! I really disliked having to cast the transaction result in the old one.

Here are a few comments I have:

  1. I was somewhat confused on where the error result/message would be given a failed transaction. Looking at the test case provided above, there were technically three ways
  • Via the controller's getResultCode() and getResults()
  • Via the controller's getErrorResponseCode()
  • Via the actual response returned from the controller.

I ended up using the first one to extract the error message. I think in my testing when trying to void a transaction ID that didn't exist, the controller's getErrorResponseCode() returned null. I think there can be some improvement to standardize this.

  1. I was excited about the new API because I thought I would be able to write my tests using mocking instead of stubbing (I had to resort to stubbing to test the old API). That was until I found out I had to instantiate my own CreateTransactionController. Hence, I had to rely on stubbing again.

Is it possible to introduce a service layer? The service layer, say AuthorizeNetService would have methods such as 'createTransaction(CreateTransactionRequest createTransactionRequest)' and that method would be responsible for instantiating the controller and executing the call. That way, we wouldn't deal with a "Controller", we'd deal with a "Service".

  1. I also noticed that when voiding a transaction using the new API, it was about 0.5 seconds slower than using the old API. Any ideas why this is the case?

Thanks guys -- I am really impressed with the level of support I've received from Authorize.net.

Alfred

@alfredaday
Copy link
Author

Also, one other point: Why does the API require the client to separate track 1 data versus track 2 data? It would be much easier if we could pass the entire track data to the API and the API would have the logic to split track 1 versus track 2 data. The way it is now, every client that wants to use the Authorize.net API needs to implement this logic. I had to go out of my way to figure out the format and the best way of parsing through it and I think it'd be great if the API would just do it for me.

@gnongsie
Copy link
Contributor

Hi Alfred,

It's been a long time coming but I think we've come up with an explanation for this exception. We were testing your code and we were able to reproduce this same exception (response code = 1.0). As stated by Rajeev and Brian, this is not something that should happen. It appears that the response itself contains a float at that location or it could have been type-casted during the unmarshalling of the response.

However, specific to your code, we've found the problem. If you would consider the original post, your code is making a card not present (CNP) transaction for voiding the transaction. This is where the problem resides.

Consider the lines:

merchant.setDeviceType(net.authorize.DeviceType.VIRTUAL_TERMINAL);
merchant.setMarketType(net.authorize.MarketType.RETAIL);

Your code is creating a transaction which is a card present transaction (as evidenced by the class of the response). The two lines above specifically creates a card present transaction. So the transaction ID which you extract using the line

transactionId = result.getTransId();

is specific to a card present transaction.

Now, in the second part of your code, I assume you want to void this same transaction, since you are using the same transaction ID.

However, you are creating a CNP transaction and using it to try to void a card present transaction. This happens because you failed to include the lines that created the card present transaction in the first place (the first code segment specified above).

Because of this, the response which you are getting is not a card present response. Kindly note the difference in the class of the responses for the two transactions. The first response is of class net.authorize.aim.cardpresent.Result<Transaction>, whereas the second response is of class net.authorize.aim.Result<Transaction>. Under our tests, this is a huge factor in reproducing this exception.

Now, however, if you included the lines

merchant.setDeviceType(net.authorize.DeviceType.VIRTUAL_TERMINAL);
merchant.setMarketType(net.authorize.MarketType.RETAIL);

in the code to void the transaction, just after you created the merchant (as you did in the first part of the code), this exception does not occur. The response code received and unmarshalled is an integer, which is the expected data type.

Hope this helps,

Gabriel

@dleshem
Copy link

dleshem commented Aug 12, 2016

I just encountered the same issue. Are there any plans to fix this code?

To fix, simply change line 30 of net.authorize.aim.Result from

Integer.parseInt(responseCodeStr)

to

(int)Double.parseDouble(responseCodeStr)

@gnongsie
Copy link
Contributor

Hi Danny, I'm currently working on this. I already have the fix in place and I'm in the process of testing it out. We will keep you updated about this.

Thanks,
Gabriel

@gnongsie
Copy link
Contributor

A pull request for this issue has been sent.

@dleshem
Copy link

dleshem commented Aug 18, 2016

Thanks @gnongsie, waiting for release.

@dleshem
Copy link

dleshem commented Jul 17, 2017

@gnongsie We just had another production issue related to this "1.0" vs. "1" NumberFormatException. I had to spend over an hour debugging this.

Do you still have plans to fix this? It's been almost a year... and the pull request has not been merged!
#39

@gnongsie
Copy link
Contributor

Thanks for reminding me of this. I think the pull request got rejected due to failures in the merge tests. The fix has been merged and will be released in the next version.

I must apologize for the lateness. I was not aware that the fix did not go through.

Thanks again.

@ghost
Copy link

ghost commented Mar 21, 2018

The issue is within Auth.NET's API. They should NOT be responding with 1.0 as a response code. Their documentation does not tell us how to handle this and the code fix to this projuect masks failed credit card transaction failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

6 participants
@alfredaday @brianmc @dleshem @ramittal @gnongsie and others