Let users upgrade to group plans via subscription dashboard (#4704)

Users on an individual plan don't have a way to upgrade to a group
subscription without contacting support. As a temporary measure, we're
adding a way to do this by re-using the existing group plan modal from
the plans pages, to allow users to configure and upgrade to a group plan
directly.

This is currently only available for USD, EUR, and GBP - since although
we now support other currencies in Recurly, the group plans modal does
not yet support them. The user however can not change currency here,
their group subscription will be in the same currency as their current
individual subscription.

The group plan modal has been duplicated rather than extended, to keep
this code seperate as it is potentially only a stopgap measure - and we
don't want to be untangling the additional logic from the existing
modal/template later down the line.

GitOrigin-RevId: f310eb10ef00d43076981589ee45893e7d9ab881
This commit is contained in:
Thomas 2021-09-09 16:05:08 +02:00 committed by Copybot
parent c6da905e5b
commit 5f550b0a11
6 changed files with 189 additions and 1 deletions

View file

@ -121,6 +121,7 @@ async function userSubscriptionPage(req, res) {
const data = {
title: 'your_subscription',
plans,
groupPlans: GroupPlansData,
user,
hasSubscription,
fromPlansPage,

View file

@ -0,0 +1,59 @@
script(type="text/ng-template", id="groupPlanModalUpgradeTemplate")
.modal-header
h3 Save 30% or more with a group license
.modal-body.plans
.container-fluid
.row
.col-md-6.text-center
.circle.circle-lg
| {{ displayPrice }}
span.small / year
br
span.circle-subtext For {{ selected.size }} users
ul.list-unstyled
li Each user will have access to:
li  
li(ng-if="selected.plan_code == 'collaborator'")
strong #{translate("collabs_per_proj", {collabcount:10})}
li(ng-if="selected.plan_code == 'professional'")
strong #{translate("unlimited_collabs")}
+features_premium
.col-md-6
form.form
.form-group
label(for='plan_code')
| Plan
select.form-control(id="plan_code", ng-model="selected.plan_code")
option(ng-repeat="plan_code in options.plan_codes", value="{{plan_code.code}}") {{ plan_code.display }}
.form-group
label(for='size')
| Number of users
select.form-control(id="size", ng-model="selected.size")
option(ng-repeat="size in options.sizes", value="{{size}}") {{ size }}
.form-group
label(for='currency')
| Currency
select.form-control(disabled id="currency", ng-model="selected.currency")
option(ng-repeat="currency in options.currencies", value="{{currency.code}}") {{ currency.display }}
.form-group
label(for='usage')
| Usage
select.form-control(id="usage", ng-model="selected.usage")
option(ng-repeat="usage in options.usages", value="{{usage.code}}") {{ usage.display }}
p.small.text-center.row-spaced-small(ng-show="selected.usage == 'educational'")
| The 40% educational discount can be used by students or faculty using Overleaf for teaching
p.small.text-center.row-spaced-small(ng-show="selected.usage == 'enterprise'")
| Save an additional 40% on groups of 10 or more with our educational discount
.modal-footer
.text-center
p
strong Your new subscription will be billed immediately to your current payment method.
hr.thin
button.btn.btn-primary.btn-lg(ng-disabled='inflight' ng-click="upgrade()") Upgrade Now
hr.thin
a(
href
ng-controller="ContactGeneralModal"
ng-click="openModal()"
) Need more than 50 licenses? Please get in touch

View file

@ -12,6 +12,7 @@ block append meta
meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)
meta(name="ol-subscription" data-type="json" content=personalSubscription)
meta(name="ol-recomendedCurrency" content=personalSubscription.recurly.currency)
meta(name="ol-groupPlans" data-type="json" content=groupPlans)
block content
main.content.content-alt#main-content(ng-cloak)

View file

@ -21,6 +21,8 @@ div(ng-controller="RecurlySubscriptionController")
a(href, ng-click="switchToChangePlanView()", ng-if="showChangePlanButton") !{translate("change_plan")}.
if (personalSubscription.pendingPlan)
p #{translate("want_change_to_apply_before_plan_end")}
else if (personalSubscription.plan.groupPlan)
p #{translate("contact_support_to_change_group_subscription")}
if (personalSubscription.recurly.trialEndsAtFormatted && personalSubscription.recurly.trial_ends_at > Date.now())
p You're on a free trial which ends on <strong ng-non-bindable>#{personalSubscription.recurly.trialEndsAtFormatted}</strong>
p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount: personalSubscription.recurly.price, collectionDate: personalSubscription.recurly.nextPaymentDueAt}, ['strong', 'strong'])}
@ -64,6 +66,19 @@ div(ng-controller="RecurlySubscriptionController")
+printPlans(plans.individualMonthlyPlans)
+printPlans(plans.individualAnnualPlans)
div(ng-controller="ChangePlanToGroupFormController")
h2 #{translate('looking_multiple_licenses')}
div(ng-show="isValidCurrencyForUpgrade")
span #{translate('reduce_costs_group_licenses')}
br
br
a.btn.btn-success(
href="#groups"
ng-click="openGroupPlanModal()"
) #{translate('change_to_group_plan')}
div(ng-hide="isValidCurrencyForUpgrade")
span #{translate('contact_support_to_upgrade_to_group_subscription')}
.div(ng-controller="RecurlyCancellationController", ng-show="showCancellation").text-center
p
@ -129,4 +144,7 @@ script(type='text/ng-template', id='cancelPendingPlanChangeModalTemplate')
ng-click="confirmCancelPendingPlanChange()"
)
span(ng-hide="inflight") #{translate("revert_pending_plan_change")}
span(ng-show="inflight") #{translate("processing")}…
span(ng-show="inflight") #{translate("processing")}…
include ../_plans_page_mixins
include ../_modal_group_upgrade

View file

@ -91,6 +91,112 @@ App.factory('RecurlyPricing', function ($q, MultiCurrencyPricing) {
}
})
App.controller('ChangePlanToGroupFormController', function ($scope, $modal) {
if (!ensureRecurlyIsSetup()) return
const subscription = getMeta('ol-subscription')
const currency = subscription.recurly.currency
if (['USD', 'GBP', 'EUR'].includes(currency)) {
$scope.isValidCurrencyForUpgrade = true
}
$scope.openGroupPlanModal = function () {
const planCode = subscription.plan.planCode
$scope.defaultGroupPlan = planCode.includes('professional')
? 'professional'
: 'collaborator'
$scope.currentPlanCurrency = currency
$modal.open({
templateUrl: 'groupPlanModalUpgradeTemplate',
controller: 'GroupPlansModalUpgradeController',
scope: $scope,
})
}
})
App.controller(
'GroupPlansModalUpgradeController',
function ($scope, $modal, $location, $http) {
$scope.options = {
plan_codes: [
{
display: 'Collaborator',
code: 'collaborator',
},
{
display: 'Professional',
code: 'professional',
},
],
currencies: [
{
display: 'USD ($)',
code: 'USD',
},
{
display: 'GBP (£)',
code: 'GBP',
},
{
display: 'EUR (€)',
code: 'EUR',
},
],
currencySymbols: {
USD: '$',
EUR: '€',
GBP: '£',
},
sizes: [2, 3, 4, 5, 10, 20, 50],
usages: [
{
display: 'Enterprise',
code: 'enterprise',
},
{
display: 'Educational',
code: 'educational',
},
],
}
$scope.prices = getMeta('ol-groupPlans')
const currency = $scope.currentPlanCurrency
// default selected
$scope.selected = {
plan_code: $scope.defaultGroupPlan || 'collaborator',
currency,
size: '10',
usage: 'enterprise',
}
$scope.recalculatePrice = function () {
const { usage, plan_code, currency, size } = $scope.selected
const price = $scope.prices[usage][plan_code][currency][size]
const currencySymbol = $scope.options.currencySymbols[currency]
$scope.displayPrice = `${currencySymbol}${price}`
}
$scope.$watch('selected', $scope.recalculatePrice, true)
$scope.recalculatePrice()
$scope.upgrade = function () {
const { plan_code, size, usage } = $scope.selected
const body = {
_csrf: window.csrfToken,
plan_code: `group_${plan_code}_${size}_${usage}`,
}
$scope.inflight = true
$http
.post(`/user/subscription/update`, body)
.then(() => location.reload())
}
}
)
App.controller(
'ChangePlanFormController',
function ($scope, $modal, RecurlyPricing) {

View file

@ -1045,7 +1045,10 @@
"currently_subscribed_to_plan": "You are currently subscribed to the <0>__planName__</0> plan.",
"your_plan_is_changing_at_term_end": "Your plan is changing to <0>__pendingPlanName__</0> at the end of the current billing period.",
"want_change_to_apply_before_plan_end": "If you wish this change to apply before the end of your current billing period, please contact us.",
"contact_support_to_change_group_subscription": "Please contact support if you wish to change your group subscription.",
"contact_support_to_upgrade_to_group_subscription": "Please contact support if you wish to be upgraded to a group subscription.",
"change_plan": "Change plan",
"change_to_group_plan": "Change to a group plan",
"next_payment_of_x_collectected_on_y": "The next payment of <0>__paymentAmmount__</0> will be collected on <1>__collectionDate__</1>.",
"additional_licenses": "Your subscription includes <0>__additionalLicenses__</0> additional license(s) for a total of <1>__totalLicenses__</1> licenses.",
"pending_additional_licenses": "Your subscription is changing to include <0>__pendingAdditionalLicenses__</0> additional license(s) for a total of <1>__pendingTotalLicenses__</1> licenses.",