/**
 * will add the default creditor_id if no creditor_id is given, and the default creditor is valid
 */
function _civicrm_api3_sepa_mandate_adddefaultcreditor(&$params)
{
    if (empty($params['creditor_id'])) {
        $default_creditor = CRM_Sepa_Logic_Settings::defaultCreditor();
        if ($default_creditor != NULL) {
            $params['creditor_id'] = $default_creditor->id;
        }
    }
}
 function adjustAmount($mandate_id)
 {
     // check if we are allowed to...
     if (CRM_Sepa_Logic_Settings::getSetting('allow_mandate_modification')) {
         $adjusted_amount = (double) $_REQUEST['adjust_amount'];
         if ($adjusted_amount > 0) {
             if (CRM_Sepa_BAO_SEPAMandate::adjustAmount($mandate_id, $adjusted_amount)) {
                 CRM_Core_Session::setStatus(sprintf(ts("The amount of this mandate was modified. You should send out a new prenotification to the debtor.")), ts('Advice'), 'info');
             }
         } else {
             CRM_Core_Session::setStatus(sprintf(ts("Invalid amount. Mandate not modified.")), ts('Error'), 'error');
         }
     } else {
         CRM_Core_Session::setStatus(sprintf(ts("Modifying an existing mandate is currently not allowed. You can change this on the SEPA settings page.")), ts('Error'), 'error');
     }
 }
 /**
  * Will prepare the form and look up all necessary data
  */
 function prepareCreateForm($contact_id)
 {
     // load financial types
     $this->assign("financial_types", CRM_Contribute_PseudoConstant::financialType());
     $this->assign("date", date('Y-m-d'));
     $this->assign("start_date", date('Y-m-d'));
     // first, try to load contact
     $contact = civicrm_api('Contact', 'getsingle', array('version' => 3, 'id' => $contact_id));
     if (isset($contact['is_error']) && $contact['is_error']) {
         CRM_Core_Session::setStatus(sprintf(ts("Couldn't find contact #%s"), $contact_id), ts('Error'), 'error');
         $this->assign("display_name", "ERROR");
         return;
     }
     $this->assign("contact_id", $contact_id);
     $this->assign("display_name", $contact['display_name']);
     // look up campaigns
     $campaign_query = civicrm_api('Campaign', 'get', array('version' => 3, 'is_active' => 1, 'option.limit' => 9999, 'option.sort' => 'title'));
     $campaigns = array();
     $campaigns[''] = ts("No Campaign");
     if (isset($campaign_query['is_error']) && $campaign_query['is_error']) {
         CRM_Core_Session::setStatus(ts("Couldn't load campaign list."), ts('Error'), 'error');
     } else {
         foreach ($campaign_query['values'] as $campaign_id => $campaign) {
             $campaigns[$campaign_id] = $campaign['title'];
         }
     }
     $this->assign('campaigns', $campaigns);
     // look up account in other SEPA mandates
     $known_accounts = array();
     $query_sql = "SELECT DISTINCT iban, bic FROM civicrm_sdd_mandate WHERE contact_id={$contact_id} AND (creation_date >= (NOW() - INTERVAL 60 DAY));";
     $old_mandates = CRM_Core_DAO::executeQuery($query_sql);
     while ($old_mandates->fetch()) {
         $value = $old_mandates->iban . '/' . $old_mandates->bic;
         array_push($known_accounts, array("name" => $old_mandates->iban, "value" => $value));
     }
     // look up account in CiviBanking (if enabled...)
     $iban_reference_type = CRM_Core_OptionGroup::getValue('civicrm_banking.reference_types', 'IBAN', 'value', 'String', 'id');
     if ($iban_reference_type) {
         $accounts = civicrm_api('BankingAccount', 'get', array('version' => 3, 'contact_id' => $contact_id));
         if (isset($accounts['is_error']) && $accounts['is_error']) {
             // this probably means, that CiviBanking is not installed...
         } else {
             foreach ($accounts['values'] as $account_id => $account) {
                 $account_ref = civicrm_api('BankingAccountReference', 'getsingle', array('version' => 3, 'ba_id' => $account_id, 'reference_type_id' => $iban_reference_type));
                 if (isset($account_ref['is_error']) && $account_ref['is_error']) {
                     // this would also be an error, if no reference is set...
                 } else {
                     $account_data = json_decode($account['data_parsed']);
                     if (isset($account_data->BIC)) {
                         // we have IBAN and BIC -> add:
                         $value = $account_ref['reference'] . '/' . $account_data->BIC;
                         array_push($known_accounts, array("name" => $account_ref['reference'], "value" => $value));
                     }
                 }
             }
         }
     }
     // remove duplicate entries
     $known_account_names = array();
     foreach ($known_accounts as $index => $entry) {
         if (isset($known_account_names[$entry['name']])) {
             unset($known_accounts[$index]);
         } else {
             $known_account_names[$entry['name']] = $index;
         }
     }
     // add default entry
     array_push($known_accounts, array("name" => ts("enter new account"), "value" => "/"));
     $this->assign("known_accounts", $known_accounts);
     // look up creditors
     $creditor_query = civicrm_api('SepaCreditor', 'get', array('version' => 3));
     $creditors = array();
     if (isset($creditor_query['is_error']) && $creditor_query['is_error']) {
         CRM_Core_Session::setStatus(ts("Couldn't find any creditors."), ts('Error'), 'error');
     } else {
         foreach ($creditor_query['values'] as $creditor_id => $creditor) {
             $creditors[$creditor_id] = $creditor['name'];
         }
     }
     $this->assign('creditors', $creditors);
     // add cycle_days per creditor
     $creditor2cycledays = array();
     foreach ($creditors as $creditor_id => $creditor_name) {
         $creditor2cycledays[$creditor_id] = CRM_Sepa_Logic_Settings::getListSetting("cycledays", range(1, 28), $creditor_id);
     }
     $this->assign("creditor2cycledays", json_encode($creditor2cycledays));
     // all seems to be ok.
     $this->assign("submit_url", CRM_Utils_System::url('civicrm/sepa/cmandate'));
     // copy known parameters
     $copy_params = array('contact_id', 'creditor_id', 'total_amount', 'financial_type_id', 'campaign_id', 'source', 'note', 'iban', 'bic', 'date', 'mandate_type', 'start_date', 'cycle_day', 'interval', 'end_date', 'reference');
     foreach ($copy_params as $parameter) {
         if (isset($_REQUEST[$parameter])) {
             $this->assign($parameter, $_REQUEST[$parameter]);
         }
     }
     // set default creditor, if not provided
     if (empty($_REQUEST['creditor_id'])) {
         $default_creditor = CRM_Sepa_Logic_Settings::defaultCreditor();
         if ($default_creditor != NULL) {
             $this->assign('creditor_id', $default_creditor->id);
         }
     }
     // FIXME: Create "default" setting for mandate type (see SEPA-332)
     //$this->assign('mandate_type', isset($_REQUEST['mandate_type']) ? $_REQUEST['mandate_type'] : 'RCUR');
 }
 /**
  * Test update of recurring payments after a deferred submission
  *
  * @author Björn Endres
  * @see https://github.com/Project60/sepa_dd/issues/190
  */
 public function testRCURGracePeriod_190()
 {
     $rcur_notice = 6;
     CRM_Sepa_Logic_Settings::setSetting('batching.RCUR.notice', $rcur_notice);
     CRM_Sepa_Logic_Settings::setSetting('batching.RCUR.grace', 2 * $rcur_notice);
     $contactId = $this->individualCreate();
     $collection_date = strtotime("+1 days");
     $deferred_collection_date = strtotime("+{$rcur_notice} days");
     // count the existing contributions
     $count = $this->callAPISuccess("Contribution", "getcount", array('version' => 3));
     // create a mandate, that's already late
     $parameters = array('version' => 3, 'type' => 'RCUR', 'status' => 'RCUR', 'contact_id' => $contactId, 'financial_type_id' => 1, 'amount' => '6.66', 'start_date' => date('YmdHis'), 'date' => date('YmdHis'), 'cycle_day' => date('d', $collection_date), 'frequency_interval' => 1, 'frequency_unit' => 'month', 'iban' => "BE68844010370034", 'bic' => "TESTTEST", 'creditor_id' => $this->getCreditor(), 'is_enabled' => 1);
     $this->callAPISuccess("SepaMandate", "createfull", $parameters);
     // batch it
     $this->callAPISuccess("SepaAlternativeBatching", "update", array("type" => "RCUR", 'version' => 3));
     // check contributions count again
     $newcount = $this->callAPISuccess("Contribution", "getcount", array('version' => 3));
     $this->assertEquals($count + 1, $newcount, "A contribution should have been created!");
     // adjust collection date, close the group and thus modify the contribution's receive date
     $txgroup = $this->callAPISuccess("SepaTransactionGroup", "getsingle", array('version' => 3));
     CRM_Sepa_BAO_SEPATransactionGroup::adjustCollectionDate($txgroup['id'], date('Y-m-d', $deferred_collection_date));
     CRM_Sepa_Logic_Group::close($txgroup['id']);
     // batch again
     $this->callAPISuccess("SepaAlternativeBatching", "update", array("type" => "RCUR", 'version' => 3));
     // verify, that NO new contribution is created
     $newcount = $this->callAPISuccess("Contribution", "getcount", array('version' => 3));
     $this->assertEquals($count + 1, $newcount, "Yet another contribution has been created. Issue #190 still active!");
 }
 public function buildQuickForm()
 {
     CRM_Utils_System::setTitle(ts('Sepa Direct Debit - Settings'));
     $customFields = CRM_Core_BAO_CustomField::getFields();
     $cf = array();
     foreach ($customFields as $k => $v) {
         $cf[$k] = $v['label'];
     }
     // add all form elements and validation rules
     foreach ($this->config_fields as $key => $value) {
         $elementName = $this->domainToString($value[0]);
         $elem = $this->addElement('text', $elementName, $value[1], isset($value[2]) ? $value[2] : array());
         if (!in_array($elementName, array('cycledays', 'custom_txmsg'))) {
             // integer only rules, except for cycledays (list)
             $this->addRule($this->domainToString($value[0]), sprintf(ts("Please enter the %s as number (integers only)."), $value[1]), 'positiveInteger');
             $this->addRule($this->domainToString($value[0]), sprintf(ts("Please enter the %s as number (integers only)."), $value[1]), 'required');
         }
     }
     // country drop down field
     $config = CRM_Core_Config::singleton();
     $i18n = CRM_Core_I18n::singleton();
     $climit = array();
     $cnames = array();
     $ciso = array();
     $filtered = array();
     $climit = $config->countryLimit();
     CRM_Core_PseudoConstant::populate($cnames, 'CRM_Core_DAO_Country', TRUE, 'name', 'is_active');
     CRM_Core_PseudoConstant::populate($ciso, 'CRM_Core_DAO_Country', TRUE, 'iso_code');
     foreach ($ciso as $key => $value) {
         foreach ($climit as $active_country) {
             if ($active_country == $value) {
                 $filtered[$key] = $cnames[$key];
             }
         }
     }
     $i18n->localizeArray($filtered, array('context' => 'country'));
     asort($filtered);
     // do not use array_merge() because it discards the original indizes
     $country_ids = array('' => ts('- select -')) + $filtered;
     $exw = CRM_Core_BAO_Setting::getItem('SEPA Direct Debit Preferences', 'exclude_weekends');
     if ($exw) {
         $exw = array('checked' => 'checked');
     } else {
         $exw = array();
     }
     // add creditor form elements
     $this->addElement('text', 'addcreditor_creditor_id', ts("Creditor Contact"));
     $this->addElement('text', 'addcreditor_name', ts("Name"));
     $this->addElement('text', 'addcreditor_id', ts("Identifier"));
     $this->addElement('text', 'addcreditor_address', ts("Address"));
     $this->addElement('select', 'addcreditor_country_id', ts("Country"), $country_ids);
     $this->addElement('text', 'addcreditor_bic', ts("BIC"));
     $this->addElement('text', 'addcreditor_iban', ts("IBAN"));
     $this->addElement('select', 'addcreditor_pain_version', ts("PAIN Version"), array('' => ts('- select -')) + CRM_Core_OptionGroup::values('sepa_file_format'));
     $this->addElement('checkbox', 'is_test_creditor', ts("Is a Test Creditor"), "", array('value' => '0'));
     $this->addElement('checkbox', 'exclude_weekends', ts("Exclude Weekends"), "", $exw);
     $this->addElement('hidden', 'edit_creditor_id', '', array('id' => 'edit_creditor_id'));
     $this->addElement('hidden', 'add_creditor_id', '', array('id' => 'add_creditor_id'));
     // add custom form elements and validation rules
     $index = 0;
     foreach ($this->custom_fields as $key => $value) {
         $this->addElement('text', $this->domainToString($value[0]), $value[1], array('placeholder' => CRM_Core_BAO_Setting::getItem('SEPA Direct Debit Preferences', $this->domainToString($this->config_fields[$index][0]))));
         $elementName = $this->domainToString($value[0]);
         if (!in_array($elementName, array('custom_cycledays', 'custom_txmsg'))) {
             // integer only rules, except for cycledays (list)
             $this->addRule($elementName, sprintf(ts("Please enter the %s as number (integers only)."), $value[1]), 'positiveInteger');
         }
         $index++;
     }
     // register and add extra validation rules
     $this->registerRule('sepa_cycle_day_list', 'callback', 'sepa_cycle_day_list', 'CRM_Sepa_Logic_Settings');
     $this->addRule('cycledays', ts('Please give a comma separated list of valid days.'), 'sepa_cycle_day_list');
     $this->addRule('custom_cycledays', ts('Please give a comma separated list of valid days.'), 'sepa_cycle_day_list');
     // get creditor list
     $creditors_default_list = array();
     $creditor_query = civicrm_api('SepaCreditor', 'get', array('version' => 3, 'option.limit' => 99999));
     if (!empty($creditor_query['is_error'])) {
         return civicrm_api3_create_error("Cannot get creditor list: " . $creditor_query['error_message']);
     } else {
         $creditors = array();
         foreach ($creditor_query['values'] as $creditor) {
             $creditors[] = $creditor;
             $creditors_default_list[$creditor['id']] = $creditor['name'];
         }
     }
     $this->assign('creditors', $creditors);
     $default_creditors = $this->addElement('select', 'batching_default_creditor', ts("Default Creditor"), array('' => ts('- select -')) + $creditors_default_list);
     $default_creditors->setSelected(CRM_Sepa_Logic_Settings::getSetting('batching.default.creditor'));
     // add general config options
     $amm_options = CRM_Sepa_Logic_Settings::getSetting('allow_mandate_modification') ? array('checked' => 'checked') : array();
     $this->addElement('checkbox', 'allow_mandate_modification', ts("Mandate Modifications"), NULL, $amm_options);
     parent::buildQuickForm();
 }
/**
 * CiviCRM validateForm hook
 *
 * make sure, people don't create (broken) payment with SDD payment instrument, but w/o mandates
 */
function sepa_civicrm_validateForm($formName, &$fields, &$files, &$form, &$errors)
{
    if ($formName == 'CRM_Contribute_Form_Contribution') {
        // we'll just focus on the payment_instrument_id
        if (empty($fields['payment_instrument_id'])) {
            return;
        }
        // find the contribution id
        $contribution_id = $form->getVar('_id');
        if (empty($contribution_id)) {
            return;
        }
        // find the attached mandate, if exists
        $mandates = CRM_Sepa_Logic_Settings::getMandateFor($contribution_id);
        if (empty($mandates)) {
            // the contribution has no mandate,
            //   so we should not allow the payment_instrument be set to an SDD one
            if (CRM_Sepa_Logic_Settings::isSDD(array('payment_instrument_id' => $fields['payment_instrument_id']))) {
                $errors['payment_instrument_id'] = ts("This contribution has no mandate and cannot simply be changed to a SEPA payment instrument.");
            }
        } else {
            // the contribution has a mandate which determines the payment instrument
            // ..but first some sanity checks...
            if (count($mandates) != 1) {
                error_log("org.project60.sepa_dd: contribution [{$contribution_id}] has more than one mandate.");
            }
            // now compare requested with expected payment instrument
            $mandate_id = key($mandates);
            $mandate_pi = $mandates[$mandate_id];
            $requested_pi = CRM_Core_OptionGroup::getValue('payment_instrument', $fields['payment_instrument_id'], 'value', 'String', 'name');
            if ($requested_pi != $mandate_pi) {
                $errors['payment_instrument_id'] = sprintf(ts("This contribution has a mandate, its payment instrument has to be '%s'"), $mandate_pi);
            }
        }
    }
}
 /**
  * This method will mark the given transaction group as 'received':
  *   - set txgroup status to 'received'
  *   - change status from 'In Progress' to 'Completed' for all contributions
  *   - (store/update the bank account information)
  *
  * @return error message, unless successful
  */
 static function received($txgroup_id)
 {
     // step 0: check lock
     $lock = CRM_Sepa_Logic_Settings::getLock();
     if (empty($lock)) {
         return "Batching in progress. Please try again later.";
     }
     // step 1: gather data
     $group_status_id_open = (int) CRM_Core_OptionGroup::getValue('batch_status', 'Open', 'name');
     $group_status_id_closed = (int) CRM_Core_OptionGroup::getValue('batch_status', 'Closed', 'name');
     $group_status_id_received = (int) CRM_Core_OptionGroup::getValue('batch_status', 'Received', 'name');
     $status_pending = (int) CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name');
     $status_closed = (int) CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
     $status_inprogress = (int) CRM_Core_OptionGroup::getValue('contribution_status', 'In Progress', 'name');
     if (empty($group_status_id_received)) {
         return civicrm_api3_create_error("Status 'Received' does not exist!");
     }
     // step 0: load the group object
     $txgroup = civicrm_api('SepaTransactionGroup', 'getsingle', array('id' => $txgroup_id, 'version' => 3));
     if (!empty($txgroup['is_error'])) {
         $lock->release();
         return "Cannot find transaction group " . $txgroup_id;
     }
     // check status
     if ($txgroup['status_id'] != $group_status_id_closed) {
         $lock->release();
         return "Transaction group " . $txgroup_id . " is not 'closed'.";
     }
     // step 1.1: fix contributions, that have no financial transactions. (happens due to a status-bug in civicrm)
     $find_rotten_contributions_sql = "\n    SELECT\n     contribution.id AS contribution_id\n    FROM\n      civicrm_sdd_contribution_txgroup AS txn_to_contribution\n    LEFT JOIN\n      civicrm_contribution AS contribution ON contribution.id = txn_to_contribution.contribution_id\n    WHERE\n      txn_to_contribution.txgroup_id IN ({$txgroup_id})\n    AND\n      contribution.id NOT IN (SELECT entity_id FROM civicrm_entity_financial_trxn WHERE entity_table='civicrm_contribution');\n    ";
     $rotten_contribution = CRM_Core_DAO::executeQuery($find_rotten_contributions_sql);
     while ($rotten_contribution->fetch()) {
         $contribution_id = $rotten_contribution->contribution_id;
         // set these rotten contributions to 'Pending', no 'pay_later'
         CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution SET contribution_status_id={$status_pending}, is_pay_later=0 WHERE id={$contribution_id};");
         // now they will get their transactions back when they get set to 'completed' in the next step...
         error_log("org.project60.sepa: reset bad contribution [{$contribution_id}] to 'Pending'.");
     }
     // step 1.2: in CiviCRM before 4.4.4, the status 'In Progress' => 'Completed' was not allowed:
     if (version_compare(CRM_Utils_System::version(), '4.4.4', '<')) {
         // therefore, we change all these contributions' statuses back to 'Pending'
         $fix_status_query = "\n      UPDATE\n          civicrm_contribution\n      SET\n          contribution_status_id = {$status_pending},\n          is_pay_later = 0\n      WHERE \n          contribution_status_id = {$status_inprogress}\n      AND id IN (SELECT contribution_id FROM civicrm_sdd_contribution_txgroup WHERE txgroup_id={$txgroup_id});\n      ";
         CRM_Core_DAO::executeQuery($fix_status_query);
     }
     // step 2: update all the contributions
     $find_txgroup_contributions_sql = "\n    SELECT\n     contribution.id AS contribution_id\n    FROM\n      civicrm_sdd_contribution_txgroup AS txn_to_contribution\n    LEFT JOIN\n      civicrm_contribution AS contribution ON contribution.id = txn_to_contribution.contribution_id\n    WHERE\n      contribution_status_id != {$status_closed}\n    AND\n      txn_to_contribution.txgroup_id IN ({$txgroup_id});\n    ";
     $contribution = CRM_Core_DAO::executeQuery($find_txgroup_contributions_sql);
     $error_count = 0;
     while ($contribution->fetch()) {
         // update status for $contribution->contribution_id
         //   and set receive_date to collection_date (see https://github.com/Project60/sepa_dd/issues/190)
         $result = civicrm_api('Contribution', 'create', array('version' => 3, 'id' => $contribution->contribution_id, 'contribution_status_id' => $status_closed, 'receive_date' => date('YmdHis', strtotime($txgroup['collection_date']))));
         if (!empty($result['is_error'])) {
             $error_count += 1;
             error_log("org.project60.sepa: " . $result['error_message']);
         }
     }
     // step 3: update group status
     $result = civicrm_api('SepaTransactionGroup', 'create', array('id' => $txgroup_id, 'status_id' => $group_status_id_received, 'version' => 3));
     if (!empty($result['is_error'])) {
         $lock->release();
         return "Cannot update transaction group status for ID " . $txgroup_id;
     }
     // check if there was problems
     if ($error_count) {
         $lock->release();
         return "{$error_count} contributions could not be updated to status 'completed'.";
     }
     $lock->release();
 }
 /**
  * This method will adjust the collection date, 
  *   so it can still be submitted by the give submission date
  * 
  * @param txgroup_id              the transaction group for which the file should be created
  * @param latest_submission_date  the date when it should be submitted
  * 
  * @return an update array with the txgroup or a string with an error message
  */
 static function adjustCollectionDate($txgroup_id, $latest_submission_date)
 {
     $txgroup = civicrm_api('SepaTransactionGroup', 'getsingle', array('version' => 3, 'id' => $txgroup_id));
     if (!empty($txgroup['is_error'])) {
         return $txgroup['error_message'];
     }
     $test_date_parse = strtotime($latest_submission_date);
     if (empty($test_date_parse)) {
         return "Bad date adjustment given!";
     }
     $notice_period = (int) CRM_Sepa_Logic_Settings::getSetting("batching.{$txgroup['type']}.notice", $txgroup['sdd_creditor_id']);
     $new_collection_date = date('YmdHis', strtotime("{$latest_submission_date} + {$notice_period} days"));
     $new_latest_submission_date = date('YmdHis', strtotime("{$latest_submission_date}"));
     $result = civicrm_api('SepaTransactionGroup', 'create', array('version' => 3, 'id' => $txgroup_id, 'collection_date' => $new_collection_date, 'latest_submission_date' => $new_latest_submission_date));
     if (!empty($result['is_error'])) {
         return $result['error_message'];
     }
     // reload the item
     $txgroup = civicrm_api('SepaTransactionGroup', 'getsingle', array('version' => 3, 'id' => $txgroup_id));
     if (!empty($txgroup['is_error'])) {
         return $txgroup['error_message'];
     } else {
         return $txgroup;
     }
 }
 /**
  * This is the counterpart to the doDirectPayment method. This method creates
  * partial mandates, where the subsequent payment processess produces a payment.
  *
  * This function here should be called after the payment process was completed.
  * It will process all the PARTIAL mandates and connect them with created contributions.
  */
 public static function processPartialMandates()
 {
     // load all the PARTIAL mandates
     $partial_mandates = civicrm_api3('SepaMandate', 'get', array('version' => 3, 'status' => 'PARTIAL', 'option.limit' => 9999));
     foreach ($partial_mandates['values'] as $mandate_id => $mandate) {
         if ($mandate['type'] == 'OOFF') {
             // in the OOFF case, we need to find the contribution, and connect it
             $contribution = civicrm_api('Contribution', 'getsingle', array('version' => 3, 'trxn_id' => $mandate['reference']));
             if (empty($contribution['is_error'])) {
                 // check collection date
                 $ooff_notice = (int) CRM_Sepa_Logic_Settings::getSetting("batching.OOFF.notice", $mandate['creditor_id']);
                 $first_collection_date = strtotime("+{$ooff_notice} days");
                 $collection_date = strtotime($contribution['receive_date']);
                 if ($collection_date < $first_collection_date) {
                     // adjust collection date to the earliest possible one
                     $collection_date = $first_collection_date;
                 }
                 // FOUND! Update the contribution...
                 $contribution_bao = new CRM_Contribute_BAO_Contribution();
                 $contribution_bao->get('id', $contribution['id']);
                 $contribution_bao->is_pay_later = 0;
                 $contribution_bao->receive_date = date('YmdHis', $collection_date);
                 $contribution_bao->contribution_status_id = (int) CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name');
                 $contribution_bao->payment_instrument_id = (int) CRM_Core_OptionGroup::getValue('payment_instrument', 'OOFF', 'name');
                 $contribution_bao->save();
                 // ...and connect it to the mandate
                 $mandate_update = array();
                 $mandate_update['id'] = $mandate['id'];
                 $mandate_update['entity_id'] = $contribution['id'];
                 $mandate_update['type'] = $mandate['type'];
                 if (empty($mandate['contact_id'])) {
                     // this happens when the payment gets created AFTER the doDirectPayment method
                     $mandate_update['contact_id'] = $contribution_bao->contact_id;
                 }
                 // initialize according to the creditor settings
                 CRM_Sepa_BAO_SEPACreditor::initialiseMandateData($mandate['creditor_id'], $mandate_update);
                 // finally, write the changes to the mandate
                 civicrm_api3('SepaMandate', 'create', $mandate_update);
             } else {
                 // if NOT FOUND or error, delete the partial mandate
                 civicrm_api3('SepaMandate', 'delete', array('id' => $mandate_id));
             }
         } elseif ($mandate['type'] == 'RCUR') {
             // in the RCUR case, we also need to find the contribution, and connect it
             // load the contribution AND the associated recurring contribution
             $contribution = civicrm_api('Contribution', 'getsingle', array('version' => 3, 'trxn_id' => $mandate['reference']));
             $rcontribution = civicrm_api('ContributionRecur', 'getsingle', array('version' => 3, 'trxn_id' => $mandate['reference']));
             if (empty($contribution['is_error']) && empty($rcontribution['is_error'])) {
                 // we need to set the receive date to the correct collection date, otherwise it will be created again (w/o)
                 $rcur_notice = (int) CRM_Sepa_Logic_Settings::getSetting("batching.RCUR.notice", $mandate['creditor_id']);
                 $now = strtotime(date('Y-m-d', strtotime("now +{$rcur_notice} days")));
                 // round to full day
                 $collection_date = CRM_Sepa_Logic_Batching::getNextExecutionDate($rcontribution, $now);
                 // fix contribution
                 $contribution_bao = new CRM_Contribute_BAO_Contribution();
                 $contribution_bao->get('id', $contribution['id']);
                 $contribution_bao->is_pay_later = 0;
                 $contribution_bao->contribution_status_id = (int) CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name');
                 $contribution_bao->payment_instrument_id = (int) CRM_Core_OptionGroup::getValue('payment_instrument', 'FRST', 'name');
                 $contribution_bao->receive_date = date('YmdHis', strtotime($collection_date));
                 $contribution_bao->save();
                 // fix recurring contribution
                 $rcontribution_bao = new CRM_Contribute_BAO_ContributionRecur();
                 $rcontribution_bao->get('id', $rcontribution['id']);
                 $rcontribution_bao->start_date = date('YmdHis', strtotime($rcontribution_bao->start_date));
                 $rcontribution_bao->create_date = date('YmdHis', strtotime($rcontribution_bao->create_date));
                 $rcontribution_bao->modified_date = date('YmdHis', strtotime($rcontribution_bao->modified_date));
                 $rcontribution_bao->contribution_status_id = (int) CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name');
                 $rcontribution_bao->payment_instrument_id = (int) CRM_Core_OptionGroup::getValue('payment_instrument', 'FRST', 'name');
                 $rcontribution_bao->save();
                 // ...and connect it to the mandate
                 $mandate_update = array();
                 $mandate_update['id'] = $mandate['id'];
                 $mandate_update['entity_id'] = $rcontribution['id'];
                 $mandate_update['type'] = $mandate['type'];
                 if (empty($mandate['contact_id'])) {
                     $mandate_update['contact_id'] = $contribution['contact_id'];
                     $mandate['contact_id'] = $contribution['contact_id'];
                 }
                 //NO: $mandate_update['first_contribution_id'] = $contribution['id'];
                 // initialize according to the creditor settings
                 CRM_Sepa_BAO_SEPACreditor::initialiseMandateData($mandate['creditor_id'], $mandate_update);
                 // finally, write the changes to the mandate
                 civicrm_api3('SepaMandate', 'create', $mandate_update);
                 // ...and trigger notification
                 // FIXME: WORKAROUND, see https://github.com/Project60/org.project60.sepa/issues/296)
                 CRM_Contribute_BAO_ContributionPage::recurringNotify(CRM_Core_Payment::RECURRING_PAYMENT_START, $mandate['contact_id'], $contribution_bao->contribution_page_id, $rcontribution_bao);
             } else {
                 // something went wrong, delete partial
                 error_log("org.project60.sepa: deleting partial mandate " . $mandate['reference']);
                 civicrm_api3('SepaMandate', 'delete', array('id' => $mandate_id));
             }
         }
     }
 }
 /**
  * changes the amount of a SEPA mandate
  * 
  * @return success as boolean
  * @author endres -at- systopia.de 
  */
 static function adjustAmount($mandate_id, $adjusted_amount)
 {
     $adjusted_amount = (double) $adjusted_amount;
     $contribution_id_pending = CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name');
     // use a lock, in case somebody is batching just now
     $lock = CRM_Sepa_Logic_Settings::getLock();
     if (empty($lock)) {
         CRM_Core_Session::setStatus(sprintf(ts("Cannot adjust mandate [%s], batching in progress!"), $mandate_id), ts('Error'), 'error');
         return FALSE;
     }
     // first, load the mandate
     $mandate = civicrm_api("SepaMandate", "getsingle", array('id' => $mandate_id, 'version' => 3));
     if (isset($mandate['is_error'])) {
         CRM_Core_Session::setStatus(sprintf(ts("Cannot read mandate [%s]. Error was: '%s'"), $mandate_id, $mandate['error_message']), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // check the mandate type
     if ($mandate['type'] != "RCUR") {
         CRM_Core_Session::setStatus(ts("You can only adjust the amount of recurring contribution mandates."), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // load the contribution
     $contribution_id = $mandate['entity_id'];
     $contribution = civicrm_api('ContributionRecur', "getsingle", array('id' => $contribution_id, 'version' => 3));
     if (isset($contribution['is_error']) && $contribution['is_error']) {
         CRM_Core_Session::setStatus(sprintf(ts("Cannot read contribution [%s]. Error was: '%s'"), $contribution_id, $contribution['error_message']), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // check the amount
     if ($adjusted_amount <= 0) {
         CRM_Core_Session::setStatus(ts("The amount cannot be changed to zero or less."), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // check the amount
     $old_amount = (double) $contribution['amount'];
     if ($old_amount == $adjusted_amount) {
         CRM_Core_Session::setStatus(ts("The requested amount is the same as the current one."), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // modify the amount in the recurring contribution
     $query = array('version' => 3, 'id' => $contribution_id, 'amount' => $adjusted_amount, 'currency' => 'EUR');
     $result = civicrm_api("ContributionRecur", "create", $query);
     if (!empty($result['is_error'])) {
         CRM_Core_Session::setStatus(sprintf(ts("Cannot modify recurring contribution [%s]. Error was: '%s'"), $contribution_id, $result['error_message']), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // find already created contributions. Those also need to be modified
     $contributions2adjust = array();
     $adjusted_ids = array();
     $find_need2adjust = "\n    SELECT id\n    FROM civicrm_contribution\n    WHERE receive_date >= DATE(NOW())\n      AND contribution_recur_id = {$contribution_id}\n      AND contribution_status_id = {$contribution_id_pending};";
     $contributions2adjust_query = CRM_Core_DAO::executeQuery($find_need2adjust);
     while ($contributions2adjust_query->fetch()) {
         $contributions2adjust[] = $contributions2adjust_query->id;
     }
     // ...and adjust them:
     foreach ($contributions2adjust as $contribution2adjust_id) {
         $update_result = civicrm_api("Contribution", "create", array('version' => 3, 'id' => $contribution2adjust_id, 'total_amount' => $adjusted_amount, 'contribution_status_id' => $contribution_id_pending));
         if (!empty($update_result['is_error'])) {
             CRM_Core_Session::setStatus(sprintf(ts("Cannot update scheduled contribution [%s]. Error was: '%s'"), $contribution2adjust_id, $update_result['error_message']), ts('Error'), 'warn');
         } else {
             array_push($adjusted_ids, $contribution2adjust_id);
         }
     }
     if (count($adjusted_ids)) {
         CRM_Core_Session::setStatus(sprintf(ts("Successfully updated %d generated contributions."), count($adjusted_ids)), ts('Mandate updated.'), 'info');
     }
     $lock->release();
     return TRUE;
 }
 /**
  * Maintenance: Close all mandates that have expired
  */
 static function closeEnded()
 {
     // check lock
     $lock = CRM_Sepa_Logic_Settings::getLock();
     if (empty($lock)) {
         return "Batching in progress. Please try again later.";
     }
     $contribution_status_closed = (int) CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
     // first, load all of the mandates, that have run out
     $sql_query = "\n      SELECT\n        mandate.id AS mandate_id,\n        mandate.date AS mandate_date,\n        mandate.entity_id AS mandate_entity_id,\n        mandate.creation_date AS mandate_creation_date,\n        mandate.validation_date AS mandate_validation_date,\n        rcontribution.end_date AS end_date\n      FROM civicrm_sdd_mandate AS mandate\n      INNER JOIN civicrm_contribution_recur AS rcontribution       ON mandate.entity_id = rcontribution.id\n      WHERE mandate.type = 'RCUR'\n        AND mandate.status IN ('RCUR','FRST')\n        AND end_date <= DATE(NOW());";
     $results = CRM_Core_DAO::executeQuery($sql_query);
     $mandates_to_end = array();
     while ($results->fetch()) {
         array_push($mandates_to_end, array('mandate_id' => $results->mandate_id, 'recur_id' => $results->mandate_entity_id, 'creation_date' => $results->mandate_creation_date, 'validation_date' => $results->mandate_validation_date, 'date' => $results->mandate_date));
     }
     // then, end them one by one
     foreach ($mandates_to_end as $mandate_to_end) {
         $change_mandate = civicrm_api('SepaMandate', 'create', array('id' => $mandate_to_end['mandate_id'], 'date' => $mandate_to_end['date'], 'creation_date' => $mandate_to_end['creation_date'], 'validation_date' => $mandate_to_end['validation_date'], 'status' => 'COMPLETE', 'version' => 3));
         if (isset($change_mandate['is_error']) && $change_mandate['is_error']) {
             $lock->release();
             return sprintf("Couldn't set mandate '%s' to 'complete. Error was: '%s'", $mandates_to_end['mandate_id'], $change_mandate['error_message']);
         }
         $change_rcur = civicrm_api('ContributionRecur', 'create', array('id' => $mandate_to_end['recur_id'], 'contribution_status_id' => $contribution_status_closed, 'modified_date' => date('YmdHis'), 'currency' => 'EUR', 'version' => 3));
         if (isset($change_rcur['is_error']) && $change_rcur['is_error']) {
             $lock->release();
             return sprintf("Couldn't set recurring contribution '%s' to 'complete. Error was: '%s'", $mandates_to_end['recur_id'], $change_rcur['error_message']);
         }
     }
     $lock->release();
 }
 /**
  * Reads the default creditor from the settings
  * Will only return a creditor if it exists and if it's active
  * 
  * @return CRM_Sepa_BAO_SEPACreditor object or NULL
  */
 static function defaultCreditor()
 {
     $default_creditor_id = (int) CRM_Sepa_Logic_Settings::getSetting('batching_default_creditor');
     if (empty($default_creditor_id)) {
         return NULL;
     }
     $default_creditor = new CRM_Sepa_DAO_SEPACreditor();
     $default_creditor->get('id', $default_creditor_id);
     if (empty($default_creditor->mandate_active)) {
         return NULL;
     } else {
         return $default_creditor;
     }
 }
 /**
  * Test civicrm_api3_sepa_mandate_create with default creditor
  *
  * @author niko bochan
  */
 public function testCreateWithDefaultCreditor()
 {
     $contactId = $this->individualCreate();
     $contribution = $this->callAPISuccess("Contribution", "create", array('version' => 3, 'financial_type_id' => 1, 'contribution_status_id' => 2, 'total_amount' => '100.00', 'currency' => 'EUR', 'contact_id' => $contactId));
     $create_data = array('version' => 3, 'type' => 'OOFF', 'status' => 'INIT', 'entity_id' => $contribution['id'], 'entity_table' => 'civicrm_contribution', 'contact_id' => $contactId, 'start_date' => date('YmdHis'), 'receive_date' => date('YmdHis'), 'date' => date('YmdHis'), 'iban' => "BE68844010370034", 'bic' => "TESTTEST", 'is_enabled' => 1);
     // this should fail, since no creditor_id is set and no default creditor either
     $this->callAPIFailure("SepaMandate", "create", $create_data);
     // set default creditor
     $creditor_id = $this->getCreditor();
     CRM_Sepa_Logic_Settings::setSetting('batching_default_creditor', $creditor_id);
     // this should work, since no creditor_id is set BUT the default creditor is
     $this->callAPISuccess("SepaMandate", "create", $create_data);
     // set bad default creditor
     CRM_Sepa_Logic_Settings::setSetting('batching_default_creditor', '999');
     // this should fail, since no creditor_id is set and the default creditor is bad
     $this->callAPIFailure("SepaMandate", "create", $create_data);
 }