/**
 * 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']);
         }
     }
 }