X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;ds=sidebyside;f=trustcommerce.php;h=d36e42b22cc33fdae944a15eada67c7d2953df9a;hb=1d2685936e83096c436b8f250502d711a243eebf;hp=ac2ea7d37239398d74bb9fdda0e3891b441607b7;hpb=14f15ff30e07016237024dca5bd827e35f511257;p=trustcommerce.git diff --git a/trustcommerce.php b/trustcommerce.php index ac2ea7d..d36e42b 100644 --- a/trustcommerce.php +++ b/trustcommerce.php @@ -1,10 +1,26 @@ . + * * Copyright (C) 2012 * Licensed to CiviCRM under the GPL v3 or higher * * Written and contributed by Ward Vandewege (http://www.fsf.org) * Modified by Lisa Marie Maginnis (http://www.fsf.org) + * Copyright © 2015 David Thompson * */ @@ -18,6 +34,7 @@ class org_fsf_payment_trustcommerce extends CRM_Core_Payment { CONST AUTH_DECLINED = 'decline'; CONST AUTH_BADDATA = 'baddata'; CONST AUTH_ERROR = 'error'; + CONST AUTH_BLACKLIST = 'blacklisted'; static protected $_mode = NULL; @@ -53,6 +70,7 @@ class org_fsf_payment_trustcommerce extends CRM_Core_Payment { srand(time()); $this->_setParam('sequence', rand(1, 1000)); $this->logging_level = TRUSTCOMMERCE_LOGGING_LEVEL; + } /** @@ -93,8 +111,9 @@ class org_fsf_payment_trustcommerce extends CRM_Core_Payment { $tc_params = $this->_getTrustCommerceFields(); /* Are we recurring? If so add the extra API fields. */ - if (CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']) { + if (CRM_Utils_Array::value('is_recur', $params) == 1) { $tc_params = $this->_getRecurPaymentFields($tc_params); + $recur=1; } /* Pass our cooked params to the alter hook, per Core/Payment/Dummy.php */ @@ -105,18 +124,35 @@ class org_fsf_payment_trustcommerce extends CRM_Core_Payment { return self::error(9004, 'It appears that this transaction is a duplicate. Have you already submitted the form once? If so there may have been a connection problem. You can try your transaction again. If you continue to have problems please contact the site administrator.'); } - /* Call the TC API, and grab the reply */ - $reply = tclink_send($tc_params); + /* This implements a local blacklist, and passes us though as a normal failure + * if the luser is on the blacklist. */ + if(!$this->_isBlacklisted()) { + /* Call the TC API, and grab the reply */ + $reply = $this->_sendTCRequest($tc_params); + } else { + $this->_logger($tc_params); + $reply['status'] = self::AUTH_BLACKLIST; + } /* Parse our reply */ - $result = $this->_getTrustCommerceReply($reply); + $result = $this->_getTCReply($reply); if($result == 0) { /* We were successful, congrats. Lets wrap it up: * Convert back to dollars * Save the transaction ID */ + + if (array_key_exists('billingid', $reply)) { + $params['recurr_profile_id'] = $reply['billingid']; + CRM_Core_DAO::setFieldValue( + 'CRM_Contribute_DAO_ContributionRecur', + $this->_getParam('contributionRecurID'), + 'processor_id', $reply['billingid'] + ); + } $params['trxn_id'] = $reply['transid']; + $params['gross_amount'] = $tc_params['amount'] / 100; return $params; @@ -127,6 +163,122 @@ class org_fsf_payment_trustcommerce extends CRM_Core_Payment { } } + /** + * Update CC info for a recurring contribution + */ + function updateSubscriptionBillingInfo(&$message = '', $params = array()) { + $expYear = $params['credit_card_exp_date']['Y']; + $expMonth = $params['credit_card_exp_date']['M']; + + $tc_params = array( + 'custid' => $this->_paymentProcessor['user_name'], + 'password' => $this->_paymentProcessor['password'], + 'action' => 'store', + 'billingid' => $params['subscriptionId'], + 'avs' => 'y', // Enable address verification + 'address1' => $params['street_address'], + 'zip' => $params['postal_code'], + 'name' => $this->_formatBillingName($params['first_name'], + $params['last_name']), + 'cc' => $params['credit_card_number'], + 'cvv' => $params['cvv2'], + 'exp' => $this->_formatExpirationDate($expYear, $expMonth), + 'amount' => $this->_formatAmount($params['amount']), + ); + + CRM_Utils_Hook::alterPaymentProcessorParams($this, $params, $tc_params); + + $reply = $this->_sendTCRequest($tc_params); + $result = $this->_getTCReply($reply); + + if($result === 0) { + $message = 'Successfully updated TC billing id ' . $tc_params['billingid']; + + return TRUE; + } else { + return FALSE; + } + } + + // TODO: Use the formatting functions throughout the entire class to + // dedupe the conversions done elsewhere in a less reusable way. + function _formatAmount($amount) { + return $amount * 100; + } + + function _formatBillingName($firstName, $lastName) { + return "$firstName $lastName"; + } + + function _formatExpirationDate($year, $month) { + $exp_month = str_pad($month, 2, '0', STR_PAD_LEFT); + $exp_year = substr($year, -2); + + return "$exp_month$exp_year"; + } + + function _isBlacklisted() { + if($this->_isIPBlacklisted()) { + return TRUE; + } else if($this->_IsAgentBlacklisted()) { + return TRUE; + } + return FALSE; + } + + function _isAgentBlacklisted() { + $ip = $_SERVER['REMOTE_ADDR']; + $agent = $_SERVER['HTTP_USER_AGENT']; + $dao = CRM_Core_DAO::executeQuery('SELECT * FROM `trustcommerce_useragent_blacklist`'); + while($dao->fetch()) { + if(preg_match('/'.$dao->name.'/', $agent) === 1) { + error_log(' [client '.$ip.'] [agent '.$agent.'] - Blacklisted by USER_AGENT rule #'.$dao->id); + return TRUE; + } + } + return FALSE; + } + + function _isIPBlacklisted() { + $ip = $_SERVER['REMOTE_ADDR']; + $agent = $_SERVER['HTTP_USER_AGENT']; + $ip = ip2long($ip); + $blacklist = array(); + $dao = CRM_Core_DAO::executeQuery('SELECT * FROM `trustcommerce_blacklist`'); + while($dao->fetch()) { + if($ip >= $dao->start && $ip <= $dao->end) { + error_log('[client '.long2ip($ip).'] [agent '.$agent.'] Blacklisted by IP rule #'.$dao->id); + return TRUE; + } + } + return FALSE; + } + + function _sendTCRequest($request) { + $this->_logger($request); + return tclink_send($request); + } + + function _logger($params) { + $msg = ''; + foreach ($params as $key => $data) { + /* Delete any data we should not be writing to disk. This includes: + * custid, password, cc, exp, and cvv + */ + switch($key) { + case 'custid': + case 'password': + case 'cc': + case 'exp': + case 'cvv': + break; + default: + $msg .= ' '.$key.' => '.$data; + } + } + error_log('[client '.$_SERVER['REMOTE_ADDR'].'] TrustCommerce:'.$msg); + } + /** * Gets the recurring billing fields for the TC API * @param array $fields The fields to modify. @@ -152,7 +304,7 @@ class org_fsf_payment_trustcommerce extends CRM_Core_Payment { $cycle = 'y'; break; } - + /* Translate frequency interval from CiviCRM -> TC * Payments are the same, HOWEVER a payment of 1 (forever) should be 0 in TC */ if($payments == 1) { @@ -161,6 +313,8 @@ class org_fsf_payment_trustcommerce extends CRM_Core_Payment { $fields['cycle'] = '1'.$cycle; /* The billing cycle in years, months, weeks, or days. */ $fields['payments'] = $payments; + $fields['authnow'] = 'y'; + $fields['start'] = date("Y-m-d"); /* Start date is required when 'authnow' is used. */ $fields['action'] = 'store'; /* Change our mode to `store' mode. */ return $fields; @@ -170,21 +324,31 @@ class org_fsf_payment_trustcommerce extends CRM_Core_Payment { * @param $reply array The result of a call to tclink_send(). * @return mixed self::error() if transaction failed, otherwise returns 0. */ - function _getTrustCommerceReply($reply) { + function _getTCReply($reply) { /* DUPLIATE CODE, please refactor. ~lisa */ if (!$reply) { return self::error(9002, 'Could not initiate connection to payment gateway'); } + $this->_logger($reply); + switch($reply['status']) { + case self::AUTH_BLACKLIST: + return self::error(9001, "Your transaction was declined: error #90210"); + break; case self::AUTH_APPROVED: // It's all good break; case self::AUTH_DECLINED: - // TODO FIXME be more or less specific? + // TODO FIXME be more or less specific? // declinetype can be: decline, avs, cvv, call, expiredcard, carderror, authexpired, fraud, blacklist, velocity // See TC documentation for more info + switch($reply['declinetype']) { + case 'avs': + return self::error(9009, "Your transaction was declined for address verification reasons. If your address was correct please contact us at donate@fsf.org before attempting to retry your transaction."); + break; + } return self::error(9009, "Your transaction was declined: {$reply['declinetype']}"); break; case self::AUTH_BADDATA: @@ -322,9 +486,9 @@ class org_fsf_payment_trustcommerce extends CRM_Core_Payment { $tc_params['custid'] = $this->_getParam('user_name'); $tc_params['password'] = $this->_getParam('password'); $tc_params['action'] = 'unstore'; - $tc_params['billingid'] = CRM_Utils_Array::value('subscriptionId', $params); + $tc_params['billingid'] = CRM_Utils_Array::value('trxn_id', $params); - $result = tclink_send($tc_params); + $result = $this->_sendTCRequest($tc_params); /* Test if call failed */ if(!$result) { @@ -333,7 +497,32 @@ class org_fsf_payment_trustcommerce extends CRM_Core_Payment { /* We are done, pass success */ return TRUE; } - + + function changeSubscriptionAmount(&$message = '', $params = array()) { + $tc_params['custid'] = $this->_paymentProcessor['user_name']; + $tc_params['password'] = $this->_paymentProcessor['password']; + $tc_params['action'] = 'store'; + + $tc_params['billingid'] = CRM_Utils_Array::value('subscriptionId', $params); + $tc_params['payments'] = CRM_Utils_Array::value('installments', $params); + $tc_params['amount'] = CRM_Utils_Array::value('amount', $params) * 100; + + if($tc_params['payments'] == 1) { + $tc_params['payments'] = 0; + } + $reply = $this->_sendTCRequest($tc_params); + $result = $this->_getTCReply($reply); + + /* Test if call failed */ + if(!$result) { + return self::error(9002, 'Could not initiate connection to payment gateway'); + } + + /* We are done, pass success */ + return TRUE; + + } + public function install() { return TRUE; }