From dccd9f4f18039d706cd149abcc03ae01c808f930 Mon Sep 17 00:00:00 2001 From: Edsel Roque Lopez Date: Fri, 8 Jul 2016 23:13:25 +0530 Subject: [PATCH] CRM-18854 [needs-review] Added support for recurring contributions for online pledges (#8558) * MISC-110 Added pledge start date for pledge block * MISC-110 Added pledge start date to online contribution form * MISC-110 Added recur record for pledge * MISC-110 Added changes suggested by Eileen * MISC-110 Added upgrade code for pledge_start_date * MISC-110 Added unit test for createRecur function * MISC-110 Removed use of form and set payment instrument id * MISC-110 Added webtest for Pledge Recurring contribution * MISC-110 Removed unfinished unit test * MISC-110 Resolved Jenkins errors * MISC-110 Added apiSuccess assertion * MISC-110 Added changes from UP-3 * MISC-113 Added UI for frontend and set defaults * MISC-113 Bug fixes and improvements * MISC-113 Added date fields for start date on online contribution form * MISC-113 Added fields in payment processor table * MISC-113 Added processing for start date * MISC-110 Added changes to set contribution receive date for future payments * MISC-110 Typo error fixes * MISC-110 Added future start date to payment processor MISC-113 Added JS for adjust recurring start date MISC-110 Added future start date values on install MISC-113 Added script on install and upgrade MISC-110 Added form rule for pledge start date MISC-110 Removed explicit create of recur record -- minor change -- minor change -- minor change MISC-113 Added unit test MISC-113 Removed old webtest MISC-113 Resolved jenkins errors MISC-110 Style changes MISC-113 Allowed NULL values for future start date MISC-110 Removed future_start_date field from payment processor MISC-110 Removed future_start_date field from data * MISC-113 Changed serialize to json_encode MISC-113 Jenkins style fixes * MISC-113 Bug fixes for start date * MISC-113 Added unit test for contribution page submit with pledge with future start date MISC-110 Bug fix * MISC-113 Added id = 0 instead of pay_later text * MISC-110 Refactored assignment of contributionRecurID Conflicts: CRM/Contribute/BAO/Contribution/Utils.php * MISC-113 Handled issue with membership recur payments * MISC-110 Added upgrade script to 4.7.10 --- CRM/Contribute/BAO/Contribution/Utils.php | 5 - CRM/Contribute/Form/Contribution/Confirm.php | 30 +++++ CRM/Contribute/Form/ContributionPage.php | 17 +++ .../Form/ContributionPage/Amount.php | 96 ++++++++++++--- CRM/Pledge/BAO/Pledge.php | 86 +++++++++++++ CRM/Pledge/BAO/PledgeBlock.php | 45 +++++++ CRM/Upgrade/Incremental/sql/4.7.10.mysql.tpl | 6 + CRM/Utils/Date.php | 16 +++ api/v3/Contribution.php | 3 + .../CRM/Contribute/Form/Contribution/Main.tpl | 11 ++ .../Form/ContributionPage/Amount.tpl | 114 +++++++++++++++++- tests/phpunit/CRM/Pledge/BAO/PledgeTest.php | 28 +++++ .../phpunit/CiviTest/CiviSeleniumTestCase.php | 6 +- tests/phpunit/api/v3/ContributionPageTest.php | 80 ++++++++++++ xml/schema/Contribute/ContributionPage.xml | 8 ++ xml/schema/Pledge/PledgeBlock.xml | 26 ++++ xml/templates/civicrm_data.tpl | 6 +- 17 files changed, 559 insertions(+), 24 deletions(-) diff --git a/CRM/Contribute/BAO/Contribution/Utils.php b/CRM/Contribute/BAO/Contribution/Utils.php index 80e453cb0d..89fbfa3495 100644 --- a/CRM/Contribute/BAO/Contribution/Utils.php +++ b/CRM/Contribute/BAO/Contribution/Utils.php @@ -120,11 +120,6 @@ class CRM_Contribute_BAO_Contribution_Utils { $paymentParams['contributionTypeID'] = $contributionTypeId; $paymentParams['item_name'] = $form->_params['description']; - if ($contribution && $form->_values['is_recur'] && $contribution->contribution_recur_id - ) { - $form->_params['contributionRecurID'] = $contribution->contribution_recur_id; - } - $paymentParams['qfKey'] = $form->controller->_key; if ($component == 'membership') { return array('contribution' => $contribution); diff --git a/CRM/Contribute/Form/Contribution/Confirm.php b/CRM/Contribute/Form/Contribution/Confirm.php index bb8ae0c4e2..7b2f88e2da 100644 --- a/CRM/Contribute/Form/Contribution/Confirm.php +++ b/CRM/Contribute/Form/Contribution/Confirm.php @@ -932,6 +932,11 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr $pledgeParams['frequency_day'] = 1; } $pledgeParams['create_date'] = $pledgeParams['start_date'] = $pledgeParams['scheduled_date'] = date("Ymd"); + $pledgeBlock = CRM_Pledge_BAO_PledgeBlock::getPledgeBlock($contribution->contribution_page_id); + if (CRM_Utils_Array::value('start_date', $params) || !CRM_Utils_Array::value('is_pledge_start_date_visible', $pledgeBlock)) { + $pledgeStartDate = CRM_Utils_Array::value('start_date', $params, NULL); + $pledgeParams['start_date'] = $pledgeParams['scheduled_date'] = CRM_Pledge_BAO_Pledge::getPledgeStartDate($pledgeStartDate, $pledgeBlock); + } $pledgeParams['status_id'] = $contribution->contribution_status_id; $pledgeParams['max_reminders'] = $form->_values['max_reminders']; $pledgeParams['initial_reminder_day'] = $form->_values['initial_reminder_day']; @@ -1104,6 +1109,10 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr } CRM_Utils_System::redirect(CRM_Utils_System::url($urlString, $urlParams)); } + // Only set contribution recur ID for contributions since offline membership recur payments are handled somewhere else. + if (!is_a($form, "CRM_Member_Form_Membership")) { + $form->_params['contributionRecurID'] = $recurring->id; + } return $recurring->id; } @@ -1846,6 +1855,15 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr // hack these in for test support. $form->_fields['billing_first_name'] = 1; $form->_fields['billing_last_name'] = 1; + // CRM-18854 - Set form values to allow pledge to be created for api test. + if (CRM_Utils_Array::value('pledge_block_id', $params)) { + $form->_values['pledge_block_id'] = $params['pledge_block_id']; + $pledgeBlock = CRM_Pledge_BAO_PledgeBlock::getPledgeBlock($params['id']); + $form->_values['max_reminders'] = $pledgeBlock['max_reminders']; + $form->_values['initial_reminder_day'] = $pledgeBlock['initial_reminder_day']; + $form->_values['additional_reminder_day'] = $pledgeBlock['additional_reminder_day']; + $form->_values['is_email_receipt'] = FALSE; + } $priceSetID = $form->_params['priceSetId'] = $paramsProcessedForForm['price_set_id']; $priceFields = CRM_Price_BAO_PriceSet::getSetDetail($priceSetID); $priceSetFields = reset($priceFields); @@ -1929,6 +1947,17 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr // fix currency ID $this->_params['currencyID'] = CRM_Core_Config::singleton()->defaultCurrency; + // CRM-18854 + if (CRM_Utils_Array::value('adjust_recur_start_date', $this->_values)) { + $pledgeBlock = CRM_Pledge_BAO_PledgeBlock::getPledgeBlock($this->_id); + if (CRM_Utils_Array::value('start_date', $this->_params) || !CRM_Utils_Array::value('is_pledge_start_date_visible', $pledgeBlock)) { + $pledgeStartDate = CRM_Utils_Array::value('start_date', $this->_params, NULL); + $this->_params['receive_date'] = CRM_Pledge_BAO_Pledge::getPledgeStartDate($pledgeStartDate, $pledgeBlock); + $recurParams = CRM_Pledge_BAO_Pledge::buildRecurParams($this->_params); + $this->_params = array_merge($this->_params, $recurParams); + } + } + //carry payment processor id. if (CRM_Utils_Array::value('id', $this->_paymentProcessor)) { $this->_params['payment_processor_id'] = $this->_paymentProcessor['id']; @@ -2315,6 +2344,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr 'payment_processor_id' => $this->_paymentProcessor['id'], 'is_transactional' => FALSE, 'fee_amount' => CRM_Utils_Array::value('fee_amount', $result), + 'receive_date' => CRM_Utils_Array::value('receive_date', $result), ) ); } diff --git a/CRM/Contribute/Form/ContributionPage.php b/CRM/Contribute/Form/ContributionPage.php index e196b61c7f..07e731b5e5 100644 --- a/CRM/Contribute/Form/ContributionPage.php +++ b/CRM/Contribute/Form/ContributionPage.php @@ -293,9 +293,26 @@ class CRM_Contribute_Form_ContributionPage extends CRM_Core_Form { 'max_reminders', 'initial_reminder_day', 'additional_reminder_day', + 'pledge_start_date', + 'is_pledge_start_date_visible', + 'is_pledge_start_date_editable', ); foreach ($pledgeBlock as $key) { $defaults[$key] = CRM_Utils_Array::value($key, $pledgeBlockDefaults); + if ($key == 'pledge_start_date' && CRM_Utils_Array::value($key, $pledgeBlockDefaults)) { + $defaultPledgeDate = (array) json_decode($pledgeBlockDefaults['pledge_start_date']); + $pledgeDateFields = array( + 'pledge_calendar_date' => 'calendar_date', + 'pledge_calendar_month' => 'calendar_month', + ); + $defaults['pledge_default_toggle'] = key($defaultPledgeDate); + foreach ($pledgeDateFields as $key => $value) { + if (array_key_exists($value, $defaultPledgeDate)) { + $defaults[$key] = reset($defaultPledgeDate); + $this->assign($key, reset($defaultPledgeDate)); + } + } + } } if (!empty($pledgeBlockDefaults['pledge_frequency_unit'])) { $defaults['pledge_frequency_unit'] = array_fill_keys(explode(CRM_Core_DAO::VALUE_SEPARATOR, diff --git a/CRM/Contribute/Form/ContributionPage/Amount.php b/CRM/Contribute/Form/ContributionPage/Amount.php index 3c46e992a5..b646d04b94 100644 --- a/CRM/Contribute/Form/ContributionPage/Amount.php +++ b/CRM/Contribute/Form/ContributionPage/Amount.php @@ -87,22 +87,28 @@ class CRM_Contribute_Form_ContributionPage_Amount extends CRM_Contribute_Form_Co $this->addElement('checkbox', 'is_monetary', ts('Execute real-time monetary transactions')); - $paymentProcessor = CRM_Core_PseudoConstant::paymentProcessor(); - $recurringPaymentProcessor = array(); - - if (!empty($paymentProcessor)) { - $paymentProcessorIds = implode(',', array_keys($paymentProcessor)); - $query = " -SELECT id - FROM civicrm_payment_processor - WHERE id IN ({$paymentProcessorIds}) - AND is_recur = 1"; - $dao = CRM_Core_DAO::executeQuery($query); - while ($dao->fetch()) { - $recurringPaymentProcessor[] = $dao->id; + $paymentProcessors = CRM_Financial_BAO_PaymentProcessor::getAllPaymentProcessors('live'); + $recurringPaymentProcessor = $futurePaymentProcessor = $paymentProcessor = array(); + + if (!empty($paymentProcessors)) { + foreach ($paymentProcessors as $id => $processor) { + if ($id != 0) { + $paymentProcessor[$id] = $processor['name']; + } + if (CRM_Utils_Array::value('is_recur', $processor)) { + $recurringPaymentProcessor[] = $id; + } + if (CRM_Utils_Array::value('object', $processor) && $processor['object']->supports('FutureRecurStartDate')) { + $futurePaymentProcessor[] = $id; + } } } - $this->assign('recurringPaymentProcessor', $recurringPaymentProcessor); + if (count($recurringPaymentProcessor)) { + $this->assign('recurringPaymentProcessor', $recurringPaymentProcessor); + } + if (count($futurePaymentProcessor)) { + $this->assign('futurePaymentProcessor', $futurePaymentProcessor); + } if (count($paymentProcessor)) { $this->assign('paymentProcessor', $paymentProcessor); } @@ -168,6 +174,23 @@ SELECT id $this->addElement('text', 'initial_reminder_day', ts('Send payment reminder'), array('size' => 3)); $this->addElement('text', 'max_reminders', ts('Send up to'), array('size' => 3)); $this->addElement('text', 'additional_reminder_day', ts('Send additional reminders'), array('size' => 3)); + if (!empty($futurePaymentProcessor)) { + // CRM-18854 + $this->addElement('checkbox', 'adjust_recur_start_date', ts('Adjust Recurring Start Date'), NULL, + array('onclick' => "showHideByValue('adjust_recur_start_date',true,'recurDefaults','table-row','radio',false);") + ); + $this->addDate('pledge_calendar_date', ts('Specific Calendar Date')); + $month = CRM_Utils_Date::getCalendarDayOfMonth(); + $this->add('select', 'pledge_calendar_month', ts('Specific day of Month'), $month); + $pledgeDefaults = array( + 'contribution_date' => ts('Day of Contribution'), + 'calendar_date' => ts('Specific Calendar Date'), + 'calendar_month' => ts('Specific day of Month'), + ); + $this->addRadio('pledge_default_toggle', ts('Recurring Contribution Start Date Default'), $pledgeDefaults, array('allowClear' => FALSE), '

'); + $this->addElement('checkbox', 'is_pledge_start_date_visible', ts('Show Recurring Donation Start Date?'), NULL); + $this->addElement('checkbox', 'is_pledge_start_date_editable', ts('Allow Edits to Recurring Donation Start date?'), NULL); + } } //add currency element. @@ -320,6 +343,13 @@ SELECT id } } + // CRM-18854 Check if recurring start date is in the future. + if (CRM_Utils_Array::value('pledge_calendar_date', $fields)) { + if (date('Ymd') > date('Ymd', strtotime($fields['pledge_calendar_date']))) { + $errors['pledge_calendar_date'] = ts('The recurring start date cannot be prior to the current date.'); + } + } + //check for the amount label (mandatory) if (!empty($fields['amount_block_is_active']) && empty($fields['amount_label'])) { $errors['amount_label'] = ts('Please enter the contribution amount label.'); @@ -481,6 +511,38 @@ SELECT id $params['is_recur_installments'] = CRM_Utils_Array::value('is_recur_installments', $params, FALSE); } + if (CRM_Utils_Array::value('adjust_recur_start_date', $params)) { + $fieldValue = ''; + $pledgeDateFields = array( + 'calendar_date' => 'pledge_calendar_date', + 'calendar_month' => 'pledge_calendar_month', + ); + if ($params['pledge_default_toggle'] == 'contribution_date') { + $fieldValue = json_encode(array('contribution_date' => date('Ymd'))); + } + else { + foreach ($pledgeDateFields as $key => $pledgeDateField) { + if (CRM_Utils_Array::value($pledgeDateField, $params) && $params['pledge_default_toggle'] == $key) { + $fieldValue = json_encode(array($key => $params[$pledgeDateField])); + break; + } + } + } + $params['pledge_start_date'] = $fieldValue; + } + else { + $params['pledge_start_date'] = ''; + $params['adjust_recur_start_date'] = 0; + $params['is_pledge_start_date_visible'] = 0; + $params['is_pledge_start_date_editable'] = 0; + } + if (!CRM_Utils_Array::value('is_pledge_start_date_visible', $params)) { + $params['is_pledge_start_date_visible'] = 0; + } + if (!CRM_Utils_Array::value('is_pledge_start_date_editable', $params)) { + $params['is_pledge_start_date_editable'] = 0; + } + if (array_key_exists('payment_processor', $params) && !CRM_Utils_System::isNull($params['payment_processor']) ) { @@ -712,6 +774,9 @@ SELECT id 'max_reminders', 'initial_reminder_day', 'additional_reminder_day', + 'pledge_start_date', + 'is_pledge_start_date_visible', + 'is_pledge_start_date_editable', ); foreach ($pledgeBlock as $key) { $pledgeBlockParams[$key] = CRM_Utils_Array::value($key, $params); @@ -719,6 +784,9 @@ SELECT id $pledgeBlockParams['is_pledge_interval'] = CRM_Utils_Array::value('is_pledge_interval', $params, FALSE ); + $pledgeBlockParams['pledge_start_date'] = CRM_Utils_Array::value('pledge_start_date', + $params, FALSE + ); // create pledge block. CRM_Pledge_BAO_PledgeBlock::create($pledgeBlockParams); } diff --git a/CRM/Pledge/BAO/Pledge.php b/CRM/Pledge/BAO/Pledge.php index 2ec83ff235..c4c0084c6e 100644 --- a/CRM/Pledge/BAO/Pledge.php +++ b/CRM/Pledge/BAO/Pledge.php @@ -1202,4 +1202,90 @@ SELECT pledge.contact_id as contact_id, return array_diff(array_flip($paymentStatus), self::getNonTransactionalStatus()); } + /** + * Create array for recur record for pledge. + * @return array + * params for recur record + */ + public static function buildRecurParams($params) { + $recurParams = array( + 'is_recur' => TRUE, + 'auto_renew' => TRUE, + 'frequency_unit' => $params['pledge_frequency_unit'], + 'frequency_interval' => $params['pledge_frequency_interval'], + 'installments' => $params['pledge_installments'], + 'start_date' => $params['receive_date'], + ); + return $recurParams; + } + + /** + * Get pledge start date. + * + * @return string + * start date + */ + public static function getPledgeStartDate($date, $pledgeBlock) { + $startDate = (array) json_decode($pledgeBlock['pledge_start_date']); + list($field, $value) = each($startDate); + if (!CRM_Utils_Array::value('is_pledge_start_date_visible', $pledgeBlock)) { + return date('Ymd', strtotime($value)); + } + if (!CRM_Utils_Array::value('is_pledge_start_date_editable', $pledgeBlock)) { + return $date; + } + switch ($field) { + case 'contribution_date': + $date = date('Ymd'); + break; + + case 'calendar_date': + $date = date('Ymd', strtotime($date)); + break; + + case 'calendar_month': + $date = self::getPaymentDate($date); + $date = date('Ymd', strtotime($date)); + break; + + default: + break; + + } + return $date; + } + + /** + * Get first payment date for pledge. + * + */ + public static function getPaymentDate($day) { + if ($day == 31) { + // Find out if current month has 31 days, if not, set it to 30 (last day). + $t = date('t'); + if ($t != $day) { + $day = $t; + } + } + $current = date('d'); + switch (TRUE) { + case ($day == $current): + $date = date('m/d/Y'); + break; + + case ($day > $current): + $date = date('m/d/Y', mktime(0, 0, 0, date('m'), $day, date('Y'))); + break; + + case ($day < $current): + $date = date('m/d/Y', mktime(0, 0, 0, date('m', strtotime("+1 month")), $day, date('Y'))); + break; + + default: + break; + + } + return $date; + } + } diff --git a/CRM/Pledge/BAO/PledgeBlock.php b/CRM/Pledge/BAO/PledgeBlock.php index 4fe87d80ed..357686bf42 100644 --- a/CRM/Pledge/BAO/PledgeBlock.php +++ b/CRM/Pledge/BAO/PledgeBlock.php @@ -202,6 +202,7 @@ class CRM_Pledge_BAO_PledgeBlock extends CRM_Pledge_DAO_PledgeBlock { 'scheduled_date', 'scheduled_amount', 'currency', + 'pledge_start_date', ); CRM_Core_DAO::commonRetrieveAll('CRM_Pledge_DAO_PledgePayment', 'pledge_id', $form->_values['pledge_id'], $allPayments, $returnProperties @@ -300,6 +301,50 @@ class CRM_Pledge_BAO_PledgeBlock extends CRM_Pledge_DAO_PledgeBlock { } } $form->addElement('select', 'pledge_frequency_unit', NULL, $freqUnits); + // CRM-18854 + if (CRM_Utils_Array::value('is_pledge_start_date_visible', $pledgeBlock)) { + if (CRM_Utils_Array::value('pledge_start_date', $pledgeBlock)) { + $defaults = array(); + $date = (array) json_decode($pledgeBlock['pledge_start_date']); + list($field, $value) = each($date); + switch ($field) { + case 'contribution_date': + $form->addDate('start_date', ts('First installment payment')); + $paymentDate = $value = date('d/m/Y'); + list($defaults['start_date'], $defaults['start_date_time']) = CRM_Utils_Date::setDateDefaults($value); + $form->assign('is_date', TRUE); + break; + + case 'calendar_date': + $form->addDate('start_date', ts('First installment payment')); + list($defaults['start_date'], $defaults['start_date_time']) = CRM_Utils_Date::setDateDefaults($value); + $form->assign('is_date', TRUE); + $paymentDate = $value; + break; + + case 'calendar_month': + $month = CRM_Utils_Date::getCalendarDayOfMonth(); + $form->add('select', 'start_date', ts('Day of month installments paid'), $month); + $paymentDate = CRM_Pledge_BAO_Pledge::getPaymentDate($value); + list($defaults['start_date'], $defaults['start_date_time']) = CRM_Utils_Date::setDateDefaults($paymentDate); + break; + + default: + break; + + } + $form->setDefaults($defaults); + $form->assign('start_date_display', $paymentDate); + $form->assign('start_date_editable', FALSE); + if (CRM_Utils_Array::value('is_pledge_start_date_editable', $pledgeBlock)) { + $form->assign('start_date_editable', TRUE); + if ($field == 'calendar_month') { + $form->assign('is_date', FALSE); + $form->setDefaults(array('start_date' => $value)); + } + } + } + } } } diff --git a/CRM/Upgrade/Incremental/sql/4.7.10.mysql.tpl b/CRM/Upgrade/Incremental/sql/4.7.10.mysql.tpl index 03bf2447e7..a1101ff8a7 100644 --- a/CRM/Upgrade/Incremental/sql/4.7.10.mysql.tpl +++ b/CRM/Upgrade/Incremental/sql/4.7.10.mysql.tpl @@ -6,3 +6,9 @@ INSERT INTO civicrm_option_value (option_group_id, {localize field='label'}label{/localize}, value, name, grouping, filter, is_default, weight, {localize field='description'}description{/localize}, is_optgroup, is_reserved, is_active, component_id, visibility_id) VALUES (@option_group_id_report, {localize}'{ts escape="sql"}{/ts}'{/localize}, 'contribute/tiralBalance', 'CRM_Report_Form_Contribute_DeferredRevenue', NULL, 0, NULL, @option_group_id_report_wt+1, {localize}'{ts escape="sql"}Deferred Revenue Details Report{/ts}'{/localize}, 0, 0, 1, @contributeCompId, NULL); + +-- CRM-18854 +ALTER TABLE civicrm_pledge_block ADD pledge_start_date varchar(64) NULL DEFAULT NULL COMMENT 'The date that the first scheduled pledge occurs.'; +ALTER TABLE civicrm_pledge_block ADD is_pledge_start_date_visible TINYINT(4) NOT NULL DEFAULT 0 COMMENT 'If true - recurring start date is shown.'; +ALTER TABLE civicrm_pledge_block ADD is_pledge_start_date_editable TINYINT(4) NOT NULL DEFAULT 0 COMMENT 'If true - recurring start date is editable.'; +ALTER TABLE civicrm_contribution_page ADD adjust_recur_start_date TINYINT(4) NOT NULL DEFAULT 0 COMMENT 'If true - user is able to adjust payment start date.' AFTER is_recur_installments; diff --git a/CRM/Utils/Date.php b/CRM/Utils/Date.php index a53acb6cd1..c9fc8b0ecd 100644 --- a/CRM/Utils/Date.php +++ b/CRM/Utils/Date.php @@ -1886,4 +1886,20 @@ class CRM_Utils_Date { return $formattedDate; } + /** + * Function to return days of the month. + * + * @return array + */ + public static function getCalendarDayOfMonth() { + $month = array(); + for ($i = 1; $i <= 31; $i++) { + $month[$i] = $i; + if ($i == 31) { + $month[$i] = $i . ' / Last day of month'; + } + } + return $month; + } + } diff --git a/api/v3/Contribution.php b/api/v3/Contribution.php index 13b5357f83..5d6bae177e 100644 --- a/api/v3/Contribution.php +++ b/api/v3/Contribution.php @@ -630,6 +630,9 @@ function _ipn_process_transaction(&$params, $contribution, $input, $ids, $firstC if (!empty($params['trxn_date'])) { $input['trxn_date'] = $params['trxn_date']; } + if (!empty($params['receive_date'])) { + $input['receive_date'] = $params['receive_date']; + } if (empty($contribution->contribution_page_id)) { static $domainFromName; static $domainFromEmail; diff --git a/templates/CRM/Contribute/Form/Contribution/Main.tpl b/templates/CRM/Contribute/Form/Contribution/Main.tpl index d0406229ed..17a430dab5 100644 --- a/templates/CRM/Contribute/Form/Contribution/Main.tpl +++ b/templates/CRM/Contribute/Form/Contribution/Main.tpl @@ -112,6 +112,17 @@ {/if} {$form.pledge_frequency_unit.html} {ts}for{/ts} {$form.pledge_installments.html} {ts}installments.{/ts} +
+ {if $start_date_editable} + {if $is_date} +
{$form.start_date.label}
{include file="CRM/common/jcalendar.tpl" elementName=start_date}
+ {else} +
{$form.start_date.label}
{$form.start_date.html}
+ {/if} + {else} +
{$form.start_date.label}
+
{$start_date_display|date_format}
+ {/if}
{/if} diff --git a/templates/CRM/Contribute/Form/ContributionPage/Amount.tpl b/templates/CRM/Contribute/Form/ContributionPage/Amount.tpl index 929ffb2a47..4e64a05874 100644 --- a/templates/CRM/Contribute/Form/ContributionPage/Amount.tpl +++ b/templates/CRM/Contribute/Form/ContributionPage/Amount.tpl @@ -119,6 +119,10 @@ +{if $futurePaymentProcessor} +   {include file="CRM/common/jcalendar.tpl" elementName=pledge_calendar_date} +   {$form.pledge_calendar_month.html}
{ts}Recurring payment will be processed this day of the month following submission of this contribution page.{/ts}
+{/if}
@@ -152,6 +156,26 @@ {$form.additional_reminder_day.html} {ts}Days after the last one sent, up to the maximum number of reminders.{/ts} + {if $futurePaymentProcessor} + {$form.adjust_recur_start_date.label} + {$form.adjust_recur_start_date.html}
+
+ {$form.pledge_default_toggle.label} + + + + + + + + + + +
{$form.pledge_default_toggle.html}
{$form.is_pledge_start_date_visible.html} {$form.is_pledge_start_date_visible.label}
{$form.is_pledge_start_date_editable.html} {$form.is_pledge_start_date_editable.label}
+
+ + + {/if} @@ -191,9 +215,53 @@
{include file="CRM/common/formButtons.tpl" location="bottom"}
- {literal} {/literal} {if $form.is_recur} @@ -339,6 +441,16 @@ invert = "false" } {/if} +{if $form.adjust_recur_start_date} +{include file="CRM/common/showHideByFieldValue.tpl" + trigger_field_id ="adjust_recur_start_date" + trigger_value ="true" + target_element_id ="recurDefaults" + target_element_type ="table-row" + field_type ="radio" + invert = "false" +} +{/if} {if $civiPledge} {include file="CRM/common/showHideByFieldValue.tpl" trigger_field_id = "is_pledge_active" diff --git a/tests/phpunit/CRM/Pledge/BAO/PledgeTest.php b/tests/phpunit/CRM/Pledge/BAO/PledgeTest.php index 255ba30ced..1b46347283 100644 --- a/tests/phpunit/CRM/Pledge/BAO/PledgeTest.php +++ b/tests/phpunit/CRM/Pledge/BAO/PledgeTest.php @@ -129,4 +129,32 @@ class CRM_Pledge_BAO_PledgeTest extends CiviUnitTestCase { $this->assertEquals(count($pledgeId), 1, "Pledge was retrieved"); } + /** + * Test build recur params. + */ + public function testGetPledgeStartDate() { + $startDate = json_encode(array('calendar_month' => 6)); + + $params = array( + 'pledge_start_date' => $startDate, + 'is_pledge_start_date_editable' => TRUE, + 'is_pledge_start_date_visible' => TRUE, + ); + + // Try with relative date + $date = CRM_Pledge_BAO_Pledge::getPledgeStartDate(6, $params); + $paymentDate = CRM_Pledge_BAO_Pledge::getPaymentDate(6); + + $this->assertEquals(date('m/d/Y', strtotime($date)), $paymentDate, "The two dates do not match"); + + // Try with fixed date + $params = array( + 'pledge_start_date' => json_encode(array('contribution_date' => '2016-06-10')), + 'is_pledge_start_date_visible' => FALSE, + ); + + $date = CRM_Pledge_BAO_Pledge::getPledgeStartDate($date, $params); + $this->assertEquals($date, '20160610', "The two dates do not match"); + } + } diff --git a/tests/phpunit/CiviTest/CiviSeleniumTestCase.php b/tests/phpunit/CiviTest/CiviSeleniumTestCase.php index 234b36abec..b166e1c6a1 100644 --- a/tests/phpunit/CiviTest/CiviSeleniumTestCase.php +++ b/tests/phpunit/CiviTest/CiviSeleniumTestCase.php @@ -1132,7 +1132,8 @@ class CiviSeleniumTestCase extends PHPUnit_Extensions_SeleniumTestCase { $isConfirmEnabled = TRUE, $financialType = 'Donation', $fixedAmount = TRUE, - $membershipsRequired = TRUE + $membershipsRequired = TRUE, + $isPledgeStart = FALSE ) { if (!$hash) { $hash = substr(sha1(rand()), 0, 7); @@ -1226,6 +1227,9 @@ class CiviSeleniumTestCase extends PHPUnit_Extensions_SeleniumTestCase { $this->type('initial_reminder_day', 3); $this->type('max_reminders', 2); $this->type('additional_reminder_day', 1); + if ($isPledgeStart) { + $this->webtestFillDate('pledge_start_date', '+1 month'); + } } elseif ($recurring) { $this->click('is_recur'); diff --git a/tests/phpunit/api/v3/ContributionPageTest.php b/tests/phpunit/api/v3/ContributionPageTest.php index 2783718535..b40d3b8a51 100644 --- a/tests/phpunit/api/v3/ContributionPageTest.php +++ b/tests/phpunit/api/v3/ContributionPageTest.php @@ -790,6 +790,21 @@ class api_v3_ContributionPageTest extends CiviUnitTestCase { )); } + /** + * Set up pledge block. + */ + public function setUpPledgeBlock() { + $params = array( + 'entity_table' => 'civicrm_contribution_page', + 'entity_id' => $this->_ids['contribution_page'], + 'pledge_frequency_unit' => 'week', + 'is_pledge_interval' => 0, + 'pledge_start_date' => json_encode(array('calendar_date' => date('Ymd', strtotime("+1 month")))), + ); + $pledgeBlock = CRM_Pledge_BAO_PledgeBlock::create($params); + $this->_ids['pledge_block_id'] = $pledgeBlock->id; + } + /** * The default data set does not include a complete default membership price set - not quite sure why. * @@ -918,4 +933,69 @@ class api_v3_ContributionPageTest extends CiviUnitTestCase { $this->_paymentProcessor = $this->callAPISuccess('payment_processor', 'getsingle', array('id' => $this->params['payment_processor_id'])); } + /** + * Test submit recurring pledge. + * + * - we process 1 pledge with a future start date. A recur contribution and the pledge should be created with first payment date in the future. + */ + public function testSubmitPledgePaymentPaymentProcessorRecurFuturePayment() { + $this->params['adjust_recur_start_date'] = TRUE; + $this->params['is_pay_later'] = FALSE; + $this->setUpContributionPage(); + $this->setUpPledgeBlock(); + $this->setupPaymentProcessor(); + $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); + $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success')); + + $submitParams = array( + 'id' => (int) $this->_ids['contribution_page'], + 'amount' => 100, + 'billing_first_name' => 'Billy', + 'billing_middle_name' => 'Goat', + 'billing_last_name' => 'Gruff', + 'email' => 'billy@goat.gruff', + 'payment_processor_id' => 1, + 'credit_card_number' => '4111111111111111', + 'credit_card_type' => 'Visa', + 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040), + 'cvv2' => 123, + 'pledge_frequency_interval' => 1, + 'pledge_frequency_unit' => 'week', + 'pledge_installments' => 3, + 'is_pledge' => TRUE, + 'pledge_block_id' => (int) $this->_ids['pledge_block_id'], + ); + + $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL); + + // Check if contribution created. + $contribution = $this->callAPISuccess('contribution', 'getsingle', array( + 'contribution_page_id' => $this->_ids['contribution_page'], + 'contribution_status_id' => 'Completed', // Will be pending when actual payment processor is used (dummy processor does not support future payments). + )); + + $this->assertEquals('create_first_success', $contribution['trxn_id']); + + // Check if pledge created. + $pledge = $this->callAPISuccess('pledge', 'getsingle', array()); + $this->assertEquals(date('Ymd', strtotime($pledge['pledge_start_date'])), date('Ymd', strtotime("+1 month"))); + $this->assertEquals($pledge['pledge_amount'], 300.00); + + // Check if pledge payments created. + $params = array( + 'pledge_id' => $pledge['id'], + ); + $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params); + $this->assertEquals($pledgePayment['count'], 3); + $this->assertEquals(date('Ymd', strtotime($pledgePayment['values'][1]['scheduled_date'])), date('Ymd', strtotime("+1 month"))); + $this->assertEquals($pledgePayment['values'][1]['scheduled_amount'], 100.00); + $this->assertEquals($pledgePayment['values'][1]['status_id'], 1); // Will be pending when actual payment processor is used (dummy processor does not support future payments). + + // Check contribution recur record. + $recur = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $contribution['contribution_recur_id'])); + $this->assertEquals(date('Ymd', strtotime($recur['start_date'])), date('Ymd', strtotime("+1 month"))); + $this->assertEquals($recur['amount'], 100.00); + $this->assertEquals($recur['contribution_status_id'], 5); // In progress status. + } + } diff --git a/xml/schema/Contribute/ContributionPage.xml b/xml/schema/Contribute/ContributionPage.xml index 0b33847f78..7c821d0177 100644 --- a/xml/schema/Contribute/ContributionPage.xml +++ b/xml/schema/Contribute/ContributionPage.xml @@ -147,6 +147,14 @@ if true - asks user for recurring installments 4.3 + + adjust_recur_start_date + Adjust Recurring Start Date + boolean + 0 + if true - user is able to adjust payment start date + 4.7 + is_pay_later Pay Later diff --git a/xml/schema/Pledge/PledgeBlock.xml b/xml/schema/Pledge/PledgeBlock.xml index d0282070cf..5467caed27 100644 --- a/xml/schema/Pledge/PledgeBlock.xml +++ b/xml/schema/Pledge/PledgeBlock.xml @@ -84,4 +84,30 @@ Send additional reminder this many days after last one sent, up to maximum number of reminders. 2.1 + + pledge_start_date + varchar + 64 + Pledge Start Date + The date the first scheduled pledge occurs. + 4.7 + + + is_pledge_start_date_visible + boolean + Show Recurring Donation Start Date? + 0 + true + If true - recurring start date is shown. + 4.7 + + + is_pledge_start_date_editable + boolean + Allow Edits to Recurring Donation Start Date? + 0 + true + If true - recurring start date is editable. + 4.7 + diff --git a/xml/templates/civicrm_data.tpl b/xml/templates/civicrm_data.tpl index 88aa38502f..cd1b9a3931 100644 --- a/xml/templates/civicrm_data.tpl +++ b/xml/templates/civicrm_data.tpl @@ -1129,7 +1129,7 @@ INSERT INTO `civicrm_payment_processor_type` (name, title, description, is_active, is_default, user_name_label, password_label, signature_label, subject_label, class_name, url_site_default, url_api_default, url_recur_default, url_button_default, url_site_test_default, url_api_test_default, url_recur_test_default, url_button_test_default, billing_mode, is_recur ) VALUES ('PayPal_Standard', '{ts escape="sql"}PayPal - Website Payments Standard{/ts}', NULL,1,0,'{ts escape="sql"}Merchant Account Email{/ts}',NULL,NULL,NULL,'Payment_PayPalImpl','https://www.paypal.com/',NULL,'https://www.paypal.com/',NULL,'https://www.sandbox.paypal.com/',NULL,'https://www.sandbox.paypal.com/',NULL,4,1), - ('PayPal', '{ts escape="sql"}PayPal - Website Payments Pro{/ts}', NULL,1,0,'{ts escape="sql"}User Name{/ts}','{ts escape="sql"}Password{/ts}','{ts escape="sql"}Signature{/ts}',NULL,'Payment_PayPalImpl','https://www.paypal.com/','https://api-3t.paypal.com/','https://www.paypal.com/','https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif','https://www.sandbox.paypal.com/','https://api-3t.sandbox.paypal.com/','https://www.sandbox.paypal.com/','https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',3, 1 ), + ('PayPal', '{ts escape="sql"}PayPal - Website Payments Pro{/ts}', NULL,1,0,'{ts escape="sql"}User Name{/ts}','{ts escape="sql"}Password{/ts}','{ts escape="sql"}Signature{/ts}',NULL,'Payment_PayPalImpl','https://www.paypal.com/','https://api-3t.paypal.com/','https://www.paypal.com/','https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif','https://www.sandbox.paypal.com/','https://api-3t.sandbox.paypal.com/','https://www.sandbox.paypal.com/','https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',3, 1), ('PayPal_Express', '{ts escape="sql"}PayPal - Express{/ts}', NULL,1,0,'{ts escape="sql"}User Name{/ts}','{ts escape="sql"}Password{/ts}','{ts escape="sql"}Signature{/ts}',NULL,'Payment_PayPalImpl','https://www.paypal.com/','https://api-3t.paypal.com/',NULL,'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif','https://www.sandbox.paypal.com/','https://api-3t.sandbox.paypal.com/',NULL,'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',2, 1), ('AuthNet', '{ts escape="sql"}Authorize.Net{/ts}', NULL,1,0,'{ts escape="sql"}API Login{/ts}','{ts escape="sql"}Payment Key{/ts}','{ts escape="sql"}MD5 Hash{/ts}',NULL,'Payment_AuthorizeNet','https://secure2.authorize.net/gateway/transact.dll',NULL,'https://api2.authorize.net/xml/v1/request.api',NULL,'https://test.authorize.net/gateway/transact.dll',NULL,'https://apitest.authorize.net/xml/v1/request.api',NULL,1,1), ('PayJunction', '{ts escape="sql"}PayJunction{/ts}', NULL,1,0,'User Name','Password',NULL,NULL,'Payment_PayJunction','https://payjunction.com/quick_link',NULL,NULL,NULL,'https://www.payjunctionlabs.com/quick_link',NULL,NULL,NULL,1,1), @@ -1137,8 +1137,8 @@ VALUES ('Payment_Express', '{ts escape="sql"}DPS Payment Express{/ts}', NULL,1,0,'User ID','Key','Mac Key - pxaccess only',NULL,'Payment_PaymentExpress','https://www.paymentexpress.com/pleaseenteraurl',NULL,NULL,NULL,'https://www.paymentexpress.com/pleaseenteratesturl',NULL,NULL,NULL,4,0), ('Dummy', '{ts escape="sql"}Dummy Payment Processor{/ts}',NULL,1,1,'{ts escape="sql"}User Name{/ts}',NULL,NULL,NULL,'Payment_Dummy',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,1), ('Elavon', '{ts escape="sql"}Elavon Payment Processor{/ts}','{ts escape="sql"}Elavon / Nova Virtual Merchant{/ts}',1,0,'{ts escape="sql"}SSL Merchant ID {/ts}','{ts escape="sql"}SSL User ID{/ts}','{ts escape="sql"}SSL PIN{/ts}',NULL,'Payment_Elavon','https://www.myvirtualmerchant.com/VirtualMerchant/processxml.do',NULL,NULL,NULL,'https://www.myvirtualmerchant.com/VirtualMerchant/processxml.do',NULL,NULL,NULL,1,0), - ('Realex', '{ts escape="sql"}Realex Payment{/ts}', NULL,1,0,'Merchant ID', 'Password', NULL, 'Account', 'Payment_Realex', 'https://epage.payandshop.com/epage.cgi', NULL, NULL, NULL, 'https://epage.payandshop.com/epage-remote.cgi', NULL, NULL, NULL, 1, 0 ), - ('PayflowPro', '{ts escape="sql"}PayflowPro{/ts}', NULL,1,0,'Vendor ID', 'Password', 'Partner (merchant)', 'User', 'Payment_PayflowPro', 'https://Payflowpro.paypal.com', NULL, NULL, NULL, 'https://pilot-Payflowpro.paypal.com', NULL, NULL, NULL, 1, 0 ), + ('Realex', '{ts escape="sql"}Realex Payment{/ts}', NULL,1,0,'Merchant ID', 'Password', NULL, 'Account', 'Payment_Realex', 'https://epage.payandshop.com/epage.cgi', NULL, NULL, NULL, 'https://epage.payandshop.com/epage-remote.cgi', NULL, NULL, NULL, 1, 0), + ('PayflowPro', '{ts escape="sql"}PayflowPro{/ts}', NULL,1,0,'Vendor ID', 'Password', 'Partner (merchant)', 'User', 'Payment_PayflowPro', 'https://Payflowpro.paypal.com', NULL, NULL, NULL, 'https://pilot-Payflowpro.paypal.com', NULL, NULL, NULL, 1, 0), ('FirstData', '{ts escape="sql"}FirstData (aka linkpoint){/ts}', '{ts escape="sql"}FirstData (aka linkpoint){/ts}', 1, 0, 'Store name', 'certificate path', NULL, NULL, 'Payment_FirstData', 'https://secure.linkpt.net', NULL, NULL, NULL, 'https://staging.linkpt.net', NULL, NULL, NULL, 1, NULL); -- 2.25.1