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