Authorize.net C# Code
UPDATE 2: The final code for this project is available for download on the Free Code page.
UPDATE: The code for this posting was updated 2-19-2008. I removed the x_password reference. It is not listed in the current documentation, and according to Authorize.Net, the request needs to include the API Login ID and the TransactionCode instead. I also changed isTestMode to IsTestMode for consistency. I also confirmed with them that I need to submit the request from an SSL page, so I am still waiting for that before I can test the code.
My company has an account with Authorize.net, and I am in the process of using it to replace our old PayPal system for online Credit Card purchases. We’ve used their Virtual Terminal system for several years, and once long ago I integrated their CC processing with a PHP site.
I guess it has to do with supplying a ubiquitous solution, but I find that their developer integration tools are somewhat lacking. They have several different methods of submitting and processing payments, and I believe we used SIM (Server Integration Method) previously. In this solution, also my first official foray into the world of ASP.NET, we will be using AIM (Advanced Integration Method) which gives us a much finer level of control over the user experience.
There is a developer’s guide and sample code, but I found the sample code to be lacking in anything terribly useful, like a “sample”. It does not really show you how to send the request or handle the response. I assumed correctly that I am not the first person who wanted a better example, so I hit the Google pavement and found this little gem from 2004 at The Jackol’s Den.
This code gave me a great start, but in looking at it I decided I wanted something a little more flexible. This sample shows all the code in a single method which I assume is supposed to be embedded in an ASP.NET page. I would like to have something more reusable, so I restructured the code a bit.
First, there were a number of items hard coded, such as the Authorize.net version and account information. I could see where you might want this information to be stored in a database or configuration file somewhere, so I put the account information a separate class.
public class AuthNetAccountInfo { public string AuthNetVersion {get; set;} public string AuthNetLoginID { get; set; } // public string AuthNetPassword { get; set; } public string AuthNetTransKey { get; set; } public bool IsTestMode { get; set; } }
Second, some of the actual charge information was hard coded while the rest were passed in to the method as parameters. One item that was hard coded was the description, meaning that this approach would only be good for a single item (either that or the description had to be overly generic). Another is the card number and expiration date! Surely anyone would recognize that this could not go to production, but making it ready for prime time would again mean tying this method to a single page. Parameters such as first name, last name, address, and amount are passed in making the signature of the method long and a little unwieldy. So I created a TransactionRequestInfo class to handle all of these details.
public class TransactionRequestInfo { public string FirstName { get; set; } public string LastName { get; set; } public string Address { get; set; } public string City { get; set; } public string State { get; set; } public string Country { get; set; } public string Description { get; set; } private decimal _amount; public decimal ChargeAmount { get { return _amount; } set { _amount = Decimal.Round(value, 2); } } private string _zip; public string Zip { get { return _zip; } set { int res; if (int.TryParse(value, out res)) { if (res > 99999) { throw new ArgumentException("Zip Code Value invalid"); } else { _zip = res.ToString().PadLeft(5, '0'); } } else { throw new ArgumentException("Zip code must be numeric"); } } } private string _securityCode; public string SecurityCode { get { return _securityCode; } set { int res; if (int.TryParse(value, out res)) { if (res > 999) { throw new ArgumentException("Security Code Value invalid"); } else { _securityCode = res.ToString().PadLeft(3, '0'); } } else { throw new ArgumentException("Security code must be numeric"); } } } private string _cardNumber; public string CardNumber { get { return _cardNumber; } set { long res; if (long.TryParse(value, out res)) { _cardNumber = res.ToString(); } else { throw new ArgumentException("Card Number may only contain numbers"); } } } private string _expDate; public string ExpDate { get { return _expDate; } set { int res; if (int.TryParse(value, out res)) { string exp = res.ToString().PadLeft(4, '0'); int month = int.Parse(exp.Substring(0, 2)); int yr = int.Parse(exp.Substring(2, 2)); if (yr > DateTime.Now.Year || (yr == DateTime.Now.Year && month >= DateTime.Now.Month)) { _expDate = month.ToString().PadLeft(2, '0') + "/" + yr.ToString().PadLeft(2, '0'); } else { throw new ArgumentException("Expiration Date already passed"); } } else { throw new ArgumentException("Zip code must be numeric"); } } } }
Finally, the original method returned a Boolean indicating whether or not the charge was successful. It does a lot of work deciphering and interpreting the errors and building good error messages, but it writes the message to a Label control. Again, this code is intended for a single page. I felt that we needed a real response mechanism, so I created another class for that as well.
public class TransactionResponseInfo { public string AuthorizationCode { get; set; } public string TransactionID { get; set; } public string ReturnCode { get; set; } public string Message { get; set; } }
Finally, I made a Transaction class to hold the method and made the method static. As you’ll see, most of the original code remains intact, with the necessary changes to incorporate the new class structure discussed above.
public static class Transaction { public static TransactionResponseInfo ProcessPayment( TransactionRequestInfo transaction, AuthNetAccountInfo account) { TransactionResponseInfo response = new TransactionResponseInfo(); WebClient objRequest = new WebClient(); System.Collections.Specialized.NameValueCollection objInf = new System.Collections.Specialized.NameValueCollection(30); //System.Collections.Specialized.NameValueCollection objRetInf = // new System.Collections.Specialized.NameValueCollection(30); byte[] objRetBytes; string[] objRetVals; string strError; #region Set Request Values objInf.Add("x_version", account.AuthNetVersion); objInf.Add("x_delim_data", "True"); objInf.Add("x_login", account.AuthNetLoginID); // objInf.Add("x_password", account.AuthNetPassword); objInf.Add("x_tran_key", account.AuthNetTransKey); objInf.Add("x_relay_response", "False"); objInf.Add("x_delim_char", ","); objInf.Add("x_encap_char", "|"); // Billing Address objInf.Add("x_first_name", transaction.FirstName); objInf.Add("x_last_name", transaction.LastName); objInf.Add("x_address", transaction.Address); objInf.Add("x_city", transaction.City); objInf.Add("x_state", transaction.State); objInf.Add("x_zip", transaction.Zip); objInf.Add("x_country", transaction.Country); // Card Details objInf.Add("x_card_num", transaction.CardNumber); objInf.Add("x_exp_date", transaction.ExpDate); // Authorization code of the card (CCV) objInf.Add("x_card_code", transaction.SecurityCode); objInf.Add("x_method", "CC"); objInf.Add("x_type", "AUTH_CAPTURE"); objInf.Add("x_amount", transaction.ChargeAmount.ToString()); objInf.Add("x_description", transaction.Description); // Currency setting. Check the guide for other supported currencies objInf.Add("x_currency_code", "USD"); if (account.IsTestMode) { // Pure Test Server objInf.Add("x_test_request", "True"); objRequest.BaseAddress = "https://test.authorize.net/gateway/transact.dll"; } else if (!account.IsTestMode) { // Actual Server objInf.Add("x_test_request", "False"); objRequest.BaseAddress = "https://secure.authorize.net/gateway/transact.dll"; } else { throw new Exception("Transaction Mode Invalid"); } #endregion try { // POST request objRetBytes = objRequest.UploadValues(objRequest.BaseAddress, "POST", objInf); objRetVals = System.Text.Encoding.ASCII.GetString(objRetBytes).Split(",".ToCharArray()); // Process Return Values response.ReturnCode = objRetVals[0].Trim(char.Parse("|")); if (objRetVals[0].Trim(char.Parse("|")) == "1") { // Returned Authorisation Code response.AuthorizationCode = objRetVals[4].Trim(char.Parse("|")); // Returned Transaction ID response.TransactionID = objRetVals[6].Trim(char.Parse("|")); strError = "Transaction completed successfully."; } else { // Error! strError = objRetVals[3].Trim(char.Parse("|")) + " (" + objRetVals[2].Trim(char.Parse("|")) + ")"; if (objRetVals[2].Trim(char.Parse("|")) == "44") { // CCV transaction decline strError += "Our Card Code Verification (CCV) returned " + "the following error: "; switch (objRetVals[38].Trim(char.Parse("|"))) { case "N": strError += "Card Code does not match."; break; case "P": strError += "Card Code was not processed."; break; case "S": strError += "Card Code should be on card but was not indicated."; break; case "U": strError += "Issuer was not certified for Card Code."; break; } } if (objRetVals[2].Trim(char.Parse("|")) == "45") { if (strError.Length > 1) strError += "n"; // AVS transaction decline strError += "Our Address Verification System (AVS) " + "returned the following error: "; switch (objRetVals[5].Trim(char.Parse("|"))) { case "A": strError += " the zip code entered does not match " + "the billing address."; break; case "B": strError += " no information was provided for the AVS check."; break; case "E": strError += " a general error occurred in the AVS system."; break; case "G": strError += " the credit card was issued by a non-US bank."; break; case "N": strError += " neither the entered street address nor zip " + "code matches the billing address."; break; case "P": strError += " AVS is not applicable for this transaction."; break; case "R": strError += " please retry the transaction; the AVS system " + "was unavailable or timed out."; break; case "S": strError += " the AVS service is not supported by your " + "credit card issuer."; break; case "U": strError += " address information is unavailable for the " + "credit card."; break; case "W": strError += " the 9 digit zip code matches, but the " + "street address does not."; break; case "Z": strError += " the zip code matches, but the address does not."; break; } } } } catch (Exception ex) { strError = ex.Message; } response.Message = strError; return response; } }
One of the changes I made was the Test Mode handling. Previously, this was also hard coded, requiring the developer to uncomment certain code to make the service “live”. I felt it would be a good enhancement to make this code driven. This way, the test vs. live status can be switched on and off by the end user (or via configuration). The code uses System.Collections.Specialized.NameValueCollection objects extensively, which I wanted to upgrade to a generic Dictionary<string, string>, but the System.Net.WebClient methods required the old NameValueCollection.
I’m not ready to actually publish this code yet: I haven’t written the code to implement this and it still needs to be tested. If I understand AIM, the request will need to come from a server that has an SSL Certificate installed, which as of yet I do not have. {UPDATE: I rechecked the documentation and it says if you have SSL you “may” use AIM, so perhaps this is optional}
Once I have it all working, I will add it to the Free Code section. In the meantime, feel free to try what is here and let me know your thoughts.
1- Can u tell me the difference of implementation in AIM and SIM
both of these implementation steps seems same.
2- when is SSL Certificate is checked
3- In real scenarion from whr will this transaction key be coming
Hi Sadia,
You would be better off asking Authorize.Net, or reading the Developer’s guide(s) for the answers to #1 and #2. I implemented SIM years ago in PHP, but in order to call them from ASP.NET I had to use AIM. I really can’t tell you the difference.
For #3, I’m not sure which question you mean to ask, either where you get the Transaction Key or where/how you store/access it in your ASP.NET application, so I’ll go ahead and answer both:
1) You acquire the Transaction Key from Authorize.Net:
– log into your account (https://secure.authorize.net)
– click “Settings” under the Account header
– click “API Login ID and Transaction Key”
– follow the instructions from there
2) The AuthNetAccountInfo class has Version, LoginID, AuthNetTransKey, and IsTestMode properties. I store mine in an XML file in the App_Data folder. It doesn’t really matter: You can of course hard code them if you like, or serialize AuthNetAccountInfo into a locally stored binary. I wanted to be able to update these settings directly on the server without needing access to a compiler or updating the application. That and the desire to use Linq To XML made this an easy decision for me.
I hope this answers your questions. I’ll be posting an update and the official release of this code in the coming days.
Thankyou Joel you have been really helpful.
Looking over your code, in the Transaction class, the ProcessPayment function is not exposed at all. I wasn’t sure if that was intended or not. From what I can tell, if you don’t change it to public, there would be no way for the class function to be called to process the payment. If you had something else in mind, please share π This is a great article and I appreciate your response.
Hi Jrodd,
I looked in the live production version and it is public. You are right, it won’t do you any good this way! I’ll edit the above to correct it.
I really need to get the update article put together for this code since it is basically finished now. Glad you found it useful!
I had to tweak some of the logic in the ExpDate property of the TransactionRequestInfo class where the year was converting to an int and not cross referencing say 9 being greater than 2008. I convert it to string, then substring it to just look at the last two digits of the current year versus the whole year. Might want to tweak that a bit too π But other than that, it was quite helpful! Very much, thanks!
To answer the question about AIM vs. SIM. With SIM, you use server-side code (ASP/vbscript, ASP.NET/C#/VB.Net, PHP, etc.) which useses your API Login, Transaction Key, Amount, and a couple other params, then it generates some form fields (which are typically hidden) that contain all of this information in an encrypted format. Within this form, you can add fields for the user to enter any other information *except* their cc number, expiration date or CCV code. This form is submitted directly to Authorize.net’s servers where the user can then enter any remaining information required for the transaction. Since no critical CC data is transmitted between the client’s browser and *your* server, using SIM does not require an SSL certificate on your site.
The AIM method has the user enter all of their information, including the CC data in forms on your site and submit to your server. Then server-side code on your server reads the form fields, generates a specially formatted request and performs an HTTP GET to submit the payment request to Authorize.net’s servers and receive the response in one call. Your server-side code then parses the response to determine if it succeeded or failed and to extract other information like the Authorization Code or Transaction ID. From their your server-side code can store the results in a database, send email notifications, display the results on a page to the user, or whatever your requirements deem. However, because the user’s credit card data in transferred between their browser and your server, you need an SSL certificate to enable the browser to encrypt the data before sending.
SSL certificates are no longer super-expensive, though. You can get them as low as $14.99/year at http://www.godaddy.com. (Hint: if you go straight to GoDaddy, the price is $19.99/year for a standard SSL. But if you do a search on “Buy SSL” in google, it displays a sponsored link advertising them for $14.99 :-))
Ultimately, the decision to use SIM or AIM depends on your requirements. One problem with SIM is that the amount is calculated into the encrypted form field *before* the form is displayed to the user, so if you only have a single page where the user enters their information as well as sets the amount they are paying, using SIM becomes more problematic. For example, I have built several pages for clients that take donations. They are typically non-profits and want to avoid using an SSL certificate and just put up a simple page to enter the amount and submit to SIM. But since SIM requires the amount prior to displaying the form, these pages either end up using AIM or, as in at least one case, the donation form submits to an intermediate page which generates another form with the data and submits that directly to Authorize.net’s SIM form to collect the rest of the user’s information.
Authorize.net also has their new “Simple Payment” service which is supposed to get around this but it still has issues which make it not the right solution for many sites.
I hope this helps clarify things.
Dave Parker
Authorize.net Certified Developer
President/CEO
IT DevWorks, LLC
http://www.itdevworks.com
Great information Dave. Your explanation definitely fits my scenario. The first time I used Authorize.Net was from a non-SSL PHP page that routed the user to an Authorize.Net page for payment. My current project assembles all the information on our page, then submits the request and handles the response on the server.
Thanks for the clarification.
Hello Dave,
This is a great article. Its help me alot.
Now i need to work on refund process.
Please help me for this also.
Thanks & Regards
Ravi vyas
Hi, I implemented this example. Some times it is working properly. some times i am getting the error that “A Valid Amount is required(5)”. Can you explain me reason behind that error?
Rami,
I’m not trying to sound flip, but apparently you are submitting an invalid value for the amount to Authorize.net. Make sure you aren’t sending blanks, or that the amount is greater than $0, or something along those lines.
You should be able to get more information from the AIM guide you can download from within your Authorize.net account.
(TESTMODE) A valid amount is required. (5)
getting this error.
I have turned off my Test account.
hard coded amount=3$.
hello can u give sim asp.net integration code
Wonderful blog about web development and nice information you have shared here. This post is very helpful and i love reading it. Here i like to share about demellows.com that is very helpful website works for web design and development with advanced features to create an attractive site adding a bit of flash in your presentation so that you can warm up your crowd and bring in the WOW factor. http://www.demellows.com/
Hi,
How to pass the value of track1 (x_track1) to the authorize.net ?
What should be the track1 value?
Please help me asap.
regards
Vinoth
Hi,
Can we achieve Itemized order info in SIM ?. I know we can do this in AIM.
Please let me know.
Thanks in advance,
Sathish.
Can’t help but laugh at an “if, else if, else” block on a bool.
That code is 4 1/2 years old, there’s lots of stuff I can’t help but laugh at today! π