Merge pull request #23206 from braders/feature/pledge-payment-nodefaults
[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 16
602de060 17use Civi\Payment\Exception\PaymentProcessorException;
18
6a488035 19/**
353ffa53
TO
20 * Note that in order to use FirstData / LinkPoint you need a certificate (.pem) file issued by them
21 * and a store number. You can configure the path to the certificate and the store number
22 * through the front end of civiCRM. The path is as seen by the server not the url
23 * -----------------------------------------------------------------------------------------------
24 * The basic functionality of this processor is that variables from the $params object are transformed
25 * into xml using a function provided by the processor. The xml is submitted to the processor's https site
26 * using curl and the response is translated back into an array using the processor's function.
27 *
28 * If an array ($params) is returned to the calling function it is treated as a success and the values from
29 * the array are merged into the calling functions array.
30 *
31 * If an result of class error is returned it is treated as a failure
32 *
33 * -----------------------------------------------------------------------------------------------
d5cc0fc2 34 */
6a488035 35
c866eb5f
TO
36/**
37 * From Payment processor documentation
38 * For testing purposes, you can use any of the card numbers listed below. The test card numbers
39 * will not result in any charges to the card. Use these card numbers with any expiration date in the
40 * future.
41 * Visa Level 2 - 4275330012345675 (replies with a referral message)
42 * JCB - 3566007770003510
43 * Discover - 6011000993010978
44 * MasterCard - 5424180279791765
45 * Visa - 4005550000000019 or 4111111111111111
46 * MasterCard Level 2 - 5404980000008386
47 * Diners - 36555565010005
48 * Amex - 372700997251009
49 *
50 * **************************
51 * 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
52 *
53 * For live testing uncomment the result field below and set the value to the response you wish to get from the payment processor
54 * **************************
55 */
6a488035 56class CRM_Core_Payment_FirstData extends CRM_Core_Payment {
6a488035 57
d5cc0fc2 58 /**
fe482240 59 * Constructor.
6a488035 60 *
6a0b768e
TO
61 * @param string $mode
62 * The mode of operation: live or test.
6a488035 63 *
602de060 64 * @param array $paymentProcessor
77b97be7 65 */
00be9182 66 public function __construct($mode, &$paymentProcessor) {
6a488035
TO
67 $this->_mode = $mode;
68 $this->_paymentProcessor = $paymentProcessor;
69 }
70
d5cc0fc2 71 /**
54957108 72 * Map fields from params array.
73 *
6a488035 74 * This function is set up and put here to make the mapping of fields
54957108 75 * as visually clear as possible for easy editing
6a488035
TO
76 *
77 * Comment out irrelevant fields
54957108 78 *
79 * @param array $params
80 *
81 * @return array
d5cc0fc2 82 */
00be9182 83 public function mapProcessorFieldstoParams($params) {
6a488035 84 /*concatenate full customer name first - code from EWAY gateway
e70a7fc0 85 */
6a488035 86
602de060 87 $credit_card_name = $params['first_name'] . ' ';
6a488035 88 if (strlen($params['middle_name']) > 0) {
602de060 89 $credit_card_name .= $params['middle_name'] . ' ';
6a488035
TO
90 }
91 $credit_card_name .= $params['last_name'];
92
93 //compile array
94
95 /**********************************************************
96 * Payment Processor field name **fields from $params array ***
97 *******************************************************************/
98
99 $requestFields['cardnumber'] = $params['credit_card_number'];
100 $requestFields['chargetotal'] = $params['amount'];
101 $requestFields['cardexpmonth'] = sprintf('%02d', (int) $params['month']);
102 $requestFields['cardexpyear'] = substr($params['year'], 2, 2);
103 $requestFields['cvmvalue'] = $params['cvv2'];
104 $requestFields['cvmindicator'] = "provided";
105 $requestFields['name'] = $credit_card_name;
106 $requestFields['address1'] = $params['street_address'];
107 $requestFields['city'] = $params['city'];
108 $requestFields['state'] = $params['state_province'];
109 $requestFields['zip'] = $params['postal_code'];
110 $requestFields['country'] = $params['country'];
111 $requestFields['email'] = $params['email'];
112 $requestFields['ip'] = $params['ip_address'];
113 $requestFields['transactionorigin'] = "Eci";
9d451db8 114 // 32 character string
6a488035 115 $requestFields['invoice_number'] = $params['invoiceID'];
aefd7f6b 116 $requestFields['ordertype'] = 'Sale';
6a488035
TO
117 $requestFields['comments'] = $params['description'];
118 //**********************set 'result' for live testing **************************
119 // $requestFields[ 'result' ] = ""; #set to "Good", "Decline" or "Duplicate"
120 // $requestFields[ '' ] = $params[ 'qfKey' ];
121 // $requestFields[ '' ] = $params[ 'amount_other' ];
122 // $requestFields[ '' ] = $params[ 'billing_first_name' ];
123 // $requestFields[ '' ] = $params[ 'billing_middle_name' ];
124 // $requestFields[ '' ] = $params[ 'billing_last_name' ];
125
126 // $requestFields[ '' ] = $params[ 'contributionType_name' ];
127 // $requestFields[ '' ] = $params[ 'contributionPageID' ];
128 // $requestFields[ '' ] = $params[ 'contributionType_accounting_code' ];
129 // $requestFields[ '' ] = $params['amount_level' ];
130 // $requestFields[ '' ] = $params['credit_card_type' ];
131 // $requestFields[ 'addrnum' ] = numeric portion of street address - not yet implemented
132 // $requestFields[ 'taxexempt' ] taxexempt status (Y or N) - not implemented
133
134 return $requestFields;
135 }
136
d5cc0fc2 137 /**
6a488035
TO
138 * This function sends request and receives response from
139 * the processor
54957108 140 *
d4cc1ce8
MW
141 * @param array|PropertyBag $params
142 *
143 * @param string $component
144 *
145 * @return array
146 * Result array (containing at least the key payment_status_id)
54957108 147 *
d4cc1ce8 148 * @throws \Civi\Payment\Exception\PaymentProcessorException
d5cc0fc2 149 */
d4cc1ce8
MW
150 public function doPayment(&$params, $component = 'contribute') {
151 $propertyBag = \Civi\Payment\PropertyBag::cast($params);
152 $this->_component = $component;
153 $statuses = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id', 'validate');
154
155 // If we have a $0 amount, skip call to processor and set payment_status to Completed.
156 // Conceivably a processor might override this - perhaps for setting up a token - but we don't
157 // have an example of that at the moment.
158 if ($propertyBag->getAmount() == 0) {
159 $result['payment_status_id'] = array_search('Completed', $statuses);
160 $result['payment_status'] = 'Completed';
161 return $result;
162 }
163
6a488035 164 if ($params['is_recur'] == TRUE) {
2d296f18 165 throw new CRM_Core_Exception(ts('First Data - recurring payments not implemented'));
6a488035
TO
166 }
167
168 if (!defined('CURLOPT_SSLCERT')) {
2d296f18 169 throw new CRM_Core_Exception(ts('%1 - Gateway requires curl with SSL support', [1 => $paymentProcessor]));
6a488035
TO
170 }
171
172 /**********************************************************
173 * Create the array of variables to be sent to the processor from the $params array
174 * passed into this function
175 **********************************************************/
176 $requestFields = self::mapProcessorFieldstoParams($params);
177
178 /**********************************************************
179 * create FirstData request object
180 **********************************************************/
181 require_once 'FirstData/lphp.php';
182 // $mylphp=new lphp;
183
184 /**********************************************************
185 * define variables for connecting with the gateway
186 **********************************************************/
187
9d451db8 188 // Name and location of certificate file
6a488035 189 $key = $this->_paymentProcessor['password'];
9d451db8 190 // Your store number
6a488035
TO
191 $requestFields["configfile"] = $this->_paymentProcessor['user_name'];
192 $port = "1129";
193 $host = $this->_paymentProcessor['url_site'] . ":" . $port . "/LSGSXML";
194
6a488035
TO
195 //----------------------------------------------------------------------------------------------------
196 // Check to see if we have a duplicate before we send
197 //----------------------------------------------------------------------------------------------------
d253aeb8 198 if ($this->checkDupe($params['invoiceID'], CRM_Utils_Array::value('contributionID', $params))) {
602de060 199 throw new PaymentProcessorException('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.', 9003);
6a488035
TO
200 }
201 //----------------------------------------------------------------------------------------------------
202 // Convert to XML using function provided by payment processor
203 //----------------------------------------------------------------------------------------------------
204 $requestxml = lphp::buildXML($requestFields);
205
6a488035 206 /*----------------------------------------------------------------------------------------------------
e70a7fc0
TO
207 // Send to the payment information using cURL
208 /----------------------------------------------------------------------------------------------------
209 */
6a488035 210
6a488035
TO
211 $ch = curl_init($host);
212 if (!$ch) {
602de060 213 throw new PaymentProcessorException('Could not initiate connection to payment gateway', 9004);
6a488035
TO
214 }
215
6a488035
TO
216 curl_setopt($ch, CURLOPT_POST, 1);
217 curl_setopt($ch, CURLOPT_POSTFIELDS, $requestxml);
218 curl_setopt($ch, CURLOPT_SSLCERT, $key);
aaffa79f 219 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, Civi::settings()->get('verifySSL') ? 2 : 0);
220 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, Civi::settings()->get('verifySSL'));
6a488035
TO
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
4d03ddb9 225 if (ini_get('open_basedir') == '') {
439b9688
LS
226 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
227 }
6a488035
TO
228
229 // Send the data out over the wire
230 //--------------------------------
231 $responseData = curl_exec($ch);
232
233 //----------------------------------------------------------------------------------------------------
234 // See if we had a curl error - if so tell 'em and bail out
235 //
236 // NOTE: curl_error does not return a logical value (see its documentation), but
237 // a string, which is empty when there was no error.
238 //----------------------------------------------------------------------------------------------------
239 if ((curl_errno($ch) > 0) || (strlen(curl_error($ch)) > 0)) {
240 $errorNum = curl_errno($ch);
241 $errorDesc = curl_error($ch);
242
243 // Paranoia - in the unlikley event that 'curl' errno fails
2aa397bc
TO
244 if ($errorNum == 0) {
245 $errorNum = 9005;
246 }
6a488035
TO
247
248 // Paranoia - in the unlikley event that 'curl' error fails
2aa397bc
TO
249 if (strlen($errorDesc) == 0) {
250 $errorDesc = "Connection to payment gateway failed";
251 }
6a488035 252 if ($errorNum == 60) {
602de060 253 throw new PaymentProcessorException("Curl error - " . $errorDesc . ' Try this link for more information http://curl.haxx.se/docs/sslcerts.html', $errorNum);
6a488035
TO
254 }
255
602de060 256 throw new PaymentProcessorException('Curl error - ' . $errorDesc . ' your key is located at ' . $key . ' the url is ' . $host . ' xml is ' . $requestxml . ' processor response = ' . $processorResponse, $errorNum);
6a488035
TO
257 }
258
259 //----------------------------------------------------------------------------------------------------
260 // If null data returned - tell 'em and bail out
261 //
262 // NOTE: You will not necessarily get a string back, if the request failed for
263 // any reason, the return value will be the boolean false.
264 //----------------------------------------------------------------------------------------------------
265 if (($responseData === FALSE) || (strlen($responseData) == 0)) {
602de060 266 throw new PaymentProcessorException('Error: Connection to payment gateway failed - no data returned.', 9006);
6a488035
TO
267 }
268
269 //----------------------------------------------------------------------------------------------------
270 // If gateway returned no data - tell 'em and bail out
271 //----------------------------------------------------------------------------------------------------
272 if (empty($responseData)) {
602de060 273 throw new PaymentProcessorException('Error: No data returned from payment gateway.', 9007);
6a488035
TO
274 }
275
276 //----------------------------------------------------------------------------------------------------
277 // Success so far - close the curl and check the data
278 //----------------------------------------------------------------------------------------------------
279 curl_close($ch);
280
281 //----------------------------------------------------------------------------------------------------
ceb10dc7 282 // Payment successfully sent to gateway - process the response now
6a488035
TO
283 //----------------------------------------------------------------------------------------------------
284 //
285 $processorResponse = lphp::decodeXML($responseData);
286
287 // transaction failed, print the reason
602de060 288 if ($processorResponse['r_approved'] !== "APPROVED") {
289 throw new PaymentProcessorException('Error: [' . $processorResponse['r_error'] . '] - from payment processor', 9009);
6a488035
TO
290 }
291 else {
292
293 //-----------------------------------------------------------------------------------------------------
294 // Cross-Check - the unique 'TrxnReference' we sent out should match the just received 'TrxnReference'
295 //
296 // this section not used as the processor doesn't appear to pass back our invoice no. Code in eWay model if
297 // used later
298 //-----------------------------------------------------------------------------------------------------
299
300 //=============
301 // Success !
302 //=============
303 $params['trxn_result_code'] = $processorResponse['r_message'];
304 $params['trxn_id'] = $processorResponse['r_ref'];
d4cc1ce8
MW
305 $params['payment_status_id'] = array_search('Completed', $statuses);
306 $params['payment_status'] = 'Completed';
6a488035
TO
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 }
518fa0ee 320
d5cc0fc2 321 /**
fe482240 322 * This public function checks to see if we have the right processor config values set.
6a488035
TO
323 *
324 * NOTE: Called by Events and Contribute to check config params are set prior to trying
325 * register any credit card details
326 *
77b97be7
EM
327 * @return null|string
328 * @internal param string $mode the mode we are operating in (live or test) - not used
6a488035
TO
329 *
330 * returns string $errorMsg if any errors found - null if OK
331 *
d5cc0fc2 332 * function checkConfig( $mode ) CiviCRM V1.9 Declaration
333 * CiviCRM V2.0 Declaration
77b97be7 334 */
00be9182 335 public function checkConfig() {
be2fb01f 336 $errorMsg = [];
6a488035
TO
337
338 if (empty($this->_paymentProcessor['user_name'])) {
339 $errorMsg[] = ts(' Store Name is not set for this payment processor');
340 }
341
342 if (empty($this->_paymentProcessor['url_site'])) {
343 $errorMsg[] = ts(' URL is not set for this payment processor');
344 }
345
346 if (!empty($errorMsg)) {
347 return implode('<p>', $errorMsg);
348 }
602de060 349 return NULL;
6a488035 350 }
96025800 351
6a488035
TO
352}
353// end class CRM_Core_Payment_FirstData