/** * hook_civicrm_pre * * Handle special cases of creating contribution (regular and recurring) records when using IATS Payments * * 1. CiviCRM assumes all recurring contributions need to be confirmed using the IPN mechanism. This is not true for iATS recurring contributions. * So when creating a contribution that is part of a recurring series, test for status = 2, and set to status = 1 instead, unless we're using the fixed day feature * Do this only for the initial contribution record. * The (subsequent) recurring contributions' status id is set explicitly in the job that creates it, this modification breaks that process. * * 2. For ACH/EFT, we also have the opposite problem - all contributions will need to verified by iATS and only later set to status success or * failed via the acheft verify job. We also want to modify the payment instrument from CC to ACH/EFT * * TODO: update this code with constants for the various id values of 1 and 2. * TODO: CiviCRM should have nicer ways to handle this. */ function iats_civicrm_pre($op, $objectName, $objectId, &$params) { // since this function gets called a lot, quickly determine if I care about the record being created if ('create' == $op && ('Contribution' == $objectName || 'ContributionRecur' == $objectName) && !empty($params['contribution_status_id'])) { // watchdog('iats_civicrm','hook_civicrm_pre for Contribution <pre>@params</pre>',array('@params' => print_r($params)); // figure out the payment processor id, not nice $version = CRM_Utils_System::version(); $payment_processor_id = 'ContributionRecur' == $objectName ? $params['payment_processor_id'] : (!empty($params['payment_processor']) ? $params['payment_processor'] : (!empty($params['contribution_recur_id']) ? _iats_civicrm_get_payment_processor_id($params['contribution_recur_id']) : 0)); if ($type = _iats_civicrm_is_iats($payment_processor_id)) { $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings'); $allow_days = empty($settings['days']) ? array('-1') : $settings['days']; switch ($type . $objectName) { case 'iATSServiceContribution': // cc contribution, test if it's been set to status 2 on a recurring contribution // cc contribution, test if it's been set to status 2 on a recurring contribution case 'iATSServiceSWIPEContribution': // for civi version before 4.6.6, we had to force the status to 1 if (2 == $params['contribution_status_id'] && !empty($params['contribution_recur_id']) && max($allow_days) <= 0 && version_compare($version, '4.6.6') < 0) { // but only for the first one $count = civicrm_api('Contribution', 'getcount', array('version' => 3, 'contribution_recur_id' => $params['contribution_recur_id'])); if (is_array($count) && empty($count['result']) || empty($count)) { // watchdog('iats_civicrm','hook_civicrm_pre updating status_id for objectName @id, count <pre>!count</pre>, params <pre>!params</pre>, ',array('@id' => $objectName, '!count' => print_r($count,TRUE),'!params' => print_r($params,TRUE))); $params['contribution_status_id'] = 1; } } break; case 'iATSServiceContributionRecur': // cc/swipe/ACHEFT recurring contribution record // cc/swipe/ACHEFT recurring contribution record case 'iATSServiceSWIPEContributionRecur': case 'iATSServiceACHEFTContributionRecur': // the next scheduled contribution date field name is civicrm version dependent $field_name = _iats_civicrm_nscd_fid(); // when creating a recurring contribution record via a civicrm contribution form // we've already taken the first payment, so calculate the next one (core assumes the intial contribution is pending) // we set this to 'in-progress' even for ACH/EFT if the first one hasn't been verified, because we still want to be attempting later ones // this condition helps avoid mangling records being imported from a csv file if (5 != $params['contribution_status_id'] && empty($params[$field_name])) { $params['contribution_status_id'] = 5; $params['trxn_id'] = NULL; // civi wants to put the returned trxn_id in here $next = strtotime('+' . $params['frequency_interval'] . ' ' . $params['frequency_unit']); $params[$field_name] = date('YmdHis', $next); } if ($type == 'iATSServiceACHEFT') { // fix the payment type for ACH/EFT $params['payment_instrument_id'] = 2; } break; case 'iATSServiceACHEFTContribution': // ach/eft contribution: update the payment instrument $params['payment_instrument_id'] = 2; // and push the status to 2 if civicrm thinks it's 1, i.e. for one-time contributions // in other words, never create ach/eft contributions as complete, always push back to pending and verify if ($params['contribution_status_id'] == 1) { $params['contribution_status_id'] = 2; } break; case 'iATSServiceUKDDContribution': // UK DD contribution: update the payment instrument, fix the receive date $params['payment_instrument_id'] = 2; if ($start_date = strtotime($_POST['payer_validate_start_date'])) { $params['receive_date'] = date('Ymd', $start_date) . '120000'; } break; case 'iATSServiceUKDDContributionRecur': // UK DD recurring contribution record: update the payment instrument, fix the start_date $params['payment_instrument_id'] = 2; if ($start_date = strtotime($_POST['payer_validate_start_date'])) { $params['start_date'] = date('Ymd', $start_date) . '120000'; } break; } if ($type != 'iATSServiceUKDD' && $objectName == 'Contribution') { // new, non-UKDD contribution records in a schedule are forced to comply with any restrictions if (0 < max($allow_days)) { $from_time = _iats_contributionrecur_next(strtotime($params['receive_date']), $allow_days); $params['receive_date'] = date('Ymd', $from_time) . '030000'; } } } // watchdog('iats_civicrm','ignoring hook_civicrm_pre for objectName @id',array('@id' => $objectName)); } // if I've set fixed monthly recurring dates, force any iats (non uk dd) recurring contribution schedule records to comply // it's a bit draconian, and you likely want to give administrators the ability to modify these schedules // this is separate from the above because I want to deal with both create and edit possibilities if ('ContributionRecur' == $objectName && ('create' == $op || 'edit' == $op)) { if ($type = _iats_civicrm_is_iats($params['payment_processor_id'])) { if ($type != 'iATSServiceUKDD' && !empty($params['next_sched_contribution_date'])) { $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings'); $allow_days = empty($settings['days']) ? array('-1') : $settings['days']; if (0 < max($allow_days)) { // force one of the fixed days, and set the cycle_day at the same time $init_time = 'create' == $op ? time() : strtotime($params['next_sched_contribution_date']); $from_time = _iats_contributionrecur_next($init_time, $allow_days); $params['next_sched_contribution_date'] = date('YmdHis', $from_time); $params['cycle_day'] = date('j', $from_time); // day of month without leading 0 } } if (empty($params['installments'])) { // fix a civi bug while I'm here $params['installments'] = '0'; } } } }
function doDirectPayment(&$params) { if (!$this->_profile) { return self::error('Unexpected error, missing profile'); } // use the iATSService object for interacting with iATS. Recurring contributions go through a more complex process. require_once "CRM/iATS/iATSService.php"; $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']; $methodType = $isRecur ? 'customer' : 'process'; $method = $isRecur ? 'create_credit_card_customer' : 'cc'; $iats = new iATS_Service_Request(array('type' => $methodType, 'method' => $method, 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID'])); $request = $this->convertParams($params, $method); $request['customerIPAddress'] = function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']; $credentials = array('agentCode' => $this->_paymentProcessor['user_name'], 'password' => $this->_paymentProcessor['password']); // Get the API endpoint URL for the method's transaction mode. // TODO: enable override of the default url in the request object // $url = $this->_paymentProcessor['url_site']; // make the soap request $response = $iats->request($credentials, $request); if (!$isRecur) { // process the soap response into a readable result, logging any credit card transactions $result = $iats->result($response); if ($result['status']) { $params['contribution_status_id'] = 1; // success $params['payment_status_id'] = 1; // for versions >= 4.6.6, the proper key $params['trxn_id'] = trim($result['remote_id']) . ':' . time(); $params['gross_amount'] = $params['amount']; return $params; } else { return self::error($result['reasonMessage']); } } else { // save the client info in my custom table, then (maybe) run the transaction $customer = $iats->result($response, FALSE); // print_r($customer); if ($customer['status']) { $processresult = $response->PROCESSRESULT; $customer_code = (string) $processresult->CUSTOMERCODE; $exp = sprintf('%02d%02d', $params['year'] % 100, $params['month']); $email = ''; if (isset($params['email'])) { $email = $params['email']; } elseif (isset($params['email-5'])) { $email = $params['email-5']; } elseif (isset($params['email-Primary'])) { $email = $params['email-Primary']; } $query_params = array(1 => array($customer_code, 'String'), 2 => array($request['customerIPAddress'], 'String'), 3 => array($exp, 'String'), 4 => array($params['contactID'], 'Integer'), 5 => array($email, 'String'), 6 => array($params['contributionRecurID'], 'Integer')); CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_customer_codes\n (customer_code, ip, expiry, cid, email, recur_id) VALUES (%1, %2, %3, %4, %5, %6)", $query_params); $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings'); $allow_days = empty($settings['days']) ? array('-1') : $settings['days']; if (max($allow_days) <= 0) { // run the transaction immediately $iats = new iATS_Service_Request(array('type' => 'process', 'method' => 'cc_with_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID'])); $request = array('invoiceNum' => $params['invoiceID']); $request['total'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount'])); $request['customerCode'] = $customer_code; $request['customerIPAddress'] = function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']; $response = $iats->request($credentials, $request); $result = $iats->result($response); if ($result['status']) { $params['contribution_status_id'] = 1; // success $params['payment_status_id'] = 1; // for versions >= 4.6.6, the proper key $params['trxn_id'] = trim($result['remote_id']) . ':' . time(); $params['gross_amount'] = $params['amount']; $params['next_sched_contribution'] = strtotime('+' . $params['frequency_interval'] . ' ' . $params['frequency_unit']); return $params; } else { return self::error($result['reasonMessage']); } } else { // I've got a schedule to adhere to! $params['contribution_status_id'] = 2; // pending $params['payment_status_id'] = 2; // for versions >= 4.6.6, the proper key $from_time = _iats_contributionrecur_next(time(), $allow_days); $params['next_sched_contribution'] = $params['receive_date'] = date('Ymd', $from_time) . '030000'; return $params; } return self::error('Unexpected error'); } else { return self::error($customer['reasonMessage']); } } }