Commit 8b24cbe2 authored by mattwire's avatar mattwire

Support future recur start date for memberships on frontend

parent c4006cbc
......@@ -367,8 +367,6 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
* @param \CRM_Core_Form $form
*/
public function buildForm(&$form) {
$startDateFrequencyIntervals = \Civi::settings()->get('stripe_enable_public_future_recur_start');
// Don't use \Civi::resources()->addScriptFile etc as they often don't work on AJAX loaded forms (eg. participant backend registration)
$jsVars = [
'id' => $form->_paymentProcessor['id'],
......@@ -381,16 +379,8 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
'apiVersion' => CRM_Stripe_Check::API_VERSION,
'csrfToken' => class_exists('\Civi\Firewall\Firewall') ? \Civi\Firewall\Firewall::getCSRFToken() : NULL,
'country' => CRM_Core_BAO_Country::defaultContactCountry(),
'startDateFrequencyIntervals' => $startDateFrequencyIntervals,
];
\Civi::resources()->addVars(E::SHORT_NAME, $jsVars);
// Assign to smarty so we can add via Card.tpl for drupal webform because addVars doesn't work in that context
$form->assign('stripeJSVars', $jsVars);
// Enable JS validation for forms so we only (submit) create a paymentIntent when the form has all fields validated.
$form->assign('isJsValidate', TRUE);
// Add help and javascript
CRM_Core_Region::instance('billing-block')->add(
['template' => 'CRM/Core/Payment/Stripe/Card.tpl', 'weight' => -1]);
......@@ -416,42 +406,15 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
)
]);
// We can choose which frequency_intervals to enable future recurring start date for.
// If none are enabled (or the contribution page does not have any that are enabled in Stripe settings)
// then don't load the futurerecur elements on the form.
$enableFutureRecur = FALSE;
if (!empty($form->_values['recur_frequency_unit'])) {
$formFrequencyIntervals = explode(CRM_Core_DAO::VALUE_SEPARATOR, $form->_values['recur_frequency_unit']);
$startDateFrequencyIntervals = \Civi::settings()
->get('stripe_enable_public_future_recur_start');
$enableFutureRecur = FALSE;
foreach ($formFrequencyIntervals as $interval) {
if (in_array($interval, $startDateFrequencyIntervals)) {
$enableFutureRecur = TRUE;
break;
}
}
}
// Add form element and js to select future recurring start date
if (!$this->isBackOffice() && $enableFutureRecur && $this->supportsFutureRecurStartDate()) {
$startDates = CRM_Stripe_Recur::getFutureMonthlyStartDates();
if ($startDates) {
$form->addElement('select', 'receive_date', ts('Date of first contribution'), $startDates);
CRM_Core_Region::instance('billing-block')->add([
'template' => 'CRM/Core/Payment/Stripe/BillingBlockRecurringExtra.tpl',
]);
CRM_Core_Region::instance('billing-block')->add([
'scriptUrl' => \Civi::service('asset_builder')->getUrl(
'recurStart.js',
[
'path' => \Civi::resources()
->getPath(E::LONG_NAME, 'js/recur_start.js'),
'mimetype' => 'application/javascript',
]
)
]);
}
}
// Add the future recur start date functionality
CRM_Stripe_Recur::buildFormFutureRecurStartDate($form, $this, $jsVars);
\Civi::resources()->addVars(E::SHORT_NAME, $jsVars);
// Assign to smarty so we can add via Card.tpl for drupal webform because addVars doesn't work in that context
$form->assign('stripeJSVars', $jsVars);
// Enable JS validation for forms so we only (submit) create a paymentIntent when the form has all fields validated.
$form->assign('isJsValidate', TRUE);
}
/**
......@@ -720,13 +683,13 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
// it is required by cancelSubscription (where it is called subscription_id)
'processor_id' => $this->getPaymentProcessorSubscriptionID(),
'auto_renew' => 1,
'cycle_day' => date('d'),
'next_sched_contribution_date' => $this->calculateNextScheduledDate($params),
];
$recurParams['cycle_day'] = date('d', strtotime($recurParams['next_sched_contribution_date']));
if (!empty($params['installments'])) {
// We set an end date if installments > 0
if (empty($params['start_date'])) {
$params['start_date'] = date('YmdHis');
if (empty($params['receive_date'])) {
$params['receive_date'] = date('YmdHis');
}
if ($params['installments']) {
$recurParams['end_date'] = $this->calculateEndDate($params);
......@@ -944,7 +907,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
* @throws \CRM_Core_Exception
*/
public function calculateEndDate($params) {
$requiredParams = ['start_date', 'installments', 'recurFrequencyInterval', 'recurFrequencyUnit'];
$requiredParams = ['receive_date', 'installments', 'recurFrequencyInterval', 'recurFrequencyUnit'];
foreach ($requiredParams as $required) {
if (!isset($params[$required])) {
$message = 'Stripe calculateEndDate: Missing mandatory parameter: ' . $required;
......@@ -972,7 +935,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
}
$numberOfUnits = $params['installments'] * $params['recurFrequencyInterval'];
$endDate = new DateTime($params['start_date']);
$endDate = new DateTime($params['receive_date']);
$endDate->add(new DateInterval("P{$numberOfUnits}{$frequencyUnit}"));
return $endDate->format('Ymd') . '235959';
}
......@@ -993,7 +956,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
throw new CRM_Core_Exception($message);
}
}
if (empty($params['start_date']) && empty($params['next_sched_contribution_date'])) {
if (empty($params['receive_date']) && empty($params['next_sched_contribution_date'])) {
$startDate = date('YmdHis');
}
elseif (!empty($params['next_sched_contribution_date'])) {
......@@ -1002,7 +965,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
}
}
else {
$startDate = $params['start_date'];
$startDate = $params['receive_date'];
}
switch ($params['recurFrequencyUnit']) {
......
......@@ -70,4 +70,67 @@ class CRM_Stripe_Recur {
return $startDates;
}
/**
* Build the form functionality for future recurring start date
*
* @param \CRM_Core_Form|\CRM_Contribute_Form_Contribution_Main $form
* The payment form
* @param \CRM_Core_Payment_Stripe $paymentProcessor
* The payment object
* @param array $jsVars
* (reference) Array of variables to be assigned to CRM.vars.stripe in the javascript domain
*/
public static function buildFormFutureRecurStartDate($form, $paymentProcessor, &$jsVars) {
// We can choose which frequency_intervals to enable future recurring start date for.
// If none are enabled (or the contribution page does not have any that are enabled in Stripe settings)
// then don't load the futurerecur elements on the form.
$enableFutureRecur = FALSE;
$startDateFrequencyIntervals = \Civi::settings()->get('stripe_enable_public_future_recur_start');
if (!empty($form->_values['recur_frequency_unit'])) {
$formFrequencyIntervals = explode(CRM_Core_DAO::VALUE_SEPARATOR, $form->_values['recur_frequency_unit']);
$enableFutureRecur = FALSE;
foreach ($formFrequencyIntervals as $interval) {
if (in_array($interval, $startDateFrequencyIntervals)) {
$enableFutureRecur = TRUE;
break;
}
}
}
elseif (!empty($form->_membershipBlock)) {
if (isset($form->_membershipBlock['auto_renew'])) {
foreach ($form->_membershipBlock['auto_renew'] as $membershipType => $autoRenew) {
if (!empty($autoRenew)) {
$interval = civicrm_api3('MembershipType', 'getvalue', ['id' => $membershipType, 'return' => 'duration_unit']);
if (in_array($interval, $startDateFrequencyIntervals)) {
$enableFutureRecur = TRUE;
}
break;
}
}
}
}
// Add form element and js to select future recurring start date
if ($enableFutureRecur && !$paymentProcessor->isBackOffice() && $paymentProcessor->supportsFutureRecurStartDate()) {
$jsVars['startDateFrequencyIntervals'] = $startDateFrequencyIntervals;
$startDates = CRM_Stripe_Recur::getFutureMonthlyStartDates();
if ($startDates) {
$form->addElement('select', 'receive_date', ts('Start date'), $startDates);
CRM_Core_Region::instance('billing-block')->add([
'template' => 'CRM/Core/Payment/Stripe/BillingBlockRecurringExtra.tpl',
]);
CRM_Core_Region::instance('billing-block')->add([
'scriptUrl' => \Civi::service('asset_builder')->getUrl(
'recurStart.js',
[
'path' => \Civi::resources()
->getPath(E::LONG_NAME, 'js/recur_start.js'),
'mimetype' => 'application/javascript',
]
)
]);
}
}
}
}
......@@ -19,6 +19,11 @@ Then your users will see an option to select a start date for the contribution:
![contribution page future recur start date](images/contribution_futurerecur.png)
Or if memberships are enabled on the form (example here for a single future start date on the 1st of the month):
![membership future payment start date](images/membership_futurerecursingle.png)
## Cancelling Recurring Contributions
You can cancel a recurring contribution from the Stripe Dashboard or from within CiviCRM.
......
......@@ -2,52 +2,82 @@
* only show option when recurring is selected
* start by removing any previously injected similar field
*/
CRM.$(function($) {
(function ($, ts) {
'use strict';
if ($('.is_recur-section').length) {
$('.is_recur-section #stripe-recurring-start-date').remove();
$('.is_recur-section').append($('#stripe-recurring-start-date'));
$('input[id="is_recur"]').on('change', function() {
toggleRecur();
});
$('select#frequency_unit').on('change', function() {
toggleRecur();
});
toggleRecur();
}
else {
// I'm not on the right kind of page, just remove the extra field
$('#stripe-recurring-start-date').remove();
}
var recurSection = '.is_recur-section';
var recurMode = 'recur';
if (!$(recurSection).length) {
recurSection = '#allow_auto_renew';
recurMode = 'membership';
}
if (!$(recurSection).length) {
// I'm not on the right kind of page, just remove the extra field
$('#stripe-recurring-start-date').remove();
}
function toggleRecur() {
var isRecur = $('input[id="is_recur"]:checked');
if (isRecur.val() > 0) {
if ($('select#frequency_unit').length > 0) {
var selectedFrequencyUnit = $('select#frequency_unit').val();
if (CRM.$.inArray(selectedFrequencyUnit, CRM.vars.stripe.startDateFrequencyIntervals) < 0) {
hideStartDate();
return;
}
}
if ($('#receive_date option').length === 1) {
// We only have one option. No need to offer selection - just show the date
$('#receive_date').parent('div.content').prev('div.label').hide();
$('#receive_date').next('div.description').hide();
$('#receive_date').hide();
$('#recur-start-date-description').remove();
$('#receive_date').after('<div class="description" id="recur-start-date-description">Your recurring contribution will start on' + $('#receive_date').text() + '</div>');
}
$('#stripe-recurring-start-date').show().val('');
}
else {
hideStartDate();
}
// Remove/insert the recur start date element just below the recur selections
$(recurSection + ' #stripe-recurring-start-date').remove();
$(recurSection).append($('#stripe-recurring-start-date'));
// It is hard to detect when changing memberships etc.
// So trigger on all input element changes on the form.
var billingFormID = CRM.payment.getBillingForm().id;
var debounceTimeoutId;
var inputOnChangeRunning = false;
$('#' + billingFormID + ' input').on('change', function() {
// As this runs on all input elements on the form we debounce so it runs max once every 200ms
if (inputOnChangeRunning) {
return;
}
clearTimeout(debounceTimeoutId);
inputOnChangeRunning = true;
debounceTimeoutId = setTimeout(function() { toggleRecur(); inputOnChangeRunning = false; }, 200);
toggleRecur();
});
// Trigger when we change the frequency unit selector (eg. month, year) on recur
$('select#frequency_unit').on('change', function() {
toggleRecur();
});
toggleRecur();
function toggleRecur() {
if (CRM.payment.getIsRecur()) {
if ($('select#frequency_unit').length > 0) {
var selectedFrequencyUnit = $('select#frequency_unit').val();
if (CRM.$.inArray(selectedFrequencyUnit, CRM.vars.stripe.startDateFrequencyIntervals) < 0) {
hideStartDate();
return;
}
}
if ($('#receive_date option').length === 1) {
// We only have one option. No need to offer selection - just show the date
$('#receive_date').parent('div.content').prev('div.label').hide();
$('#receive_date').next('div.description').hide();
$('#receive_date').hide();
$('#recur-start-date-description').remove();
var recurStartMessage = '';
if (recurMode === 'membership') {
recurStartMessage = ts('Your membership payment will start on %1', {1: $('#receive_date').text()});
}
else {
recurStartMessage = ts('Your recurring contribution will start on %1', {1: $('#receive_date').text()})
}
$('#receive_date').after(
'<div class="description" id="recur-start-date-description">' + recurStartMessage + '</div>'
);
}
$('#stripe-recurring-start-date').show().val('');
}
else {
hideStartDate();
}
function hideStartDate() {
$('#stripe-recurring-start-date').hide();
$("#stripe-recurring-start-date option:selected").prop("selected", false);
$("#stripe-recurring-start-date option:first").prop("selected", "selected");
}
}
}(CRM.$, CRM.ts('com.drastikbydesign.stripe')));
function hideStartDate() {
$('#stripe-recurring-start-date').hide();
$("#stripe-recurring-start-date option:selected").prop("selected", false);
$("#stripe-recurring-start-date option:first").prop("selected", "selected");
}
}
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment