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