INFRA-132 - CRM/Core - phpcbf
[civicrm-core.git] / CRM / Core / Payment / FirstData.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | FirstData Core Payment Module for CiviCRM version 4.6 |
6a488035
TO
5 +--------------------------------------------------------------------+
6 | Licensed to CiviCRM under the Academic Free License version 3.0 |
7 | |
8 | Written & Contributed by Eileen McNaughton - Nov March 2008 |
9 +--------------------------------------------------------------------+
10 | This processor is based heavily on the Eway processor by Peter |
11 |Barwell |
12 | |
13 | |
14 +--------------------------------------------------------------------+
15*/
16
17/**
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.
25
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.
28
29 If an result of class error is returned it is treated as a failure
30
31 -----------------------------------------------------------------------------------------------
32 **/
33
34/*From Payment processor documentation
35For testing purposes, you can use any of the card numbers listed below. The test card numbers
36will not result in any charges to the card. Use these card numbers with any expiration date in the
37future.
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
46*
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
49
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***************************/
52class CRM_Core_Payment_FirstData extends CRM_Core_Payment {
53 # (not used, implicit in the API, might need to convert?)
7da04cde 54 const CHARSET = 'UFT-8';
6a488035
TO
55
56 /**
57 * We only need one instance of this object. So we use the singleton
58 * pattern and cache the instance in this variable
59 *
60 * @var object
61 * @static
62 */
63 static private $_singleton = NULL;
64
65 /**********************************************************
66 * Constructor
67 *
6a0b768e
TO
68 * @param string $mode
69 * The mode of operation: live or test.
6a488035 70 *
77b97be7
EM
71 * @param $paymentProcessor
72 *
73 * @return \CRM_Core_Payment_FirstData *******************************************************
74 */
00be9182 75 public function __construct($mode, &$paymentProcessor) {
6a488035
TO
76 // live or test
77 $this->_mode = $mode;
78 $this->_paymentProcessor = $paymentProcessor;
79 }
80
81 /**
100fef9d 82 * Singleton function used to manage this object
6a488035 83 *
6a0b768e
TO
84 * @param string $mode
85 * The mode of operation: live or test.
6a488035 86 *
77b97be7
EM
87 * @param object $paymentProcessor
88 *
6a488035
TO
89 * @return object
90 * @static
6a488035 91 */
00be9182 92 public static function &singleton($mode, &$paymentProcessor) {
52767de0
EM
93 if (!empty($paymentProcessor['id'])) {
94 $cacheKey = $paymentProcessor['id'];
6a488035 95 }
52767de0
EM
96 else {
97 //@todo eliminated instances of this in favour of id-specific instances.
98 $cacheKey = $mode . '_' . $paymentProcessor['name'];
99 }
100 if (self::$_singleton[$cacheKey] === NULL) {
101 self::$_singleton[$cacheKey] = new CRM_Core_Payment_FirstData($mode, $paymentProcessor);
102 }
103 return self::$_singleton[$cacheKey];
6a488035
TO
104 }
105
106 /**********************************************************
107 * This function is set up and put here to make the mapping of fields
108 * from the params object as visually clear as possible for easy editing
109 *
110 * Comment out irrelevant fields
111 **********************************************************/
00be9182 112 public function mapProcessorFieldstoParams($params) {
6a488035
TO
113 /*concatenate full customer name first - code from EWAY gateway
114 */
115
116 $credit_card_name = $params['first_name'] . " ";
117 if (strlen($params['middle_name']) > 0) {
118 $credit_card_name .= $params['middle_name'] . " ";
119 }
120 $credit_card_name .= $params['last_name'];
121
122 //compile array
123
124 /**********************************************************
125 * Payment Processor field name **fields from $params array ***
126 *******************************************************************/
127
128 $requestFields['cardnumber'] = $params['credit_card_number'];
129 $requestFields['chargetotal'] = $params['amount'];
130 $requestFields['cardexpmonth'] = sprintf('%02d', (int) $params['month']);
131 $requestFields['cardexpyear'] = substr($params['year'], 2, 2);
132 $requestFields['cvmvalue'] = $params['cvv2'];
133 $requestFields['cvmindicator'] = "provided";
134 $requestFields['name'] = $credit_card_name;
135 $requestFields['address1'] = $params['street_address'];
136 $requestFields['city'] = $params['city'];
137 $requestFields['state'] = $params['state_province'];
138 $requestFields['zip'] = $params['postal_code'];
139 $requestFields['country'] = $params['country'];
140 $requestFields['email'] = $params['email'];
141 $requestFields['ip'] = $params['ip_address'];
142 $requestFields['transactionorigin'] = "Eci";
143 #32 character string
144 $requestFields['invoice_number'] = $params['invoiceID'];
145 $requestFields['ordertype'] = $params['payment_action'];
146 $requestFields['comments'] = $params['description'];
147 //**********************set 'result' for live testing **************************
148 // $requestFields[ 'result' ] = ""; #set to "Good", "Decline" or "Duplicate"
149 // $requestFields[ '' ] = $params[ 'qfKey' ];
150 // $requestFields[ '' ] = $params[ 'amount_other' ];
151 // $requestFields[ '' ] = $params[ 'billing_first_name' ];
152 // $requestFields[ '' ] = $params[ 'billing_middle_name' ];
153 // $requestFields[ '' ] = $params[ 'billing_last_name' ];
154
155 // $requestFields[ '' ] = $params[ 'contributionType_name' ];
156 // $requestFields[ '' ] = $params[ 'contributionPageID' ];
157 // $requestFields[ '' ] = $params[ 'contributionType_accounting_code' ];
158 // $requestFields[ '' ] = $params['amount_level' ];
159 // $requestFields[ '' ] = $params['credit_card_type' ];
160 // $requestFields[ 'addrnum' ] = numeric portion of street address - not yet implemented
161 // $requestFields[ 'taxexempt' ] taxexempt status (Y or N) - not implemented
162
163 return $requestFields;
164 }
165
166 /**********************************************************
167 * This function sends request and receives response from
168 * the processor
169 **********************************************************/
00be9182 170 public function doDirectPayment(&$params) {
6a488035
TO
171 if ($params['is_recur'] == TRUE) {
172 CRM_Core_Error::fatal(ts('%1 - recurring payments not implemented', array(1 => $paymentProcessor)));
173 }
174
175 if (!defined('CURLOPT_SSLCERT')) {
176 CRM_Core_Error::fatal(ts('%1 - Gateway requires curl with SSL support', array(1 => $paymentProcessor)));
177 }
178
179 /**********************************************************
180 * Create the array of variables to be sent to the processor from the $params array
181 * passed into this function
182 **********************************************************/
183 $requestFields = self::mapProcessorFieldstoParams($params);
184
185 /**********************************************************
186 * create FirstData request object
187 **********************************************************/
188 require_once 'FirstData/lphp.php';
189 // $mylphp=new lphp;
190
191 /**********************************************************
192 * define variables for connecting with the gateway
193 **********************************************************/
194
195 # Name and location of certificate file
196 $key = $this->_paymentProcessor['password'];
197 # Your store number
198 $requestFields["configfile"] = $this->_paymentProcessor['user_name'];
199 $port = "1129";
200 $host = $this->_paymentProcessor['url_site'] . ":" . $port . "/LSGSXML";
201
6a488035
TO
202 //----------------------------------------------------------------------------------------------------
203 // Check to see if we have a duplicate before we send
204 //----------------------------------------------------------------------------------------------------
205 if ($this->_checkDupe($params['invoiceID'])) {
206 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 //----------------------------------------------------------------------------------------------------
209 // Convert to XML using function provided by payment processor
210 //----------------------------------------------------------------------------------------------------
211 $requestxml = lphp::buildXML($requestFields);
212
6a488035
TO
213 /*----------------------------------------------------------------------------------------------------
214 // Send to the payment information using cURL
215 /----------------------------------------------------------------------------------------------------
216 */
217
6a488035
TO
218 $ch = curl_init($host);
219 if (!$ch) {
220 return self::errorExit(9004, 'Could not initiate connection to payment gateway');
221 }
222
6a488035
TO
223 curl_setopt($ch, CURLOPT_POST, 1);
224 curl_setopt($ch, CURLOPT_POSTFIELDS, $requestxml);
225 curl_setopt($ch, CURLOPT_SSLCERT, $key);
17c04b52 226 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'verifySSL') ? 2 : 0);
6a488035
TO
227 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'verifySSL'));
228 // return the result on success, FALSE on failure
229 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
230 curl_setopt($ch, CURLOPT_TIMEOUT, 36000);
231 // ensures any Location headers are followed
439b9688
LS
232 if (ini_get('open_basedir') == '' && ini_get('safe_mode') == 'Off') {
233 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
234 }
6a488035
TO
235
236 // Send the data out over the wire
237 //--------------------------------
238 $responseData = curl_exec($ch);
239
240 //----------------------------------------------------------------------------------------------------
241 // See if we had a curl error - if so tell 'em and bail out
242 //
243 // NOTE: curl_error does not return a logical value (see its documentation), but
244 // a string, which is empty when there was no error.
245 //----------------------------------------------------------------------------------------------------
246 if ((curl_errno($ch) > 0) || (strlen(curl_error($ch)) > 0)) {
247 $errorNum = curl_errno($ch);
248 $errorDesc = curl_error($ch);
249
250 // Paranoia - in the unlikley event that 'curl' errno fails
2aa397bc
TO
251 if ($errorNum == 0) {
252 $errorNum = 9005;
253 }
6a488035
TO
254
255 // Paranoia - in the unlikley event that 'curl' error fails
2aa397bc
TO
256 if (strlen($errorDesc) == 0) {
257 $errorDesc = "Connection to payment gateway failed";
258 }
6a488035
TO
259 if ($errorNum == 60) {
260 return self::errorExit($errorNum, "Curl error - " . $errorDesc . " Try this link for more information http://curl.haxx.se/docs/sslcerts.html");
261 }
262
263 return self::errorExit($errorNum, "Curl error - " . $errorDesc . " your key is located at " . $key . " the url is " . $host . " xml is " . $requestxml . " processor response = " . $processorResponse);
264 }
265
266 //----------------------------------------------------------------------------------------------------
267 // If null data returned - tell 'em and bail out
268 //
269 // NOTE: You will not necessarily get a string back, if the request failed for
270 // any reason, the return value will be the boolean false.
271 //----------------------------------------------------------------------------------------------------
272 if (($responseData === FALSE) || (strlen($responseData) == 0)) {
273 return self::errorExit(9006, "Error: Connection to payment gateway failed - no data returned.");
274 }
275
276 //----------------------------------------------------------------------------------------------------
277 // If gateway returned no data - tell 'em and bail out
278 //----------------------------------------------------------------------------------------------------
279 if (empty($responseData)) {
280 return self::errorExit(9007, "Error: No data returned from payment gateway.");
281 }
282
283 //----------------------------------------------------------------------------------------------------
284 // Success so far - close the curl and check the data
285 //----------------------------------------------------------------------------------------------------
286 curl_close($ch);
287
288 //----------------------------------------------------------------------------------------------------
ceb10dc7 289 // Payment successfully sent to gateway - process the response now
6a488035
TO
290 //----------------------------------------------------------------------------------------------------
291 //
292 $processorResponse = lphp::decodeXML($responseData);
293
294 // transaction failed, print the reason
295 if ($processorResponse["r_approved"] != "APPROVED") {
296 return self::errorExit(9009, "Error: [" . $processorResponse['r_error'] . "] - from payment processor");
297 }
298 else {
299
300 //-----------------------------------------------------------------------------------------------------
301 // Cross-Check - the unique 'TrxnReference' we sent out should match the just received 'TrxnReference'
302 //
303 // this section not used as the processor doesn't appear to pass back our invoice no. Code in eWay model if
304 // used later
305 //-----------------------------------------------------------------------------------------------------
306
307 //=============
308 // Success !
309 //=============
310 $params['trxn_result_code'] = $processorResponse['r_message'];
311 $params['trxn_id'] = $processorResponse['r_ref'];
312 CRM_Core_Error::debug_log_message("r_authresponse " . $processorResponse['r_authresponse']);
313 CRM_Core_Error::debug_log_message("r_code " . $processorResponse['r_code']);
314 CRM_Core_Error::debug_log_message("r_tdate " . $processorResponse['r_tdate']);
315 CRM_Core_Error::debug_log_message("r_avs " . $processorResponse['r_avs']);
316 CRM_Core_Error::debug_log_message("r_ordernum " . $processorResponse['r_ordernum']);
317 CRM_Core_Error::debug_log_message("r_error " . $processorResponse['r_error']);
318 CRM_Core_Error::debug_log_message("csp " . $processorResponse['r_csp']);
319 CRM_Core_Error::debug_log_message("r_message " . $processorResponse['r_message']);
320 CRM_Core_Error::debug_log_message("r_ref " . $processorResponse['r_ref']);
321 CRM_Core_Error::debug_log_message("r_time " . $processorResponse['r_time']);
322 return $params;
323 }
324 }
325 // end function doDirectPayment
326
327 /**
328 * Checks to see if invoice_id already exists in db
329 *
6a0b768e
TO
330 * @param int $invoiceId
331 * The ID to check.
6a488035
TO
332 *
333 * @return bool True if ID exists, else false
334 */
00be9182 335 public function _checkDupe($invoiceId) {
6a488035
TO
336 $contribution = new CRM_Contribute_DAO_Contribution();
337 $contribution->invoice_id = $invoiceId;
338 return $contribution->find();
339 }
340
341 /**************************************************
342 * Produces error message and returns from class
343 **************************************************/
00be9182 344 public function &errorExit($errorCode = NULL, $errorMessage = NULL) {
6a488035
TO
345 $e = CRM_Core_Error::singleton();
346
347 if ($errorCode) {
348 $e->push($errorCode, 0, NULL, $errorMessage);
349 }
350 else {
351 $e->push(9000, 0, NULL, 'Unknown System Error.');
352 }
353 return $e;
354 }
355
356 /**************************************************
357 * NOTE: 'doTransferCheckout' not implemented
358 **************************************************/
00be9182 359 public function doTransferCheckout(&$params, $component) {
6a488035
TO
360 CRM_Core_Error::fatal(ts('This function is not implemented'));
361 }
362
363 /********************************************************************************************
364 * This public function checks to see if we have the right processor config values set
365 *
366 * NOTE: Called by Events and Contribute to check config params are set prior to trying
367 * register any credit card details
368 *
77b97be7
EM
369 * @return null|string
370 * @internal param string $mode the mode we are operating in (live or test) - not used
6a488035
TO
371 *
372 * returns string $errorMsg if any errors found - null if OK
373 *
77b97be7
EM
374 ******************************************************************************************
375 */
6a488035
TO
376 // function checkConfig( $mode ) // CiviCRM V1.9 Declaration
377 // CiviCRM V2.0 Declaration
00be9182 378 public function checkConfig() {
6a488035
TO
379 $errorMsg = array();
380
381 if (empty($this->_paymentProcessor['user_name'])) {
382 $errorMsg[] = ts(' Store Name is not set for this payment processor');
383 }
384
385 if (empty($this->_paymentProcessor['url_site'])) {
386 $errorMsg[] = ts(' URL is not set for this payment processor');
387 }
388
389 if (!empty($errorMsg)) {
390 return implode('<p>', $errorMsg);
391 }
392 else {
393 return NULL;
394 }
395 }
396}
397// end class CRM_Core_Payment_FirstData