Skip to content

Instantly share code, notes, and snippets.

@mtwentyman
Created April 1, 2011 20:00
Show Gist options
  • Save mtwentyman/898740 to your computer and use it in GitHub Desktop.
Save mtwentyman/898740 to your computer and use it in GitHub Desktop.
Library to handle transactions with authorize.net
<?php
class AuthorizeNetCimException extends Exception {}
class AuthorizeNetCim {
const PAYMENT_METHOD_CC = 'credit_card';
const PAYMENT_METHOD_BANK = 'bank_account';
const PAYMENT_METHOD_CHECK = 'check';
const TEST_VALIDATION_MODE = 'testMode';
const LIVE_VALIDATION_MODE = 'liveMode';
private $test = true;
private $success = false;
private $error = true;
public $rawResponse = 'No response.';
public $xmldom;
private $login, $transkey, $apiURL, $xml, $requestMethod, $response, $resultCode, $code, $text,
$profileId, $validationMode, $paymentProfileId, $results,
$validationDirectResponse, $validationDirectResponseResults, $validationDirectResponseList, $validationDirectResponseListResults,
$params, $items;
public function __construct($login, $transkey, $apiURL, $validationMode = self::TEST_VALIDATION_MODE) {
$this->login = $login;
$this->transkey = $transkey;
$this->validationMode = $validationMode;
$this->apiURL = $apiURL;
if (!trim($this->login) || !trim($this->transkey)) {
throw new AuthorizeNetCimException('You have not passed in your Authnet login credentials.');
}
$this->resetParams();
$this->clearItems();
$this->xmldom = new DOMDocument();
}
public function __toString() {
if (!$this->params) return (string) $this;
$output = '<table summary="Authnet Results" id="authnet">' . "\n";
$output .= '<tr>' . "\n\t\t" . '<th colspan="2"><b>Outgoing Parameters</b></th>' . "\n" . '</tr>' . "\n";
foreach ($this->params as $key => $value) {
$output .= "\t" . '<tr>' . "\n\t\t" . '<td><b>' . $key . '</b></td>';
$output .= '<td>' . $value . '</td>' . "\n" . '</tr>' . "\n";
}
$output .= '</table>' . "\n";
return $output;
}
public function resetParams() {
$this->params = array();
$this->params['customerType'] = 'individual';
$this->params['validationMode'] = $this->validationMode;
$this->params['taxExempt'] = 'false';
$this->params['recurringBilling'] = 'false';
}
public function clearItems() {
$this->items = array();
}
private function process($retries = 3) {
$count = 0;
$this->writeLog($this->xml, 'Request: Method='.$this->requestMethod);
while ($count < $retries) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->apiURL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->xml);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$this->rawResponse = curl_exec($ch);
$this->writeLog($this->rawResponse,'Response');
// check to see if an error occurred
if (curl_errno($ch)) {
$this->success = false;
$this->error = true;
$this->code = "";
$this->text = 'Unable to contact payment processor. Please try again later.';
// log the error request
$this->writeLog('Unable to contact payment processor: ' . curl_error($ch), 'Connection Error');
break;
}
$this->parseResults();
if ($this->resultCode === 'Ok') {
$this->success = true;
$this->error = false;
break;
} else {
$this->success = false;
$this->error = true;
break;
}
$count++;
}
curl_close($ch);
}
private function wrapRequest($name, $contents) {
return '<?xml version="1.0" encoding="utf-8"?>'.
"\n<{$name}Request xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\">" .
$this->merchantAuth() .
$contents .
"\n</{$name}Request>\n";
}
private function merchantAuth() {
return '<merchantAuthentication><name>' . $this->login .
'</name><transactionKey>' . $this->transkey .
'</transactionKey></merchantAuthentication>';
}
private function wrapXml($name, $contents) {
return "<$name>".$this->wrapCDATA($name,$contents)."</$name>\n";
}
private function wrapCDATA($name,$contents)
{
if ( ($name == 'description') || ($name == 'company') ) {
return "<![CDATA[$contents]]>";
}
else {
return $contents;
}
}
private function wrapParam($name, $param_key=null) {
if($param_key==null) { $param_key = $name; }
return isset($this->params[$param_key]) ? $this->wrapXml($name, $this->params[$param_key]) : '';
}
private function wrapParams() {
$args = func_get_args();
// TODO: implement ability to prefix for params like taxAmount
// $prefix = null;
// if(gettype($args[count($args)-1]) == 'array') {
// $last = array_pop($args)['prefix'];
// $prefix = $last['prefix'] || null;
// }
$xml = '';
foreach($args as $name) {
$xml .= $this->wrapParam($name);
}
return $xml;
}
private function addressXml() {
return $this->wrapXml('address',
$this->wrapParams('firstName', 'lastName', 'company', 'address',
'city', 'state', 'zip', 'country', 'phoneNumber', 'faxNumber', 'customerAddressId')
);
}
private function billToXml() {
return $this->wrapXml('billTo', $this->wrapParams('firstName', 'lastName',
'company', 'address', 'city', 'state', 'zip', 'country', 'phoneNumber', 'faxNumber'));
}
private function paymentXml($type=self::PAYMENT_METHOD_CC) {
if ($type === self::PAYMENT_METHOD_CC) {
$xml = $this->wrapXml('creditCard', $this->wrapParams('cardNumber', 'expirationDate','cardCode'));
} else if ($type === self::PAYMENT_METHOD_BANK) {
$xml = $this->bankAccountXml();
}
// TODO: raise error on invalid type
return $xml;
}
private function bankAccountXml() {
return $this->wrapXml('bankAccount', $this->wrapParams('accountType', 'routingNumber', 'accountNumber', 'nameOnAccount',
'echeckType', 'bankName')
);
}
private function shipToListXml() {
return isset($this->params['shipAddress']) ?
$this->wrapXml('shipToList',
$this->wrapParams('shipFirstName', 'shipLastName', 'shipCompany', 'shipAddress',
'shipCity', 'shipState', 'shipZip', 'shipCountry', 'shipPhoneNumber', 'shipFaxNumber'
)
) : '';
}
private function writeLog($contents='', $header='') {
// Log the response from authorize.net
$timestamp = date('r');
$logfile = sfConfig::get('sf_root_dir') . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'authorizenet.log';
file_put_contents($logfile, "\n<!--- AuthorizeNetCim: {$header} Timestamp: {$timestamp} --->\n{$contents}\n" , FILE_APPEND);
}
/**
* Create a customer and profile at once
* @param string $type should match /^(credit_card|bank_account)$/
* @example
* <code>
* $instance = new self($login, $transkey, (bool)$live, $validation);
* $instance->setParameters(
* 'firstName' => 'First', // required
* 'lastName' => 'Last', // required
* 'email' => 'alias@email.com', // required (if merchantCustomerId is null)
* 'merchantCustomerId' => 1, // required (if email is null)
* 'customerType' => 'individual' // required, defaults to individual automatically
* // may also be 'business'
* 'description' => 'anything', // optional
* 'company' => 'Company', // for payment profile, optional
* 'address' => 'Main St.', // for payment profile, required
* 'city' => 'New York', // for payment profile, required
* 'state' => 'NY', // for payment profile, required
* 'zip' => '10009', // for payment profile, required
* 'country' => 'United States', // for payment profile, required
* 'phoneNumber' => '8005551212', // for payment profile, required
* 'faxNumber' => '8005551212', // for payment profile, required
* 'cardNumber' => '4111111111111111', // for payment profile, required & valid test number
* 'expirationDate' => '2010-10', // for payment profile, required
* 'cardCode' => '123' // for payment profile, required
* );
* $instance->createCustomerProfile();
* </code>
* @return void
*/
public function createCustomerProfile($type=self::PAYMENT_METHOD_CC) {
$this->requestMethod = __FUNCTION__;
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParam('refId') .
$this->wrapXml('profile',
$this->wrapParams('merchantCustomerId', 'description', 'email') .
$this->wrapXml('paymentProfiles',
$this->wrapParams('customerType') .
$this->billToXml() .
$this->wrapXml('payment', $this->paymentXml($type))
) .
$this->shipToListXml()
) .
$this->wrapParams('validationMode')
);
$this->process();
}
public function createCustomerPaymentProfile($type=self::PAYMENT_METHOD_CC) {
$this->requestMethod = __FUNCTION__;
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParams('refId', 'customerProfileId') .
$this->wrapXml('paymentProfile',
$this->wrapParams('customerType') .
$this->billToXml() .
$this->wrapXml('payment', $this->paymentXml($type))
) .
$this->wrapParams('validationMode')
);
$this->process();
}
public function createCustomerShippingAddress() {
$this->requestMethod = __FUNCTION__;
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParams('refId','customerProfileId') . $this->addressXml()
);
$this->process();
}
/**
* How to charge an existing payment profile.
* @param string $type should match /^profileTrans(AuthCapture|CaptureOnly|AuthOnly)$/
* @example
* <code>
* $instance = new self($login, $transkey, (bool)$live, $validation);
* $instance->setParameters(
* 'customerProfileId' => 1, // required
* 'customerPaymentProfileId' => 1, // required
* 'refId' => 111, // optional
* 'amount' => '12.34', // required, must be properly formatted string with decimal
* 'cardCode' => '123', // required
* 'taxExempt' => 'false' // required, false is default
* );
* // repeat as needed to itemize order record
* $instance->setLineItem(50, 'sock puppet', 'nice sock puppet description', 2, '11.00');
* // (item_id, name, description, quantity, price_per_item);
* $instance->createCustomerProfileTransaction($type);
* </code>
* @return void
*/
public function createCustomerProfileTransaction($type='profileTransAuthCapture') {
$this->requestMethod = __FUNCTION__;
$types = array('profileTransAuthCapture', 'profileTransCaptureOnly', 'profileTransAuthOnly');
if (!in_array($type, $types)) {
throw new AuthorizeNetCimException('createCustomerProfileTransaction() parameter must be "profileTransAuthCapture", "profileTransCaptureOnly", "profileTransAuthOnly", or empty');
}
$this->xml = '<?xml version="1.0" encoding="utf-8"?>
<createCustomerProfileTransactionRequest xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
' . $this->merchantAuth() . $this->wrapParams('refId') .'
<transaction>
<' . $type . '>'. $this->wrapParams('amount');
if (isset($this->params['taxAmount'])) {
$this->xml .= '
<tax>
<amount>'. $this->params['taxAmount'] .'</amount>
<name>'. $this->params['taxName'] .'</name>
<description>'. $this->params['taxDescription'] .'</description>
</tax>';
}
if (isset($this->params['shipAmount'])) {
$this->xml .= '
<shipping>
<amount>'. $this->params['shipAmount'] .'</amount>
<name>'. $this->params['shipName'] .'</name>
<description>'. $this->params['shipDescription'] .'</description>
</shipping>';
}
if (isset($this->params['dutyAmount'])) {
$this->xml .= '
<duty>
<amount>'. $this->params['dutyAmount'] .'</amount>
<name>'. $this->params['dutyName'] .'</name>
<description>'. $this->params['dutyDescription'] .'</description>
</duty>';
}
$this->xml .= $this->getLineItems();
$this->xml .= $this->wrapParams('customerProfileId','customerPaymentProfileId','customerShippingAddressId');
if (isset($this->params['invoiceNumber'])) {
$this->xml .= $this->wrapXml('order',
$this->wrapParams('invoiceNumber','description','purchaseOrderNumber'));
}
$this->xml .= $this->wrapParams('taxExempt','recurringBilling','cardCode');
$this->xml .= '
</' . $type . '>
</transaction>
</createCustomerProfileTransactionRequest>';
$this->process();
}
public function deleteCustomerProfile($customerProfileId=null) {
$this->requestMethod = __FUNCTION__;
if($customerProfileId != null) {
$this->setParameters(array('customerProfileId' => $customerProfileId));
}
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParams('refId', 'customerProfileId')
);
$this->process();
}
public function deleteCustomerPaymentProfile($customerProfileId=null, $customerPaymentProfileId=null) {
$this->requestMethod = __FUNCTION__;
if($customerPaymentProfileId != null) {
$this->setParameters(array(
'customerProfileId' => $customerProfileId,
'customerPaymentProfileId' => $customerPaymentProfileId
));
}
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParams('refId', 'customerProfileId', 'customerPaymentProfileId')
);
$this->process();
}
public function deleteCustomerShippingAddress() {
$this->requestMethod = __FUNCTION__;
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParams('refId', 'customerProfileId', 'customerAddressId')
);
$this->process();
}
public function getCustomerProfile() {
$this->requestMethod = __FUNCTION__;
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParams('customerProfileId')
);
$this->process();
}
public function getCustomerPaymentProfile() {
$this->requestMethod = __FUNCTION__;
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParams('customerProfileId', 'customerPaymentProfileId')
);
$this->process();
}
public function getCustomerShippingAddress() {
$this->requestMethod = __FUNCTION__;
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParams('refId', 'customerProfileId', 'customerAddressId')
);
$this->process();
}
public function updateCustomerProfile() {
$this->requestMethod = __FUNCTION__;
$this->xml = $this->wrapRequest(__FUNCTION__, $this->wrapParam('refId') .
$this->wrapXml('profile', $this->wrapParams('merchantCustomerId',
'description', 'email', 'customerProfileId')
)
);
$this->process();
}
public function updateCustomerPaymentProfile($type=self::PAYMENT_METHOD_CC) {
$this->requestMethod = __FUNCTION__;
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParams('refId', 'customerProfileId') .
$this->wrapXml('paymentProfile',
$this->billToXml() .
$this->wrapXml('payment', $this->paymentXml($type)) .
$this->wrapParams('customerPaymentProfileId')
) .
$this->wrapParams('validationMode')
);
$this->process();
}
public function updateCustomerShippingAddress() {
$this->requestMethod = __FUNCTION__;
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParams('refId','customerProfileId') . $this->addressXml()
);
$this->process();
}
public function validateCustomerPaymentProfile() {
$this->requestMethod = __FUNCTION__;
$this->xml = $this->wrapRequest(__FUNCTION__,
$this->wrapParams('refId', 'customerProfileId',
'customerPaymentProfileId', 'customerAddressId', 'validationMode')
);
$this->process();
}
private function getLineItems() {
$tempXml = '';
foreach ($this->items as $item) {
foreach ($item as $key => $value) {
$tempXml .= $this->wrapXml($key, $value);
}
}
if(!empty($tempXml)) {
$tempXml = $this->wrapXml('lineItems', $tempXml);
}
return $tempXml;
}
public function setLineItem($itemId, $name, $description, $quantity, $unitprice, $taxable='false') {
$this->items[] = array(
'itemId' => $itemId,
'name' => $name,
'description' => $description,
'quantity' => $quantity,
'unitPrice' => $unitprice,
'taxable' => $taxable
);
}
public function setParameter($field = '', $value = null) {
$field = (is_string($field)) ? trim($field) : $field;
$value = (is_string($value)) ? trim($value) : $value;
if (!is_string($field)) {
throw new AuthorizeNetCimException('setParameter() arg 1 must be a string: ' . gettype($field) . ' given.');
}
if (!is_string($value) && !is_numeric($value) && !is_bool($value)) {
throw new AuthorizeNetCimException('setParameter() arg 2 must be a string, numeric, or boolean value: ' . gettype($value) . ' given.');
}
if (empty($field)) {
throw new AuthorizeNetCimException('setParameter() requires a parameter field to be named.');
}
// griley - ucommenting this logic check. Are blank values not permitted?
/*
if ($value === '') {
throw new AuthorizeNetCimException('setParameter() requires a parameter value to be assigned: '.$field);
}
*/
$this->params[$field] = $value;
}
public function parametersSet($params=null) {
$found = array();
if(!is_array($params)) $params=func_get_args();
foreach ($params as $field) {
$found[]= $this->params[$field] ? false : true;
}
return !in_array(false, $found);
}
public function setParameters($arr) {
foreach ($arr as $field => $value) {
$this->setParameter($field, $value);
}
}
public function unsetParameters() {
foreach (func_get_args() as $field) {
unset($this->params[$field]);
}
}
private function parseResults() {
$this->resultCode = $this->parseXML('<resultCode>', '</resultCode>');
$this->code = $this->parseXML('<code>', '</code>');
$this->text = $this->parseXML('<text>', '</text>');
$this->validationDirectResponse = $this->parseXML('<validationDirectResponse>', '</validationDirectResponse>');
$this->validationDirectResponseList = $this->parseXML('<validationDirectResponseList><string>', '</string></validationDirectResponseList>');
$this->validationDirectResponseListResults = explode(',',$this->validationDirectResponseList);
$this->directResponse = $this->parseXML('<directResponse>', '</directResponse>');
$this->profileId = (int) $this->parseXML('<customerProfileId>', '</customerProfileId>');
$this->addressId = (int) $this->parseXML('<customerAddressId>', '</customerAddressId>');
$this->paymentProfileId = (int) $this->parseXML('<customerPaymentProfileIdList><numericString>', '</numericString></customerPaymentProfileIdList>');
$this->results = explode(',', $this->directResponse);
}
private function parseXML($start, $end) {
if (strpos($this->rawResponse,$start) === false) {
return null;
}
else {
return preg_replace('|^.*?'.$start.'(.*?)'.$end.'.*?$|i', '$1', substr($this->rawResponse, 335));
}
}
public function isSuccessful() {
return $this->success;
}
public function isError() {
return $this->error;
}
public function getErrorResponse() {
/*
* first inspect the response code - errors other than the ones below
* are things that the user shouldn't see anyway. we will log them
*/
if (!in_array($this->code,array('E00013','E00014','E00015','E00016','E00019','E00027','E00029'))) {
$this->writeLog('','Unhandled Error occurred: response='.$this->getFullResponse());
return 'An error occurred while processing your transaction.';
}
/*
* the detailed error response is returned in different places
* depending on what request was made
*/
if ($this->requestMethod == 'createCustomerProfile') {
if (empty($this->validationDirectResponseList)) {
return strip_tags($this->text);
}
else {
return $this->validationDirectResponseListResults['3'];
}
}
else {
return strip_tags($this->text);
}
}
public function getFullResponse() {
return 'Code: ' . $this->code . ' Message: ' . strip_tags($this->text);
}
public function getResponse() {
return strip_tags($this->text);
}
public function getCode() {
return $this->code;
}
public function getProfileID() {
return $this->profileId;
}
public function validationDirectResponse() {
return $this->validationDirectResponse;
}
public function validationDirectResponseList() {
return $this->validationDirectResponseList;
}
public function getCustomerAddressId() {
return $this->addressId;
}
public function getDirectResponse() {
return $this->directResponse;
}
public function getPaymentProfileId() {
return $this->paymentProfileId;
}
public function getResponseCode() {
return $this->results[0];
}
public function getResponseSubcode() {
return $this->results[1];
}
public function getResponseReasonCode() {
return $this->results[2];
}
public function getResponseReasonText() {
return $this->results[3];
}
public function getAuthCode() {
return $this->results[4];
}
public function getAVSResponse() {
return $this->results[5];
}
public function getTransactionID() {
return $this->results[6];
}
public function getInvoiceNumber() {
return $this->results[7];
}
public function getDescription() {
return $this->results[8];
}
public function getPaymentMethod() {
return $this->results[10];
}
public function getFirstName() {
return $this->results[13];
}
public function getLastName() {
return $this->results[14];
}
public function getCVVResponse() {
return $this->results[38];
}
public function getCAVVResponse() {
return $this->results[39];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment