Merge pull request #4892 from colemanw/INFRA-132
[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
c866eb5f
TO
34/**
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
38 * future.
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
47 *
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
50 *
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 * **************************
53 */
6a488035
TO
54class CRM_Core_Payment_FirstData extends CRM_Core_Payment {
55 # (not used, implicit in the API, might need to convert?)
7da04cde 56 const CHARSET = 'UFT-8';
6a488035
TO
57
58 /**
59 * We only need one instance of this object. So we use the singleton
60 * pattern and cache the instance in this variable
61 *
62 * @var object
63 * @static
64 */
65 static private $_singleton = NULL;
66
67 /**********************************************************
68 * Constructor
69 *
6a0b768e
TO
70 * @param string $mode
71 * The mode of operation: live or test.
6a488035 72 *
77b97be7
EM
73 * @param $paymentProcessor
74 *
75 * @return \CRM_Core_Payment_FirstData *******************************************************
76 */
00be9182 77 public function __construct($mode, &$paymentProcessor) {
6a488035
TO
78 // live or test
79 $this->_mode = $mode;
80 $this->_paymentProcessor = $paymentProcessor;
81 }
82
6a488035
TO
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
86 *
87 * Comment out irrelevant fields
88 **********************************************************/
00be9182 89 public function mapProcessorFieldstoParams($params) {
6a488035
TO
90 /*concatenate full customer name first - code from EWAY gateway
91 */
92
93 $credit_card_name = $params['first_name'] . " ";
94 if (strlen($params['middle_name']) > 0) {
95 $credit_card_name .= $params['middle_name'] . " ";
96 }
97 $credit_card_name .= $params['last_name'];
98
99 //compile array
100
101 /**********************************************************
102 * Payment Processor field name **fields from $params array ***
103 *******************************************************************/
104
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";
120 #32 character string
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' ];
131
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
139
140 return $requestFields;
141 }
142
143 /**********************************************************
144 * This function sends request and receives response from
145 * the processor
146 **********************************************************/
00be9182 147 public function doDirectPayment(&$params) {
6a488035
TO
148 if ($params['is_recur'] == TRUE) {
149 CRM_Core_Error::fatal(ts('%1 - recurring payments not implemented', array(1 => $paymentProcessor)));
150 }
151
152 if (!defined('CURLOPT_SSLCERT')) {
153 CRM_Core_Error::fatal(ts('%1 - Gateway requires curl with SSL support', array(1 => $paymentProcessor)));
154 }
155
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);
161
162 /**********************************************************
163 * create FirstData request object
164 **********************************************************/
165 require_once 'FirstData/lphp.php';
166 // $mylphp=new lphp;
167
168 /**********************************************************
169 * define variables for connecting with the gateway
170 **********************************************************/
171
172 # Name and location of certificate file
173 $key = $this->_paymentProcessor['password'];
174 # Your store number
175 $requestFields["configfile"] = $this->_paymentProcessor['user_name'];
176 $port = "1129";
177 $host = $this->_paymentProcessor['url_site'] . ":" . $port . "/LSGSXML";
178
6a488035
TO
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.');
184 }
185 //----------------------------------------------------------------------------------------------------
186 // Convert to XML using function provided by payment processor
187 //----------------------------------------------------------------------------------------------------
188 $requestxml = lphp::buildXML($requestFields);
189
6a488035
TO
190 /*----------------------------------------------------------------------------------------------------
191 // Send to the payment information using cURL
192 /----------------------------------------------------------------------------------------------------
193 */
194
6a488035
TO
195 $ch = curl_init($host);
196 if (!$ch) {
197 return self::errorExit(9004, 'Could not initiate connection to payment gateway');
198 }
199
6a488035
TO
200 curl_setopt($ch, CURLOPT_POST, 1);
201 curl_setopt($ch, CURLOPT_POSTFIELDS, $requestxml);
202 curl_setopt($ch, CURLOPT_SSLCERT, $key);
17c04b52 203 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'verifySSL') ? 2 : 0);
6a488035
TO
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
439b9688
LS
209 if (ini_get('open_basedir') == '' && ini_get('safe_mode') == 'Off') {
210 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
211 }
6a488035
TO
212
213 // Send the data out over the wire
214 //--------------------------------
215 $responseData = curl_exec($ch);
216
217 //----------------------------------------------------------------------------------------------------
218 // See if we had a curl error - if so tell 'em and bail out
219 //
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);
226
227 // Paranoia - in the unlikley event that 'curl' errno fails
2aa397bc
TO
228 if ($errorNum == 0) {
229 $errorNum = 9005;
230 }
6a488035
TO
231
232 // Paranoia - in the unlikley event that 'curl' error fails
2aa397bc
TO
233 if (strlen($errorDesc) == 0) {
234 $errorDesc = "Connection to payment gateway failed";
235 }
6a488035
TO
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");
238 }
239
240 return self::errorExit($errorNum, "Curl error - " . $errorDesc . " your key is located at " . $key . " the url is " . $host . " xml is " . $requestxml . " processor response = " . $processorResponse);
241 }
242
243 //----------------------------------------------------------------------------------------------------
244 // If null data returned - tell 'em and bail out
245 //
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.");
251 }
252
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.");
258 }
259
260 //----------------------------------------------------------------------------------------------------
261 // Success so far - close the curl and check the data
262 //----------------------------------------------------------------------------------------------------
263 curl_close($ch);
264
265 //----------------------------------------------------------------------------------------------------
ceb10dc7 266 // Payment successfully sent to gateway - process the response now
6a488035
TO
267 //----------------------------------------------------------------------------------------------------
268 //
269 $processorResponse = lphp::decodeXML($responseData);
270
271 // transaction failed, print the reason
272 if ($processorResponse["r_approved"] != "APPROVED") {
273 return self::errorExit(9009, "Error: [" . $processorResponse['r_error'] . "] - from payment processor");
274 }
275 else {
276
277 //-----------------------------------------------------------------------------------------------------
278 // Cross-Check - the unique 'TrxnReference' we sent out should match the just received 'TrxnReference'
279 //
280 // this section not used as the processor doesn't appear to pass back our invoice no. Code in eWay model if
281 // used later
282 //-----------------------------------------------------------------------------------------------------
283
284 //=============
285 // Success !
286 //=============
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']);
299 return $params;
300 }
301 }
302 // end function doDirectPayment
303
304 /**
305 * Checks to see if invoice_id already exists in db
306 *
6a0b768e
TO
307 * @param int $invoiceId
308 * The ID to check.
6a488035
TO
309 *
310 * @return bool True if ID exists, else false
311 */
00be9182 312 public function _checkDupe($invoiceId) {
6a488035
TO
313 $contribution = new CRM_Contribute_DAO_Contribution();
314 $contribution->invoice_id = $invoiceId;
315 return $contribution->find();
316 }
317
318 /**************************************************
319 * Produces error message and returns from class
320 **************************************************/
00be9182 321 public function &errorExit($errorCode = NULL, $errorMessage = NULL) {
6a488035
TO
322 $e = CRM_Core_Error::singleton();
323
324 if ($errorCode) {
325 $e->push($errorCode, 0, NULL, $errorMessage);
326 }
327 else {
328 $e->push(9000, 0, NULL, 'Unknown System Error.');
329 }
330 return $e;
331 }
332
333 /**************************************************
334 * NOTE: 'doTransferCheckout' not implemented
335 **************************************************/
00be9182 336 public function doTransferCheckout(&$params, $component) {
6a488035
TO
337 CRM_Core_Error::fatal(ts('This function is not implemented'));
338 }
339
340 /********************************************************************************************
341 * This public function checks to see if we have the right processor config values set
342 *
343 * NOTE: Called by Events and Contribute to check config params are set prior to trying
344 * register any credit card details
345 *
77b97be7
EM
346 * @return null|string
347 * @internal param string $mode the mode we are operating in (live or test) - not used
6a488035
TO
348 *
349 * returns string $errorMsg if any errors found - null if OK
350 *
77b97be7
EM
351 ******************************************************************************************
352 */
6a488035
TO
353 // function checkConfig( $mode ) // CiviCRM V1.9 Declaration
354 // CiviCRM V2.0 Declaration
00be9182 355 public function checkConfig() {
6a488035
TO
356 $errorMsg = array();
357
358 if (empty($this->_paymentProcessor['user_name'])) {
359 $errorMsg[] = ts(' Store Name is not set for this payment processor');
360 }
361
362 if (empty($this->_paymentProcessor['url_site'])) {
363 $errorMsg[] = ts(' URL is not set for this payment processor');
364 }
365
366 if (!empty($errorMsg)) {
367 return implode('<p>', $errorMsg);
368 }
369 else {
370 return NULL;
371 }
372 }
373}
374// end class CRM_Core_Payment_FirstData