/** * See if the is_test flag is properly passed on through batching * * @see https://github.com/Project60/sepa_dd/issues/114 * @author endres -at- systopia.de */ public function testTestMandates() { $frst_notice = CRM_Core_BAO_Setting::getItem('SEPA Direct Debit Preferences', 'batching_FRST_notice'); $this->assertNotEmpty($frst_notice, "No FRST notice period specified!"); CRM_Core_BAO_Setting::setItem('SEPA Direct Debit Preferences', 'batching_RCUR_notice', $frst_notice); $rcur_notice = CRM_Core_BAO_Setting::getItem('SEPA Direct Debit Preferences', 'batching_RCUR_notice'); $this->assertNotEmpty($rcur_notice, "No RCUR notice period specified!"); $this->assertEquals($frst_notice, $rcur_notice, "Notice periods should be the same."); $cycle_day = date("d", strtotime("+{$frst_notice} days")); // 2) create a RCUR mandate, due for collection right now $mandate = $this->createMandate(array('type' => 'RCUR', 'status' => 'FRST'), array('cycle_day' => $cycle_day)); CRM_Sepa_Logic_Batching::updateRCUR($mandate['creditor_id'], 'FRST'); $rcontrib = $this->callAPISuccess("ContributionRecur", "getsingle", array("id" => $mandate['entity_id'])); $this->assertEquals('0', $rcontrib['is_test'], "Test flag should not be set!"); $contrib = $this->callAPISuccess("Contribution", "getsingle", array("contribution_recur_id" => $rcontrib['id'])); $this->assertEquals('0', $contrib['is_test'], "Test flag should not be set!"); // 3) create a RCUR TEST mandate, due for collection right now $ccount = CRM_Core_DAO::singleValueQuery("SELECT count(id) FROM civicrm_contribution;"); $tccount = CRM_Core_DAO::singleValueQuery("SELECT count(id) FROM civicrm_contribution WHERE is_test=1;"); $mandate = $this->createMandate(array('type' => 'RCUR', 'status' => 'FRST'), array('is_test' => 1, 'cycle_day' => $cycle_day)); CRM_Sepa_Logic_Batching::updateRCUR($mandate['creditor_id'], 'FRST'); $this->assertDBQuery($ccount + 1, "SELECT count(id) FROM civicrm_contribution;"); $rcontrib = $this->callAPISuccess("ContributionRecur", "getsingle", array("id" => $mandate['entity_id'])); $this->assertEquals('1', $rcontrib['is_test'], "Test flag should be set!"); // cannot use API for Contribution, wouldn't load test contributions $this->assertDBQuery($tccount + 1, "SELECT count(id) FROM civicrm_contribution WHERE is_test=1;"); // 4) create a OOFF TEST mandate $mandate = $this->createMandate(array('type' => 'OOFF', 'status' => 'INIT'), array('is_test' => 1)); CRM_Sepa_Logic_Batching::updateRCUR($mandate['creditor_id'], 'FRST'); $contrib = $this->callAPISuccess("Contribution", "getsingle", array("id" => $mandate['entity_id'])); $this->assertEquals('1', $contrib['is_test'], "Test flag should be set!"); }
/** * API CALL TO UPDATE TXGROUPs ("Batching") * * @package CiviCRM_SEPA * */ function civicrm_api3_sepa_alternative_batching_update($params) { // get creditor list $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['id']; } } if ($params['type'] == 'OOFF') { foreach ($creditors as $creditor_id) { CRM_Sepa_Logic_Batching::updateOOFF($creditor_id); } } elseif ($params['type'] == 'RCUR' || $params['type'] == 'FRST') { // first: make sure, that there are no outdated mandates: CRM_Sepa_Logic_Batching::closeEnded(); // then, run the update for recurring mandates foreach ($creditors as $creditor_id) { CRM_Sepa_Logic_Batching::updateRCUR($creditor_id, $params['type']); } } else { return civicrm_api3_create_error(sprintf("Unknown batching mode '%s'.", $params['type'])); } return civicrm_api3_create_success(); }
function civicrm_api3_sepa_transaction_group_createnext($params) { $errors = $counter = 0; $values = array(); $group = (int) $params["id"]; if (!$group) { throw new API_Exception("Incorrect or missing value for group id"); } $contribs = civicrm_api("sepa_contribution_group", "getdetail", $params); foreach ($contribs["values"] as $old) { if (!$old['recur_id']) { throw new API_Exception("Trying to create next payment for non-recurrent contribution?"); } $date = strtotime(substr($old["receive_date"], 0, 10)); $next_collectionDate = strtotime("+" . $old["frequency_interval"] . " " . $old["frequency_unit"], $date); $next_collectionDate = date('YmdHis', $next_collectionDate); $new = $old; $new["hash"] = md5(uniqid(rand(), true)); $new["source"] = "SEPA recurring contribution"; unset($new["id"]); unset($new["contribution_id"]); $new["receive_date"] = $next_collectionDate; $new["contribution_status_id"] = 2; $new["contribution_recur_id"] = $new["recur_id"]; unset($new["recur_id"]); /* CRM_Core_DAO::executeQuery(" UPDATE civicrm_contribution_recur SET next_sched_contribution = %1 WHERE id = %2 ", array( 1 => array($next_collectionDate, 'String'), 2 => array($new["contribution_recur_id"], 'Integer') ) ); */ $new["version"] = 3; $new["sequential"] = 1; /* $total += $new["total_amount"]; ++$counter; continue; */ $result = civicrm_api('contribution', 'create', $new); if ($result['is_error']) { $output[] = $result['error_message']; ++$errors; continue; } else { ++$counter; $total += $result["total_amount"]; $mandate = new CRM_Sepa_BAO_SEPAMandate(); $contrib = new CRM_Contribute_BAO_Contribution(); $contrib->get('id', $result["id"]); //it sucks to have to fetch again, just to get the BAO // $mandate->get('id', $old["mandate_id"]); // $values[] = $result["values"]; $group = CRM_Sepa_Logic_Batching::batchContributionByCreditor($contrib, $old["creditor_id"], $old["payment_instrument_id"]); $values = $group->toArray(); } } if (!$errors) { $values["nb_contrib"] = $counter; $values["total"] = $total; return civicrm_api3_create_success(array($values), $params, 'address', $contrib); } else { civicrm_api3_create_error("Could not create " . $errors . " new contributions", $output); } }
/** * 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)); } } } }