#native_company# #native_desc#
#native_cta#

Authorize.net complete script

By Kevin Mcarthur
on March 18, 2002

Version: 1.1

Type: Class

Category: Money

License: Other

Description: interface for authorize.net in a PHP class.
supports versions 3.0 and 3.1 of the authorize.net server software. curl is called as libcurl and exec methods depending on server setup. md5 hash checking is also carried out to verify connection validity. Check out my website for the most updated version at http://www.kevin-mcarthur.com

<?PHP
/***********************************************************************************************

Class ST_Authorize_Net_Transaction
AUTHOR: Kevin McArthur.
DATE: Mar 4, 2002.
URL: http://www.kevin-mcarthur.com
UPDATED: 3/10/02

CHANGELOG: 

I'm not version numbering this library because theres no need to identify the version, if it works dont upgrade.
I guarantee NO backwards compatibility with this version of the library with code written for the old version.
if you have a site designed around an older version of this lib DO NOT INSTALL THIS ONE if you expect it to work
exactly as the other one did.

3/10/02: 
	- Moved comments to external file in zip.
	- Added improved exec security for non libcurl clients (NOT TESTED, NOT ENABLED BY DEFAULT)
		- From phpbuilder. (use variable to enable. If you cant find it you shouldnt be using it.)
			$fp=popen("$curl -e $authnet_referer -D - $authnet_url","w+"); 
			fputs($fp,$data); 
			while(!feof($fp)) {
				$return_string[]=fgets($fp); 
			}
			pclose($fp); 
		- Imporant to note that I copied n pasted this verbatim off phpbuilder (Eric Thelin's) code in the anacom thread and since I use libcurl I havnt tested. Use at own risk.
	- Added configuration warnings array to return values.
	- Converted return array from array[x] based to array['keyname'] based. (WILL MESS UP OLD CODE FOR SURE).
	- Added anonymous usage statistics to script for my use. Collects $ST_site_url as a referer and calls usage.php on my server with no options or data.
		- I am not capturing any client data. This simply calls a cgi on my server whenever the script is run which incriments a counter.
		- NO CLIENT DATA IS LOGGED - Its opensource how could I. you can see whats being passed in cleartext.
		- Furthermore if you're totally against helping me determine my userbase I have provided a variable to allow you to disable this call.
		
SUPPORTED AUTHORIZE.NET VERSIONS: 3.0, 3.1

Credit goes out to Michael Reynolds for his PostAuthNet.php function was the original basis for
this class file. Check out his version at http://michael.spinweb.net/articles.php?ID=7

Q: If Mike already made a php library for authorize.net why did you write this one?
A: Mikes version works fine for a lot of people, however, does not work as a class and doesnt
provide a lot of the extra data that this class returns. I would suggest if you are writing a
simple site and not an ecommerce application that you may want to use his version as the output
does not require as much interpretation as the return from this class. This version also supports
libcurl whereas mikes' version uses an exec call to curl. (This script supports both methods)

IMPORTANT INSTALLATION NOTES:
It is strongly recommended that you compile php with the --with-curl configure option. However
I realise that this is not always an option on shared servers so I have provided a variable to
allow you to use a binary of curl. 

Curl can be found at: http://curl.haxx.se/

USAGE:

SEE AUTHORIZE.NET DOCS FOR VALUES:

$transaction = new ST_Authorize_Net_Transaction();
$transaction->setLoginInfo(
	[Your authorize.net Login], 
	[Your authorize.net password {optional for most transactions}]
	);
$transaction->setTransactionInfo(
	[Version {3.0 or 3.1}],
	[Transaction Type],
	[Transaction amount X.XX],
	[Test mode {Optional} either "true" or dont define it.],
	[Transaction Id {optional for most}],
	[Authcode {optional for most}]
	);
	*note: when using authcode and not trans id you must provide a placeholder like ,"",authcode... or the function will fail.
$transaction->setCreditCardInfo(
	[Card Number],
 	[Expiry Date],
	[First Name],
	[Last Name],
	[Company Name],
	[Address],
	[City],
	[State],
	[Zip],
	[Country],
	[Phone],
	[Email],
	[CVV2 Code {N/A in version 3.0}]
	);
$transaction->setInternalInfo(
	[Description],
	[Invoice Number],
	[Customer ID {optional for most}]
	);
	$result=$transaction->GetResponse();
	
	
	you can now parse $result.
	
	$result is an array with subarrays that is output like this:
	
	$result[0] = 1:0 if transaction was a success and md5's match.
	$result[1] = 1:0 if fraud is suspected. (md5 mismatch)
	$result[2] = array of warnings. (cvv2,avs etc) *not to be shown to the client, for admin purposes.
	$result[3] = array of decline reasons.  Not the same as [5][3] (includes avs/cvv2 errors that [5][3] will not contain
	$result[4] = string md5 match string (generated locally from returned anet values)
	$result[5] = array (exploded) full data from auth.net (remember arrays start at 0 and the docs start at 1).
	
	*Note a nice echo routine is:
	
	foreach($result as $key => $value) {
        if(is_array($result[$key])) {
                foreach($result[$key] as $key2 => $value2) {
                        if($value2 != "") {
                                echo "[". $key ."][". $key2 ."] = ". $value2 ."<BR>";
                        }
                }
        } else {
                echo "[". $key ."] = ". $value ."<BR>";
        }
	}
	This will output all the values returned by the $result variable. You could also use var_dump if you wanted.
	
	Copyright: This file is licenced as freeware however, may not be redistributed or modified in any way without my permission.
			   The only source for this file SHOULD BE my website. If you downloaded this file somewhere else then Kevin-mcarthur.com
			   please let me know. I distribute these files free because they bring advertising and clients to my portfolio. This is 
			   what I get in exchange for days n days of programming and bug testing. If you link directly to this file from your website
			   you are cheating me of my visitors and I will not take kindly to this. If you wish to make a change to this file for everyone
			   to use let me know. I'm very receptive to users who wish to contribute.
			   
	Warranty: I provide no warranty whatsoever for the function or fraud detection abilities of this script. Offically,
			  this script is a alpha script and doesnt work or do anything. I make NO WARRANTIES. If you decide to use this on your
			  multi-billion-dollar system and its exploited dont come whining to me because I told you, IT DOESNT WORK.
			  I have not done extensive checking on this script through every eventuality and cannot be exected to be bug-free.
			  This file is designed to help you write your own library and not just cut-n-paste this one. I am being clear here. I am
			  not responsible for how you use this script what so ever. And for gods sake dont use this on any mission-critical systems
			  where an outage would cost lost buisness.
	
	/rant
	
	*EXAMPLE CALL FILE WITH BLANKS FILLED IN:
	
	require_once('./includes/class.st_authorize_net_transaction.php');
	$transaction = new ST_Authorize_Net_Transaction();
	$transaction->setLoginInfo("somelogin");
	$transaction->setTransactionInfo("3.1","AUTH_CAPTURE","1.00");
	$transaction->setCreditCardInfo("4222222222222", "0802", "joe", "blow", "", "123 fakeville", "new york", "NY", "10018", "US", "123-234-5678", "[email protected]", "1234");
	$transaction->setInternalInfo("some description", "12345");
	$result=$transaction->GetResponse();
	foreach($result as $key => $value) {
	    if(is_array($result[$key])) {
	        foreach($result[$key] as $key2 => $value2) {
	            if($value2 != "") {
	                echo "[". $key ."][". $key2 ."] = ". $value2 ."<BR>";
	            }
	        }
	    } else {
	        echo "[". $key ."] = ". $value ."<BR>";
	    }
	}
	
	Note*: 422222222222 is a number designed to generate errors on the authorize.net system. They also provide ones that will not cause these errors.

***********************************************************************************************/

$curl = "/usr/local/bin/curl"; //needed if php is not compiled with libcurl.
$authnet_url = "https://secure.authorize.net/gateway/transact.dll";
$authnet_referer = "http://your.properly.configured.referrer.url";
$secret_code = "your ADC private secret code thing.";
$ST_site_url = "http://www.yoursite.com"; // Base dotcom not page calling script... main portal page etc. Collected by statistics routine.

//MODIFY AFTER HERE AT YOUR OWN RISK. YOU DO NOT HAVE LICENCE TO REDISTRIBUTE THIS FILE CHANGED WITHOUT MY EXPLICIT WRITTEN PERMISSION.

$ST_allow_stats = 1; //OVERLY ASKING FOR PERMISSION TO COLLECT ANONYMOUS USAGE STATISTICS. PLEASE DO NOT DISABLE.
class ST_Authorize_Net_Transaction {
	var $ST_login, $ST_password, $ST_auth_code, $ST_trans_id; 
	var $ST_anet_version, $ST_test_request; 
	var $ST_transaction_type; 
	var $ST_billed_amount, $ST_card_number, $ST_cvv2_code, $ST_exp_date;
	var $ST_first_name, $ST_last_name, $ST_company_name;
	var $ST_address, $ST_city, $ST_state, $ST_zip, $ST_country, $ST_phone, $ST_email;
	var $ST_customer_id, $ST_invoice_number, $ST_description;
	var $ST_ship_company_name, $ST_ship_first_name, $ST_ship_last_name;
	var $ST_ship_address, $ST_Ship_city, $ST_ship_state, $ST_ship_zip, $ST_ship_country;
	
	//Optional for most types.
	function setLoginInfo($v_login, $v_password = null) {
		$this->ST_login = $v_login;
		if($v_password !== null) {
			$this->ST_password = $v_password;
		}
	}
	
	function setTransactionInfo($v_version, $v_type, $v_billed, $v_test_request = null, $v_trans_id = null, $v_authcode = null) {
		$this->ST_anet_version = $v_version;
		$this->ST_transaction_type = $v_type;
		$this->ST_billed_amount = $v_billed;
		if($v_test_request !== null) {
			if(strtolower($v_test_request) == "true") {
				$this->ST_test_request = strtoupper($v_test_request);
			}
		}
		if($v_trans_id !== null) {
			$this->ST_trans_id = $v_trans_id;
		}
		if($v_authcode !== null) {
			$this->ST_auth_code = $v_authcode;
		}
	}
	
	function setCreditCardInfo($v_cardno, $v_date, $v_firstname, $v_lastname, $v_coname, $v_address, $v_city, $v_state, $v_zip, $v_country, $v_phone, $v_email, $v_cvv2 = null) {
		$this->ST_card_number = $v_cardno;
		$this->ST_exp_date = $v_date;
		$this->ST_first_name = $v_firstname;
		$this->ST_last_name = $v_lastname;
		$this->ST_company_name = $v_coname;
		$this->ST_address = $v_address;
		$this->ST_city = $v_city;
		$this->ST_state = $v_state;
		$this->ST_zip = $v_zip;
		$this->ST_country = $v_country;
		$this->ST_phone = $v_phone;
		$this->ST_email = $v_email;
		if ($v_cvv2 !== null) {
			$this->ST_cvv2_code = $v_cvv2;
		}
	}
	
	function setInternalInfo($v_desc, $v_invoiceno, $v_custid = null) {
		$this->ST_description = $v_desc;
		$this->ST_invoice_number = $v_invoiceno;
		if($v_custid !== null) {
			$this->ST_customer_id = $v_custid;
		}
	}
	
	//Optional shipping info. This call is not needed really.
	function setShippingInfo($v_coname, $v_firstname, $v_lastname, $v_address, $v_city, $v_state, $v_zip, $v_country) {
		$this->ST_ship_first_name = $v_firstname;
		$this->ST_ship_last_name = $v_lastname;
		$this->ST_ship_company_name = $v_coname;
		$this->ST_ship_address = $v_address;
		$this->ST_ship_city = $v_city;
		$this->ST_ship_state = $v_state;
		$this->ST_ship_zip = $v_zip;
		$this->ST_ship_country = $v_country;
	}
	
	function GetResponse() {
		global $curl, $authnet_url, $authnet_referer, $secret_code, $ST_allow_stats, $ST_site_url;
		$data  = "x_ADC_Delim_Data=TRUE&";
		$data .= "x_ADC_URL=FALSE&";
		if($this->ST_test_request !== null) {
			$data .= "x_Test_Request=$this->ST_test_request&";
		}
		$data .= "x_Login=$this->ST_login&";
		if($this->ST_password !== null) {
			$data .= "x_Password=$this->ST_password&";
		}
		$data .= "x_Version=$this->ST_anet_version&";
		if($this->ST_auth_code !== null) {
			$data .= "x_Auth_Code=$this->ST_auth_code&";
		}
		$data .= "x_trans_id=$this->ST_trans_id&";
		$data .= "x_Type=$this->ST_transaction_type&";
		$data .= "x_Amount=$this->ST_billed_amount&";
		$data .= "x_Method=CC&";
		$data .= "x_Card_Num=$this->ST_card_number&";
		if ($this->ST_anet_version == "3.1") {
			if($this->ST_cvv2_code !== null) {
				$data .= "x_Card_Code=$this->ST_cvv2_code&";
			}
		}
		$data .= "x_Exp_Date=$this->ST_exp_date&";
		$data .= "x_First_Name=$this->ST_first_name&";
		$data .= "x_Last_Name=$this->ST_last_name&";
		$data .= "x_Company=$this->ST_company_name&";
		$data .= "x_Address=$this->ST_address&";
		$data .= "x_City=$this->ST_city&";
		$data .= "x_State=$this->ST_state&";
		$data .= "x_Zip=$this->ST_zip&";
		$data .= "x_Country=$this->ST_country&";
		$data .= "x_Phone=$this->ST_phone&";
		$data .= "x_Email=$this->ST_email&";
		if ($this->ST_customer_id !== null) {
			$data .= "x_Cust_ID=$account_id&";
		}
		$data .= "x_Invoice_Num=$this->ST_invoice_number&";
		$data .= "x_Description=$this->ST_description&";
		if($this->ST_ship_first_name !== null && $this->ST_ship_last_name !== null && $this->ST_ship_company_name !== null && $this->ST_ship_first_name !== null && $this->ST_ship_address !== null && $this->ST_ship_city !== null && $this->ST_ship_state !== null && $this->ST_ship_zip !== null && $this->ST_ship_country !== null) {
			$data .= "x_Ship_To_First_Name=$this->ST_ship_first_name&";
			$data .= "x_Ship_To_Last_Name=$this->ST_ship_last_name&";
			$data .= "x_Ship_To_Company=$this->ST_ship_company_name&";
			$data .= "x_Ship_To_Address=$this->ST_ship_addres&";
			$data .= "x_Ship_To_City=$this->ST_ship_city&";
			$data .= "x_Ship_To_State=$this->ST_ship_state&";
			$data .= "x_Ship_To_Zip=$this->ST_ship_zip&";
			$data .= "x_Ship_To_Country=$this->ST_ship_country&";
		}
		$data = ereg_replace(" ", "+", $data);
		$exec_special_curl = 0; //Change to 1 to enable more secure curl exec. NOT TESTED!!!!!.
		if(in_array("curl", get_loaded_extensions())) {
			$curlsession = curl_init($authnet_url);
			curl_setopt($curlsession, CURLOPT_POST, 1);
			curl_setopt($curlsession, CURLOPT_POSTFIELDS, $data);
			curl_setopt($curlsession, CURLOPT_REFERER, $authnet_referer);
			curl_setopt($curlsession, CURLOPT_RETURNTRANSFER, 1);
			$return_string = curl_exec($curlsession);
			curl_close($curlsession);
			if($ST_allow_stats == 1) {
				$curlsession2 = curl_init("http://www.kevin-mcarthur.com/usage.php");
				curl_setopt($curlsession2, CURLOPT_REFERER, $ST_site_url);
				curl_exec($curlsession2);
				curl_close($curlsession2);
			}
		} else {
			if($exec_special_curl) {
				//Following if case is EXPERIMENTAL... I HAVE NOT TESTED THIS _AT ALL_
				//This is from a post to the anacom thread on phpbuilder. By Eric Thelin.
				//method uses STDIN to pass the data to the curl library such that any ps etc command would just show curl being called and not the data being passed.
				$fp=popen("$curl -e $authnet_referer -D - $authnet_url","w+"); 
				fputs($fp,$data); 
				while(!feof($fp)) {
					$return_string[]=fgets($fp); 
				}
				pclose($fp);
				//End Experimental Code.
				if($ST_allow_stats == 1) {
					//not needed to be secure as it contains no useful information
					$execcmd = $curl ." -e ". $ST_site_url ." http://www.kevin-mcarthur.com/usage.php";
					exec($execcmd);
				}
			} else {
				$execcmd = $curl ." -e ". $authnet_referer ." -d "". $data ."" ". $authnet_url;
				exec($execcmd, $return_string);
				if($ST_allow_stats == 1) {
					$execcmd = $curl ." -e ". $ST_site_url ." http://www.kevin-mcarthur.com/usage.php";
					exec($execcmd);
				}
			}
		}
		if($return_string == "" || !isset($return_string) || $return_string === null) {
			$transaction_declined[] = "We are unable to contact our credit card processor right now, please retry in a few minutes";
			$retval[0] = 0;
			$retval[1] = 0;
			$retval[2] = null;
			$retval[3] = $transaction_declined; //string array reasons
			$retval[4] = null;
			$retval[5] = $return_string;
			return $retval;
			die();
		}
		$auth_status = explode(",", $return_string);
		$transaction_fraudulent = 0;
		$transaction_successful = 0;
		$md5string = $secret_code . $this->ST_login . $auth_status[6] . $this->ST_billed_amount;
		$md5match = strtoupper(md5($md5string));
		if ($auth_status[37] == $md5match) {
			$transaction_valid = 1;
		} else {
			$transaction_valid = 0;
		}
		if($auth_status[0] == "1") {
			if($transaction_valid == 1) {
				if($this->ST_anet_version == "3.1") {
					if($this->ST_cvv2_code !== null) {
						if($auth_status[38] == "P" || $auth_status[38] == "U") {
							$transaction_warnings[] = "CVV2: Not processed or not available for this transaction. CVV2 code: ". $this->ST_cvv2_code;
						}
					}
				}
				switch($auth_status[5]) {
					//A,W,Z are ones that are not rejected by the avs by default that i feel should raise a warning but not decline the transaction.
					case 'A':
						$transaction_warnings[] = "AVS: Address (Street) matches, ZIP does not.";
						break;
					case 'W':
						$transaction_warnings[] = "AVS: Street Address: No Match -- All 9 Digits of Zip: Match.";
						break;
					case 'Z':
						$transaction_warnings[] = "AVS: Street Address: No Match -- First 5 Digits of Zip: Match.";
						break;
					case 'P':
						$transaction_warnings[] = "AVS: Not processed or not available for this transaction.";
						break;
					//You can add more codes here if you want to allow auth.net to process riskier transactions, THIS CASE ONLY APPLYS TO APPROVED TRANSACTIONS.
					default:
						break;
				}
				$transaction_successful = 1;
			} else {
				$contact_phone = '123-345-5678';
				$transaction_successful = 0;
				$transaction_declined[] = "MD5 mismatch indicates transaction tampering. Your IP has logged for investigation.";
				$transaction_declined[] = "Call ". $contact_phone ." if you saw this message to explain how you generated this error.";
				$configuration_warnings[] = "You most likely triggered this error due to a misconfiguration of the adc secret code setting. However it could also be a fraudulent transaction attempt.";
				$configuration_warnings[] = "You NEED to charge back this users credit card!!!! Do this through the auth.net control pannel. FUNDS MAY HAVE BEEN CAPTURED!";
				$transaction_fraudulent = 1;
				//ADD IP LOGGING FUNCTION CALL ETC HERE IF YOU WISH TO LOG.
				//ADD CUSTOM HANDLER TO LET YOU KNOW YOU NEED TO CHARGE THE CARD BACK.
				//TODO: ADD charge back routine if type is auth_capture.
			}
		} else if ($auth_status[0] == "2") {
			$transaction_successful = 0;
			if($this->ST_anet_version == "3.1") {
				if($this->ST_cvv2_code !== null) {
					if($auth_status[38] != "M" && $auth_status[38] != "P" && $auth_status[38] != "U" && $auth_status[38] != " ") {
						$transaction_declined[] = "CVV2: code mismatch.";
					}
				} else {
					if($auth_status[38] == "S") {
						$transaction_declined[] = "CVV2: code exists on card but was not submited";
					}
				}
			}
			switch($auth_status[5]) {
				//See authorize.net documentation for what each error is precisely
				case 'A':
					$transaction_declined[] = "AVS: address mismatch.";
					break;
				case 'B':
					$transaction_declined[] = "AVS: insufficient address detail to complete avs check.";
					break;
				case 'E':
					$transaction_declined[] = "AVS: general error, try again in 120 seconds.";
					break;
				case 'G':
					$transaction_declined[] = "AVS: non us card. Authorize.net only supports US issued cards.";
					break;
				case 'N':
					$transaction_declined[] = "AVS: address mismatch.";
					break;
				case 'R':
					$transaction_declined[] = "AVS: general error, try again in 120 seconds.";
					break;
				case 'S':
					$transaction_declined[] = "AVS: avs not supported by card issuing bank.";
					break;
				case 'U':
					$transaction_declined[] = "AVS: avs not supported by card issuing bank.";
					break;
				case 'W':
					$transaction_declined[] = "AVS: address mismatch.";
					break;
				case 'Z':
					$transaction_declined[] = "AVS: address mismatch.";
					break;
				default:
					break;
			}
		} else if ($auth_status[0] == "3") {
			$transaction_successful = 0;
			$transaction_declined[] = "Authorize.net: Error:  ". $auth_status[3] .". Retry in 120 seconds.";
		}
		//Prepare the associatave array for return
		$retval['ST_TRANSACTION_SUCCESSFUL'] = $transaction_successful; //1:0 if transaction was a success and md5's match.
		$retval['ST_TRANSACTION_FRAUDULENT'] = $transaction_fraudulent; //1:0 if fraud is suspected. (md5 mismatch)
		$retval['ST_TRANSACTION_WARNINGS'] = $transaction_warnings; //array of warnings. (cvv2,avs etc)
		$retval['ST_TRANSACTION_DECLINED'] = $transaction_declined; //array of decline reasons.  Not the same as ['ST_TRANSACTION_DATA'][3]
		$retval['ST_CONFIGURATION_WARNINGS'] = $configuration_warnings; //I added this array in the latest version to help you detect common configuration problems.
		$retval['ST_MD5_MATCH_STRING'] = $md5match; //md5 match string (generated locally)
		$retval['ST_TRANSACTION_DATA'] = $auth_status; //full data from auth.net (ARRAY)
		return $retval; 
	}
}
?>