dev/core#1945 Fix recur access regression
[civicrm-core.git] / CRM / Core / Payment / eWAY.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /*
13 +--------------------------------------------------------------------+
14 | eWAY Core Payment Module for CiviCRM version 5 & 1.9 |
15 +--------------------------------------------------------------------+
16 | Licensed to CiviCRM under the Academic Free License version 3.0 |
17 | |
18 | Written & Contributed by Dolphin Software P/L - March 2008 |
19 +--------------------------------------------------------------------+
20 | |
21 | This file is a part of CiviCRM. |
22 | |
23 | This code was initially based on the recent PayJunction module |
24 | contributed by Phase2 Technology, and then plundered bits from |
25 | the AuthorizeNet module contributed by Ideal Solution, and |
26 | referenced the eWAY code in Drupal 5.7's ecommerce-5.x-3.4 and |
27 | ecommerce-5.x-4.x-dev modules. |
28 | |
29 | Plus a bit of our own code of course - Peter Barwell |
30 | contact PB@DolphinSoftware.com.au if required. |
31 | |
32 | NOTE: This initial eWAY module does not yet allow for recurring |
33 | payments - contact Peter Barwell or add yourself (or both) |
34 | |
35 | NOTE: The eWAY gateway only allows a single currency per account |
36 | (per eWAY CustomerID) ie you can only have one currency per |
37 | added Payment Processor. |
38 | The only way to add multi-currency is to code it so that a |
39 | different CustomerID is used per currency. |
40 | |
41 +--------------------------------------------------------------------+
42 */
43
44 /**
45 * -----------------------------------------------------------------------------------------------
46 * From the eWAY supplied 'Web.config' dated 25-Sep-2006 - check date and update links if required
47 * -----------------------------------------------------------------------------------------------
48 *
49 * LIVE gateway with CVN
50 * https://www.eway.com.au/gateway_cvn/xmlpayment.asp
51 *
52 * LIVE gateway without CVN
53 * https://www.eway.com.au/gateway/xmlpayment.asp
54 *
55 *
56 * Test gateway with CVN
57 * https://www.eway.com.au/gateway_cvn/xmltest/TestPage.asp
58 *
59 * Test gateway without CVN
60 * https://www.eway.com.au/gateway/xmltest/TestPage.asp
61 *
62 *
63 * LIVE gateway for Stored Transactions
64 * https://www.eway.com.au/gateway/xmlstored.asp
65 *
66 *
67 * -----------------------------------------------------------------------------------------------
68 * From the eWAY web-site - http://www.eway.com.au/Support/Developer/PaymentsRealTime.aspx
69 * -----------------------------------------------------------------------------------------------
70 * The test Customer ID is 87654321 - this is the only ID that will work on the test gateway.
71 * The test Credit Card number is 4444333322221111
72 * - this is the only credit card number that will work on the test gateway.
73 * The test Total Amount should end in 00 or 08 to get a successful response (e.g. $10.00 or $10.08)
74 * ie - all other amounts will return a failed response.
75 *
76 * -----------------------------------------------------------------------------------------------
77 */
78
79 use Civi\Payment\Exception\PaymentProcessorException;
80
81 // require Standard eWAY API libraries
82 require_once 'eWAY/eWAY_GatewayRequest.php';
83 require_once 'eWAY/eWAY_GatewayResponse.php';
84
85 /**
86 * Class CRM_Core_Payment_eWAY.
87 */
88 class CRM_Core_Payment_eWAY extends CRM_Core_Payment {
89
90 /**
91 * *******************************************************
92 * Constructor
93 *
94 * @param string $mode
95 * The mode of operation: live or test.
96 *
97 * @param int $paymentProcessor
98 *
99 * *******************************************************
100 */
101 public function __construct($mode, &$paymentProcessor) {
102
103 // live or test
104 $this->_mode = $mode;
105 $this->_paymentProcessor = $paymentProcessor;
106 }
107
108 /**
109 * Sends request and receive response from eWAY payment process.
110 *
111 * @param array $params
112 *
113 * @return array|object
114 * @throws \Exception
115 */
116 public function doDirectPayment(&$params) {
117 if (CRM_Utils_Array::value('is_recur', $params) == TRUE) {
118 throw new CRM_Core_Exception(ts('eWAY - recurring payments not implemented'));
119 }
120
121 if (!defined('CURLOPT_SSLCERT')) {
122 throw new CRM_Core_Exception(ts('eWAY - Gateway requires curl with SSL support'));
123 }
124
125 // eWAY Client ID
126 $ewayCustomerID = $this->_paymentProcessor['user_name'];
127 // eWAY Gateway URL
128 $gateway_URL = $this->_paymentProcessor['url_site'];
129
130 //------------------------------------
131 // create eWAY gateway objects
132 //------------------------------------
133 $eWAYRequest = new GatewayRequest();
134
135 if (($eWAYRequest == NULL) || (!($eWAYRequest instanceof GatewayRequest))) {
136 throw new PaymentProcessorException('Error: Unable to create eWAY Request object.', 9001);
137 }
138
139 $eWAYResponse = new GatewayResponse();
140
141 if (($eWAYResponse == NULL) || (!($eWAYResponse instanceof GatewayResponse))) {
142 throw new PaymentProcessorException(9002, 'Error: Unable to create eWAY Response object.', 9002);
143 }
144
145 /*
146 //-------------------------------------------------------------
147 // NOTE: eWAY Doesn't use the following at the moment:
148 //-------------------------------------------------------------
149 $creditCardType = $params['credit_card_type'];
150 $currentcyID = $params['currencyID'];
151 $country = $params['country'];
152 */
153
154 //-------------------------------------------------------------
155 // Prepare some composite data from _paymentProcessor fields
156 //-------------------------------------------------------------
157 $fullAddress = $params['street_address'] . ", " . $params['city'] . ", " . $params['state_province'] . ".";
158 $expireYear = substr($params['year'], 2, 2);
159 $expireMonth = sprintf('%02d', (int) $params['month']);
160 $description = $params['description'];
161 $txtOptions = "";
162
163 $amountInCents = round(((float) $params['amount']) * 100);
164
165 $credit_card_name = $params['first_name'] . " ";
166 if (strlen($params['middle_name']) > 0) {
167 $credit_card_name .= $params['middle_name'] . " ";
168 }
169 $credit_card_name .= $params['last_name'];
170
171 //----------------------------------------------------------------------------------------------------
172 // We use CiviCRM's param's 'invoiceID' as the unique transaction token to feed to eWAY
173 // Trouble is that eWAY only accepts 16 chars for the token, while CiviCRM's invoiceID is an 32.
174 // As its made from a "$invoiceID = md5(uniqid(rand(), true));" then using the fierst 16 chars
175 // should be alright
176 //----------------------------------------------------------------------------------------------------
177 $uniqueTrnxNum = substr($params['invoiceID'], 0, 16);
178
179 //----------------------------------------------------------------------------------------------------
180 // OPTIONAL: If TEST Card Number force an Override of URL and CutomerID.
181 // During testing CiviCRM once used the LIVE URL.
182 // This code can be uncommented to override the LIVE URL that if CiviCRM does that again.
183 //----------------------------------------------------------------------------------------------------
184 // if ( ( $gateway_URL == "https://www.eway.com.au/gateway_cvn/xmlpayment.asp")
185 // && ( $params['credit_card_number'] == "4444333322221111" ) ) {
186 // $ewayCustomerID = "87654321";
187 // $gateway_URL = "https://www.eway.com.au/gateway_cvn/xmltest/testpage.asp";
188 // }
189
190 //----------------------------------------------------------------------------------------------------
191 // Now set the payment details - see http://www.eway.com.au/Support/Developer/PaymentsRealTime.aspx
192 //----------------------------------------------------------------------------------------------------
193 // 8 Chars - ewayCustomerID - Required
194 $eWAYRequest->EwayCustomerID($ewayCustomerID);
195 // 12 Chars - ewayTotalAmount (in cents) - Required
196 $eWAYRequest->InvoiceAmount($amountInCents);
197 // 50 Chars - ewayCustomerFirstName
198 $eWAYRequest->PurchaserFirstName($params['first_name']);
199 // 50 Chars - ewayCustomerLastName
200 $eWAYRequest->PurchaserLastName($params['last_name']);
201 // 50 Chars - ewayCustomerEmail
202 $eWAYRequest->PurchaserEmailAddress($params['email']);
203 // 255 Chars - ewayCustomerAddress
204 $eWAYRequest->PurchaserAddress($fullAddress);
205 // 6 Chars - ewayCustomerPostcode
206 $eWAYRequest->PurchaserPostalCode($params['postal_code']);
207 // 1000 Chars - ewayCustomerInvoiceDescription
208 $eWAYRequest->InvoiceDescription($description);
209 // 50 Chars - ewayCustomerInvoiceRef
210 $eWAYRequest->InvoiceReference($params['invoiceID']);
211 // 50 Chars - ewayCardHoldersName - Required
212 $eWAYRequest->CardHolderName($credit_card_name);
213 // 20 Chars - ewayCardNumber - Required
214 $eWAYRequest->CardNumber($params['credit_card_number']);
215 // 2 Chars - ewayCardExpiryMonth - Required
216 $eWAYRequest->CardExpiryMonth($expireMonth);
217 // 2 Chars - ewayCardExpiryYear - Required
218 $eWAYRequest->CardExpiryYear($expireYear);
219 // 4 Chars - ewayCVN - Required if CVN Gateway used
220 $eWAYRequest->CVN($params['cvv2']);
221 // 16 Chars - ewayTrxnNumber
222 $eWAYRequest->TransactionNumber($uniqueTrnxNum);
223 // 255 Chars - ewayOption1
224 $eWAYRequest->EwayOption1($txtOptions);
225 // 255 Chars - ewayOption2
226 $eWAYRequest->EwayOption2($txtOptions);
227 // 255 Chars - ewayOption3
228 $eWAYRequest->EwayOption3($txtOptions);
229
230 $eWAYRequest->CustomerIPAddress($params['ip_address']);
231 $eWAYRequest->CustomerBillingCountry($params['country']);
232
233 // Allow further manipulation of the arguments via custom hooks ..
234 CRM_Utils_Hook::alterPaymentProcessorParams($this, $params, $eWAYRequest);
235
236 //----------------------------------------------------------------------------------------------------
237 // Check to see if we have a duplicate before we send
238 //----------------------------------------------------------------------------------------------------
239 if ($this->checkDupe($params['invoiceID'], CRM_Utils_Array::value('contributionID', $params))) {
240 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);
241 }
242
243 //----------------------------------------------------------------------------------------------------
244 // Convert to XML and send the payment information
245 //----------------------------------------------------------------------------------------------------
246 $requestxml = $eWAYRequest->ToXML();
247
248 $submit = curl_init($gateway_URL);
249
250 if (!$submit) {
251 throw new PaymentProcessorException('Could not initiate connection to payment gateway', 9004);
252 }
253
254 curl_setopt($submit, CURLOPT_POST, TRUE);
255 // return the result on success, FALSE on failure
256 curl_setopt($submit, CURLOPT_RETURNTRANSFER, TRUE);
257 curl_setopt($submit, CURLOPT_POSTFIELDS, $requestxml);
258 curl_setopt($submit, CURLOPT_TIMEOUT, 36000);
259 // if open_basedir or safe_mode are enabled in PHP settings CURLOPT_FOLLOWLOCATION won't work so don't apply it
260 // it's not really required CRM-5841
261 if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off')) {
262 // ensures any Location headers are followed
263 curl_setopt($submit, CURLOPT_FOLLOWLOCATION, 1);
264 }
265
266 // Send the data out over the wire
267 //--------------------------------
268 $responseData = curl_exec($submit);
269
270 //----------------------------------------------------------------------------------------------------
271 // See if we had a curl error - if so tell 'em and bail out
272 //
273 // NOTE: curl_error does not return a logical value (see its documentation), but
274 // a string, which is empty when there was no error.
275 //----------------------------------------------------------------------------------------------------
276 if ((curl_errno($submit) > 0) || (strlen(curl_error($submit)) > 0)) {
277 $errorNum = curl_errno($submit);
278 $errorDesc = curl_error($submit);
279
280 // Paranoia - in the unlikley event that 'curl' errno fails
281 if ($errorNum == 0) {
282 $errorNum = 9005;
283 }
284
285 // Paranoia - in the unlikley event that 'curl' error fails
286 if (strlen($errorDesc) == 0) {
287 $errorDesc = 'Connection to eWAY payment gateway failed';
288 }
289
290 throw new PaymentProcessorException($errorDesc, $errorNum);
291 }
292
293 //----------------------------------------------------------------------------------------------------
294 // If null data returned - tell 'em and bail out
295 //
296 // NOTE: You will not necessarily get a string back, if the request failed for
297 // any reason, the return value will be the boolean false.
298 //----------------------------------------------------------------------------------------------------
299 if (($responseData === FALSE) || (strlen($responseData) == 0)) {
300 throw new PaymentProcessorException('Error: Connection to payment gateway failed - no data returned.', 9006);
301 }
302
303 //----------------------------------------------------------------------------------------------------
304 // If gateway returned no data - tell 'em and bail out
305 //----------------------------------------------------------------------------------------------------
306 if (empty($responseData)) {
307 throw new PaymentProcessorException('Error: No data returned from payment gateway.', 9007);
308 }
309
310 //----------------------------------------------------------------------------------------------------
311 // Success so far - close the curl and check the data
312 //----------------------------------------------------------------------------------------------------
313 curl_close($submit);
314
315 //----------------------------------------------------------------------------------------------------
316 // Payment successfully sent to gateway - process the response now
317 //----------------------------------------------------------------------------------------------------
318 $eWAYResponse->ProcessResponse($responseData);
319
320 //----------------------------------------------------------------------------------------------------
321 // See if we got an OK result - if not tell 'em and bail out
322 //----------------------------------------------------------------------------------------------------
323 if (self::isError($eWAYResponse)) {
324 $eWayTrxnError = $eWAYResponse->Error();
325 CRM_Core_Error::debug_var('eWay Error', $eWayTrxnError, TRUE, TRUE);
326 if (substr($eWayTrxnError, 0, 6) === 'Error:') {
327 throw new PaymentProcessorException($eWayTrxnError, 9008);
328 }
329 $eWayErrorCode = substr($eWayTrxnError, 0, 2);
330 $eWayErrorDesc = substr($eWayTrxnError, 3);
331
332 throw new PaymentProcessorException('Error: [' . $eWayErrorCode . "] - " . $eWayErrorDesc . '.', 9008);
333 }
334
335 //=============
336 // Success !
337 //=============
338 $beaglestatus = $eWAYResponse->BeagleScore();
339 if (!empty($beaglestatus)) {
340 $beaglestatus = ': ' . $beaglestatus;
341 }
342 $params['trxn_result_code'] = $eWAYResponse->Status() . $beaglestatus;
343 $params['gross_amount'] = $eWAYResponse->Amount();
344 $params['trxn_id'] = $eWAYResponse->TransactionNumber();
345
346 return $params;
347 }
348
349 /**
350 * Checks the eWAY response status - returning a boolean false if status != 'true'.
351 *
352 * @param object $response
353 *
354 * @return bool
355 */
356 public function isError(&$response) {
357 $status = $response->Status();
358
359 if ((stripos($status, 'true')) === FALSE) {
360 return TRUE;
361 }
362 return FALSE;
363 }
364
365 /**
366 * This public function checks to see if we have the right processor config values set
367 *
368 * NOTE: Called by Events and Contribute to check config params are set prior to trying
369 * register any credit card details
370 *
371 * @return null|string
372 * returns string $errorMsg if any errors found - null if OK
373 */
374 public function checkConfig() {
375 $errorMsg = [];
376
377 if (empty($this->_paymentProcessor['user_name'])) {
378 $errorMsg[] = ts('eWAY CustomerID is not set for this payment processor');
379 }
380
381 if (empty($this->_paymentProcessor['url_site'])) {
382 $errorMsg[] = ts('eWAY Gateway URL is not set for this payment processor');
383 }
384
385 if (!empty($errorMsg)) {
386 return implode('<p>', $errorMsg);
387 }
388 return NULL;
389 }
390
391 }