CRM-12457
[civicrm-core.git] / CRM / Core / Payment / eWAY.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | This file is a part of CiviCRM. |
7 | |
8 | CiviCRM is free software; you can copy, modify, and distribute it |
9 | under the terms of the GNU Affero General Public License |
10 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
11 | |
12 | CiviCRM is distributed in the hope that it will be useful, but |
13 | WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
15 | See the GNU Affero General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU Affero General Public |
18 | License and the CiviCRM Licensing Exception along |
19 | with this program; if not, contact CiviCRM LLC |
20 | at info[AT]civicrm[DOT]org. If you have questions about the |
21 | GNU Affero General Public License or the licensing of CiviCRM, |
22 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
23 +--------------------------------------------------------------------+
24 */
25
26
27 /*
28 +--------------------------------------------------------------------+
29 | eWAY Core Payment Module for CiviCRM version 4.3 & 1.9 |
30 +--------------------------------------------------------------------+
31 | Licensed to CiviCRM under the Academic Free License version 3.0 |
32 | |
33 | Written & Contributed by Dolphin Software P/L - March 2008 |
34 +--------------------------------------------------------------------+
35 | |
36 | This file is a part of CiviCRM. |
37 | |
38 | This code was initially based on the recent PayJunction module |
39 | contributed by Phase2 Technology, and then plundered bits from |
40 | the AuthorizeNet module contributed by Ideal Solution, and |
41 | referenced the eWAY code in Drupal 5.7's ecommerce-5.x-3.4 and |
42 | ecommerce-5.x-4.x-dev modules. |
43 | |
44 | Plus a bit of our own code of course - Peter Barwell |
45 | contact PB@DolphinSoftware.com.au if required. |
46 | |
47 | NOTE: This initial eWAY module does not yet allow for recuring |
48 | payments - contact Peter Barwell or add yourself (or both) |
49 | |
50 | NOTE: The eWAY gateway only allows a single currency per account |
51 | (per eWAY CustomerID) ie you can only have one currency per |
52 | added Payment Processor. |
53 | The only way to add multi-currency is to code it so that a |
54 | different CustomerID is used per currency. |
55 | |
56 +--------------------------------------------------------------------+
57 */
58
59 /**
60 -----------------------------------------------------------------------------------------------
61 From the eWAY supplied 'Web.config' dated 25-Sep-2006 - check date and update links if required
62 -----------------------------------------------------------------------------------------------
63
64 LIVE gateway with CVN
65 https://www.eway.com.au/gateway_cvn/xmlpayment.asp
66
67 LIVE gateway without CVN
68 https://www.eway.com.au/gateway/xmlpayment.asp
69
70
71 Test gateway with CVN
72 https://www.eway.com.au/gateway_cvn/xmltest/TestPage.asp
73
74 Test gateway without CVN
75 https://www.eway.com.au/gateway/xmltest/TestPage.asp
76
77
78 LIVE gateway for Stored Transactions
79 https://www.eway.com.au/gateway/xmlstored.asp
80
81
82 -----------------------------------------------------------------------------------------------
83 From the eWAY web-site - http://www.eway.com.au/Support/Developer/PaymentsRealTime.aspx
84 -----------------------------------------------------------------------------------------------
85 The test Customer ID is 87654321 - this is the only ID that will work on the test gateway.
86 The test Credit Card number is 4444333322221111
87 - this is the only credit card number that will work on the test gateway.
88 The test Total Amount should end in 00 or 08 to get a successful response (e.g. $10.00 or $10.08)
89 ie - all other amounts will return a failed response.
90
91 -----------------------------------------------------------------------------------------------
92 **/
93 class CRM_Core_Payment_eWAY extends CRM_Core_Payment {
94 # (not used, implicit in the API, might need to convert?)
95 CONST CHARSET = 'UTF-8';
96
97 /**
98 * We only need one instance of this object. So we use the singleton
99 * pattern and cache the instance in this variable
100 *
101 * @var object
102 * @static
103 */
104 static private $_singleton = NULL;
105
106 /**********************************************************
107 * Constructor
108 *
109 * @param string $mode the mode of operation: live or test
110 *
111 * @return void
112 **********************************************************/
113 function __construct($mode, &$paymentProcessor) {
114 // require Standaard eWAY API libraries
115 require_once 'eWAY/eWAY_GatewayRequest.php';
116 require_once 'eWAY/eWAY_GatewayResponse.php';
117
118 // live or test
119 $this->_mode = $mode;
120 $this->_paymentProcessor = $paymentProcessor;
121 $this->_processorName = ts('eWay');
122 }
123
124 /**
125 * singleton function used to manage this object
126 *
127 * @param string $mode the mode of operation: live or test
128 *
129 * @return object
130 * @static
131 *
132 */
133 static function &singleton($mode, &$paymentProcessor) {
134 $processorName = $paymentProcessor['name'];
135 if (self::$_singleton[$processorName] === NULL) {
136 self::$_singleton[$processorName] = new CRM_Core_Payment_eWAY($mode, $paymentProcessor);
137 }
138 return self::$_singleton[$processorName];
139 }
140
141 /**********************************************************
142 * This function sends request and receives response from
143 * eWAY payment process
144 **********************************************************/
145 function doDirectPayment(&$params) {
146 if ($params['is_recur'] == TRUE) {
147 CRM_Core_Error::fatal(ts('eWAY - recurring payments not implemented'));
148 }
149
150 if (!defined('CURLOPT_SSLCERT')) {
151 CRM_Core_Error::fatal(ts('eWAY - Gateway requires curl with SSL support'));
152 }
153
154 // eWAY Client ID
155 $ewayCustomerID = $this->_paymentProcessor['user_name'];
156 // eWAY Gateway URL
157 $gateway_URL = $this->_paymentProcessor['url_site'];
158
159 //------------------------------------
160 // create eWAY gateway objects
161 //------------------------------------
162 $eWAYRequest = new GatewayRequest;
163
164 if (($eWAYRequest == NULL) || (!($eWAYRequest instanceof GatewayRequest))) {
165 return self::errorExit(9001, "Error: Unable to create eWAY Request object.");
166 }
167
168 $eWAYResponse = new GatewayResponse;
169
170 if (($eWAYResponse == NULL) || (!($eWAYResponse instanceof GatewayResponse))) {
171 return self::errorExit(9002, "Error: Unable to create eWAY Response object.");
172 }
173
174 /*
175 //-------------------------------------------------------------
176 // NOTE: eWAY Doesn't use the following at the moment:
177 //-------------------------------------------------------------
178 $creditCardType = $params['credit_card_type'];
179 $currentcyID = $params['currencyID'];
180 $country = $params['country'];
181 */
182
183
184 //-------------------------------------------------------------
185 // Prepare some composite data from _paymentProcessor fields
186 //-------------------------------------------------------------
187 $fullAddress = $params['street_address'] . ", " . $params['city'] . ", " . $params['state_province'] . ".";
188 $expireYear = substr($params['year'], 2, 2);
189 $expireMonth = sprintf('%02d', (int) $params['month']);
190 // CiviCRM V1.9 - Picks up reasonable description
191 //$description = $params['amount_level'];
192 // CiviCRM V2.0 - Picks up description
193 $description = $params['description'];
194 $txtOptions = "";
195
196 $amountInCents = round(((float) $params['amount']) * 100);
197
198 $credit_card_name = $params['first_name'] . " ";
199 if (strlen($params['middle_name']) > 0) {
200 $credit_card_name .= $params['middle_name'] . " ";
201 }
202 $credit_card_name .= $params['last_name'];
203
204 //----------------------------------------------------------------------------------------------------
205 // We use CiviCRM's param's 'invoiceID' as the unique transaction token to feed to eWAY
206 // Trouble is that eWAY only accepts 16 chars for the token, while CiviCRM's invoiceID is an 32.
207 // As its made from a "$invoiceID = md5(uniqid(rand(), true));" then using the fierst 16 chars
208 // should be alright
209 //----------------------------------------------------------------------------------------------------
210 $uniqueTrnxNum = substr($params['invoiceID'], 0, 16);
211
212 //----------------------------------------------------------------------------------------------------
213 // OPTIONAL: If TEST Card Number force an Override of URL and CutomerID.
214 // During testing CiviCRM once used the LIVE URL.
215 // This code can be uncommented to override the LIVE URL that if CiviCRM does that again.
216 //----------------------------------------------------------------------------------------------------
217 // if ( ( $gateway_URL == "https://www.eway.com.au/gateway_cvn/xmlpayment.asp")
218 // && ( $params['credit_card_number'] == "4444333322221111" ) ) {
219 // $ewayCustomerID = "87654321";
220 // $gateway_URL = "https://www.eway.com.au/gateway_cvn/xmltest/testpage.asp";
221 // }
222
223 //----------------------------------------------------------------------------------------------------
224 // Now set the payment details - see http://www.eway.com.au/Support/Developer/PaymentsRealTime.aspx
225 //----------------------------------------------------------------------------------------------------
226 // 8 Chars - ewayCustomerID - Required
227 $eWAYRequest->EwayCustomerID($ewayCustomerID);
228 // 12 Chars - ewayTotalAmount (in cents) - Required
229 $eWAYRequest->InvoiceAmount($amountInCents);
230 // 50 Chars - ewayCustomerFirstName
231 $eWAYRequest->PurchaserFirstName($params['first_name']);
232 // 50 Chars - ewayCustomerLastName
233 $eWAYRequest->PurchaserLastName($params['last_name']);
234 // 50 Chars - ewayCustomerEmail
235 $eWAYRequest->PurchaserEmailAddress($params['email']);
236 // 255 Chars - ewayCustomerAddress
237 $eWAYRequest->PurchaserAddress($fullAddress);
238 // 6 Chars - ewayCustomerPostcode
239 $eWAYRequest->PurchaserPostalCode($params['postal_code']);
240 // 1000 Chars - ewayCustomerInvoiceDescription
241 $eWAYRequest->InvoiceDescription($description);
242 // 50 Chars - ewayCustomerInvoiceRef
243 $eWAYRequest->InvoiceReference($params['invoiceID']);
244 // 50 Chars - ewayCardHoldersName - Required
245 $eWAYRequest->CardHolderName($credit_card_name);
246 // 20 Chars - ewayCardNumber - Required
247 $eWAYRequest->CardNumber($params['credit_card_number']);
248 // 2 Chars - ewayCardExpiryMonth - Required
249 $eWAYRequest->CardExpiryMonth($expireMonth);
250 // 2 Chars - ewayCardExpiryYear - Required
251 $eWAYRequest->CardExpiryYear($expireYear);
252 // 4 Chars - ewayCVN - Required if CVN Gateway used
253 $eWAYRequest->CVN($params['cvv2']);
254 // 16 Chars - ewayTrxnNumber
255 $eWAYRequest->TransactionNumber($uniqueTrnxNum);
256 // 255 Chars - ewayOption1
257 $eWAYRequest->EwayOption1($txtOptions);
258 // 255 Chars - ewayOption2
259 $eWAYRequest->EwayOption2($txtOptions);
260 // 255 Chars - ewayOption3
261 $eWAYRequest->EwayOption3($txtOptions);
262
263 $eWAYRequest->CustomerIPAddress($params['ip_address']);
264 $eWAYRequest->CustomerBillingCountry($params['country']);
265
266 // Allow further manipulation of the arguments via custom hooks ..
267 CRM_Utils_Hook::alterPaymentProcessorParams($this, $params, $eWAYRequest);
268
269 //----------------------------------------------------------------------------------------------------
270 // Check to see if we have a duplicate before we send
271 //----------------------------------------------------------------------------------------------------
272 if ($this->_checkDupe($params['invoiceID'])) {
273 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.');
274 }
275
276 //----------------------------------------------------------------------------------------------------
277 // Convert to XML and send the payment information
278 //----------------------------------------------------------------------------------------------------
279 $requestxml = $eWAYRequest->ToXML();
280
281 $submit = curl_init($gateway_URL);
282
283 if (!$submit) {
284 return self::errorExit(9004, 'Could not initiate connection to payment gateway');
285 }
286
287 curl_setopt($submit, CURLOPT_POST, TRUE);
288 // return the result on success, FALSE on failure
289 curl_setopt($submit, CURLOPT_RETURNTRANSFER, TRUE);
290 curl_setopt($submit, CURLOPT_POSTFIELDS, $requestxml);
291 curl_setopt($submit, CURLOPT_TIMEOUT, 36000);
292 // if open_basedir or safe_mode are enabled in PHP settings CURLOPT_FOLLOWLOCATION won't work so don't apply it
293 // it's not really required CRM-5841
294 if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off')) {
295 // ensures any Location headers are followed
296 curl_setopt($submit, CURLOPT_FOLLOWLOCATION, 1);
297 }
298
299 // Send the data out over the wire
300 //--------------------------------
301 $responseData = curl_exec($submit);
302
303 //----------------------------------------------------------------------------------------------------
304 // See if we had a curl error - if so tell 'em and bail out
305 //
306 // NOTE: curl_error does not return a logical value (see its documentation), but
307 // a string, which is empty when there was no error.
308 //----------------------------------------------------------------------------------------------------
309 if ((curl_errno($submit) > 0) || (strlen(curl_error($submit)) > 0)) {
310 $errorNum = curl_errno($submit);
311 $errorDesc = curl_error($submit);
312
313 // Paranoia - in the unlikley event that 'curl' errno fails
314 if ($errorNum == 0)
315 $errorNum = 9005;
316
317 // Paranoia - in the unlikley event that 'curl' error fails
318 if (strlen($errorDesc) == 0)
319 $errorDesc = "Connection to eWAY payment gateway failed";
320
321 return self::errorExit($errorNum, $errorDesc);
322 }
323
324 //----------------------------------------------------------------------------------------------------
325 // If null data returned - tell 'em and bail out
326 //
327 // NOTE: You will not necessarily get a string back, if the request failed for
328 // any reason, the return value will be the boolean false.
329 //----------------------------------------------------------------------------------------------------
330 if (($responseData === FALSE) || (strlen($responseData) == 0)) {
331 return self::errorExit(9006, "Error: Connection to payment gateway failed - no data returned.");
332 }
333
334 //----------------------------------------------------------------------------------------------------
335 // If gateway returned no data - tell 'em and bail out
336 //----------------------------------------------------------------------------------------------------
337 if (empty($responseData)) {
338 return self::errorExit(9007, "Error: No data returned from payment gateway.");
339 }
340
341 //----------------------------------------------------------------------------------------------------
342 // Success so far - close the curl and check the data
343 //----------------------------------------------------------------------------------------------------
344 curl_close($submit);
345
346 //----------------------------------------------------------------------------------------------------
347 // Payment successfully sent to gateway - process the response now
348 //----------------------------------------------------------------------------------------------------
349 $eWAYResponse->ProcessResponse($responseData);
350
351 //----------------------------------------------------------------------------------------------------
352 // See if we got an OK result - if not tell 'em and bail out
353 //----------------------------------------------------------------------------------------------------
354 if (self::isError($eWAYResponse)) {
355 $eWayTrxnError = $eWAYResponse->Error();
356
357 if (substr($eWayTrxnError, 0, 6) == "Error:") {
358 return self::errorExit(9008, $eWayTrxnError);
359 }
360 $eWayErrorCode = substr($eWayTrxnError, 0, 2);
361 $eWayErrorDesc = substr($eWayTrxnError, 3);
362
363 return self::errorExit(9008, "Error: [" . $eWayErrorCode . "] - " . $eWayErrorDesc . ".");
364 }
365
366 //-----------------------------------------------------------------------------------------------------
367 // Cross-Check - the unique 'TrxnReference' we sent out should match the just received 'TrxnReference'
368 //
369 // PLEASE NOTE: If this occurs (which is highly unlikely) its a serious error as it would mean we have
370 // received an OK status from eWAY, but their Gateway has not returned the correct unique
371 // token - ie something is broken, BUT money has been taken from the client's account,
372 // so we can't very well error-out as CiviCRM will then not process the registration.
373 // There is an error message commented out here but my prefered response to this unlikley
374 // possibility is to email 'support@eWAY.com.au'
375 //-----------------------------------------------------------------------------------------------------
376 $eWayTrxnReference_OUT = $eWAYRequest->GetTransactionNumber();
377 $eWayTrxnReference_IN = $eWAYResponse->InvoiceReference();
378
379 if ($eWayTrxnReference_IN != $eWayTrxnReference_OUT) {
380 // return self::errorExit( 9009, "Error: Unique Trxn code was not returned by eWAY Gateway. This is extremely unusual! Please contact the administrator of this site immediately with details of this transaction.");
381
382 self::send_alert_email($eWAYResponse->TransactionNumber(),
383 $eWayTrxnReference_OUT, $eWayTrxnReference_IN, $requestxml, $responseData
384 );
385 }
386
387 /*
388 //----------------------------------------------------------------------------------------------------
389 // Test mode always returns trxn_id = 0 - so we fix that here
390 //
391 // NOTE: This code was taken from the AuthorizeNet payment processor, however it now appears
392 // unecessary for the eWAY gateway - Left here in case it proves useful
393 //----------------------------------------------------------------------------------------------------
394 if ( $this->_mode == 'test' ) {
395 $query = "SELECT MAX(trxn_id) FROM civicrm_contribution WHERE trxn_id LIKE 'test%'";
396 $p = array( );
397 $trxn_id = strval( CRM_Core_Dao::singleValueQuery( $query, $p ) );
398 $trxn_id = str_replace( 'test', '', $trxn_id );
399 $trxn_id = intval($trxn_id) + 1;
400 $params['trxn_id'] = sprintf('test%08d', $trxn_id);
401 } else {
402 $params['trxn_id'] = $eWAYResponse->TransactionNumber();
403 }
404 */
405
406
407 //=============
408 // Success !
409 //=============
410 $beaglestatus = $eWAYResponse->BeagleScore();
411 if (!empty($beaglestatus)) {
412 $beaglestatus = ": " . $beaglestatus;
413 }
414 $params['trxn_result_code'] = $eWAYResponse->Status() . $beaglestatus;
415 $params['gross_amount'] = $eWAYResponse->Amount();
416 $params['trxn_id'] = $eWAYResponse->TransactionNumber();
417
418 return $params;
419 }
420 // end function doDirectPayment
421
422 /**
423 * Checks to see if invoice_id already exists in db
424 *
425 * @param int $invoiceId The ID to check
426 *
427 * @return bool True if ID exists, else false
428 */
429 function _checkDupe($invoiceId) {
430 $contribution = new CRM_Contribute_DAO_Contribution();
431 $contribution->invoice_id = $invoiceId;
432 return $contribution->find();
433 }
434
435 /*************************************************************************************************
436 * This function checks the eWAY response status - returning a boolean false if status != 'true'
437 *************************************************************************************************/
438 function isError(&$response) {
439 $status = $response->Status();
440
441 if ((stripos($status, "true")) === FALSE) {
442 return TRUE;
443 }
444 return FALSE;
445 }
446
447 /**************************************************
448 * Produces error message and returns from class
449 **************************************************/
450 function &errorExit($errorCode = NULL, $errorMessage = NULL) {
451 $e = CRM_Core_Error::singleton();
452
453 if ($errorCode) {
454 $e->push($errorCode, 0, NULL, $errorMessage);
455 }
456 else {
457 $e->push(9000, 0, NULL, 'Unknown System Error.');
458 }
459 return $e;
460 }
461
462 /**************************************************
463 * NOTE: 'doTransferCheckout' not implemented
464 **************************************************/
465 function doTransferCheckout(&$params, $component) {
466 CRM_Core_Error::fatal(ts('This function is not implemented'));
467 }
468
469 /********************************************************************************************
470 * This public function checks to see if we have the right processor config values set
471 *
472 * NOTE: Called by Events and Contribute to check config params are set prior to trying
473 * register any credit card details
474 *
475 * @param string $mode the mode we are operating in (live or test) - not used but could be
476 * to check that the 'test' mode CustomerID was equal to '87654321' and that the URL was
477 * set to https://www.eway.com.au/gateway_cvn/xmltest/TestPage.asp
478 *
479 * returns string $errorMsg if any errors found - null if OK
480 *
481 ********************************************************************************************/
482 //function checkConfig( $mode ) // CiviCRM V1.9 Declaration
483 // CiviCRM V2.0 Declaration
484 function checkConfig() {
485 $errorMsg = array();
486
487 if (empty($this->_paymentProcessor['user_name'])) {
488 $errorMsg[] = ts('eWAY CustomerID is not set for this payment processor');
489 }
490
491 if (empty($this->_paymentProcessor['url_site'])) {
492 $errorMsg[] = ts('eWAY Gateway URL is not set for this payment processor');
493 }
494
495 if (!empty($errorMsg)) {
496 return implode('<p>', $errorMsg);
497 }
498 else {
499 return NULL;
500 }
501 }
502
503 function send_alert_email($p_eWAY_tran_num, $p_trxn_out, $p_trxn_back, $p_request, $p_response) {
504 // Initialization call is required to use CiviCRM APIs.
505 civicrm_initialize(TRUE);
506
507
508 list($fromName, $fromEmail) = CRM_Core_BAO_Domain::getNameAndEmail();
509 $from = "$fromName <$fromEmail>";
510
511 $toName = 'Support at eWAY';
512 $toEmail = 'Support@eWAY.com.au';
513
514 $subject = "ALERT: Unique Trxn Number Failure : eWAY Transaction # = [" . $p_eWAY_tran_num . "]";
515
516 $message = "
517 TRXN sent out with request = '$p_trxn_out'.
518 TRXN sent back with response = '$p_trxn_back'.
519
520 This is a ['$this->_mode'] transaction.
521
522
523 Request XML =
524 ---------------------------------------------------------------------------
525 $p_request
526 ---------------------------------------------------------------------------
527
528
529 Response XML =
530 ---------------------------------------------------------------------------
531 $p_response
532 ---------------------------------------------------------------------------
533
534
535 Regards
536
537 The CiviCRM eWAY Payment Processor Module
538 ";
539 //$cc = 'Name@Domain';
540
541 // create the params array
542 $params = array();
543
544 $params['groupName'] = 'eWay Email Sender';
545 $params['from'] = $from;
546 $params['toName'] = $toName;
547 $params['toEmail'] = $toEmail;
548 $params['subject'] = $subject;
549 $params['cc'] = $cc;
550 $params['text'] = $message;
551
552 CRM_Utils_Mail::send($params);
553 }
554 }
555 // end class CRM_Core_Payment_eWAY
556