3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
28 +--------------------------------------------------------------------+
29 | eWAY Core Payment Module for CiviCRM version 4.7 & 1.9 |
30 +--------------------------------------------------------------------+
31 | Licensed to CiviCRM under the Academic Free License version 3.0 |
33 | Written & Contributed by Dolphin Software P/L - March 2008 |
34 +--------------------------------------------------------------------+
36 | This file is a part of CiviCRM. |
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. |
44 | Plus a bit of our own code of course - Peter Barwell |
45 | contact PB@DolphinSoftware.com.au if required. |
47 | NOTE: This initial eWAY module does not yet allow for recurring |
48 | payments - contact Peter Barwell or add yourself (or both) |
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. |
56 +--------------------------------------------------------------------+
60 * -----------------------------------------------------------------------------------------------
61 * From the eWAY supplied 'Web.config' dated 25-Sep-2006 - check date and update links if required
62 * -----------------------------------------------------------------------------------------------
64 * LIVE gateway with CVN
65 * https://www.eway.com.au/gateway_cvn/xmlpayment.asp
67 * LIVE gateway without CVN
68 * https://www.eway.com.au/gateway/xmlpayment.asp
71 * Test gateway with CVN
72 * https://www.eway.com.au/gateway_cvn/xmltest/TestPage.asp
74 * Test gateway without CVN
75 * https://www.eway.com.au/gateway/xmltest/TestPage.asp
78 * LIVE gateway for Stored Transactions
79 * https://www.eway.com.au/gateway/xmlstored.asp
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.
91 * -----------------------------------------------------------------------------------------------
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';
98 * We only need one instance of this object. So we use the singleton
99 * pattern and cache the instance in this variable
103 static private $_singleton = NULL;
106 * *******************************************************
109 * @param string $mode
110 * The mode of operation: live or test.
112 * @param int $paymentProcessor
114 * *******************************************************
116 public function __construct($mode, &$paymentProcessor) {
117 // require Standard eWAY API libraries
118 require_once 'eWAY/eWAY_GatewayRequest.php';
119 require_once 'eWAY/eWAY_GatewayResponse.php';
122 $this->_mode
= $mode;
123 $this->_paymentProcessor
= $paymentProcessor;
124 $this->_processorName
= ts('eWay');
128 * *******************************************************
129 * This function sends request and receives response from
130 * eWAY payment process
131 * *******************************************************
133 public function doDirectPayment(&$params) {
134 if (CRM_Utils_Array
::value('is_recur', $params) == TRUE) {
135 CRM_Core_Error
::fatal(ts('eWAY - recurring payments not implemented'));
138 if (!defined('CURLOPT_SSLCERT')) {
139 CRM_Core_Error
::fatal(ts('eWAY - Gateway requires curl with SSL support'));
143 $ewayCustomerID = $this->_paymentProcessor
['user_name'];
145 $gateway_URL = $this->_paymentProcessor
['url_site'];
147 //------------------------------------
148 // create eWAY gateway objects
149 //------------------------------------
150 $eWAYRequest = new GatewayRequest();
152 if (($eWAYRequest == NULL) ||
(!($eWAYRequest instanceof GatewayRequest
))) {
153 return self
::errorExit(9001, "Error: Unable to create eWAY Request object.");
156 $eWAYResponse = new GatewayResponse();
158 if (($eWAYResponse == NULL) ||
(!($eWAYResponse instanceof GatewayResponse
))) {
159 return self
::errorExit(9002, "Error: Unable to create eWAY Response object.");
163 //-------------------------------------------------------------
164 // NOTE: eWAY Doesn't use the following at the moment:
165 //-------------------------------------------------------------
166 $creditCardType = $params['credit_card_type'];
167 $currentcyID = $params['currencyID'];
168 $country = $params['country'];
171 //-------------------------------------------------------------
172 // Prepare some composite data from _paymentProcessor fields
173 //-------------------------------------------------------------
174 $fullAddress = $params['street_address'] . ", " . $params['city'] . ", " . $params['state_province'] . ".";
175 $expireYear = substr($params['year'], 2, 2);
176 $expireMonth = sprintf('%02d', (int) $params['month']);
177 // CiviCRM V1.9 - Picks up reasonable description
178 //$description = $params['amount_level'];
179 // CiviCRM V2.0 - Picks up description
180 $description = $params['description'];
183 $amountInCents = round(((float) $params['amount']) * 100);
185 $credit_card_name = $params['first_name'] . " ";
186 if (strlen($params['middle_name']) > 0) {
187 $credit_card_name .= $params['middle_name'] . " ";
189 $credit_card_name .= $params['last_name'];
191 //----------------------------------------------------------------------------------------------------
192 // We use CiviCRM's param's 'invoiceID' as the unique transaction token to feed to eWAY
193 // Trouble is that eWAY only accepts 16 chars for the token, while CiviCRM's invoiceID is an 32.
194 // As its made from a "$invoiceID = md5(uniqid(rand(), true));" then using the fierst 16 chars
196 //----------------------------------------------------------------------------------------------------
197 $uniqueTrnxNum = substr($params['invoiceID'], 0, 16);
199 //----------------------------------------------------------------------------------------------------
200 // OPTIONAL: If TEST Card Number force an Override of URL and CutomerID.
201 // During testing CiviCRM once used the LIVE URL.
202 // This code can be uncommented to override the LIVE URL that if CiviCRM does that again.
203 //----------------------------------------------------------------------------------------------------
204 // if ( ( $gateway_URL == "https://www.eway.com.au/gateway_cvn/xmlpayment.asp")
205 // && ( $params['credit_card_number'] == "4444333322221111" ) ) {
206 // $ewayCustomerID = "87654321";
207 // $gateway_URL = "https://www.eway.com.au/gateway_cvn/xmltest/testpage.asp";
210 //----------------------------------------------------------------------------------------------------
211 // Now set the payment details - see http://www.eway.com.au/Support/Developer/PaymentsRealTime.aspx
212 //----------------------------------------------------------------------------------------------------
213 // 8 Chars - ewayCustomerID - Required
214 $eWAYRequest->EwayCustomerID($ewayCustomerID);
215 // 12 Chars - ewayTotalAmount (in cents) - Required
216 $eWAYRequest->InvoiceAmount($amountInCents);
217 // 50 Chars - ewayCustomerFirstName
218 $eWAYRequest->PurchaserFirstName($params['first_name']);
219 // 50 Chars - ewayCustomerLastName
220 $eWAYRequest->PurchaserLastName($params['last_name']);
221 // 50 Chars - ewayCustomerEmail
222 $eWAYRequest->PurchaserEmailAddress($params['email']);
223 // 255 Chars - ewayCustomerAddress
224 $eWAYRequest->PurchaserAddress($fullAddress);
225 // 6 Chars - ewayCustomerPostcode
226 $eWAYRequest->PurchaserPostalCode($params['postal_code']);
227 // 1000 Chars - ewayCustomerInvoiceDescription
228 $eWAYRequest->InvoiceDescription($description);
229 // 50 Chars - ewayCustomerInvoiceRef
230 $eWAYRequest->InvoiceReference($params['invoiceID']);
231 // 50 Chars - ewayCardHoldersName - Required
232 $eWAYRequest->CardHolderName($credit_card_name);
233 // 20 Chars - ewayCardNumber - Required
234 $eWAYRequest->CardNumber($params['credit_card_number']);
235 // 2 Chars - ewayCardExpiryMonth - Required
236 $eWAYRequest->CardExpiryMonth($expireMonth);
237 // 2 Chars - ewayCardExpiryYear - Required
238 $eWAYRequest->CardExpiryYear($expireYear);
239 // 4 Chars - ewayCVN - Required if CVN Gateway used
240 $eWAYRequest->CVN($params['cvv2']);
241 // 16 Chars - ewayTrxnNumber
242 $eWAYRequest->TransactionNumber($uniqueTrnxNum);
243 // 255 Chars - ewayOption1
244 $eWAYRequest->EwayOption1($txtOptions);
245 // 255 Chars - ewayOption2
246 $eWAYRequest->EwayOption2($txtOptions);
247 // 255 Chars - ewayOption3
248 $eWAYRequest->EwayOption3($txtOptions);
250 $eWAYRequest->CustomerIPAddress($params['ip_address']);
251 $eWAYRequest->CustomerBillingCountry($params['country']);
253 // Allow further manipulation of the arguments via custom hooks ..
254 CRM_Utils_Hook
::alterPaymentProcessorParams($this, $params, $eWAYRequest);
256 //----------------------------------------------------------------------------------------------------
257 // Check to see if we have a duplicate before we send
258 //----------------------------------------------------------------------------------------------------
259 if ($this->checkDupe($params['invoiceID'], CRM_Utils_Array
::value('contributionID', $params))) {
260 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.');
263 //----------------------------------------------------------------------------------------------------
264 // Convert to XML and send the payment information
265 //----------------------------------------------------------------------------------------------------
266 $requestxml = $eWAYRequest->ToXML();
268 $submit = curl_init($gateway_URL);
271 return self
::errorExit(9004, 'Could not initiate connection to payment gateway');
274 curl_setopt($submit, CURLOPT_POST
, TRUE);
275 // return the result on success, FALSE on failure
276 curl_setopt($submit, CURLOPT_RETURNTRANSFER
, TRUE);
277 curl_setopt($submit, CURLOPT_POSTFIELDS
, $requestxml);
278 curl_setopt($submit, CURLOPT_TIMEOUT
, 36000);
279 // if open_basedir or safe_mode are enabled in PHP settings CURLOPT_FOLLOWLOCATION won't work so don't apply it
280 // it's not really required CRM-5841
281 if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off')) {
282 // ensures any Location headers are followed
283 curl_setopt($submit, CURLOPT_FOLLOWLOCATION
, 1);
286 // Send the data out over the wire
287 //--------------------------------
288 $responseData = curl_exec($submit);
290 //----------------------------------------------------------------------------------------------------
291 // See if we had a curl error - if so tell 'em and bail out
293 // NOTE: curl_error does not return a logical value (see its documentation), but
294 // a string, which is empty when there was no error.
295 //----------------------------------------------------------------------------------------------------
296 if ((curl_errno($submit) > 0) ||
(strlen(curl_error($submit)) > 0)) {
297 $errorNum = curl_errno($submit);
298 $errorDesc = curl_error($submit);
300 // Paranoia - in the unlikley event that 'curl' errno fails
301 if ($errorNum == 0) {
305 // Paranoia - in the unlikley event that 'curl' error fails
306 if (strlen($errorDesc) == 0) {
307 $errorDesc = "Connection to eWAY payment gateway failed";
310 return self
::errorExit($errorNum, $errorDesc);
313 //----------------------------------------------------------------------------------------------------
314 // If null data returned - tell 'em and bail out
316 // NOTE: You will not necessarily get a string back, if the request failed for
317 // any reason, the return value will be the boolean false.
318 //----------------------------------------------------------------------------------------------------
319 if (($responseData === FALSE) ||
(strlen($responseData) == 0)) {
320 return self
::errorExit(9006, "Error: Connection to payment gateway failed - no data returned.");
323 //----------------------------------------------------------------------------------------------------
324 // If gateway returned no data - tell 'em and bail out
325 //----------------------------------------------------------------------------------------------------
326 if (empty($responseData)) {
327 return self
::errorExit(9007, "Error: No data returned from payment gateway.");
330 //----------------------------------------------------------------------------------------------------
331 // Success so far - close the curl and check the data
332 //----------------------------------------------------------------------------------------------------
335 //----------------------------------------------------------------------------------------------------
336 // Payment successfully sent to gateway - process the response now
337 //----------------------------------------------------------------------------------------------------
338 $eWAYResponse->ProcessResponse($responseData);
340 //----------------------------------------------------------------------------------------------------
341 // See if we got an OK result - if not tell 'em and bail out
342 //----------------------------------------------------------------------------------------------------
343 if (self
::isError($eWAYResponse)) {
344 $eWayTrxnError = $eWAYResponse->Error();
345 CRM_Core_Error
::debug_var('eWay Error', $eWayTrxnError, TRUE, TRUE);
346 if (substr($eWayTrxnError, 0, 6) == "Error:") {
347 return self
::errorExit(9008, $eWayTrxnError);
349 $eWayErrorCode = substr($eWayTrxnError, 0, 2);
350 $eWayErrorDesc = substr($eWayTrxnError, 3);
352 return self
::errorExit(9008, "Error: [" . $eWayErrorCode . "] - " . $eWayErrorDesc . ".");
355 //-----------------------------------------------------------------------------------------------------
356 // Cross-Check - the unique 'TrxnReference' we sent out should match the just received 'TrxnReference'
358 // PLEASE NOTE: If this occurs (which is highly unlikely) its a serious error as it would mean we have
359 // received an OK status from eWAY, but their Gateway has not returned the correct unique
360 // token - ie something is broken, BUT money has been taken from the client's account,
361 // so we can't very well error-out as CiviCRM will then not process the registration.
362 // There is an error message commented out here but my preferred response to this unlikley
363 // possibility is to email 'support@eWAY.com.au'
364 //-----------------------------------------------------------------------------------------------------
365 $eWayTrxnReference_OUT = $eWAYRequest->GetTransactionNumber();
366 $eWayTrxnReference_IN = $eWAYResponse->InvoiceReference();
368 if ($eWayTrxnReference_IN != $eWayTrxnReference_OUT) {
369 // 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.");
371 self
::send_alert_email($eWAYResponse->TransactionNumber(),
372 $eWayTrxnReference_OUT, $eWayTrxnReference_IN, $requestxml, $responseData
377 //----------------------------------------------------------------------------------------------------
378 // Test mode always returns trxn_id = 0 - so we fix that here
380 // NOTE: This code was taken from the AuthorizeNet payment processor, however it now appears
381 // unnecessary for the eWAY gateway - Left here in case it proves useful
382 //----------------------------------------------------------------------------------------------------
383 if ( $this->_mode == 'test' ) {
384 $query = "SELECT MAX(trxn_id) FROM civicrm_contribution WHERE trxn_id LIKE 'test%'";
386 $trxn_id = strval( CRM_Core_Dao::singleValueQuery( $query, $p ) );
387 $trxn_id = str_replace( 'test', '', $trxn_id );
388 $trxn_id = intval($trxn_id) + 1;
389 $params['trxn_id'] = sprintf('test%08d', $trxn_id);
392 $params['trxn_id'] = $eWAYResponse->TransactionNumber();
399 $beaglestatus = $eWAYResponse->BeagleScore();
400 if (!empty($beaglestatus)) {
401 $beaglestatus = ": " . $beaglestatus;
403 $params['trxn_result_code'] = $eWAYResponse->Status() . $beaglestatus;
404 $params['gross_amount'] = $eWAYResponse->Amount();
405 $params['trxn_id'] = $eWAYResponse->TransactionNumber();
409 // end function doDirectPayment
412 * **********************************************************************************************
413 * This function checks the eWAY response status - returning a boolean false if status != 'true'
414 * ************************************************************************************************
416 public function isError(&$response) {
417 $status = $response->Status();
419 if ((stripos($status, "true")) === FALSE) {
426 * ************************************************
427 * Produces error message and returns from class
428 * *************************************************
430 public function &errorExit($errorCode = NULL, $errorMessage = NULL) {
431 $e = CRM_Core_Error
::singleton();
434 $e->push($errorCode, 0, NULL, $errorMessage);
437 $e->push(9000, 0, NULL, 'Unknown System Error.');
443 * *****************************************************************************************
444 * This public function checks to see if we have the right processor config values set
446 * NOTE: Called by Events and Contribute to check config params are set prior to trying
447 * register any credit card details
449 * @return null|string
450 * @internal param string $mode the mode we are operating in (live or test) - not used but could be
451 * to check that the 'test' mode CustomerID was equal to '87654321' and that the URL was
452 * set to https://www.eway.com.au/gateway_cvn/xmltest/TestPage.asp
454 * returns string $errorMsg if any errors found - null if OK
456 * *****************************************************************************************
458 public function checkConfig() {
461 if (empty($this->_paymentProcessor
['user_name'])) {
462 $errorMsg[] = ts('eWAY CustomerID is not set for this payment processor');
465 if (empty($this->_paymentProcessor
['url_site'])) {
466 $errorMsg[] = ts('eWAY Gateway URL is not set for this payment processor');
469 if (!empty($errorMsg)) {
470 return implode('<p>', $errorMsg);
478 * @param $p_eWAY_tran_num
480 * @param $p_trxn_back
484 public function send_alert_email($p_eWAY_tran_num, $p_trxn_out, $p_trxn_back, $p_request, $p_response) {
485 // Initialization call is required to use CiviCRM APIs.
486 civicrm_initialize(TRUE);
488 list($fromName, $fromEmail) = CRM_Core_BAO_Domain
::getNameAndEmail();
489 $from = "$fromName <$fromEmail>";
491 $toName = 'Support at eWAY';
492 $toEmail = 'Support@eWAY.com.au';
494 $subject = "ALERT: Unique Trxn Number Failure : eWAY Transaction # = [" . $p_eWAY_tran_num . "]";
497 TRXN sent out with request = '$p_trxn_out'.
498 TRXN sent back with response = '$p_trxn_back'.
500 This is a ['$this->_mode'] transaction.
504 ---------------------------------------------------------------------------
506 ---------------------------------------------------------------------------
510 ---------------------------------------------------------------------------
512 ---------------------------------------------------------------------------
517 The CiviCRM eWAY Payment Processor Module
519 //$cc = 'Name@Domain';
521 // create the params array
524 $params['groupName'] = 'eWay Email Sender';
525 $params['from'] = $from;
526 $params['toName'] = $toName;
527 $params['toEmail'] = $toEmail;
528 $params['subject'] = $subject;
530 $params['text'] = $message;
532 CRM_Utils_Mail
::send($params);
536 // end class CRM_Core_Payment_eWAY