3 +--------------------------------------------------------------------+
4 | FirstData Core Payment Module for CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Licensed to CiviCRM under the Academic Free License version 3.0 |
8 | Written & Contributed by Eileen McNaughton - Nov March 2008 |
9 +--------------------------------------------------------------------+
10 | This processor is based heavily on the Eway processor by Peter |
14 +--------------------------------------------------------------------+
18 * Note that in order to use FirstData / LinkPoint you need a certificate (.pem) file issued by them
19 * and a store number. You can configure the path to the certificate and the store number
20 * through the front end of civiCRM. The path is as seen by the server not the url
21 * -----------------------------------------------------------------------------------------------
22 * The basic functionality of this processor is that variables from the $params object are transformed
23 * into xml using a function provided by the processor. The xml is submitted to the processor's https site
24 * using curl and the response is translated back into an array using the processor's function.
26 * If an array ($params) is returned to the calling function it is treated as a success and the values from
27 * the array are merged into the calling functions array.
29 * If an result of class error is returned it is treated as a failure
31 * -----------------------------------------------------------------------------------------------
35 * From Payment processor documentation
36 * For testing purposes, you can use any of the card numbers listed below. The test card numbers
37 * will not result in any charges to the card. Use these card numbers with any expiration date in the
39 * Visa Level 2 - 4275330012345675 (replies with a referral message)
40 * JCB - 3566007770003510
41 * Discover - 6011000993010978
42 * MasterCard - 5424180279791765
43 * Visa - 4005550000000019 or 4111111111111111
44 * MasterCard Level 2 - 5404980000008386
45 * Diners - 36555565010005
46 * Amex - 372700997251009
48 * **************************
49 * Lines starting with CRM_Core_Error::debug_log_message output messages to files/upload/civicrm.log - you may with to comment them out once it is working satisfactorily
51 * For live testing uncomment the result field below and set the value to the response you wish to get from the payment processor
52 * **************************
54 class CRM_Core_Payment_FirstData
extends CRM_Core_Payment
{
55 # (not used, implicit in the API, might need to convert?)
56 const CHARSET
= 'UFT-8';
59 * We only need one instance of this object. So we use the singleton
60 * pattern and cache the instance in this variable
65 static private $_singleton = NULL;
67 /**********************************************************
71 * The mode of operation: live or test.
73 * @param $paymentProcessor
75 * @return \CRM_Core_Payment_FirstData *******************************************************
77 public function __construct($mode, &$paymentProcessor) {
80 $this->_paymentProcessor
= $paymentProcessor;
83 /**********************************************************
84 * This function is set up and put here to make the mapping of fields
85 * from the params object as visually clear as possible for easy editing
87 * Comment out irrelevant fields
88 **********************************************************/
89 public function mapProcessorFieldstoParams($params) {
90 /*concatenate full customer name first - code from EWAY gateway
93 $credit_card_name = $params['first_name'] . " ";
94 if (strlen($params['middle_name']) > 0) {
95 $credit_card_name .= $params['middle_name'] . " ";
97 $credit_card_name .= $params['last_name'];
101 /**********************************************************
102 * Payment Processor field name **fields from $params array ***
103 *******************************************************************/
105 $requestFields['cardnumber'] = $params['credit_card_number'];
106 $requestFields['chargetotal'] = $params['amount'];
107 $requestFields['cardexpmonth'] = sprintf('%02d', (int) $params['month']);
108 $requestFields['cardexpyear'] = substr($params['year'], 2, 2);
109 $requestFields['cvmvalue'] = $params['cvv2'];
110 $requestFields['cvmindicator'] = "provided";
111 $requestFields['name'] = $credit_card_name;
112 $requestFields['address1'] = $params['street_address'];
113 $requestFields['city'] = $params['city'];
114 $requestFields['state'] = $params['state_province'];
115 $requestFields['zip'] = $params['postal_code'];
116 $requestFields['country'] = $params['country'];
117 $requestFields['email'] = $params['email'];
118 $requestFields['ip'] = $params['ip_address'];
119 $requestFields['transactionorigin'] = "Eci";
121 $requestFields['invoice_number'] = $params['invoiceID'];
122 $requestFields['ordertype'] = $params['payment_action'];
123 $requestFields['comments'] = $params['description'];
124 //**********************set 'result' for live testing **************************
125 // $requestFields[ 'result' ] = ""; #set to "Good", "Decline" or "Duplicate"
126 // $requestFields[ '' ] = $params[ 'qfKey' ];
127 // $requestFields[ '' ] = $params[ 'amount_other' ];
128 // $requestFields[ '' ] = $params[ 'billing_first_name' ];
129 // $requestFields[ '' ] = $params[ 'billing_middle_name' ];
130 // $requestFields[ '' ] = $params[ 'billing_last_name' ];
132 // $requestFields[ '' ] = $params[ 'contributionType_name' ];
133 // $requestFields[ '' ] = $params[ 'contributionPageID' ];
134 // $requestFields[ '' ] = $params[ 'contributionType_accounting_code' ];
135 // $requestFields[ '' ] = $params['amount_level' ];
136 // $requestFields[ '' ] = $params['credit_card_type' ];
137 // $requestFields[ 'addrnum' ] = numeric portion of street address - not yet implemented
138 // $requestFields[ 'taxexempt' ] taxexempt status (Y or N) - not implemented
140 return $requestFields;
143 /**********************************************************
144 * This function sends request and receives response from
146 **********************************************************/
147 public function doDirectPayment(&$params) {
148 if ($params['is_recur'] == TRUE) {
149 CRM_Core_Error
::fatal(ts('%1 - recurring payments not implemented', array(1 => $paymentProcessor)));
152 if (!defined('CURLOPT_SSLCERT')) {
153 CRM_Core_Error
::fatal(ts('%1 - Gateway requires curl with SSL support', array(1 => $paymentProcessor)));
156 /**********************************************************
157 * Create the array of variables to be sent to the processor from the $params array
158 * passed into this function
159 **********************************************************/
160 $requestFields = self
::mapProcessorFieldstoParams($params);
162 /**********************************************************
163 * create FirstData request object
164 **********************************************************/
165 require_once 'FirstData/lphp.php';
168 /**********************************************************
169 * define variables for connecting with the gateway
170 **********************************************************/
172 # Name and location of certificate file
173 $key = $this->_paymentProcessor
['password'];
175 $requestFields["configfile"] = $this->_paymentProcessor
['user_name'];
177 $host = $this->_paymentProcessor
['url_site'] . ":" . $port . "/LSGSXML";
179 //----------------------------------------------------------------------------------------------------
180 // Check to see if we have a duplicate before we send
181 //----------------------------------------------------------------------------------------------------
182 if ($this->_checkDupe($params['invoiceID'])) {
183 return self
::errorExit(9003, 'It appears that this transaction is a duplicate. Have you already submitted the form once? If so there may have been a connection problem. Check your email for a receipt from eWAY. If you do not receive a receipt within 2 hours you can try your transaction again. If you continue to have problems please contact the site administrator.');
185 //----------------------------------------------------------------------------------------------------
186 // Convert to XML using function provided by payment processor
187 //----------------------------------------------------------------------------------------------------
188 $requestxml = lphp
::buildXML($requestFields);
190 /*----------------------------------------------------------------------------------------------------
191 // Send to the payment information using cURL
192 /----------------------------------------------------------------------------------------------------
195 $ch = curl_init($host);
197 return self
::errorExit(9004, 'Could not initiate connection to payment gateway');
200 curl_setopt($ch, CURLOPT_POST
, 1);
201 curl_setopt($ch, CURLOPT_POSTFIELDS
, $requestxml);
202 curl_setopt($ch, CURLOPT_SSLCERT
, $key);
203 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST
, CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'verifySSL') ?
2 : 0);
204 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER
, CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'verifySSL'));
205 // return the result on success, FALSE on failure
206 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, 1);
207 curl_setopt($ch, CURLOPT_TIMEOUT
, 36000);
208 // ensures any Location headers are followed
209 if (ini_get('open_basedir') == '' && ini_get('safe_mode') == 'Off') {
210 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, 1);
213 // Send the data out over the wire
214 //--------------------------------
215 $responseData = curl_exec($ch);
217 //----------------------------------------------------------------------------------------------------
218 // See if we had a curl error - if so tell 'em and bail out
220 // NOTE: curl_error does not return a logical value (see its documentation), but
221 // a string, which is empty when there was no error.
222 //----------------------------------------------------------------------------------------------------
223 if ((curl_errno($ch) > 0) ||
(strlen(curl_error($ch)) > 0)) {
224 $errorNum = curl_errno($ch);
225 $errorDesc = curl_error($ch);
227 // Paranoia - in the unlikley event that 'curl' errno fails
228 if ($errorNum == 0) {
232 // Paranoia - in the unlikley event that 'curl' error fails
233 if (strlen($errorDesc) == 0) {
234 $errorDesc = "Connection to payment gateway failed";
236 if ($errorNum == 60) {
237 return self
::errorExit($errorNum, "Curl error - " . $errorDesc . " Try this link for more information http://curl.haxx.se/docs/sslcerts.html");
240 return self
::errorExit($errorNum, "Curl error - " . $errorDesc . " your key is located at " . $key . " the url is " . $host . " xml is " . $requestxml . " processor response = " . $processorResponse);
243 //----------------------------------------------------------------------------------------------------
244 // If null data returned - tell 'em and bail out
246 // NOTE: You will not necessarily get a string back, if the request failed for
247 // any reason, the return value will be the boolean false.
248 //----------------------------------------------------------------------------------------------------
249 if (($responseData === FALSE) ||
(strlen($responseData) == 0)) {
250 return self
::errorExit(9006, "Error: Connection to payment gateway failed - no data returned.");
253 //----------------------------------------------------------------------------------------------------
254 // If gateway returned no data - tell 'em and bail out
255 //----------------------------------------------------------------------------------------------------
256 if (empty($responseData)) {
257 return self
::errorExit(9007, "Error: No data returned from payment gateway.");
260 //----------------------------------------------------------------------------------------------------
261 // Success so far - close the curl and check the data
262 //----------------------------------------------------------------------------------------------------
265 //----------------------------------------------------------------------------------------------------
266 // Payment successfully sent to gateway - process the response now
267 //----------------------------------------------------------------------------------------------------
269 $processorResponse = lphp
::decodeXML($responseData);
271 // transaction failed, print the reason
272 if ($processorResponse["r_approved"] != "APPROVED") {
273 return self
::errorExit(9009, "Error: [" . $processorResponse['r_error'] . "] - from payment processor");
277 //-----------------------------------------------------------------------------------------------------
278 // Cross-Check - the unique 'TrxnReference' we sent out should match the just received 'TrxnReference'
280 // this section not used as the processor doesn't appear to pass back our invoice no. Code in eWay model if
282 //-----------------------------------------------------------------------------------------------------
287 $params['trxn_result_code'] = $processorResponse['r_message'];
288 $params['trxn_id'] = $processorResponse['r_ref'];
289 CRM_Core_Error
::debug_log_message("r_authresponse " . $processorResponse['r_authresponse']);
290 CRM_Core_Error
::debug_log_message("r_code " . $processorResponse['r_code']);
291 CRM_Core_Error
::debug_log_message("r_tdate " . $processorResponse['r_tdate']);
292 CRM_Core_Error
::debug_log_message("r_avs " . $processorResponse['r_avs']);
293 CRM_Core_Error
::debug_log_message("r_ordernum " . $processorResponse['r_ordernum']);
294 CRM_Core_Error
::debug_log_message("r_error " . $processorResponse['r_error']);
295 CRM_Core_Error
::debug_log_message("csp " . $processorResponse['r_csp']);
296 CRM_Core_Error
::debug_log_message("r_message " . $processorResponse['r_message']);
297 CRM_Core_Error
::debug_log_message("r_ref " . $processorResponse['r_ref']);
298 CRM_Core_Error
::debug_log_message("r_time " . $processorResponse['r_time']);
302 // end function doDirectPayment
305 * Checks to see if invoice_id already exists in db
307 * @param int $invoiceId
311 * True if ID exists, else false
313 public function _checkDupe($invoiceId) {
314 $contribution = new CRM_Contribute_DAO_Contribution();
315 $contribution->invoice_id
= $invoiceId;
316 return $contribution->find();
319 /**************************************************
320 * Produces error message and returns from class
321 **************************************************/
322 public function &errorExit($errorCode = NULL, $errorMessage = NULL) {
323 $e = CRM_Core_Error
::singleton();
326 $e->push($errorCode, 0, NULL, $errorMessage);
329 $e->push(9000, 0, NULL, 'Unknown System Error.');
334 /**************************************************
335 * NOTE: 'doTransferCheckout' not implemented
336 **************************************************/
337 public function doTransferCheckout(&$params, $component) {
338 CRM_Core_Error
::fatal(ts('This function is not implemented'));
341 /********************************************************************************************
342 * This public function checks to see if we have the right processor config values set
344 * NOTE: Called by Events and Contribute to check config params are set prior to trying
345 * register any credit card details
347 * @return null|string
348 * @internal param string $mode the mode we are operating in (live or test) - not used
350 * returns string $errorMsg if any errors found - null if OK
352 ******************************************************************************************
354 // function checkConfig( $mode ) // CiviCRM V1.9 Declaration
355 // CiviCRM V2.0 Declaration
356 public function checkConfig() {
359 if (empty($this->_paymentProcessor
['user_name'])) {
360 $errorMsg[] = ts(' Store Name is not set for this payment processor');
363 if (empty($this->_paymentProcessor
['url_site'])) {
364 $errorMsg[] = ts(' URL is not set for this payment processor');
367 if (!empty($errorMsg)) {
368 return implode('<p>', $errorMsg);
375 // end class CRM_Core_Payment_FirstData