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 -----------------------------------------------------------------------------------------------
34 /*From Payment processor documentation
35 For testing purposes, you can use any of the card numbers listed below. The test card numbers
36 will not result in any charges to the card. Use these card numbers with any expiration date in the
38 Visa Level 2 - 4275330012345675 (replies with a referral message)
39 JCB - 3566007770003510
40 Discover - 6011000993010978
41 MasterCard - 5424180279791765
42 Visa - 4005550000000019 or 4111111111111111
43 MasterCard Level 2 - 5404980000008386
44 Diners - 36555565010005
45 Amex - 372700997251009
47 ***************************
48 *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
50 *For live testing uncomment the result field below and set the value to the response you wish to get from the payment processor
51 ***************************/
52 class CRM_Core_Payment_FirstData
extends CRM_Core_Payment
{
53 # (not used, implicit in the API, might need to convert?)
54 const CHARSET
= 'UFT-8';
57 * We only need one instance of this object. So we use the singleton
58 * pattern and cache the instance in this variable
63 static private $_singleton = NULL;
65 /**********************************************************
68 * @param string $mode the mode of operation: live or test
70 * @param $paymentProcessor
72 * @return \CRM_Core_Payment_FirstData *******************************************************
74 public function __construct($mode, &$paymentProcessor) {
77 $this->_paymentProcessor
= $paymentProcessor;
81 * Singleton function used to manage this object
83 * @param string $mode the mode of operation: live or test
85 * @param object $paymentProcessor
90 public static function &singleton($mode, &$paymentProcessor) {
91 if (!empty($paymentProcessor['id'])) {
92 $cacheKey = $paymentProcessor['id'];
95 //@todo eliminated instances of this in favour of id-specific instances.
96 $cacheKey = $mode . '_' . $paymentProcessor['name'];
98 if (self
::$_singleton[$cacheKey] === NULL) {
99 self
::$_singleton[$cacheKey] = new CRM_Core_Payment_FirstData($mode, $paymentProcessor);
101 return self
::$_singleton[$cacheKey];
104 /**********************************************************
105 * This function is set up and put here to make the mapping of fields
106 * from the params object as visually clear as possible for easy editing
108 * Comment out irrelevant fields
109 **********************************************************/
110 public function mapProcessorFieldstoParams($params) {
111 /*concatenate full customer name first - code from EWAY gateway
114 $credit_card_name = $params['first_name'] . " ";
115 if (strlen($params['middle_name']) > 0) {
116 $credit_card_name .= $params['middle_name'] . " ";
118 $credit_card_name .= $params['last_name'];
122 /**********************************************************
123 * Payment Processor field name **fields from $params array ***
124 *******************************************************************/
126 $requestFields['cardnumber'] = $params['credit_card_number'];
127 $requestFields['chargetotal'] = $params['amount'];
128 $requestFields['cardexpmonth'] = sprintf('%02d', (int) $params['month']);
129 $requestFields['cardexpyear'] = substr($params['year'], 2, 2);
130 $requestFields['cvmvalue'] = $params['cvv2'];
131 $requestFields['cvmindicator'] = "provided";
132 $requestFields['name'] = $credit_card_name;
133 $requestFields['address1'] = $params['street_address'];
134 $requestFields['city'] = $params['city'];
135 $requestFields['state'] = $params['state_province'];
136 $requestFields['zip'] = $params['postal_code'];
137 $requestFields['country'] = $params['country'];
138 $requestFields['email'] = $params['email'];
139 $requestFields['ip'] = $params['ip_address'];
140 $requestFields['transactionorigin'] = "Eci";
142 $requestFields['invoice_number'] = $params['invoiceID'];
143 $requestFields['ordertype'] = $params['payment_action'];
144 $requestFields['comments'] = $params['description'];
145 //**********************set 'result' for live testing **************************
146 // $requestFields[ 'result' ] = ""; #set to "Good", "Decline" or "Duplicate"
147 // $requestFields[ '' ] = $params[ 'qfKey' ];
148 // $requestFields[ '' ] = $params[ 'amount_other' ];
149 // $requestFields[ '' ] = $params[ 'billing_first_name' ];
150 // $requestFields[ '' ] = $params[ 'billing_middle_name' ];
151 // $requestFields[ '' ] = $params[ 'billing_last_name' ];
153 // $requestFields[ '' ] = $params[ 'contributionType_name' ];
154 // $requestFields[ '' ] = $params[ 'contributionPageID' ];
155 // $requestFields[ '' ] = $params[ 'contributionType_accounting_code' ];
156 // $requestFields[ '' ] = $params['amount_level' ];
157 // $requestFields[ '' ] = $params['credit_card_type' ];
158 // $requestFields[ 'addrnum' ] = numeric portion of street address - not yet implemented
159 // $requestFields[ 'taxexempt' ] taxexempt status (Y or N) - not implemented
161 return $requestFields;
164 /**********************************************************
165 * This function sends request and receives response from
167 **********************************************************/
168 public function doDirectPayment(&$params) {
169 if ($params['is_recur'] == TRUE) {
170 CRM_Core_Error
::fatal(ts('%1 - recurring payments not implemented', array(1 => $paymentProcessor)));
173 if (!defined('CURLOPT_SSLCERT')) {
174 CRM_Core_Error
::fatal(ts('%1 - Gateway requires curl with SSL support', array(1 => $paymentProcessor)));
177 /**********************************************************
178 * Create the array of variables to be sent to the processor from the $params array
179 * passed into this function
180 **********************************************************/
181 $requestFields = self
::mapProcessorFieldstoParams($params);
183 /**********************************************************
184 * create FirstData request object
185 **********************************************************/
186 require_once 'FirstData/lphp.php';
189 /**********************************************************
190 * define variables for connecting with the gateway
191 **********************************************************/
193 # Name and location of certificate file
194 $key = $this->_paymentProcessor
['password'];
196 $requestFields["configfile"] = $this->_paymentProcessor
['user_name'];
198 $host = $this->_paymentProcessor
['url_site'] . ":" . $port . "/LSGSXML";
201 //----------------------------------------------------------------------------------------------------
202 // Check to see if we have a duplicate before we send
203 //----------------------------------------------------------------------------------------------------
204 if ($this->_checkDupe($params['invoiceID'])) {
205 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.');
207 //----------------------------------------------------------------------------------------------------
208 // Convert to XML using function provided by payment processor
209 //----------------------------------------------------------------------------------------------------
210 $requestxml = lphp
::buildXML($requestFields);
214 /*----------------------------------------------------------------------------------------------------
215 // Send to the payment information using cURL
216 /----------------------------------------------------------------------------------------------------
220 $ch = curl_init($host);
222 return self
::errorExit(9004, 'Could not initiate connection to payment gateway');
226 curl_setopt($ch, CURLOPT_POST
, 1);
227 curl_setopt($ch, CURLOPT_POSTFIELDS
, $requestxml);
228 curl_setopt($ch, CURLOPT_SSLCERT
, $key);
229 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST
, CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'verifySSL') ?
2 : 0);
230 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER
, CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'verifySSL'));
231 // return the result on success, FALSE on failure
232 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, 1);
233 curl_setopt($ch, CURLOPT_TIMEOUT
, 36000);
234 // ensures any Location headers are followed
235 if (ini_get('open_basedir') == '' && ini_get('safe_mode') == 'Off') {
236 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, 1);
239 // Send the data out over the wire
240 //--------------------------------
241 $responseData = curl_exec($ch);
243 //----------------------------------------------------------------------------------------------------
244 // See if we had a curl error - if so tell 'em and bail out
246 // NOTE: curl_error does not return a logical value (see its documentation), but
247 // a string, which is empty when there was no error.
248 //----------------------------------------------------------------------------------------------------
249 if ((curl_errno($ch) > 0) ||
(strlen(curl_error($ch)) > 0)) {
250 $errorNum = curl_errno($ch);
251 $errorDesc = curl_error($ch);
253 // Paranoia - in the unlikley event that 'curl' errno fails
257 // Paranoia - in the unlikley event that 'curl' error fails
258 if (strlen($errorDesc) == 0)
259 $errorDesc = "Connection to payment gateway failed";
260 if ($errorNum == 60) {
261 return self
::errorExit($errorNum, "Curl error - " . $errorDesc . " Try this link for more information http://curl.haxx.se/docs/sslcerts.html");
264 return self
::errorExit($errorNum, "Curl error - " . $errorDesc . " your key is located at " . $key . " the url is " . $host . " xml is " . $requestxml . " processor response = " . $processorResponse);
267 //----------------------------------------------------------------------------------------------------
268 // If null data returned - tell 'em and bail out
270 // NOTE: You will not necessarily get a string back, if the request failed for
271 // any reason, the return value will be the boolean false.
272 //----------------------------------------------------------------------------------------------------
273 if (($responseData === FALSE) ||
(strlen($responseData) == 0)) {
274 return self
::errorExit(9006, "Error: Connection to payment gateway failed - no data returned.");
277 //----------------------------------------------------------------------------------------------------
278 // If gateway returned no data - tell 'em and bail out
279 //----------------------------------------------------------------------------------------------------
280 if (empty($responseData)) {
281 return self
::errorExit(9007, "Error: No data returned from payment gateway.");
284 //----------------------------------------------------------------------------------------------------
285 // Success so far - close the curl and check the data
286 //----------------------------------------------------------------------------------------------------
289 //----------------------------------------------------------------------------------------------------
290 // Payment successfully sent to gateway - process the response now
291 //----------------------------------------------------------------------------------------------------
293 $processorResponse = lphp
::decodeXML($responseData);
295 // transaction failed, print the reason
296 if ($processorResponse["r_approved"] != "APPROVED") {
297 return self
::errorExit(9009, "Error: [" . $processorResponse['r_error'] . "] - from payment processor");
301 //-----------------------------------------------------------------------------------------------------
302 // Cross-Check - the unique 'TrxnReference' we sent out should match the just received 'TrxnReference'
304 // this section not used as the processor doesn't appear to pass back our invoice no. Code in eWay model if
306 //-----------------------------------------------------------------------------------------------------
311 $params['trxn_result_code'] = $processorResponse['r_message'];
312 $params['trxn_id'] = $processorResponse['r_ref'];
313 CRM_Core_Error
::debug_log_message("r_authresponse " . $processorResponse['r_authresponse']);
314 CRM_Core_Error
::debug_log_message("r_code " . $processorResponse['r_code']);
315 CRM_Core_Error
::debug_log_message("r_tdate " . $processorResponse['r_tdate']);
316 CRM_Core_Error
::debug_log_message("r_avs " . $processorResponse['r_avs']);
317 CRM_Core_Error
::debug_log_message("r_ordernum " . $processorResponse['r_ordernum']);
318 CRM_Core_Error
::debug_log_message("r_error " . $processorResponse['r_error']);
319 CRM_Core_Error
::debug_log_message("csp " . $processorResponse['r_csp']);
320 CRM_Core_Error
::debug_log_message("r_message " . $processorResponse['r_message']);
321 CRM_Core_Error
::debug_log_message("r_ref " . $processorResponse['r_ref']);
322 CRM_Core_Error
::debug_log_message("r_time " . $processorResponse['r_time']);
326 // end function doDirectPayment
329 * Checks to see if invoice_id already exists in db
331 * @param int $invoiceId The ID to check
333 * @return bool True if ID exists, else false
335 public function _checkDupe($invoiceId) {
336 $contribution = new CRM_Contribute_DAO_Contribution();
337 $contribution->invoice_id
= $invoiceId;
338 return $contribution->find();
341 /**************************************************
342 * Produces error message and returns from class
343 **************************************************/
344 public function &errorExit($errorCode = NULL, $errorMessage = NULL) {
345 $e = CRM_Core_Error
::singleton();
348 $e->push($errorCode, 0, NULL, $errorMessage);
351 $e->push(9000, 0, NULL, 'Unknown System Error.');
356 /**************************************************
357 * NOTE: 'doTransferCheckout' not implemented
358 **************************************************/
359 public function doTransferCheckout(&$params, $component) {
360 CRM_Core_Error
::fatal(ts('This function is not implemented'));
363 /********************************************************************************************
364 * This public function checks to see if we have the right processor config values set
366 * NOTE: Called by Events and Contribute to check config params are set prior to trying
367 * register any credit card details
369 * @return null|string
370 * @internal param string $mode the mode we are operating in (live or test) - not used
372 * returns string $errorMsg if any errors found - null if OK
374 ******************************************************************************************
376 // function checkConfig( $mode ) // CiviCRM V1.9 Declaration
377 // CiviCRM V2.0 Declaration
378 public function checkConfig() {
381 if (empty($this->_paymentProcessor
['user_name'])) {
382 $errorMsg[] = ts(' Store Name is not set for this payment processor');
385 if (empty($this->_paymentProcessor
['url_site'])) {
386 $errorMsg[] = ts(' URL is not set for this payment processor');
389 if (!empty($errorMsg)) {
390 return implode('<p>', $errorMsg);
397 // end class CRM_Core_Payment_FirstData