From 71a6ba5cb8da1e73e81a13a50de2cafa1efe3b18 Mon Sep 17 00:00:00 2001 From: Lisa Marie Maginnis Date: Tue, 22 Apr 2014 11:44:46 -0400 Subject: [PATCH] Initial Commit --- info.xml | 42 ++++ trustcommerce.php | 486 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 528 insertions(+) create mode 100644 info.xml create mode 100644 trustcommerce.php diff --git a/info.xml b/info.xml new file mode 100644 index 0000000..e666cc5 --- /dev/null +++ b/info.xml @@ -0,0 +1,42 @@ + + + http://agpl.fsf.org/crm.fsf.org/CURRENT/trustcommerce-0.1.zip + trustcommerce + TrustCommerce + TrustCommerce Payment Processor + + http://www.fsf.org + + AGPL3 + + Nico Cesar, Ward Vandewege + sysadmin@fsf.org + + 2014-02-11 + 0.3 + beta + + 4.4 + + For support, please contact project team on the forums. + + Vendor ID + Password + + + https://vault.trustcommerce.com/trans/ + + https://vault.trustcommerce.com/trans/ + + + + https://vault.trustcommerce.com/trans/ + https://vault.trustcommerce.com/trans/ + https://vault.trustcommerce.com/trans/ + + + form + 1 + 1 + + diff --git a/trustcommerce.php b/trustcommerce.php new file mode 100644 index 0000000..1fa517f --- /dev/null +++ b/trustcommerce.php @@ -0,0 +1,486 @@ + (http://www.fsf.org) + * + */ + +// Define logging level (0 = off, 4 = log everything) +define('TRUSTCOMMERCE_LOGGING_LEVEL', 4); + +require_once 'CRM/Core/Payment.php'; +class org_fsf_payment_trustcommerce extends CRM_Core_Payment { + CONST CHARSET = 'iso-8859-1'; + CONST AUTH_APPROVED = 'approve'; + CONST AUTH_DECLINED = 'decline'; + CONST AUTH_BADDATA = 'baddata'; + CONST AUTH_ERROR = 'error'; + + static protected $_mode = NULL; + + static protected $_params = array(); + + /** + * We only need one instance of this object. So we use the singleton + * pattern and cache the instance in this variable + * + * @var object + * @static + */ + static private $_singleton = NULL; + + /** + * Constructor + * + * @param string $mode the mode of operation: live or test + * + * @return void + */ function __construct($mode, &$paymentProcessor) { + $this->_mode = $mode; + + $this->_paymentProcessor = $paymentProcessor; + + $this->_processorName = ts('TrustCommerce'); + + $config = CRM_Core_Config::singleton(); + $this->_setParam('user_name', $paymentProcessor['user_name']); + $this->_setParam('password', $paymentProcessor['password']); + + $this->_setParam('timestamp', time()); + srand(time()); + $this->_setParam('sequence', rand(1, 1000)); + $this->logging_level = TRUSTCOMMERCE_LOGGING_LEVEL; + } + + /** + * singleton function used to manage this object + * + * @param string $mode the mode of operation: live or test + * + * @return object + * @static + * + */ + static + function &singleton($mode, &$paymentProcessor) { + $processorName = $paymentProcessor['name']; + if (self::$_singleton[$processorName] === NULL) { + self::$_singleton[$processorName] = new org_fsf_payment_trustcommerce($mode, $paymentProcessor); + } + return self::$_singleton[$processorName]; + } + + /** + * Submit a payment using Advanced Integration Method + * + * @param array $params assoc array of input parameters for this transaction + * + * @return array the result in a nice formatted array (or an error object) + * @public + */ + function doDirectPayment(&$params) { + if (!extension_loaded("tclink")) { + return self::error(9001, 'TrustCommerce requires that the tclink module is loaded'); + } + + /* + * recurpayment function does not compile an array & then proces it - + * - the tpl does the transformation so adding call to hook here + * & giving it a change to act on the params array + */ + + $newParams = $params; + if (CRM_Utils_Array::value('is_recur', $params) && + $params['contributionRecurID'] + ) { + CRM_Utils_Hook::alterPaymentProcessorParams($this, + $params, + $newParams + ); + } + foreach ($newParams as $field => $value) { + $this->_setParam($field, $value); + } + + if (CRM_Utils_Array::value('is_recur', $params) && + $params['contributionRecurID'] + ) { + return $this->doRecurPayment($params); + } + + $postFields = array(); + $tclink = $this->_getTrustCommerceFields(); + + // Set up our call for hook_civicrm_paymentProcessor, + // since we now have our parameters as assigned for the AIM back end. + CRM_Utils_Hook::alterPaymentProcessorParams($this, + $params, + $tclink + ); + + // TrustCommerce will not refuse duplicates, so we should check if the user already submitted this transaction + if ($this->_checkDupe($tclink['ticket'])) { + 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.'); + } + + $result = tclink_send($tclink); + + if (!$result) { + return self::error(9002, 'Could not initiate connection to payment gateway'); + } + + foreach ($result as $field => $value) { + error_log("result: $field => $value"); + } + + switch($result['status']) { + case self::AUTH_APPROVED: + // It's all good + break; + case self::AUTH_DECLINED: + // 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 + return self::error(9009, "Your transaction was declined: {$result['declinetype']}"); + break; + case self::AUTH_BADDATA: + // TODO FIXME do something with $result['error'] and $result['offender'] + return self::error(9011, "Invalid credit card information. Please re-enter."); + break; + case self::AUTH_ERROR: + return self::error(9002, 'Could not initiate connection to payment gateway'); + break; + } + + // Success + + $params['trxn_id'] = $result['transid']; + $params['gross_amount'] = $tclink['amount'] / 100; + + return $params; + } + + /** + * Submit an Automated Recurring Billing subscription + * + * @param array $params assoc array of input parameters for this transaction + * + * @return array the result in a nice formatted array (or an error object) + * @public + */ + function doRecurPayment(&$params) { + $template = CRM_Core_Smarty::singleton(); + + $intervalLength = $this->_getParam('frequency_interval'); + $intervalUnit = $this->_getParam('frequency_unit'); + if ($intervalUnit == 'week') { + $intervalLength *= 7; + $intervalUnit = 'days'; + } + elseif ($intervalUnit == 'year') { + $intervalLength *= 12; + $intervalUnit = 'months'; + } + elseif ($intervalUnit == 'day') { + $intervalUnit = 'days'; + } + elseif ($intervalUnit == 'month') { + $intervalUnit = 'months'; + } + + // interval cannot be less than 7 days or more than 1 year + if ($intervalUnit == 'days') { + if ($intervalLength < 7) { + return self::error(9001, 'Payment interval must be at least one week'); + } + elseif ($intervalLength > 365) { + return self::error(9001, 'Payment interval may not be longer than one year'); + } + } + elseif ($intervalUnit == 'months') { + if ($intervalLength < 1) { + return self::error(9001, 'Payment interval must be at least one week'); + } + elseif ($intervalLength > 12) { + return self::error(9001, 'Payment interval may not be longer than one year'); + } + } + + $template->assign('intervalLength', $intervalLength); + $template->assign('intervalUnit', $intervalUnit); + + $template->assign('apiLogin', $this->_getParam('apiLogin')); + $template->assign('paymentKey', $this->_getParam('paymentKey')); + $template->assign('refId', substr($this->_getParam('invoiceID'), 0, 20)); + + //for recurring, carry first contribution id + $template->assign('invoiceNumber', $this->_getParam('contributionID')); + $firstPaymentDate = $this->_getParam('receive_date'); + if (!empty($firstPaymentDate)) { + //allow for post dated payment if set in form + $template->assign('startDate', date('Y-m-d', strtotime($firstPaymentDate))); + } + else { + $template->assign('startDate', date('Y-m-d')); + } + // for open ended subscription totalOccurrences has to be 9999 + $installments = $this->_getParam('installments'); + $template->assign('totalOccurrences', $installments ? $installments : 9999); + + $template->assign('amount', $this->_getParam('amount')); + + $template->assign('cardNumber', $this->_getParam('credit_card_number')); + $exp_month = str_pad($this->_getParam('month'), 2, '0', STR_PAD_LEFT); + $exp_year = $this->_getParam('year'); + $template->assign('expirationDate', $exp_year . '-' . $exp_month); + // name rather than description is used in the tpl - see http://www.authorize.net/support/ARB_guide.pdf + $template->assign('name', $this->_getParam('description')); + + $template->assign('email', $this->_getParam('email')); + $template->assign('contactID', $this->_getParam('contactID')); + $template->assign('billingFirstName', $this->_getParam('billing_first_name')); + $template->assign('billingLastName', $this->_getParam('billing_last_name')); + $template->assign('billingAddress', $this->_getParam('street_address')); + $template->assign('billingCity', $this->_getParam('city')); + $template->assign('billingState', $this->_getParam('state_province')); + $template->assign('billingZip', $this->_getParam('postal_code')); + $template->assign('billingCountry', $this->_getParam('country')); + + $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl'); + // submit to authorize.net + + $submit = curl_init($this->_paymentProcessor['url_recur']); + if (!$submit) { + return self::error(9002, 'Could not initiate connection to payment gateway'); + } + curl_setopt($submit, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($submit, CURLOPT_HTTPHEADER, array("Content-Type: text/xml")); + curl_setopt($submit, CURLOPT_HEADER, 1); + curl_setopt($submit, CURLOPT_POSTFIELDS, $arbXML); + curl_setopt($submit, CURLOPT_POST, 1); + curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, 0); + + $response = curl_exec($submit); + + if (!$response) { + return self::error(curl_errno($submit), curl_error($submit)); + } + + curl_close($submit); + $responseFields = $this->_ParseArbReturn($response); + + if ($responseFields['resultCode'] == 'Error') { + return self::error($responseFields['code'], $responseFields['text']); + } + + // update recur processor_id with subscriptionId + CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_ContributionRecur', $params['contributionRecurID'], + 'processor_id', $responseFields['subscriptionId'] + ); + //only impact of assigning this here is is can be used to cancel the subscription in an automated test + // if it isn't cancelled a duplicate transaction error occurs + if (CRM_Utils_Array::value('subscriptionId', $responseFields)) { + $this->_setParam('subscriptionId', $responseFields['subscriptionId']); + } + return $params; + } + + function _getTrustCommerceFields() { + // Total amount is from the form contribution field + $amount = $this->_getParam('total_amount'); + // CRM-9894 would this ever be the case?? + if (empty($amount)) { + $amount = $this->_getParam('amount'); + } + $fields = array(); + $fields['custid'] = $this->_getParam('user_name'); + $fields['password'] = $this->_getParam('password'); + $fields['action'] = 'sale'; + + // Enable address verification + $fields['avs'] = 'y'; + + $fields['address1'] = $this->_getParam('street_address'); + $fields['zip'] = $this->_getParam('postal_code'); + + $fields['name'] = $this->_getParam('billing_first_name') . ' ' . $this->_getParam('billing_last_name'); + + // This assumes currencies where the . is used as the decimal point, like USD + $amount = preg_replace("/([^0-9\\.])/i", "", $amount); + + // We need to pass the amount to TrustCommerce in dollar cents + $fields['amount'] = $amount * 100; + + // Unique identifier + $fields['ticket'] = substr($this->_getParam('invoiceID'), 0, 20); + + // cc info + $fields['cc'] = $this->_getParam('credit_card_number'); + $fields['cvv'] = $this->_getParam('cvv2'); + $exp_month = str_pad($this->_getParam('month'), 2, '0', STR_PAD_LEFT); + $exp_year = substr($this->_getParam('year'),-2); + $fields['exp'] = "$exp_month$exp_year"; + + if ($this->_mode != 'live') { + $fields['demo'] = 'y'; + } + // TODO FIXME remove + foreach ($fields as $field => $value) { + if ($field == 'custid') $value = '********'; + if ($field == 'password') $value = '********'; + if ($field == 'cc') $value = '********'; + if ($field == 'cvv') $value = '********'; + if ($field == 'exp') $value = '********'; + error_log("fields: $field => $value"); + } + + return $fields; + } + + /** + * Checks to see if invoice_id already exists in db + * + * @param int $invoiceId The ID to check + * + * @return bool True if ID exists, else false + */ + function _checkDupe($invoiceId) { + require_once 'CRM/Contribute/DAO/Contribution.php'; + $contribution = new CRM_Contribute_DAO_Contribution(); + $contribution->invoice_id = $invoiceId; + return $contribution->find(); + } + + /** + * Get the value of a field if set + * + * @param string $field the field + * + * @return mixed value of the field, or empty string if the field is + * not set + */ + function _getParam($field) { + return CRM_Utils_Array::value($field, $this->_params, ''); + } + + function &error($errorCode = NULL, $errorMessage = NULL) { + $e = CRM_Core_Error::singleton(); + if ($errorCode) { + $e->push($errorCode, 0, NULL, $errorMessage); + } + else { + $e->push(9001, 0, NULL, 'Unknown System Error.'); + } + return $e; + } + + /** + * Set a field to the specified value. Value must be a scalar (int, + * float, string, or boolean) + * + * @param string $field + * @param mixed $value + * + * @return bool false if value is not a scalar, true if successful + */ + function _setParam($field, $value) { + if (!is_scalar($value)) { + return FALSE; + } + else { + $this->_params[$field] = $value; + } + } + + /** + * This function checks to see if we have the right config values + * + * @return string the error message if any + * @public + */ + function checkConfig() { + $error = array(); + if (empty($this->_paymentProcessor['user_name'])) { + $error[] = ts('Customer ID is not set for this payment processor'); + } + + if (empty($this->_paymentProcessor['password'])) { + $error[] = ts('Password is not set for this payment processor'); + } + + if (!empty($error)) { + return implode('

', $error); + } else { + return NULL; + } + } + + function cancelSubscriptionURL($entityID = NULL, $entity = NULL) { + if ($entityID && $entity == 'membership') { + require_once 'CRM/Contact/BAO/Contact/Utils.php'; + $contactID = CRM_Core_DAO::getFieldValue("CRM_Member_DAO_Membership", $entityID, "contact_id"); + $checksumValue = CRM_Contact_BAO_Contact_Utils::generateChecksum($contactID, NULL, 'inf'); + + return CRM_Utils_System::url('civicrm/contribute/unsubscribe', + "reset=1&mid={$entityID}&cs={$checksumValue}", TRUE, NULL, FALSE, FALSE + ); + } + + return ($this->_mode == 'test') ? 'https://test.authorize.net' : 'https://authorize.net'; + } + + function cancelSubscription() { + $template = CRM_Core_Smarty::singleton(); + + $template->assign('subscriptionType', 'cancel'); + + $template->assign('apiLogin', $this->_getParam('apiLogin')); + $template->assign('paymentKey', $this->_getParam('paymentKey')); + $template->assign('subscriptionId', $this->_getParam('subscriptionId')); + + $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl'); + + // submit to authorize.net + $submit = curl_init($this->_paymentProcessor['url_recur']); + if (!$submit) { + return self::error(9002, 'Could not initiate connection to payment gateway'); + } + + curl_setopt($submit, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($submit, CURLOPT_HTTPHEADER, array("Content-Type: text/xml")); + curl_setopt($submit, CURLOPT_HEADER, 1); + curl_setopt($submit, CURLOPT_POSTFIELDS, $arbXML); + curl_setopt($submit, CURLOPT_POST, 1); + curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, 0); + + $response = curl_exec($submit); + + if (!$response) { + return self::error(curl_errno($submit), curl_error($submit)); + } + + curl_close($submit); + + $responseFields = $this->_ParseArbReturn($response); + + if ($responseFields['resultCode'] == 'Error') { + return self::error($responseFields['code'], $responseFields['text']); + } + + // carry on cancelation procedure + return TRUE; + } + + public function install() { + return TRUE; + } + + public function uninstall() { + return TRUE; + } + +} + + -- 2.25.1