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