Skip to content
Snippets Groups Projects
Commit 44e0c376 authored by mattwire's avatar mattwire
Browse files

Add support for selecting and creating subscriptions with future start date on frontend forms

parent f4ecbb9b
Branches
Tags
1 merge request!1246.5
......@@ -412,6 +412,29 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
]
)
]);
// Add form element and js to select future recurring start date
if (!$this->isBackOffice() && !empty(\Civi::settings()->get('stripe_enable_public_future_recur_start'))
&& $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',
]
)
]);
}
}
}
/**
......@@ -661,8 +684,13 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
'default_payment_method' => $stripePaymentMethod,
'metadata' => ['Description' => $params['description']],
'expand' => ['latest_invoice.payment_intent'],
'billing_cycle_anchor' => $this->getRecurBillingCycleDay($params),
];
// This is the parameter that specifies the start date for the subscription.
// If omitted the subscription will start immediately.
$billingCycleAnchor = $this->getRecurBillingCycleDay($params);
if ($billingCycleAnchor) {
$subscriptionParams['billing_cycle_anchor'] = $billingCycleAnchor;
}
// Create the stripe subscription for the customer
$stripeSubscription = $stripeCustomer->subscriptions->create($subscriptionParams);
......@@ -694,25 +722,35 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
// Update the recurring payment
civicrm_api3('ContributionRecur', 'create', $recurParams);
// Get the paymentIntent for the latest invoice
$intent = $stripeSubscription->latest_invoice['payment_intent'];
list($params, $newParams) = $this->processPaymentIntent($params, $intent);
if ($stripeSubscription->latest_invoice) {
// Get the paymentIntent for the latest invoice
$intent = $stripeSubscription->latest_invoice['payment_intent'];
list($params, $newParams) = $this->processPaymentIntent($params, $intent);
// Set the orderID (trxn_id) to the invoice ID
// The IPN will change it to the charge_id
$this->setPaymentProcessorOrderID($stripeSubscription->latest_invoice['id']);
// Set the orderID (trxn_id) to the invoice ID
// The IPN will change it to the charge_id
$this->setPaymentProcessorOrderID($stripeSubscription->latest_invoice['id']);
}
else {
// Set the orderID (trxn_id) to the subscription ID because we don't yet have an invoice.
// The IPN will change it to the invoice_id and then the charge_id
$this->setPaymentProcessorOrderID($stripeSubscription->id);
}
return $this->endDoPayment($params, $newParams);
return $this->endDoPayment($params, $newParams ?? []);
}
/**
* Get the billing cycle day (timestamp)
* @param array $params
*
* @return int|null
*/
private function getRecurBillingCycleDay($params) {
if (empty($params['receive_date'])) {
return strtotime('now');
}
else {
if (isset($params['receive_date'])) {
return strtotime($params['receive_date']);
}
return NULL;
}
/**
......
<?php
/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
use CRM_Stripe_ExtensionUtil as E;
/**
* Class CRM_Stripe_Recur
*/
class CRM_Stripe_Recur {
/**
* Get a list of [dayOfMonth => dayOfMonth] for selecting
* allowed future recur start dates in settings
*
* @return string[]
*/
public static function getRecurStartDays() {
// 0 = Special case - now = as soon as transaction is processed
$days = [0 => 'now'];
for ($i = 1; $i <= 28; $i++) {
// Add days 1 to 28 (29-31 are excluded because don't exist for some months)
$days["$i"] = "$i";
}
return $days;
}
/**
* Get list of future start dates formatted to display to user
*
* @return array|null
*/
public static function getFutureMonthlyStartDates() {
$allowDays = \Civi::settings()->get('stripe_future_recur_start_days');
// Future date options.
$startDates = [];
// If only "now" (default) is specified don't give start date options
if (count($allowDays) === 1 && ((int) $allowDays[0] === 0)) {
return NULL;
}
$todayDay = (int) date('d');
$now = date('YmdHis');
foreach ($allowDays as $dayOfMonth) {
$dayOfMonth = (int) $dayOfMonth;
if (($dayOfMonth === 0) || ($dayOfMonth === $todayDay)) {
// Today or now
$startDates[$now] = E::ts('Now');
}
else {
// Add all days permitted in settings
// We add for this month or next month depending on todays date.
$month = ($dayOfMonth < $todayDay) ? 'next' : 'this';
$date = new DateTime();
$date->setTime(3,0,0)
->modify("first day of {$month} month")
->modify("+" . ($dayOfMonth - 1) . " days");
$startDates[$date->format('YmdHis')] = CRM_Utils_Date::customFormat($date->format('YmdHis'), \Civi::settings()->get('dateformatFull'));
}
}
ksort($startDates);
return $startDates;
}
}
......@@ -216,9 +216,13 @@ CRM.$(function($) {
});
}
/**
* Payment processor is not Stripe - cleanup
*/
function notStripe() {
debugging("New payment processor is not Stripe, clearing CRM.vars.stripe");
destroyPaymentElements();
$('.is_recur-section #stripe-recurring-start-date').remove();
delete(CRM.vars.stripe);
}
......
/* custom js for public selection of future recurring start dates
* only show option when recurring is selected
* start by removing any previously injected similar field
*/
CRM.$(function($) {
'use strict';
if ($('.is_recur-section').length) {
$('.is_recur-section #stripe-recurring-start-date').remove();
$('.is_recur-section').append($('#stripe-recurring-start-date'));
cj('input[id="is_recur"]').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();
}
function toggleRecur() {
var isRecur = $('input[id="is_recur"]:checked');
if (isRecur.val() > 0) {
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 {
$('#stripe-recurring-start-date').hide();
$("#stripe-recurring-start-date option:selected").prop("selected", false);
$("#stripe-recurring-start-date option:first").prop("selected", "selected");
}
}
});
<?php
/**
* https://civicrm.org/licensing
/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
use CRM_Stripe_ExtensionUtil as E;
......@@ -11,7 +17,6 @@ return [
'type' => 'Boolean',
'html_type' => 'checkbox',
'default' => 1,
'add' => '5.13',
'is_domain' => 1,
'is_contact' => 0,
'title' => E::ts('Allow Stripe to send a receipt for one-off payments?'),
......@@ -28,7 +33,6 @@ return [
'type' => 'Boolean',
'html_type' => 'checkbox',
'default' => 0,
'add' => '5.13',
'is_domain' => 1,
'is_contact' => 0,
'title' => E::ts('Enable Stripe Javascript debugging?'),
......@@ -45,7 +49,6 @@ return [
'type' => 'Boolean',
'html_type' => 'checkbox',
'default' => 0,
'add' => '5.19',
'is_domain' => 1,
'is_contact' => 0,
'title' => E::ts('Disable billing address fields (Experimental)'),
......@@ -57,4 +60,40 @@ return [
]
],
],
'stripe_enable_public_future_recur_start' => [
'name' => 'stripe_enable_public_future_recur_start',
'type' => 'Boolean',
'html_type' => 'checkbox',
'default' => 0,
'is_domain' => 1,
'is_contact' => 0,
'title' => E::ts('Enable public selection of future recurring start dates'),
'description' => E::ts('Allow public selection of start date for a recurring contribution'),
'html_attributes' => [],
'settings_pages' => [
'stripe' => [
'weight' => 25,
]
],
],
'stripe_future_recur_start_days' => [
'name' => 'stripe_future_recur_start_days',
'type' => 'Array',
'html_type' => 'select',
'html_attributes' => [
'size' => 29,
'multiple' => TRUE
],
'pseudoconstant' => ['callback' => 'CRM_Stripe_Recur::getRecurStartDays'],
'default' => [0],
'title' => E::ts('Restrict allowable days of the month for Recurring Contributions'),
'is_domain' => 1,
'is_contact' => 0,
'description' => E::ts('Restrict allowable days of the month'),
'settings_pages' => [
'stripe' => [
'weight' => 30,
],
],
],
];
{crmScope extensionKey='com.drastikbydesign.stripe'}
<div id="stripe-recurring-start-date">
<div class="crm-section recurring-start-date">
<div class="label">{$form.receive_date.label}</div>
<div class="content">{$form.receive_date.html}</div>
<div class="clear"></div>
</div>
</div>
{/crmScope}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment