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