<?php defined( 'BASEPATH' ) or die( 'Restricted' );
/*
This code is copyright 2009-2010 by TMLA INC. ALL RIGHTS RESERVED.
Please view license.txt in /tgsf_core/legal/license.txt or
http://tgWebSolutions.com/opensource/tgsf/license.txt
for complete licensing information.
*/
define( 'ACH_DIRECT_LIVE_SERVER', '5050' );
define( 'ACH_DIRECT_TEST_SERVER', '6050' );
define( 'ACH_EOL', "\n" );
define( 'ACH_EFT_SALE', '20' );
define( 'ACH_EFT_CREDIT', '23' );
define( 'ACH_EFT_VOID', '24' );
define( 'ACH_EFT_VERIFY', '26' );
define( 'ACH_ACCOUNT_TYPE_CHECKING', 'C' );
define( 'ACH_ACCOUNT_TYPE_SAVINGS', 'S' );
define( 'ACH_RESPONSE_DENIED', 'D' );
define( 'ACH_RESPONSE_APPROVED', 'A' );
define( 'ACH_RESPONSE_ERROR', 'E' );
/**
* This is the interface to ACHDirect and paymentsgateway
*
* This is a Full Interface and not SWP!
*
* Originally Written, 9/20/2009, 11:38 PM
* By: R.Bloom
*
*
* Specific Field Information
*
* Header Fields *
*
* These fields identify the merchant and transaction type.
* There are nine additional merchant data fields that will be echoed in the response message.
*
* pg_merchant_id merchant's six digit ID code
* pg_password merchant's processing password
* pg_transaction_type indicates transaction type (see Table 10)
* pg_merchant_data_[1-9] nine fields returned with response fields
*
* Customer/Order Information Fields
*
* These fields contain the transaction details: order, amount and customer information.
*
* pg_total_amount amount to be charged/credited to customer
* pg_sales_tax_amount sales tax amount; required field for procurement card transactions, optional otherwise
* pg_consumer_id assigned by merchant, returned with response
* ecom_consumerorderid assigned by merchant, returned with response
* ecom_walletid assigned by merchant, returned with response
* pg_billto_postal_name_company company name
* ecom_billto_postal_name_first customer's first name
*
* ecom_billto_postal_name_last customer's last name
* ecom_billto_postal_street_line1 customer's street address
* ecom_billto_postal_street_line2 customer's street address (if necessary)
* ecom_billto_postal_city customer's city
* ecom_billto_postal_stateprov customer's state (abbreviated)
* ecom_billto_postal_postalcode customer's ZIP code
* ecom_billto_postal_countrycode customer's country
* ecom_billto_telecom_phone_number customer's phone number
* ecom_billto_online_email customer's email address
*
* pg_billto_ssn customer's social security number
* pg_billto_dl_number customer's driver's license number
* pg_billto_dl_state customer's driver's license state of issue
* pg_billto_date_of_birth customer's date of birth (MM/DD/YYYY)
* pg_entered_by name or ID of the person entering the data; appears in the Virtual Terminal transaction display window
*
* (PC) required for Procurement Card transactions, optional otherwise
* (AVS) required for AVS checks specified in the pg_avs_method field, optional otherwise
*
* --------------------------------------------------------------------------------
* pg_schedule_frequency specifies the frequency of the recurring transaction Value Frequency Period
*
* 10 weekly every seven days
* 15 biweekly every fourteen days
* 20 monthly same day every month
* 25 bi-monthly every two months
* 30 quarterly every 3 months
* 35 semiannually twice a year
* 40 yearly once year
*
* --------------------------------------------------------------------------------
* pg_transaction_type Type Description Comments
*
*
* Credit Card
* 10 SALE Customer is charged
* 11 AUTH ONLY Authorization only, CAPTURE transaction required
* 12 CAPTURE Completes AUTH ONLY transaction
* 13 CREDIT Customer is credited
* 14 VOID Cancels non-settled transactions
* 15 PRE-AUTH Customer charge approved from other source
*
* EFT
*
* 20 SALE Customer is charged
* 21 AUTH ONLY Authorization only, CAPTURE transaction required
* 22 CAPTURE Completes AUTH ONLY transaction
* 23 CREDIT Customer is credited
* 24 VOID Cancels non-settled transactions
* 25 FORCE Customer charged (no validation checks)
* 26 VERIFY ONLY Verification only, no customer charge
*
* Recurrance
*
* 40 SUSPEND Suspends a recurring transaction
* 41 ACTIVATE Reactivates a recurring transaction
* 42 DELETE Deletes a recurring transaction
*/
/**
* Base Class for ACH Messages. ACH Messages are basically name value pairs.
* An Array of Strings and values is kept internally in _vars to indicate possible
* values that can be sent in the itnerface. This actually gives us three things.
* A Variable, it's Name and it's value. This method allows for easy addition of
* new variables without much coding (really any coding)
* and allows for retrieving the name of a variable without writing a mapping.
*
* outgoing messages (ACHTransaction) are formatted to name value pairs with a newline
* after each pair.
*
* incoming messages (ACHResponse) are formatted as read from paymentsgeteway as name value pairs.
* the trim kills the newline that PG sends after each name value pair
*
* The placement of 'endofdata' => null in the array of fields is simply symbolic
* of it's place in an actual message and totally unnecessary for this interface
*
*/
class ACHMessage extends tgsfBase
{
protected $_vars;
//------------------------------------------------------------------------
public function __construct()
{
$this->_vars = array( 'endofdata' => null );
}
//--------------------------------------------------------------------------
/**
* Reset all values to null to clear a message
*/
public function clear()
{
$this->_vars = array_fill_keys( array_keys( $this->_vars ), null );
}
//------------------------------------------------------------------------
/**
* Echo values for any variables that are NOT null.
*/
public function debug()
{
foreach ( $this->_vars as $var => $value )
{
if ( $value !== null )
{
echo str_pad( $var, 50 ) . ' = ' . $value . PHP_EOL;
}
}
}
//------------------------------------------------------------------------
public function __get( $name )
{
if ( array_key_exists( $name, $this->_vars ) )
{
return $this->_vars[$name];
}
throw new tgsfException( '(When Getting) Unknown Value Named: ' . $name . ' in ACHMessage.' );
}
//------------------------------------------------------------------------
/**
*
*/
public function __set( $name, $value )
{
if ( array_key_exists( $name, $this->_vars ) )
{
$this->_vars[$name] = $value;
}
else
{
throw new tgsfException( '(When Setting) Unknown Value Named: ' . $name . ' in ACHMessage' );
}
}
}
// -----------------------------------------------------------------------------
// Outbound Transactions
// -----------------------------------------------------------------------------
class ACHTransaction extends ACHMessage
{
public function __construct()
{
parent::__construct();
$this->_vars = array(
// Header
'pg_merchant_id' => null,
'pg_password' => null,
'pg_total_amount' => null,
'pg_transaction_type' => null,
'pg_merchant_data_1' => null,
'pg_merchant_data_2' => null,
'pg_merchant_data_3' => null,
'pg_merchant_data_4' => null,
'pg_merchant_data_5' => null,
'pg_merchant_data_6' => null,
'pg_merchant_data_7' => null,
'pg_merchant_data_8' => null,
'pg_merchant_data_9' => null,
// Customer / Order Information
'pg_sales_tax_amount' => null,
'pg_consumer_id' => null,
'ecom_consumerorderid' => null,
'ecom_walletid' => null,
'pg_billto_postal_name_company' => null,
'ecom_payment_check_account_type' => null,
'ecom_payment_check_account' => null,
'ecom_payment_check_trn' => null,
'ecom_billto_postal_name_first' => null,
'ecom_billto_postal_name_last' => null,
'ecom_billto_postal_street_line1' => null,
'ecom_billto_postal_street_line2' => null,
'ecom_billto_postal_city' => null,
'ecom_billto_postal_stateprov' => null,
'ecom_billto_postal_postalcode' => null,
'ecom_billto_postal_countrycode' => null,
'ecom_billto_telecom_phone_number' => null,
'ecom_billto_online_email' => null,
'pg_billto_ssn' => null,
'pg_billto_dl_number' => null,
'pg_billto_dl_state' => null,
'pg_billto_date_of_birth' => null,
'pg_entered_by' => null,
'pg_original_trace_number' => null,
'pg_original_authorization_code' => null,
// Recurrance
'pg_schedule_quantity' => null,
'pg_schedule_frequency' => null,
'pg_schedule_recurring_amount' => null,
'pg_schedule_start_date' => null,
// Misc
'pg_customer_ip_address' => null,
'pg_software_name' => null,
'pg_software_version' => null,
'pg_avs_method' => null,
// End
'endofdata' => null );
}
//------------------------------------------------------------------------
/**
* Formats a DSI message for ACHDirect.
* DSI messages are name value pairs terminated by newlines
* followed by endofdata and a final newline
*
* -------------------------
* pg_terminal_id=123555
* pg_password=some_password
* endofdata
* -------------------------
*/
public function getOutput()
{
$result = '';
foreach ( $this->_vars as $name => $value )
{
if ( $value !== null )
{
$result .= $name . '=' . $value . ACH_EOL;
}
}
$result .= 'endofdata' . ACH_EOL;
return $result;
}
}
/**
* ACH Responses - a class to parse the values returned in a DSI transaction
*/
class ACHResponse extends ACHMessage
{
public function __construct()
{
parent::__construct();
$this->_vars = array(
'pg_merchant_id' => null,
'pg_transaction_type' => null,
'pg_merchant_data_1' => null,
'pg_merchant_data_2' => null,
'pg_merchant_data_3' => null,
'pg_merchant_data_4' => null,
'pg_merchant_data_5' => null,
'pg_merchant_data_6' => null,
'pg_merchant_data_7' => null,
'pg_merchant_data_8' => null,
'pg_merchant_data_9' => null,
'pg_total_amount' => null,
'pg_sales_tax_amount' => null,
'pg_consumer_id' => null,
'ecom_consumerorderid' => null,
'ecom_walletid' => null,
'ecom_billto_postal_name_first' => null,
'ecom_billto_postal_name_last' => null,
'pg_billto_postal_name_company' => null,
'ecom_billto_online_email' => null,
'pg_response_type' => null,
'pg_response_code' => null,
'pg_response_description' => null,
'pg_avs_result' => null,
'pg_trace_number' => null,
'pg_authorization_code' => null,
'pg_preauth_result' => null,
'pg_preauth_code' => null,
'pg_preauth_description' => null,
'endofdata' => null );
}
//------------------------------------------------------------------------
/**
* Process a name value pair and store in an object variable
* @param string A name/value pair separated by an "="
*/
public function process( $nvp )
{
$parts = explode( '=', $nvp );
$name = trim( $parts[0] );
if ( count( $parts ) == 2 && array_key_exists( $name, $this->_vars ))
{
$value = trim( $parts[1] );
$this->{$name} = $value;
}
else
{
throw new appException( 'Invalid Response Line: ' . $nvp );
}
}
//------------------------------------------------------------------------
function getErrorDetails()
{
if ( $this->pg_response_type != ACH_RESPONSE_ERROR )
{
return '';
}
list( $errorCode,$errorDescription) = explode( ":", $this->pg_response_description );
//$result = "Code=$errorCode, Description=$errorDescription" . PHP_EOL;
$errors = array(
'F01' => 'Manditory Field Missing',
'F03' => 'Invalid Field Name',
'F04' => 'Invalid Field Value',
'F05' => 'Duplicate Field',
'F07' => 'Conflicting Fields');
if ( array_key_exists( $errorCode, $errors ) )
{
$errorName = $errors[$errorCode];
}
else
{
$errorName = 'Unknown Error';
}
return PHP_EOL .
PHP_EOL . ' Code: ' . $errorCode .
PHP_EOL . ' Error: ' . $errorName .
PHP_EOL . ' Field: ' . $errorDescription . PHP_EOL;
return $result;
}
}
/**
* ACHDirect class - Main paymentsgateway.net Interface Class
*
*/
class ACHDirect extends tgsfBase
{
public $host;
public $port;
/**
* The constructor
* @param What port to use. Use defined values: ACH_DIRECT_TEST_SERVER and ACH_DIRECT_LIVE_SERVER
*/
public function __construct( $port = '' )
{
$this->host = config('pg_host');
$this->port = $port;
if ( empty( $port ) )
{
// Default to TEST Server for initial testing.
$this->port = ACH_DIRECT_TEST_SERVER;
//$this->port = ACH_DIRECT_LIVE_SERVER;
}
}
//------------------------------------------------------------------------
/**
* Create an ACHTransaction object that can be set and later executed.
*
*/
public function &createTransaction( $merchantID )
{
$tran = new ACHTransaction();
if ( empty( $merchantID ) )
{
$tran->pg_merchant_id = config('pg_merchant_id');
}
else
{
$tran->pg_merchant_id = $merchantID;
}
$tran->pg_password = config('pg_password');
$tran->pg_entered_by = config('pg_entered_by');
return $tran;
}
//------------------------------------------------------------------------
/**
* Returns an ACHResponse object based on the DSI communication with Payments Gateway
*/
public function &executeTransaction( &$tran )
{
$err_no = null;
$err_msg = null;
// Open a connection to host:port
$sock = fsockopen( $this->host, $this->port, $err_no, $err_msg, 10 );
if ( ! $sock )
{
throw new appException( "ACHDirect Error - Could not open a socket connection to host ($host) on port ($port): " . $err_msg );
}
// send DSI message formatted by the ACHTransaction
fputs( $sock, $tran->getOutput() );
$results = new ACHResponse();
// read back one line at a time (ACH_EOL)
while( $line = fgets( $sock ) )
{
if ( substr( $line, 0, 9 ) == 'endofdata' )
{
break;
}
// ACHMessage parses the line as a name value pair and stores the variable and value
$results->process( $line );
}
// All done, close the socket connection
fclose( $sock );
// pre-process Results
if ( $results->pg_response_type == ACH_RESPONSE_ERROR )
{
throw new appException( 'ACH Transaction Error: ' . $results->getErrorDetails() );
}
return $results;
}
}