* @copyright 1997-2005 The PHP Group * @license http://www.php.net/license/3_0.txt PHP License 3.0 * @version CVS: $Revision: 1.13 $ * @link http://pear.php.net/package/Payment_Process * @link http://www.linkpoint.net/ */ require_once('Payment/Process.php'); require_once('Payment/Process/Common.php'); require_once('Net/Curl.php'); require_once('XML/Parser.php'); $GLOBALS['_Payment_Process_LinkPoint'] = array( PAYMENT_PROCESS_ACTION_NORMAL => 'SALE', PAYMENT_PROCESS_ACTION_AUTHONLY => 'PREAUTH', PAYMENT_PROCESS_ACTION_POSTAUTH => 'POSTAUTH' ); /** * Payment_Process_LinkPoint * * This is a processor for LinkPoint's merchant payment gateway. * (http://www.linkpoint.net/) * * *** WARNING *** * This is BETA code, and has not been fully tested. It is not recommended * that you use it in a production envorinment without further testing. * * @package Payment_Process * @author Joe Stump * @version @version@ */ class Payment_Process_LinkPoint extends Payment_Process_Common { /** * Front-end -> back-end field map. * * This array contains the mapping from front-end fields (defined in * the Payment_Process class) to the field names DPILink requires. * * @see _prepare() * @access private */ var $_fieldMap = array( // Required 'login' => 'configfile', 'action' => 'ordertype', 'invoiceNumber' => 'oid', 'customerId' => 'x_cust_id', 'amount' => 'chargetotal', 'name' => '', 'zip' => 'zip', // Optional 'company' => 'company', 'address' => 'address1', 'city' => 'city', 'state' => 'state', 'country' => 'country', 'phone' => 'phone', 'email' => 'email', 'ip' => 'ip', ); /** * $_typeFieldMap * * @author Joe Stump * @access protected */ var $_typeFieldMap = array( 'CreditCard' => array( 'cardNumber' => 'cardnumber', 'cvv' => 'cvm', 'expDate' => 'expDate' ), 'eCheck' => array( 'routingCode' => 'routing', 'accountNumber' => 'account', 'type' => 'type', 'bankName' => 'bank', 'name' => 'name', 'driversLicense' => 'dl', 'driversLicenseState' => 'dlstate' ) ); /** * Default options for this processor. * * @see Payment_Process::setOptions() * @access private */ var $_defaultOptions = array( 'host' => 'secure.linkpt.net', 'port' => '1129', 'result' => 'LIVE' ); /** * Has the transaction been processed? * * @type boolean * @access private */ var $_processed = false; /** * The response body sent back from the gateway. * * @access private */ var $_responseBody = ''; /** * Constructor. * * @param array $options Class options to set. * @see Payment_Process::setOptions() * @return void */ function __construct($options = false) { parent::__construct($options); $this->_driver = 'LinkPoint'; } /** * Process the transaction. * * @author Joe Stump * @access public * @return mixed Payment_Process_Result on success, PEAR_Error on failure */ function &process() { if (!strlen($this->_options['keyfile']) || !file_exists($this->_options['keyfile'])) { return PEAR::raiseError('Invalid key file'); } // Sanity check $result = $this->validate(); if (PEAR::isError($result)) { return $result; } // Prepare the data $result = $this->_prepare(); if (PEAR::isError($result)) { return $result; } // Don't die partway through PEAR::pushErrorHandling(PEAR_ERROR_RETURN); $xml = $this->_prepareQueryString(); if (PEAR::isError($xml)) { return $xml; } $url = 'https://'.$this->_options['host'].':'.$this->_options['port']. '/LSGSXML'; $curl = new Net_Curl($url); $result = $curl->create(); if (PEAR::isError($result)) { return $result; } $curl->type = 'POST'; $curl->fields = $xml; $curl->sslCert = $this->_options['keyfile']; // LinkPoint's staging server has a boned certificate. If they are // testing against staging we need to turn off SSL host verification. if ($this->_options['host'] == 'staging.linkpt.net') { $curl->verifyPeer = false; $curl->verifyHost = 0; } $curl->userAgent = 'PEAR Payment_Process_LinkPoint 0.1'; $result = &$curl->execute(); if (PEAR::isError($result)) { return PEAR::raiseError('cURL error: '.$result->getMessage()); } else { $curl->close(); } $this->_responseBody = trim($result); $this->_processed = true; // Restore error handling PEAR::popErrorHandling(); $response = &Payment_Process_Result::factory($this->_driver, $this->_responseBody, $this); if (!PEAR::isError($response)) { $response->parse(); } return $response; } /** * Prepare the POST query string. * * @access private * @return string The query string */ function _prepareQueryString() { $data = array_merge($this->_options,$this->_data); $xml = ''."\n"; $xml .= ''."\n"; $xml .= ''."\n"; $xml .= ' '.$data['configfile'].''."\n"; $xml .= ' '.$data['keyfile'].''."\n"; $xml .= ' '.$data['authorizeUri'].''."\n"; $xml .= ' PEAR Payment_Process'."\n"; $xml .= ''."\n"; $xml .= ''."\n"; $xml .= ' '.$data['ordertype'].''."\n"; $xml .= ' '.$data['result'].''."\n"; $xml .= ''."\n"; $xml .= ''."\n"; $xml .= ' '.$data['chargetotal'].''."\n"; $xml .= ' 0.00'."\n"; $xml .= ' 0.00'."\n"; $xml .= ' '.$data['chargetotal'].''."\n"; $xml .= ''."\n"; // Set payment method to eCheck if our payment type is eCheck. // Default is Credit Card. $data['x_method'] = 'CC'; switch ($this->_payment->getType()) { case 'eCheck': return PEAR::raiseError('eCheck not currently supported', PAYMENT_PROCESS_ERROR_NOTIMPLEMENTED); $xml .= ''."\n"; $xml .= ' '."\n"; $xml .= ' '."\n"; $xml .= ' '."\n"; $xml .= ' '."\n"; $xml .= ' '."\n"; $xml .= '
'."\n"; $xml .= ' '."\n"; $xml .= ' pc|ps|bc|bs'."\n"; $xml .= ''."\n"; break; case 'CreditCard': $xml .= ''."\n"; $xml .= ' '.$data['cardnumber'].''."\n"; list($month,$year) = explode('/',$data['expDate']); if (strlen($year) == 4) { $year = substr($year,2); } $month = sprintf('%02d',$month); $xml .= ' '.$month.''."\n"; $xml .= ' '.$year.''."\n"; if (strlen($data['cvm'])) { $xml .= ' '.$data['cvm'].''."\n"; $xml .= ' provided'."\n"; } $xml .= ''."\n"; } if (isset($this->_payment->firstName) && isset($this->_payment->lastName)) { $xml .= ''."\n"; $xml .= ' '.$this->_payment->customerId.''."\n"; $xml .= ' '.$this->_payment->firstName.' '.$this->_payment->lastName.''."\n"; $xml .= ' '.$this->_payment->company.''."\n"; $xml .= ' '.$this->_payment->address.''."\n"; $xml .= ' '.$this->_payment->city.''."\n"; $xml .= ' '.$this->_payment->state.''."\n"; $xml .= ' '.$this->_payment->zip.''."\n"; $xml .= ' '.$this->_payment->country.''."\n"; $xml .= ' '.$this->_payment->phone.''."\n"; $xml .= ' '.$this->_payment->email.''."\n"; $xml .= ' '.$this->_payment->address.''."\n"; $xml .= ''."\n"; } $xml .= '
'."\n"; return $xml; } } /** * Payment_Process_Result_LinkPoint * * LinkPoint result class * * @author Joe Stump * @package Payment_Process */ class Payment_Process_Result_LinkPoint extends Payment_Process_Result { var $_statusCodeMap = array('APPROVED' => PAYMENT_PROCESS_RESULT_APPROVED, 'DECLINED' => PAYMENT_PROCESS_RESULT_DECLINED, 'FRAUD' => PAYMENT_PROCESS_RESULT_FRAUD); /** * LinkPoint status codes * * This array holds many of the common response codes. There are over 200 * response codes - so check the LinkPoint manual if you get a status * code that does not match (see "Response Reason Codes & Response * Reason Text" in the AIM manual). * * @see getStatusText() * @access private */ var $_statusCodeMessages = array( 'APPROVED' => 'This transaction has been approved.', 'DECLINED' => 'This transaction has been declined.', 'FRAUD' => 'This transaction has been determined to be fraud.'); var $_avsCodeMap = array( 'YY' => PAYMENT_PROCESS_AVS_MATCH, 'YN' => PAYMENT_PROCESS_AVS_MISMATCH, 'YX' => PAYMENT_PROCESS_AVS_ERROR, 'NY' => PAYMENT_PROCESS_AVS_MISMATCH, 'XY' => PAYMENT_PROCESS_AVS_MISMATCH, 'NN' => PAYMENT_PROCESS_AVS_MISMATCH, 'NX' => PAYMENT_PROCESS_AVS_MISMATCH, 'XN' => PAYMENT_PROCESS_AVS_MISMATCH, 'XX' => PAYMENT_PROCESS_AVS_ERROR ); var $_avsCodeMessages = array( 'YY' => 'Address matches, zip code matches', 'YN' => 'Address matches, zip code does not match', 'YX' => 'Address matches, zip code comparison not available', 'NY' => 'Address does not match, zip code matches', 'XY' => 'Address comparison not available, zip code matches', 'NN' => 'Address comparison does not match, zip code does not match', 'NX' => 'Address does not match, zip code comparison not available', 'XN' => 'Address comparison not available, zip code does not match', 'XX' => 'Address comparison not available, zip code comparison not available' ); var $_cvvCodeMap = array('M' => PAYMENT_PROCESS_CVV_MATCH, 'N' => PAYMENT_PROCESS_CVV_MISMATCH, 'P' => PAYMENT_PROCESS_CVV_ERROR, 'S' => PAYMENT_PROCESS_CVV_ERROR, 'U' => PAYMENT_PROCESS_CVV_ERROR, 'X' => PAYMENT_PROCESS_CVV_ERROR ); var $_cvvCodeMessages = array( 'M' => 'Card Code Match', 'N' => 'Card code does not match', 'P' => 'Not processed', 'S' => 'Merchant has indicated that the card code is not present on the card', 'U' => 'Issuer is not certified and/or has not proivded encryption keys', 'X' => 'No response from the credit card association was received' ); var $_fieldMap = array('r_approved' => 'code', 'r_error' => 'message', 'r_code' => 'approvalCode', 'r_ordernum' => 'transactionId' ); function Payment_Process_Response_LinkPoint($rawResponse) { $this->Payment_Process_Response($rawResponse); } /** * parse * * @author Joe Stump * @access public * @return void */ function parse() { $xml = new Payment_Processor_LinkPoint_XML_Parser(); $xml->parseString(''.$this->_rawResponse.''); if (is_array($xml->response) && count($xml->response)) { $this->avsCode = substr($xml->response['r_avs'],0,2); $this->cvvCode = substr($xml->response['r_avs'],2,1); $this->customerId = $this->_request->customerId; $this->invoiceNumber = $this->_request->invoiceNumber; $this->_mapFields($xml->response); // switch to DECLINED since a duplicate isn't *really* fraud if(eregi('duplicate',$this->message)) { $this->messageCode = 'DECLINED'; } } } } /** * Payment_Processor_LinkPoint_XML_Parser * * XML Parser for the LinkPoint response * * @author Joe Stump * @package Payment_Process */ class Payment_Processor_LinkPoint_XML_Parser extends XML_Parser { /** * $response * * @var array $response Raw response as an array * @access public */ var $response = array(); /** * $log * * @var string $tag Current tag * @access private */ var $tag = null; /** * Payment_Processor_LinkPoint_XML_Parser * * @author Joe Stump * @access public * @return void * @see XML_Parser */ function __construct() { $this->XML_Parser(); } /** * startHandler * * @author Joe Stump * @access public * @param resource $xp XML processor handler * @param string $elem Name of XML entity * @return void */ function startHandler($xp, $elem, &$attribs) { $this->tag = $elem; } /** * endHandler * * @author Joe Stump * @access public * @param resource $xp XML processor handler * @param string $elem Name of XML entity * @return void */ function endHandler($xp, $elem) { } /** * defaultHandler * * @author Joe Stump * @access public * @param resource $xp XML processor handler * @param string $data * @return void */ function defaultHandler($xp,$data) { $this->response[strtolower($this->tag)] = $data; } } ?>