protected function processCreditCardCustomer($values)
 {
     // generate another recurring contribution, matching our recurring template with submitted value
     $total_amount = $values['amount'];
     $contribution_template = _iats_civicrm_getContributionTemplate(array('contribution_recur_id' => $values['crid']));
     $contact_id = $values['cid'];
     $hash = md5(uniqid(rand(), true));
     $contribution_recur_id = $values['crid'];
     $payment_processor_id = $values['paymentProcessorId'];
     $type = _iats_civicrm_is_iats($payment_processor_id);
     $subtype = substr($type, 11);
     $source = "iATS Payments {$subtype} Recurring Contribution (id={$contribution_recur_id})";
     $receive_date = date("YmdHis", time());
     // i.e. now
     $contribution = array('version' => 3, 'contact_id' => $contact_id, 'receive_date' => $receive_date, 'total_amount' => $total_amount, 'contribution_recur_id' => $contribution_recur_id, 'invoice_id' => $hash, 'source' => $source, 'contribution_status_id' => 2, 'payment_processor' => $payment_processor_id, 'is_test' => $values['is_test']);
     foreach (array('payment_instrument_id', 'currency', 'financial_type_id') as $key) {
         $contribution[$key] = $contribution_template[$key];
     }
     $options = array('is_email_receipt' => 0, 'customer_code' => $values['customerCode'], 'subtype' => $subtype);
     // now all the hard work in this function, recycled from the original recurring payment job
     $result = _iats_process_contribution_payment($contribution, $options);
     return $result;
 }
/**
 * Job.IatsACHEFTVerify API
 *
 * @param array $params
 * @return array API result descriptor
 * @see civicrm_api3_create_success
 * @see civicrm_api3_create_error
 * @throws API_Exception
 * Look up all pending (status = 2) ACH/EFT contributions and see if they've been approved or rejected
 * Update the corresponding recurring contribution record to status = 1 (or 4)
 * This works for both the initial contribution and subsequent contributions of recurring contributions, as well as one offs.
 * TODO: what kind of alerts should be provided if it fails?
 *
 * Also lookup new UK direct debit series, and new contributions from existing series.
 */
function civicrm_api3_job_iatsacheftverify($iats_service_params)
{
    $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
    $receipt_recurring = empty($settings['receipt_recurring']) ? 0 : 1;
    define('IATS_VERIFY_DAYS', 30);
    // I've added an extra 2 days when getting candidates from CiviCRM to be sure i've got them all.
    $civicrm_verify_days = IATS_VERIFY_DAYS + 2;
    // get all the pending direct debit contributions that still need approval within the last civicrm_verify_days
    $select = 'SELECT id, trxn_id, invoice_id, contact_id, contribution_recur_id, receive_date
      FROM civicrm_contribution
      WHERE
        contribution_status_id = 2
        AND payment_instrument_id = 2
        AND receive_date > %1
        AND is_test = 0';
    $args = array(1 => array(date('c', strtotime('-' . $civicrm_verify_days . ' days')), 'String'));
    $dao = CRM_Core_DAO::executeQuery($select, $args);
    $acheft_pending = array();
    while ($dao->fetch()) {
        /* we assume that the iATS transaction id is a unique field for matching, and that it is stored as the first part of the civicrm transaction */
        /* this is not unreasonable, assuming that the site doesn't have other active direct debit payment processors with similar patterns */
        $key = current(explode(':', $dao->trxn_id, 2));
        $acheft_pending[$key] = array('id' => $dao->id, 'trxn_id' => $dao->trxn_id, 'invoice_id' => $dao->invoice_id, 'contact_id' => $dao->contact_id, 'contribution_recur_id' => $dao->contribution_recur_id, 'receive_date' => $dao->receive_date);
    }
    // and some recent UK DD recurring contributions
    $select = 'SELECT c.id, c.contribution_status_id, c.trxn_id, c.invoice_id, icc.customer_code
      FROM civicrm_contribution c
      INNER JOIN civicrm_contribution_recur cr ON c.contribution_recur_id = cr.id
      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
      INNER JOIN civicrm_iats_customer_codes icc ON cr.id = icc.recur_id
      WHERE
        c.receive_date > %1
        AND pp.class_name = %2
        AND pp.is_test = 0';
    $args[2] = array('Payment_iATSServiceUKDD', 'String');
    $dao = CRM_Core_DAO::executeQuery($select, $args);
    $ukdd_contribution = array();
    while ($dao->fetch()) {
        if (empty($ukdd_contribution[$dao->customer_code])) {
            $ukdd_contribution[$dao->customer_code] = array();
        }
        // I want to key on my trxn_id that I can match up with data from iATS, but use the invoice_id for that initial pending one
        $key = empty($dao->trxn_id) ? $dao->invoice_id : $dao->trxn_id;
        $ukdd_contribution[$dao->customer_code][$key] = array('id' => $dao->id, 'contribution_status_id' => $dao->contribution_status_id, 'invoice_id' => $dao->invoice_id);
    }
    // and now get all the non-completed UKDD sequences, in order to track new contributions from iATS
    $select = 'SELECT cr.*, icc.customer_code as customer_code, icc.cid as icc_contact_id, iukddv.acheft_reference_num as reference_num, pp.is_test
      FROM civicrm_contribution_recur cr
      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
      INNER JOIN civicrm_iats_customer_codes icc ON cr.id = icc.recur_id
      INNER JOIN civicrm_iats_ukdd_validate iukddv ON cr.id = iukddv.recur_id
      WHERE
        pp.class_name = %1
        AND pp.is_test = 0
        AND (cr.end_date IS NULL OR cr.end_date > NOW())';
    $args = array(1 => array('Payment_iATSServiceUKDD', 'String'));
    $dao = CRM_Core_DAO::executeQuery($select, $args);
    $ukdd_contribution_recur = array();
    while ($dao->fetch()) {
        $ukdd_contribution_recur[$dao->customer_code] = get_object_vars($dao);
    }
    /* get "recent" approvals and rejects from iats and match them up with my pending list, or one-offs, or UK DD via the customer code */
    require_once "CRM/iATS/iATSService.php";
    // an array of methods => contribution status of the records retrieved
    $process_methods = array('acheft_journal_csv' => 1, 'acheft_payment_box_journal_csv' => 1, 'acheft_payment_box_reject_csv' => 4);
    /* initialize some values so I can report at the end */
    $error_count = 0;
    // count the number of each record from iats analysed, and the number of each kind found
    $processed = array_fill_keys(array_keys($process_methods), 0);
    $found = array('recur' => 0, 'quick' => 0, 'new' => 0);
    // save all my api result messages as well
    $output = array();
    /* do this loop for each relevant payment processor of type ACHEFT or UKDD */
    /* since test payments are NEVER verified by iATS, don't bother checking them [unless/until they change this?] */
    $select = 'SELECT id,url_site,is_test FROM civicrm_payment_processor WHERE (class_name = %1 OR class_name = %2) AND is_test = 0';
    $args = array(1 => array('Payment_iATSServiceACHEFT', 'String'), 2 => array('Payment_iATSServiceUKDD', 'String'));
    $dao = CRM_Core_DAO::executeQuery($select, $args);
    // watchdog('civicrm_iatspayments_com', 'pending: <pre>!pending</pre>', array('!pending' => print_r($iats_acheft_recur_pending,TRUE)), WATCHDOG_NOTICE);
    while ($dao->fetch()) {
        /* get approvals from yesterday, approvals from previous days, and then rejections for this payment processor */
        $iats_service_params = array('type' => 'report', 'iats_domain' => parse_url($dao->url_site, PHP_URL_HOST)) + $iats_service_params;
        /* the is_test below should always be 0, but I'm leaving it in, in case eventually we want to be verifying tests */
        $credentials = iATS_Service_Request::credentials($dao->id, $dao->is_test);
        foreach ($process_methods as $method => $contribution_status_id) {
            // TODO: this is set to capture approvals and cancellations from the past month, for testing purposes
            // it doesn't hurt, but on a live environment, this maybe should be limited to the past week, or less?
            // or, it could be configurable for the job
            $iats_service_params['method'] = $method;
            $iats = new iATS_Service_Request($iats_service_params);
            // I'm now using the new v2 version of the payment_box_journal, so a previous hack here is now removed
            switch ($method) {
                case 'acheft_journal_csv':
                    // special case to get today's transactions, so we're as real-time as we can be
                    $request = array('date' => date('Y-m-d') . 'T23:59:59+00:00', 'customerIPAddress' => function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
                    break;
                default:
                    // box journals only go up to the end of yesterday
                    $request = array('fromDate' => date('Y-m-d', strtotime('-' . IATS_VERIFY_DAYS . ' days')) . 'T00:00:00+00:00', 'toDate' => date('Y-m-d', strtotime('-1 day')) . 'T23:59:59+00:00', 'customerIPAddress' => function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
                    break;
            }
            // make the soap request, should return a csv file
            $response = $iats->request($credentials, $request);
            $transactions = $iats->getCSV($response, $method);
            if ($method == 'acheft_journal_csv') {
                // also grab yesterday + day before yesterday + day before that + the day before that if it (in case of stat holiday - long weekend)
                $request = array('date' => date('Y-m-d', strtotime('-1 day')) . 'T23:59:59+00:00', 'customerIPAddress' => function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
                $response = $iats->request($credentials, $request);
                $transactions = array_merge($transactions, $iats->getCSV($response, $method));
                $request = array('date' => date('Y-m-d', strtotime('-2 days')) . 'T23:59:59+00:00', 'customerIPAddress' => function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
                $response = $iats->request($credentials, $request);
                $transactions = array_merge($transactions, $iats->getCSV($response, $method));
                $request = array('date' => date('Y-m-d', strtotime('-3 days')) . 'T23:59:59+00:00', 'customerIPAddress' => function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
                $response = $iats->request($credentials, $request);
                $transactions = array_merge($transactions, $iats->getCSV($response, $method));
                $request = array('date' => date('Y-m-d', strtotime('-4 days')) . 'T23:59:59+00:00', 'customerIPAddress' => function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
                $response = $iats->request($credentials, $request);
                $transactions = array_merge($transactions, $iats->getCSV($response, $method));
            }
            $processed[$method] += count($transactions);
            // watchdog('civicrm_iatspayments_com', 'transactions: <pre>!trans</pre>', array('!trans' => print_r($transactions,TRUE)), WATCHDOG_NOTICE);
            foreach ($transactions as $transaction_id => $transaction) {
                $contribution = NULL;
                // use this later to trigger an activity if it's not NULL
                // first deal with acheft_pending, [and possibly the corresponding recur sequence ? no? ]
                if (!empty($acheft_pending[$transaction_id])) {
                    /* update the contribution status */
                    /* todo: additional sanity testing? We're assuming the uniqueness of the iATS transaction id here */
                    $is_recur = 'quick client' != strtolower($transaction->customer_code);
                    $found[$is_recur ? 'recur' : 'quick']++;
                    $contribution = $acheft_pending[$transaction_id];
                    // updating a contribution status to complete needs some extra bookkeeping
                    if (1 == $contribution_status_id) {
                        // note that I'm updating the timestamp portion of the transaction id here, since this might be useful at some point
                        // should I update the receive date to when it was actually received? Would that confuse membership dates?
                        $trxn_id = $transaction_id . ':' . time();
                        $complete = array('version' => 3, 'id' => $contribution['id'], 'trxn_id' => $transaction_id . ':' . time(), 'receive_date' => $contribution['receive_date']);
                        if ($is_recur) {
                            $complete['is_email_receipt'] = $receipt_recurring;
                            /* use my saved setting for recurring completions */
                        }
                        try {
                            $contributionResult = civicrm_api3('contribution', 'completetransaction', $complete);
                        } catch (Exception $e) {
                            throw new API_Exception('Failed to complete transaction: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
                        }
                        // restore my source field that ipn irritatingly overwrites, and make sure that the trxn_id is set also
                        civicrm_api3('contribution', 'setvalue', array('version' => 3, 'id' => $contribution['id'], 'value' => $contribution['source'], 'field' => 'source'));
                        civicrm_api3('contribution', 'setvalue', array('version' => 3, 'id' => $contribution['id'], 'value' => $trxn_id, 'field' => 'trxn_id'));
                    } else {
                        $params = array('version' => 3, 'sequential' => 1, 'contribution_status_id' => $contribution_status_id, 'id' => $contribution['id']);
                        $result = civicrm_api3('Contribution', 'create', $params);
                        // update the contribution
                    }
                    // always log these requests in my cutom civicrm table for auditing type purposes
                    // watchdog('civicrm_iatspayments_com', 'contribution: <pre>!contribution</pre>', array('!contribution' => print_r($query_params,TRUE)), WATCHDOG_NOTICE);
                    $query_params = array(1 => array($transaction->customer_code, 'String'), 2 => array($contribution['contact_id'], 'Integer'), 3 => array($contribution['id'], 'Integer'), 4 => array($contribution_status_id, 'Integer'), 5 => array($contribution['contribution_recur_id'], 'Integer'));
                    if (empty($contribution['contribution_recur_id'])) {
                        unset($query_params[5]);
                        CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_verify\n              (customer_code, cid, contribution_id, contribution_status_id, verify_datetime) VALUES (%1, %2, %3, %4, NOW())", $query_params);
                    } else {
                        CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_verify\n              (customer_code, cid, contribution_id, contribution_status_id, verify_datetime, recur_id) VALUES (%1, %2, %3, %4, NOW(), %5)", $query_params);
                    }
                } elseif (isset($ukdd_contribution_recur[$transaction->customer_code])) {
                    // it's a (possibly) new recurring UKDD contribution triggered from iATS
                    // check my existing ukdd_contribution list in case it's the first one that just needs to be updated, or has already been processed
                    // I also confirm that it's got the right ach reference field, which i get from the ukdd_contribution_recur record
                    $contribution_recur = $ukdd_contribution_recur[$transaction->customer_code];
                    // build the (unique) civicrm trxn id that we can use to match up against civicrm-stored transactions
                    $trxn_id = $transaction->id . ':iATSUKDD:' . $transaction->customer_code;
                    // sanity check against the ACH Reference number, but only if I get it from iATS
                    if (!empty($transaction->achref) && $contribution_recur['reference_num'] != $transaction->achref) {
                        $output[] = ts('Unexpected error: ACH Ref. %1 does not match for customer code %2 (should be %3)', array(1 => $transaction->achref, 2 => $transaction->customer_code, 3 => $contribution_recur['reference_num']));
                        ++$error_count;
                    } elseif (isset($ukdd_contribution[$transaction->customer_code][$trxn_id])) {
                        // I can ignore it, i've already created this one
                    } else {
                        // save my contribution in civicrm
                        $contribution = array('version' => 3, 'contact_id' => $contribution_recur['contact_id'], 'receive_date' => date('c', $transaction->receive_date), 'total_amount' => $transaction->amount, 'payment_instrument_id' => $contribution_recur['payment_instrument_id'], 'contribution_recur_id' => $contribution_recur['id'], 'trxn_id' => $trxn_id, 'invoice_id' => md5(uniqid(rand(), TRUE)), 'source' => 'iATS UK DD Reference: ' . $contribution_recur['reference_num'], 'contribution_status_id' => $contribution_status_id, 'currency' => $contribution_recur['currency'], 'payment_processor' => $contribution_recur['payment_processor_id'], 'is_test' => 0);
                        if (isset($dao->contribution_type_id)) {
                            // 4.2
                            $contribution['contribution_type_id'] = $contribution_recur['contribution_type_id'];
                        } else {
                            // 4.3+
                            $contribution['financial_type_id'] = $contribution_recur['financial_type_id'];
                        }
                        // if I have an outstanding pending contribution for this series, I'll recycle and update it here
                        foreach ($ukdd_contribution[$transaction->customer_code] as $key => $contrib_ukdd) {
                            if ($contrib_ukdd['contribution_status_id'] == 2) {
                                // it's pending
                                $contribution['id'] = $contrib_ukdd['id'];
                                // don't change my invoice id in this case
                                unset($contribution['invoice_id']);
                                // ensure I don't pull this trick more than once somehow
                                unset($ukdd_contribution[$transaction->customer_code][$key]);
                                // and note that I ignore everything else about the pending contribution in civicrm
                                break;
                            }
                        }
                        // otherwise I'll make do with a template if available
                        $contribution_template = array();
                        if (empty($contribution['id'])) {
                            // populate my contribution from a template if possible
                            $contribution_template = _iats_civicrm_getContributionTemplate(array('contribution_recur_id' => $contribution_recur['id'], 'total_amount' => $transation->amount));
                            $get_from_template = array('contribution_campaign_id', 'amount_level');
                            foreach ($get_from_template as $field) {
                                if (isset($contribution_template[$field])) {
                                    $contribution[$field] = $contribution_template[$field];
                                }
                            }
                            if (!empty($contribution_template['line_items'])) {
                                $contribution['skipLineItem'] = 1;
                                $contribution['api.line_item.create'] = $contribution_template['line_items'];
                            }
                        }
                        if ($contribution_status_id == 1) {
                            // create or update as pending and then complete
                            $contribution['contribution_status_id'] = 2;
                            $result = civicrm_api('contribution', 'create', $contribution);
                            $complete = array('version' => 3, 'id' => $result['id'], 'trxn_id' => $trxn_id, 'receive_date' => $contribution['receive_date']);
                            $complete['is_email_receipt'] = $receipt_recurring;
                            /* send according to my configuration */
                            try {
                                $contributionResult = civicrm_api('contribution', 'completetransaction', $complete);
                                // restore my source field that ipn irritatingly overwrites, and make sure that the trxn_id is set also
                                civicrm_api('contribution', 'setvalue', array('version' => 3, 'id' => $contribution['id'], 'value' => $contribution['source'], 'field' => 'source'));
                                civicrm_api('contribution', 'setvalue', array('version' => 3, 'id' => $contribution['id'], 'value' => $trxn_id, 'field' => 'trxn_id'));
                            } catch (Exception $e) {
                                throw new API_Exception('Failed to complete transaction: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
                            }
                        } else {
                            // create or update
                            $result = civicrm_api('contribution', 'create', $contribution);
                        }
                        if ($result['is_error']) {
                            $output[] = $result['error_message'];
                        } else {
                            $found['new']++;
                        }
                    }
                }
                // if one of the above was true and I've got a new or confirmed contribution:
                // so log it as an activity for administrative reference
                if (!empty($contribution)) {
                    $subject_string = empty($contribution['id']) ? 'Found new iATS Payments UK DD contribution for contact id %3' : '%1 iATS Payments ACH/EFT contribution id %2 for contact id %3';
                    $subject = ts($subject_string, array(1 => $contribution_status_id == 4 ? ts('Cancelled') : ts('Verified'), 2 => $contribution['id'], 3 => $contribution['contact_id']));
                    $result = civicrm_api('activity', 'create', array('version' => 3, 'activity_type_id' => 6, 'source_contact_id' => $contribution['contact_id'], 'assignee_contact_id' => $contribution['contact_id'], 'subject' => $subject, 'status_id' => 2, 'activity_date_time' => date("YmdHis")));
                    if ($result['is_error']) {
                        $output[] = ts('An error occurred while creating activity record for contact id %1: %2', array(1 => $contribution['contact_id'], 2 => $result['error_message']));
                        ++$error_count;
                    } else {
                        $output[] = $subject;
                    }
                }
                // otherwise ignore it
            }
        }
    }
    $message = '<br />' . ts('Completed with %1 errors.', array(1 => $error_count));
    $message .= '<br />' . ts('Processed %1 approvals from today and past 4 days, %2 approval and %3 rejection records from the previous ' . IATS_VERIFY_DAYS . ' days.', array(1 => $processed['acheft_journal_csv'], 2 => $processed['acheft_payment_box_journal_csv'], 3 => $processed['acheft_payment_box_reject_csv']));
    // If errors ..
    if ($error_count) {
        return civicrm_api3_create_error($message . '</br />' . implode('<br />', $output));
    }
    // If no errors and some records processed ..
    if (array_sum($processed) > 0) {
        if (count($acheft_pending) > 0) {
            $message .= '<br />' . ts('For %1 pending ACH/EFT contributions, %2 non-recuring and %3 recurring contribution results applied.', array(1 => count($acheft_pending), 2 => $found['quick'], 3 => $found['recur']));
        }
        if (count($ukdd_contribution_recur) > 0) {
            $message .= '<br />' . ts('For %1 recurring UK direct debit contribution series, %2 new contributions found.', array(1 => count($ukdd_contribution_recur), 2 => $found['new']));
        }
        return civicrm_api3_create_success($message . '<br />' . implode('<br />', $output));
    }
    // No records processed
    return civicrm_api3_create_success(ts('No records found to process.'));
}
/**
 * Job.iATSRecurringContributions API
 *
 * @param array $params
 * @return array API result descriptor
 * @see civicrm_api3_create_success
 * @see civicrm_api3_create_error
 * @throws API_Exception
 */
function civicrm_api3_job_iatsrecurringcontributions($params)
{
    // running this job in parallell could generate bad duplicate contributions
    $lock = new CRM_Core_Lock('civimail.job.IatsRecurringContributions');
    if (!$lock->acquire()) {
        return civicrm_api3_create_success(ts('Failed to acquire lock. No contribution records were processed.'));
    }
    $catchup = !empty($params['catchup']);
    unset($params['catchup']);
    $domemberships = empty($params['ignoremembership']);
    unset($params['ignoremembership']);
    // TODO: what kind of extra security do we want or need here to prevent it from being triggered inappropriately? Or does it matter?
    // the next scheduled contribution date field name is civicrm version dependent
    define('IATS_CIVICRM_NSCD_FID', _iats_civicrm_nscd_fid());
    // $config = &CRM_Core_Config::singleton();
    // $debug  = false;
    // do my calculations based on yyyymmddhhmmss representation of the time
    // not sure about time-zone issues
    $dtCurrentDay = date("Ymd", mktime(0, 0, 0, date("m"), date("d"), date("Y")));
    $dtCurrentDayStart = $dtCurrentDay . "000000";
    $dtCurrentDayEnd = $dtCurrentDay . "235959";
    $expiry_limit = date('ym');
    // restrict this method of recurring contribution processing to only these two payment processors
    $args = array(1 => array('Payment_iATSService', 'String'), 2 => array('Payment_iATSServiceACHEFT', 'String'), 3 => array('Payment_iATSServiceSWIPE', 'String'));
    // Before triggering payments, we need to do some housekeeping of the civicrm_contribution_recur records.
    // First update the end_date and then the complete/in-progress values.
    // We do this both to fix any failed settings previously, and also
    // to deal with the possibility that the settings for the number of payments (installments) for an existing record has changed.
    // First check for recur end date values on non-open-ended recurring contribution records that are either complete or in-progress
    $select = 'SELECT cr.id, count(c.id) AS installments_done, cr.installments, cr.end_date, NOW() as test_now
      FROM civicrm_contribution_recur cr
      INNER JOIN civicrm_contribution c ON cr.id = c.contribution_recur_id
      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
      WHERE
        (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
        AND (cr.installments > 0)
        AND (cr.contribution_status_id IN (1,5))
        AND (c.contribution_status_id IN (1,2))
      GROUP BY c.contribution_recur_id';
    $dao = CRM_Core_DAO::executeQuery($select, $args);
    while ($dao->fetch()) {
        // check for end dates that should be unset because I haven't finished
        if ($dao->installments_done < $dao->installments) {
            // at least one more installment todo
            if ($dao->end_date > 0 && $dao->end_date <= $dao->test_now) {
                // unset the end_date
                $update = 'UPDATE civicrm_contribution_recur SET end_date = NULL, contribution_status_id = 5 WHERE id = %1';
                CRM_Core_DAO::executeQuery($update, array(1 => array($dao->id, 'Int')));
            }
        } elseif ($dao->installments_done >= $dao->installments) {
            // I'm done with installments
            if (empty($dao->end_date) || $dao->end_date >= $dao->test_now) {
                // this interval complete, set the end_date to an hour ago
                $update = 'UPDATE civicrm_contribution_recur SET end_date = DATE_SUB(NOW(),INTERVAL 1 HOUR) WHERE id = %1';
                CRM_Core_DAO::executeQuery($update, array(1 => array($dao->id, 'Int')));
            }
        }
    }
    // Second, make sure any open-ended recurring contributions have no end date set
    $update = 'UPDATE civicrm_contribution_recur cr
      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
      SET
        cr.end_date = NULL
      WHERE
        cr.contribution_status_id IN (1,5)
        AND NOT(cr.installments > 0)
        AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
        AND NOT(ISNULL(cr.end_date))';
    $dao = CRM_Core_DAO::executeQuery($update, $args);
    // Third, we update the status_id of the all in-progress or completed recurring contribution records
    // Unexpire uncompleted cycles
    $update = 'UPDATE civicrm_contribution_recur cr
      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
      SET
        cr.contribution_status_id = 5
      WHERE
        cr.contribution_status_id = 1
        AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
        AND (cr.end_date IS NULL OR cr.end_date > NOW())';
    $dao = CRM_Core_DAO::executeQuery($update, $args);
    // Expire or badly-defined completed cycles
    $update = 'UPDATE civicrm_contribution_recur cr
      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
      SET
        cr.contribution_status_id = 1
      WHERE
        cr.contribution_status_id = 5
        AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
        AND (
          (NOT(cr.end_date IS NULL) AND cr.end_date <= NOW())
          OR
          ISNULL(cr.frequency_unit)
          OR
          (frequency_interval = 0)
        )';
    $dao = CRM_Core_DAO::executeQuery($update, $args);
    // Now we're ready to trigger payments
    // Select the ongoing recurring payments for iATSServices where the next scheduled contribution date (NSCD) is before the end of of the current day
    $select = 'SELECT cr.*, icc.customer_code, icc.expiry as icc_expiry, icc.cid as icc_contact_id, pp.class_name as pp_class_name, pp.url_site as url_site, pp.is_test
      FROM civicrm_contribution_recur cr
      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
      INNER JOIN civicrm_iats_customer_codes icc ON cr.id = icc.recur_id
      WHERE
        cr.contribution_status_id = 5
        AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)';
    //      AND pp.is_test = 0
    if (!empty($params['recur_id'])) {
        // in case the job was called to execute a specific recurring contribution id -- not yet implemented!
        $select .= ' AND icc.recur_id = %4';
        $args[4] = array($params['recur_id'], 'Int');
    } else {
        // if (!empty($params['scheduled'])) {
        //normally, process all recurring contributions due today or earlier
        $select .= ' AND cr.' . IATS_CIVICRM_NSCD_FID . ' <= %4';
        $args[4] = array($dtCurrentDayEnd, 'String');
        // ' AND cr.next_sched_contribution >= %2
        // $args[2] = array($dtCurrentDayStart, 'String');
        if (!empty($params['cycle_day'])) {
            // also filter by cycle day
            $select .= ' AND cr.cycle_day = %5';
            $args[5] = array($params['cycle_day'], 'Int');
        }
        if (isset($params['failure_count'])) {
            // also filter by cycle day
            $select .= ' AND cr.failure_count = %6';
            $args[6] = array($params['failure_count'], 'Int');
        }
    }
    $dao = CRM_Core_DAO::executeQuery($select, $args);
    $counter = 0;
    $error_count = 0;
    $output = array();
    $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
    $receipt_recurring = empty($settings['receipt_recurring']) ? 0 : 1;
    /* while ($dao->fetch()) {
        foreach($dao as $key => $value) {
          echo "$value,";
        }
        echo "\n";
      }
      die();  */
    while ($dao->fetch()) {
        // Strategy: create the contribution record with status = 2 (= pending), try the payment, and update the status to 1 if successful
        // Try to get a contribution template for this contribution series - if none matches (e.g. if a donation amount has been changed), we'll just be naive about it.
        $contribution_template = _iats_civicrm_getContributionTemplate(array('contribution_recur_id' => $dao->id, 'total_amount' => $dao->amount));
        $contact_id = $dao->contact_id;
        $total_amount = $dao->amount;
        $hash = md5(uniqid(rand(), true));
        $contribution_recur_id = $dao->id;
        $subtype = substr($dao->pp_class_name, 19);
        $source = "iATS Payments {$subtype} Recurring Contribution (id={$contribution_recur_id})";
        $receive_ts = $catchup ? strtotime($dao->next_sched_contribution_date) : time();
        $receive_date = date("YmdHis", $receive_ts);
        // i.e. now or whenever it was supposed to run if in catchup mode
        // check if we already have an error
        $errors = array();
        if (empty($dao->customer_code)) {
            $errors[] = ts('Recur id %1 is missing a customer code.', array(1 => $contribution_recur_id));
        } else {
            if ($dao->contact_id != $dao->icc_contact_id) {
                $errors[] = ts('Recur id %1 is has a mismatched contact id for the customer code.', array(1 => $contribution_recur_id));
            }
            if ($dao->icc_expiry != '0000' && $dao->icc_expiry < $expiry_limit) {
                // $errors[] = ts('Recur id %1 is has an expired cc for the customer code.', array(1 => $contribution_recur_id));
            }
        }
        if (count($errors)) {
            $source .= ' Errors: ' . implode(' ', $errors);
        }
        $contribution = array('version' => 3, 'contact_id' => $contact_id, 'receive_date' => $receive_date, 'total_amount' => $total_amount, 'payment_instrument_id' => $dao->payment_instrument_id, 'contribution_recur_id' => $contribution_recur_id, 'invoice_id' => $hash, 'source' => $source, 'contribution_status_id' => 2, 'currency' => $dao->currency, 'payment_processor' => $dao->payment_processor_id, 'is_test' => $dao->is_test);
        $get_from_template = array('contribution_campaign_id', 'amount_level');
        foreach ($get_from_template as $field) {
            if (isset($contribution_template[$field])) {
                $contribution[$field] = is_array($contribution_template[$field]) ? implode(', ', $contribution_template[$field]) : $contribution_template[$field];
            }
        }
        if (isset($dao->contribution_type_id)) {
            // 4.2
            $contribution['contribution_type_id'] = $dao->contribution_type_id;
        } else {
            // 4.3+
            $contribution['financial_type_id'] = $dao->financial_type_id;
        }
        if (!empty($contribution_template['line_items'])) {
            $contribution['skipLineItem'] = 1;
            $contribution['api.line_item.create'] = $contribution_template['line_items'];
        }
        if (count($errors)) {
            ++$error_count;
            ++$counter;
            /* create a failed contribution record, don't bother talking to iats */
            $contribution['contribution_status_id'] = 4;
            $contributionResult = civicrm_api('contribution', 'create', $contribution);
            if ($contributionResult['is_error']) {
                $errors[] = $contributionResult['error_message'];
            }
            continue;
        } else {
            // assign basic options
            $options = array('is_email_receipt' => $receipt_recurring, 'customer_code' => $dao->customer_code, 'subtype' => $subtype);
            // if our template contribution is a membership payment, make this one also
            if ($domemberships && !empty($contribution_template['contribution_id'])) {
                try {
                    $membership_payment = civicrm_api('MembershipPayment', 'getsingle', array('version' => 3, 'contribution_id' => $contribution_template['contribution_id']));
                    if (!empty($membership_payment['membership_id'])) {
                        $options['membership_id'] = $membership_payment['membership_id'];
                    }
                } catch (Exception $e) {
                    // ignore, if will fail correctly if there is no membership payment
                }
            }
            // so far so, good ... now create the pending contribution, and save its id
            // and then try to get the money, and do one of: update the contribution to failed, complete the transaction, or update a pending ach/eft with it's transaction id
            $output[] = _iats_process_contribution_payment($contribution, $options);
        }
        /* calculate the next collection date, based on the recieve date (note effect of catchup mode)  */
        /* only move the next sched contribution date forward if the contribution is pending (e.g. ach/eft) or complete */
        if ($contribution['contribution_status_id'] < 3) {
            $next_collectionDate = strtotime("+{$dao->frequency_interval} {$dao->frequency_unit}", $receive_ts);
            $next_collectionDate = date('YmdHis', $next_collectionDate);
            CRM_Core_DAO::executeQuery("\n        UPDATE civicrm_contribution_recur\n           SET " . IATS_CIVICRM_NSCD_FID . " = %1,\n           failure_count = 0\n         WHERE id = %2\n      ", array(1 => array($next_collectionDate, 'String'), 2 => array($dao->id, 'Int')));
        } elseif (4 == $contribution['contribution_status_id']) {
            // i.e. failed
            CRM_Core_DAO::executeQuery("\n        UPDATE civicrm_contribution_recur\n           SET failure_count = failure_count + 1\n         WHERE id = %1\n      ", array(1 => array($dao->id, 'Int')));
        }
        $result = civicrm_api('activity', 'create', array('version' => 3, 'activity_type_id' => 6, 'source_contact_id' => $contact_id, 'source_record_id' => CRM_Utils_Array::value('id', $contributionResult), 'assignee_contact_id' => $contact_id, 'subject' => "Attempted iATS Payments {$subtype} Recurring Contribution for " . $total_amount, 'status_id' => 2, 'activity_date_time' => date("YmdHis")));
        if ($result['is_error']) {
            $output[] = ts('An error occurred while creating activity record for contact id %1: %2', array(1 => $contact_id, 2 => $result['error_message']));
            ++$error_count;
        } else {
            $output[] = ts('Created activity record for contact id %1', array(1 => $contact_id));
        }
        ++$counter;
    }
    // now update the end_dates and status for non-open-ended contribution series if they are complete (so that the recurring contribution status will show correctly)
    // This is a simplified version of what we did before the processing
    $select = 'SELECT cr.id, count(c.id) AS installments_done, cr.installments
      FROM civicrm_contribution_recur cr
      INNER JOIN civicrm_contribution c ON cr.id = c.contribution_recur_id
      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
      WHERE
        (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
        AND (cr.installments > 0)
        AND (cr.contribution_status_id  = 5)
      GROUP BY c.contribution_recur_id';
    $dao = CRM_Core_DAO::executeQuery($select, $args);
    while ($dao->fetch()) {
        // check if my end date should be set to now because I have finished
        if ($dao->installments_done >= $dao->installments) {
            // I'm done with installments
            // set this series complete and the end_date to now
            $update = 'UPDATE civicrm_contribution_recur SET contribution_status_id = 1, end_date = NOW() WHERE id = %1';
            CRM_Core_DAO::executeQuery($update, array(1 => array($dao->id, 'Int')));
        }
    }
    $lock->release();
    // If errors ..
    if ($error_count) {
        return civicrm_api3_create_error(ts("Completed, but with %1 errors. %2 records processed.", array(1 => $error_count, 2 => $counter)) . "<br />" . implode("<br />", $output));
    }
    // If no errors and records processed ..
    if ($counter) {
        return civicrm_api3_create_success(ts('%1 contribution record(s) were processed.', array(1 => $counter)) . "<br />" . implode("<br />", $output));
    }
    // No records processed
    return civicrm_api3_create_success(ts('No contribution records were processed.'));
}