Commit c2c0014e authored by Jay Swain's avatar Jay Swain

Expired subscriptions have a grace period

Provide users who have expired subscriptions a grace period
to renew their subscriptions before we begin downgrading them.

part of: https://gitlab.com/gitlab-org/growth/product/-/issues/1513
parent 42f861cc
......@@ -27,6 +27,8 @@ class SubscriptionPresenter < Gitlab::View::Presenter::Delegated
def remaining_days
return unless end_date
return 0 if expired?
(end_date - Date.today).to_i
end
......
---
title: Introduce new messaging to include a 30 day grace period for subscription expiration
merge_request: 35897
author:
type: changed
......@@ -2,6 +2,9 @@
module Gitlab
class ExpiringSubscriptionMessage
GRACE_PERIOD_EXTENSION_DAYS = 30.days
include Gitlab::Utils::StrongMemoize
include ActionView::Helpers::TextHelper
attr_reader :subscribable, :signed_in, :is_admin, :namespace
......@@ -26,7 +29,7 @@ module Gitlab
private
def license_message_subject
message = subscribable.expired? ? expired_subject : expiring_subject
message = expired_but_within_cutoff? ? expired_subject : expiring_subject
message = content_tag(:strong, message)
......@@ -46,19 +49,17 @@ module Gitlab
end
def expiring_subject
remaining_days = pluralize(subscribable.remaining_days, 'day')
if auto_renew?
_('Your subscription will automatically renew in %{remaining_days}.') % { remaining_days: remaining_days }
_('Your subscription will automatically renew in %{remaining_days}.') % { remaining_days: remaining_days_formatted }
else
_('Your subscription will expire in %{remaining_days}.') % { remaining_days: remaining_days }
_('Your subscription will expire in %{remaining_days}.') % { remaining_days: remaining_days_formatted }
end
end
def expiration_blocking_message
return '' unless subscribable.will_block_changes?
message = subscribable.expired? ? expired_message : expiring_message
message = expired_but_within_cutoff? ? expired_message : expiring_message
content_tag(:p, message.html_safe)
end
......@@ -66,9 +67,7 @@ module Gitlab
def expired_message
return block_changes_message if subscribable.block_changes?
remaining_days = pluralize((subscribable.block_changes_at - Date.today).to_i, 'day')
_('No worries, you can still use all the %{strong}%{plan_name}%{strong_close} features for now. You have %{remaining_days} to renew your subscription.') % { plan_name: plan_name, remaining_days: remaining_days, strong: strong, strong_close: strong_close }
_('No worries, you can still use all the %{strong}%{plan_name}%{strong_close} features for now. You have %{remaining_days} to renew your subscription.') % { plan_name: plan_name, remaining_days: remaining_days_formatted, strong: strong, strong_close: strong_close }
end
def block_changes_message
......@@ -81,7 +80,7 @@ module Gitlab
if auto_renew?
support_link = '<a href="mailto:support@gitlab.com">support@gitlab.com</a>'.html_safe
_('We tried to automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{expires_on} but something went wrong so your subscription was downgraded to the free plan. Don\'t worry, your data is safe. We suggest you check your payment method and get in touch with our support team (%{support_link}). They\'ll gladly help with your subscription renewal.') % { plan_name: plan_name, strong: strong, strong_close: strong_close, namespace_name: namespace.name, support_link: support_link, expires_on: subscribable.expires_at.strftime("%Y-%m-%d") }
_('We tried to automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{expires_on} but something went wrong so your subscription was downgraded to the free plan. Don\'t worry, your data is safe. We suggest you check your payment method and get in touch with our support team (%{support_link}). They\'ll gladly help with your subscription renewal.') % { plan_name: plan_name, strong: strong, strong_close: strong_close, namespace_name: namespace.name, support_link: support_link, expires_on: expires_at_or_cutoff_at.strftime("%Y-%m-%d") }
else
_('You didn\'t renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} so it was downgraded to the free plan.') % { plan_name: plan_name, strong: strong, strong_close: strong_close, namespace_name: namespace.name }
end
......@@ -90,16 +89,16 @@ module Gitlab
def expiring_message
return namespace_expiring_message if namespace
_('Your %{strong}%{plan_name}%{strong_close} subscription will expire on %{strong}%{expires_on}%{strong_close}. After that, you will not to be able to create issues or merge requests as well as many other features.') % { expires_on: subscribable.expires_at.strftime("%Y-%m-%d"), plan_name: plan_name, strong: strong, strong_close: strong_close }
_('Your %{strong}%{plan_name}%{strong_close} subscription will expire on %{strong}%{expires_on}%{strong_close}. After that, you will not to be able to create issues or merge requests as well as many other features.') % { expires_on: expires_at_or_cutoff_at.strftime("%Y-%m-%d"), plan_name: plan_name, strong: strong, strong_close: strong_close }
end
def namespace_expiring_message
if auto_renew?
_('We will automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{strong}%{expires_on}%{strong_close}. There\'s nothing that you need to do, we\'ll let you know when the renewal is complete. Need more seats, a higher plan or just want to review your payment method?') % { expires_on: subscribable.expires_at.strftime("%Y-%m-%d"), plan_name: plan_name, strong: strong, strong_close: strong_close, namespace_name: namespace.name }
_('We will automatically renew your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} on %{strong}%{expires_on}%{strong_close}. There\'s nothing that you need to do, we\'ll let you know when the renewal is complete. Need more seats, a higher plan or just want to review your payment method?') % { expires_on: expires_at_or_cutoff_at.strftime("%Y-%m-%d"), plan_name: plan_name, strong: strong, strong_close: strong_close, namespace_name: namespace.name }
else
message = []
message << _('Your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} will expire on %{strong}%{expires_on}%{strong_close}.') % { expires_on: subscribable.expires_at.strftime("%Y-%m-%d"), plan_name: plan_name, strong: strong, strong_close: strong_close, namespace_name: namespace.name }
message << _('Your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} will expire on %{strong}%{expires_on}%{strong_close}.') % { expires_on: expires_at_or_cutoff_at.strftime("%Y-%m-%d"), plan_name: plan_name, strong: strong, strong_close: strong_close, namespace_name: namespace.name }
message << expiring_features_message
......@@ -135,10 +134,9 @@ module Gitlab
end
def expired_subscribable_within_notification_window?
return true unless subscribable.expired?
return true unless expired_but_within_cutoff?
expired_at = subscribable.expires_at
(expired_at..(expired_at + 30.days)).cover?(Date.today)
(expires_at_or_cutoff_at + GRACE_PERIOD_EXTENSION_DAYS) > Date.today
end
def plan_name
......@@ -156,5 +154,44 @@ module Gitlab
def auto_renew?
subscribable.auto_renew?
end
def grace_period_effective_from
Date.parse('2020-07-22')
end
def self_managed?
subscribable.is_a?(::License)
end
def expires_at_or_cutoff_at
strong_memoize(:expires_at_or_cutoff_at) do
# self-managed licenses are unconcerned of our announcement.
if self_managed?
subscribable.expires_at
else
cutoff_at = grace_period_effective_from + GRACE_PERIOD_EXTENSION_DAYS
[subscribable.expires_at, cutoff_at].max
end
end
end
def expired_but_within_cutoff?
strong_memoize(:expired) do
subscribable.expired? && expires_at_or_cutoff_at < Date.today
end
end
def remaining_days_formatted
strong_memoize(:remaining_days_formatted) do
days = if expired_but_within_cutoff?
(subscribable.block_changes_at - Date.today).to_i
else
(expires_at_or_cutoff_at - Date.today).to_i
end
pluralize(days, 'day')
end
end
end
end
......@@ -8,6 +8,7 @@ RSpec.describe "Admin views license" do
before do
stub_feature_flags(licenses_app: false)
sign_in(admin)
allow_any_instance_of(Gitlab::ExpiringSubscriptionMessage).to receive(:grace_period_effective_from).and_return(Date.today - 45.days)
end
context "when license is valid" do
......
......@@ -18,10 +18,15 @@ RSpec.describe Gitlab::ExpiringSubscriptionMessage do
namespace: namespace
).message
end
let(:grace_period_effective_from) { expired_date - 35.days }
let(:today) { Time.utc(2020, 3, 7, 10) }
let(:expired_date) { Time.utc(2020, 3, 9, 10).to_date }
before do
allow_any_instance_of(Gitlab::ExpiringSubscriptionMessage).to receive(:grace_period_effective_from).and_return(grace_period_effective_from)
end
context 'subscribable installed' do
let(:expired_date) { Time.utc(2020, 3, 9, 10) }
let(:today) { Time.utc(2020, 3, 7, 10) }
let(:auto_renew) { false }
before do
......@@ -123,6 +128,7 @@ RSpec.describe Gitlab::ExpiringSubscriptionMessage do
it 'has an expiration blocking message' do
allow(subscribable).to receive(:block_changes_at).and_return(Time.utc(2020, 3, 9, 10).to_date)
allow(subscribable).to receive(:is_a?).with(::License).and_return(true)
Timecop.freeze(today) do
expect(subject).to include('No worries, you can still use all the Ultimate features for now. You have 2 days to renew your subscription.')
......@@ -135,14 +141,13 @@ RSpec.describe Gitlab::ExpiringSubscriptionMessage do
context 'subscribable is expiring soon' do
before do
allow(subscribable).to receive(:expired?).and_return(false)
allow(subscribable).to receive(:remaining_days).and_return(4)
allow(subscribable).to receive(:will_block_changes?).and_return(true)
allow(subscribable).to receive(:block_changes_at).and_return(expired_date)
end
it 'has a nice subject' do
Timecop.freeze(today) do
expect(subject).to include('Your subscription will expire in 4 days')
expect(subject).to include('Your subscription will expire in 2 days')
end
end
......@@ -193,7 +198,9 @@ RSpec.describe Gitlab::ExpiringSubscriptionMessage do
let(:auto_renew) { true }
it 'has a nice subject' do
expect(subject).to include('Your subscription will automatically renew in 4 days.')
Timecop.freeze(today) do
expect(subject).to include('Your subscription will automatically renew in 2 days.')
end
end
it 'has an expiration blocking message' do
......@@ -204,6 +211,37 @@ RSpec.describe Gitlab::ExpiringSubscriptionMessage do
end
end
end
context 'subscribable expired a long time ago' do
let(:expired_date) { today.to_date - 1.year }
let(:grace_period_effective_from) { today.to_date - 25.days }
before do
allow(subscribable).to receive(:expires_at).and_return(expired_date)
allow(subscribable).to receive(:block_changes_at).and_return(expired_date)
allow(subscribable).to receive(:expired?).and_return(true)
allow(subscribable).to receive(:will_block_changes?).and_return(true)
allow(subscribable).to receive(:block_changes?).and_return(true)
end
context 'and is past the cutoff date' do
let(:grace_period_effective_from) { today.to_date - 40.days }
it 'has a nice subject' do
Timecop.freeze(today) do
expect(subject).to include('Your subscription has been downgraded')
end
end
end
context 'and not past the cutoff date' do
it 'has a nice subject' do
Timecop.freeze(today) do
expect(subject).to include('Your subscription will expire in 5 days')
end
end
end
end
end
end
end
......
......@@ -88,5 +88,13 @@ RSpec.describe SubscriptionPresenter do
expect(subject).to eq(2)
end
end
it 'is 0 if expired' do
allow(subscription).to receive(:end_date).and_return(Time.utc(2020, 3, 1, 10).to_date)
Timecop.freeze(today) do
expect(subject).to eq(0)
end
end
end
end
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