| * @copyright 1997-2005 The PHP Group * @license http://www.php.net/license/3_0.txt PHP License 3.0 * @version CVS: $Id: Beanstream.php,v 1.33 2005/11/01 18:55:29 jausions Exp $ * @link http://pear.php.net/package/Payment_Process */ require_once 'Payment/Process.php'; require_once 'Payment/Process/Common.php'; require_once 'Net/Curl.php'; /** * Defines global variables */ $GLOBALS['_Payment_Process_BeanstreamEFT'] = array( PAYMENT_PROCESS_ACTION_NORMAL => 'D', PAYMENT_PROCESS_ACTION_AUTHONLY => NULL, PAYMENT_PROCESS_ACTION_POSTAUTH => NULL, PAYMENT_PROCESS_ACTION_VOID => 'VD' ); /** * Payment_Process_Beanstream * * This is a processor for Beanstream's merchant payment gateway. * (http://www.beanstream.com/) * * @package Payment_Process * @author Mike Benoit * @version @version@ * @link http://www.beanstream.com/ */ class Payment_Process_BeanstreamEFT 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 Beanstream requires. * * @see _prepare() * @access private */ var $_fieldMap = array( // Required 'customerId' => 'loginCompany', //'login' => 'loginUser', //'password' => 'loginPass', 'password' => 'passCode', 'action' => 'trnType', 'invoiceNumber' => 'trnOrderNumber', 'amount' => 'trnAmount', 'processDate' => 'processDate', 'batchFile' => 'batchFile', //'name' => 'name', //'address' => '', //'city' => '', //'state' => '', //'country' => '', //'postalCode' => '', //'zip' => '', //'phone' => '', //'email' => '', //'errorPage' => 'errorPage', ); /** * $_typeFieldMap * * @author Joe Stump * @access protected */ var $_typeFieldMap = array( 'ACH' => array( 'routingCode' => 'routingNumber', 'accountNumber' => 'accountNumber', 'accountCode' => 'accountCode', 'name' => 'ordName' ), 'EFT' => array( 'institutionCode' => 'institutionId', 'routingCode' => 'transitNumber', 'accountNumber' => 'accountNumber', 'name' => 'ordName' ), ); /** * Default options for this processor. * * @see Payment_Process::setOptions() * @access private */ var $_defaultOptions = array( 'authorizeUri' => 'https://www.beanstream.com/scripts/batch_upload.asp', //'requestType' => 'BACKEND', //'cavEnabled' => 0, 'ServiceVersion' => '1.1', ); /** * List of possible encapsulation characters * * @var string * @access private */ var $_encapChars = '|~#$^*_=+-`{}![]:";<>?/&'; /** * 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 = 'BeanstreamEFT'; //$this->_makeRequired('customerId','login', 'password', 'action', 'invoiceNumber'); $this->_makeRequired('customerId','password', 'action', 'invoiceNumber'); } /** * Prepare the batch data. * * This function handles the 'testTransaction' option, which is specific to * this processor. */ function _prepare() { $result = parent::_prepare(); if ( $result !== TRUE ) { return $result; } //Build batch file for each type of transaction type. if ($this->_payment->getType() == 'EFT') { $batch_fields = array('trnType', 'institutionId', 'transitNumber', 'accountNumber', 'trnAmount', 'trnOrderNumber', 'ordName' ); if ( is_array($batch_fields) ) { $batch_data[] = substr( $this->_payment->getType(), 0, 1); foreach( $batch_fields as $batch_field ) { $batch_data[] = $this->_data[$batch_field]; } } } elseif ($this->_payment->getType() == 'ACH') { $batch_fields = array('trnType', 'routingNumber', 'accountNumber', 'accountCode', 'trnAmount', 'trnOrderNumber', 'ordName' ); if ( is_array($batch_fields) ) { $batch_data[] = substr( $this->_payment->getType(), 0, 1); foreach( $batch_fields as $batch_field ) { if ( $batch_field == 'accountCode' ) { $batch_data[] = 'CC'; } else { $batch_data[] = $this->_data[$batch_field]; } } } } if ( is_array($batch_data) ) { $this->batchFile = implode(',', $batch_data); } return TRUE; } /** * Processes the transaction. * * Success here doesn't mean the transaction was approved. It means * the transaction was sent and processed without technical difficulties. * * @return mixed Payment_Process_Result on success, PEAR_Error on failure * @access public */ function &process() { // Sanity check $result = $this->validate(); if (PEAR::isError($result)) { return $result; } // Prepare the data $result = $this->_prepare(); if (PEAR::isError($result)) { return $result; } $fields = $this->_prepareQueryString(); if (PEAR::isError($fields)) { return $fields; } // Don't die partway through PEAR::pushErrorHandling(PEAR_ERROR_RETURN); //Must use GET for login information. $curl = new Net_Curl($this->_options['authorizeUri'].'?'.$fields); $curl->timeout = 300; if (PEAR::isError($curl)) { PEAR::popErrorHandling(); return $curl; } $temp_file_prefix = 'eft_'; if ( isset($this->_data['trnOrderNumber']) AND preg_match( '/^[A-Z0-9_\-]+$/i', $this->_data['trnOrderNumber'] ) ) { $temp_file_prefix .= $this->_data['trnOrderNumber'].'_'; } $temp_file = tempnam( sys_get_temp_dir(), $temp_file_prefix ).'.csv'; file_put_contents( $temp_file, $this->batchFile); if ( class_exists('\CURLFile') ) { $curl->fields = array('batchFile' => new \CURLFile( $temp_file ) ); //PHP v5.5+ method. } else { $curl->fields = array('batchFile' => '@'.$temp_file); //Pre PHP v5.5 method. } $curl->userAgent = 'PEAR Payment_Process_Beanstream 0.1'; //$curl->verboseAll(); $result = $curl->execute(); unlink($temp_file); if (PEAR::isError($result)) { PEAR::popErrorHandling(); return $result; } 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(); } $response->action = $this->action; return $response; } /** * Processes a callback from payment gateway * * Success here doesn't mean the transaction was approved. It means * the callback was received and processed without technical difficulties. * * @return mixed Payment_Process_Result on success, PEAR_Error on failure */ function &processCallback() { $this->_responseBody = $_POST; $this->_processed = true; $response = &Payment_Process_Result::factory($this->_driver, $this->_responseBody); if (!PEAR::isError($response)) { $response->_request = $this; $response->parseCallback(); $r = $response->isLegitimate(); if (PEAR::isError($r)) { return $r; } elseif ($r === false) { return PEAR::raiseError('Illegitimate callback from gateway.'); } } return $response; } /** * Get (completed) transaction status. * * @return string Two-digit status returned from gateway. */ function getStatus() { return false; } /** * Prepare the POST query string. * * You will need PHP_Compat::str_split() if you run this processor * under PHP 4. * * @access private * @return string The query string */ function _prepareQueryString() { //$url_fields = array('ServiceVersion', 'loginCompany', 'loginUser', 'loginPass', 'processDate' ); $url_fields = array('ServiceVersion', 'loginCompany', 'passCode', 'processDate' ); $data = array_merge($this->_options, $this->_data); foreach ($data as $key => $val) { if ( in_array($key, $url_fields) AND strlen($val) > 0 ) { //if ( strlen($val) > 0 ) { $return[] = $key.'='.urlencode($val); } } $retval = implode('&', $return); return $retval; } /** * Validates the charge amount. * * Charge amount must be 8 characters long, double-precision. * Current min/max are rather arbitrarily set to $0.01 and $99999.99, * respectively. * * @return boolean true on success, false otherwise */ function _validateAmount() { return Validate::number($this->amount, array( 'decimal' => '.', 'dec_prec' => 2, 'min' => 0.01, 'max' => 99999.99 )); } /** * _handleAmount * * Amounts must always be in pennies. * * @access private */ function _handleAmount() { $this->_data['trnAmount'] = $this->amount = $this->amount*100; } /** * _handleProcessDate * * We need to make sure process date is in format YYYYMMDD and if its after 11AM it must be the next day. * * @access private */ function _handleProcessDate() { if ( isset($this->processDate) ) { $epoch = (int)$this->processDate; } else { $this->processDate = FALSE; $epoch = time(); } if ( $epoch < time() ) { $epoch = time(); } $cutoff_hour = date('G', $epoch ); if ( $cutoff_hour > 10 ) { // 11AM //Process date is next day $epoch += 86400; } else { //Process date can be today. } $this->processDate = $this->_data['processDate'] = date('Ymd', $epoch ); } } class Payment_Process_Result_BeanstreamEFT extends Payment_Process_Result { var $_statusCodeMap = array('1' => PAYMENT_PROCESS_RESULT_APPROVED, '2' => PAYMENT_PROCESS_RESULT_OTHER, '3' => PAYMENT_PROCESS_RESULT_OTHER, '4' => PAYMENT_PROCESS_RESULT_REVIEW, '5' => PAYMENT_PROCESS_RESULT_REVIEW, '6' => PAYMENT_PROCESS_RESULT_REVIEW, '7' => PAYMENT_PROCESS_RESULT_DECLINED, '8' => PAYMENT_PROCESS_RESULT_REVIEW, '9' => PAYMENT_PROCESS_RESULT_REVIEW, '10' => PAYMENT_PROCESS_RESULT_REVIEW, '11' => PAYMENT_PROCESS_RESULT_REVIEW, '12' => PAYMENT_PROCESS_RESULT_REVIEW, '13' => PAYMENT_PROCESS_RESULT_REVIEW, ); var $_statusCodeMessages = array( '1' => 'File successfully received', '2' => 'Secure connection required', '3' => 'Service version not supported', '4' => 'Invalid login credentials', '5' => 'Insufficient user permissions', '6' => 'Batch Processing service not enabled', '7' => 'Invalid processing date', '8' => 'Service is busy importing another file. Try again later', '9' => 'File greater than maximum allowable size', '10' => 'Unexpected error', '11' => 'No batch file received in request', '12' => 'Merchant account status cannot be Disabled or Closed for operation', '13' => 'Upload rejected. File name is limited to 32 characters in length, including file type extension', ); var $_fieldMap = array('0' => 'code', '2' => 'messageCode', '3' => 'message', '4' => 'approvalCode', '5' => 'avsCode', '6' => 'transactionId', '7' => 'invoiceNumber', '8' => 'description', '9' => 'amount', '12' => 'customerId', '37' => 'md5Hash', '38' => 'cvvCode' ); function Payment_Process_Response_BeanstreamEFT($rawResponse) { $this->Payment_Process_Response($rawResponse); } /** * Parses the data received from the payment gateway * * @access public */ function parse() { if ( preg_match('/([\d]+)<\/code>/i', $this->_rawResponse, $code_match ) ) { if ( isset($code_match[1]) ) { $code = (int)$code_match[1]; $this->code = $code; $this->messageCode = $code; if ( $code == 1 ) { $this->_returnCode = PAYMENT_PROCESS_RESULT_APPROVED; if ( preg_match('/(.*)<\/batch_id>/i', $this->_rawResponse, $batch_id_match ) ) { $this->approvalCode = $this->transactionId = $batch_id_match[1]; } } else { $this->_returnCode = PAYMENT_PROCESS_RESULT_DECLINED; } if ( preg_match('/(.*)<\/message>/i', $this->_rawResponse, $message_match ) ) { $this->message = $message_match[1] .' (Code: '. $code .')'; } } else { $this->_returnCode = PAYMENT_PROCESS_RESULT_OTHER; $this->message = 'Error parsing response.'; } } else { $this->_returnCode = PAYMENT_PROCESS_RESULT_OTHER; $this->message = 'Error parsing response.'; } } } ?>