Commit | Line | Data |
---|---|---|
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 | 17 | use 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 | 56 | class 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; | |
8e819886 | 153 | $result = $this->setStatusPaymentPending([]); |
d4cc1ce8 MW |
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) { | |
8e819886 | 159 | $result = $this->setStatusPaymentCompleted($result); |
d4cc1ce8 MW |
160 | return $result; |
161 | } | |
162 | ||
6a488035 | 163 | if ($params['is_recur'] == TRUE) { |
2d296f18 | 164 | throw new CRM_Core_Exception(ts('First Data - recurring payments not implemented')); |
6a488035 TO |
165 | } |
166 | ||
167 | if (!defined('CURLOPT_SSLCERT')) { | |
2d296f18 | 168 | throw new CRM_Core_Exception(ts('%1 - Gateway requires curl with SSL support', [1 => $paymentProcessor])); |
6a488035 TO |
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 | ||
9d451db8 | 187 | // Name and location of certificate file |
6a488035 | 188 | $key = $this->_paymentProcessor['password']; |
9d451db8 | 189 | // Your store number |
6a488035 TO |
190 | $requestFields["configfile"] = $this->_paymentProcessor['user_name']; |
191 | $port = "1129"; | |
192 | $host = $this->_paymentProcessor['url_site'] . ":" . $port . "/LSGSXML"; | |
193 | ||
6a488035 TO |
194 | //---------------------------------------------------------------------------------------------------- |
195 | // Check to see if we have a duplicate before we send | |
196 | //---------------------------------------------------------------------------------------------------- | |
d253aeb8 | 197 | if ($this->checkDupe($params['invoiceID'], CRM_Utils_Array::value('contributionID', $params))) { |
602de060 | 198 | 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 |
199 | } |
200 | //---------------------------------------------------------------------------------------------------- | |
201 | // Convert to XML using function provided by payment processor | |
202 | //---------------------------------------------------------------------------------------------------- | |
203 | $requestxml = lphp::buildXML($requestFields); | |
204 | ||
6a488035 | 205 | /*---------------------------------------------------------------------------------------------------- |
e70a7fc0 TO |
206 | // Send to the payment information using cURL |
207 | /---------------------------------------------------------------------------------------------------- | |
208 | */ | |
6a488035 | 209 | |
6a488035 TO |
210 | $ch = curl_init($host); |
211 | if (!$ch) { | |
602de060 | 212 | throw new PaymentProcessorException('Could not initiate connection to payment gateway', 9004); |
6a488035 TO |
213 | } |
214 | ||
6a488035 TO |
215 | curl_setopt($ch, CURLOPT_POST, 1); |
216 | curl_setopt($ch, CURLOPT_POSTFIELDS, $requestxml); | |
217 | curl_setopt($ch, CURLOPT_SSLCERT, $key); | |
aaffa79f | 218 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, Civi::settings()->get('verifySSL') ? 2 : 0); |
219 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, Civi::settings()->get('verifySSL')); | |
6a488035 TO |
220 | // return the result on success, FALSE on failure |
221 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | |
222 | curl_setopt($ch, CURLOPT_TIMEOUT, 36000); | |
223 | // ensures any Location headers are followed | |
4d03ddb9 | 224 | if (ini_get('open_basedir') == '') { |
439b9688 LS |
225 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); |
226 | } | |
6a488035 TO |
227 | |
228 | // Send the data out over the wire | |
229 | //-------------------------------- | |
230 | $responseData = curl_exec($ch); | |
231 | ||
232 | //---------------------------------------------------------------------------------------------------- | |
233 | // See if we had a curl error - if so tell 'em and bail out | |
234 | // | |
235 | // NOTE: curl_error does not return a logical value (see its documentation), but | |
236 | // a string, which is empty when there was no error. | |
237 | //---------------------------------------------------------------------------------------------------- | |
238 | if ((curl_errno($ch) > 0) || (strlen(curl_error($ch)) > 0)) { | |
239 | $errorNum = curl_errno($ch); | |
240 | $errorDesc = curl_error($ch); | |
241 | ||
242 | // Paranoia - in the unlikley event that 'curl' errno fails | |
2aa397bc TO |
243 | if ($errorNum == 0) { |
244 | $errorNum = 9005; | |
245 | } | |
6a488035 TO |
246 | |
247 | // Paranoia - in the unlikley event that 'curl' error fails | |
2aa397bc TO |
248 | if (strlen($errorDesc) == 0) { |
249 | $errorDesc = "Connection to payment gateway failed"; | |
250 | } | |
6a488035 | 251 | if ($errorNum == 60) { |
602de060 | 252 | throw new PaymentProcessorException("Curl error - " . $errorDesc . ' Try this link for more information http://curl.haxx.se/docs/sslcerts.html', $errorNum); |
6a488035 TO |
253 | } |
254 | ||
602de060 | 255 | 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 |
256 | } |
257 | ||
258 | //---------------------------------------------------------------------------------------------------- | |
259 | // If null data returned - tell 'em and bail out | |
260 | // | |
261 | // NOTE: You will not necessarily get a string back, if the request failed for | |
262 | // any reason, the return value will be the boolean false. | |
263 | //---------------------------------------------------------------------------------------------------- | |
264 | if (($responseData === FALSE) || (strlen($responseData) == 0)) { | |
602de060 | 265 | throw new PaymentProcessorException('Error: Connection to payment gateway failed - no data returned.', 9006); |
6a488035 TO |
266 | } |
267 | ||
268 | //---------------------------------------------------------------------------------------------------- | |
269 | // If gateway returned no data - tell 'em and bail out | |
270 | //---------------------------------------------------------------------------------------------------- | |
271 | if (empty($responseData)) { | |
602de060 | 272 | throw new PaymentProcessorException('Error: No data returned from payment gateway.', 9007); |
6a488035 TO |
273 | } |
274 | ||
275 | //---------------------------------------------------------------------------------------------------- | |
276 | // Success so far - close the curl and check the data | |
277 | //---------------------------------------------------------------------------------------------------- | |
278 | curl_close($ch); | |
279 | ||
280 | //---------------------------------------------------------------------------------------------------- | |
ceb10dc7 | 281 | // Payment successfully sent to gateway - process the response now |
6a488035 TO |
282 | //---------------------------------------------------------------------------------------------------- |
283 | // | |
284 | $processorResponse = lphp::decodeXML($responseData); | |
285 | ||
286 | // transaction failed, print the reason | |
602de060 | 287 | if ($processorResponse['r_approved'] !== "APPROVED") { |
288 | throw new PaymentProcessorException('Error: [' . $processorResponse['r_error'] . '] - from payment processor', 9009); | |
6a488035 TO |
289 | } |
290 | else { | |
291 | ||
292 | //----------------------------------------------------------------------------------------------------- | |
293 | // Cross-Check - the unique 'TrxnReference' we sent out should match the just received 'TrxnReference' | |
294 | // | |
295 | // this section not used as the processor doesn't appear to pass back our invoice no. Code in eWay model if | |
296 | // used later | |
297 | //----------------------------------------------------------------------------------------------------- | |
298 | ||
299 | //============= | |
300 | // Success ! | |
301 | //============= | |
302 | $params['trxn_result_code'] = $processorResponse['r_message']; | |
8e819886 MW |
303 | $result['trxn_id'] = $processorResponse['r_ref']; |
304 | $result = $this->setStatusPaymentCompleted($result); | |
6a488035 TO |
305 | CRM_Core_Error::debug_log_message("r_authresponse " . $processorResponse['r_authresponse']); |
306 | CRM_Core_Error::debug_log_message("r_code " . $processorResponse['r_code']); | |
307 | CRM_Core_Error::debug_log_message("r_tdate " . $processorResponse['r_tdate']); | |
308 | CRM_Core_Error::debug_log_message("r_avs " . $processorResponse['r_avs']); | |
309 | CRM_Core_Error::debug_log_message("r_ordernum " . $processorResponse['r_ordernum']); | |
310 | CRM_Core_Error::debug_log_message("r_error " . $processorResponse['r_error']); | |
311 | CRM_Core_Error::debug_log_message("csp " . $processorResponse['r_csp']); | |
312 | CRM_Core_Error::debug_log_message("r_message " . $processorResponse['r_message']); | |
313 | CRM_Core_Error::debug_log_message("r_ref " . $processorResponse['r_ref']); | |
314 | CRM_Core_Error::debug_log_message("r_time " . $processorResponse['r_time']); | |
8e819886 | 315 | return $result; |
6a488035 TO |
316 | } |
317 | } | |
518fa0ee | 318 | |
d5cc0fc2 | 319 | /** |
fe482240 | 320 | * This public function checks to see if we have the right processor config values set. |
6a488035 TO |
321 | * |
322 | * NOTE: Called by Events and Contribute to check config params are set prior to trying | |
323 | * register any credit card details | |
324 | * | |
77b97be7 EM |
325 | * @return null|string |
326 | * @internal param string $mode the mode we are operating in (live or test) - not used | |
6a488035 TO |
327 | * |
328 | * returns string $errorMsg if any errors found - null if OK | |
329 | * | |
d5cc0fc2 | 330 | * function checkConfig( $mode ) CiviCRM V1.9 Declaration |
331 | * CiviCRM V2.0 Declaration | |
77b97be7 | 332 | */ |
00be9182 | 333 | public function checkConfig() { |
be2fb01f | 334 | $errorMsg = []; |
6a488035 TO |
335 | |
336 | if (empty($this->_paymentProcessor['user_name'])) { | |
337 | $errorMsg[] = ts(' Store Name is not set for this payment processor'); | |
338 | } | |
339 | ||
340 | if (empty($this->_paymentProcessor['url_site'])) { | |
341 | $errorMsg[] = ts(' URL is not set for this payment processor'); | |
342 | } | |
343 | ||
344 | if (!empty($errorMsg)) { | |
345 | return implode('<p>', $errorMsg); | |
346 | } | |
602de060 | 347 | return NULL; |
6a488035 | 348 | } |
96025800 | 349 | |
6a488035 TO |
350 | } |
351 | // end class CRM_Core_Payment_FirstData |