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