Hootsuite Engineering
Published in

Hootsuite Engineering

Introducing Self-Serve Plan Changes for Legacy Hootsuite Members

How we built a way for accounts on legacy plans to subscribe to the latest and greatest

For most of Hootsuite’s existence, the only paid base plan was Pro. Then the package plans were released as a replacement, but Pro members retained their existing subscription. Package plans offer a more fully-featured, friendlier user experience. Revised versions of add-ons avoid confusing deprecated concepts like quantity-based tiered pricing. Recent additions to the platform, like Hootsuite Inbox, are only accessible through package plans. There are plenty of incentives for a member to upgrade from Legacy Pro—but the one task that we neglected was to create a way in-product to perform this operation.

Our Billing infrastructure has also gone through many evolutions since those Legacy Pro days. Due to Hootsuite’s monolithic origins, accounts and their billing data were all originally kept in various Dashboard databases. Now we have a separate Billing Service to store plans and subscriptions, and an Entitlements Service to provision accounts with features. Back in 2017, we started migrating legacy accounts from the Dashboard database to Billing Service, which was a pre-requisite for our latest launch: to fully unlock the modernization flow, we supercharged our plan change UI and expanded our backend billing rules.

Repurposing the Existing Plan Change Pages

Prior to our update, Pro users were not able to use the website to change their plan.
The new plans page for Pro users has buttons that redirect to the billing change page, and is more in-line with what users on other plans see.

As groundwork, our Analytics team helped compile a list of the most popular features in our modern plans to promote, which we then narrowed down to three. Those are highlighted on the confirmation page while a more exhaustive list of feature differences is appended to the sidebar on the change page.

Since the confirmation page had only been built for plan downgrades, where one would expect some feature loss (e.g. from Team3S to Professional), we needed a new design and descriptions to accurately convey the change from legacy to modern plan; the transition could cause an increase to the user’s feature set, so the original texts would be misleading. Dashboard retrieves the user’s subscribed products from Billing Service to properly configure the page and identify which text to display to the user.

Feature lists, before and after, were added to the sidebar on the change page for transparency.
The reworked confirmation copy for Legacy members removes all references to downgrading.

New React components had to be built for handling account overages. Overages refer to features, such as number of social networks, whose capacity is lower in the new plan than the amount currently being used. If a Legacy Pro user wanted to switch but had social network overages, they would not be able to until the user selected enough to discard to be within the limits of their new plan. In order to complete Pro to Packages, we added a Team Member management box identical to the Social Network selector, and brought in a search bar from Hootsuite’s component toolbox, so that users can quickly pinpoint networks and members if they have many added to their account.

Using Rules to Add Properties to Plan Changes

Before the overage page loads, Ajax calls are made from the React app to other services to retrieve the feature usage summary, and if necessary, organization members and social network lists. This data is required to populate the lists with items to select from in the management boxes.

Users with social network overages must select which networks they wish to keep before performing the plan change.

Clicking confirm POSTs the list of plan changes, including the desired base plan, legacy add-ons to be removed, and the requested billing interval (annual or monthly). Billing Service pipes the changes through the rules engine, updates records in the database, and then propagates them to the payment platform, so that the user will be prorated/charged according to their selections.

An example change set from a Pro user changing plans.

However, the rules engine first needed to be updated to allow the transition from Legacy Pro to occur immediately. The rules engine is a class that applies a list of rules to requested account updates to dictate the timing and proration policies. Proration is the act of charging or refunding the customer a partial amount of the subscription cycle. For example, a new user in a trial can downgrade their account to Free immediately, but one not in trial trying to do the same transition will have the downgrade performed at the end of their billing cycle. A rule is made of a matching predicate, scope predicate, and a set of policies to add. A predicate is simply a condition that the engine checks against the contents of the account change request.

Below is the rule enacted upon to prorate a user moving away from Pro:

ProductPredicate((from = Some(ProductCode.PRO)))
ProductPredicate((to = Some(ProductCode.FREE)))
IntervalPredicate((from = Some(Annual)), (to = Some(Monthly)))
Set[Policy](AlignmentPolicy(Immediate), ProrationPolicy(Credit))

The conjunctive predicate joins the three predicates within, so all conditions must match for the rule to apply. The rule will match changes from Legacy Pro to a paid plan, with billing intervals other than annual to monthly. The scoping tautological predicate always returns true, which means that the rule will apply to all other changes in the change set. The policy set contains the directives to apply to the change set. For example, a removal of a Legacy Pro specific add-on as a result of the transition will also be immediate and produce a refund in the form of credit.

What the rule engine does is that it folds over the list of rules, and applies the policies of matching ones to the change set, as long as that policy category has not already had a match. This way, we can prioritize rules based on order. A set of default rules are saved for the end, in case no custom rules match at all.

rules.foldLeft((Map.empty[PolicyType, Policy], Seq[Rule]())) {
case (
(map, remainingRules),
Rule(predicate, policies, scopePredicate)
if anyOperationsMatchPredicate(operations, predicate) &&
scopePredicate == TautologicalPredicate =>
reducePolicies(policies.toSeq, map) -> remainingRules
case ((map, remainingRules), rule) =>
map -> (remainingRules :+ rule)

Then the rules engine repeats the process, but with rules that are not tautologically scoped and thus only apply to some parts of the change set. These scoped rules override policies set from the base rules.

Where are we Headed?

In early July, these new additions were launched on the website, finally empowering Pro users to self-serve their transition to the more streamlined package plans. You can see in the picture below that there was an uptick in July plan changes, and this is before any large marketing efforts. Back in April, we sent an email out to our Pro users to gauge interest, which is the reason for the bump there.

From here on out, the next steps will be to update the various prompts and tooltips to inform legacy users of the reworked plans page and encourage adoption. Further down the road, we want to improve the messaging upon completion of plan changes, and clarify the banners representing queued plan changes on the account. Hootsuite is committed to providing a secure and transparent UX for all aspects of billing — stay tuned for our next batch of updates!

About the Author
Cindy Hsu is a 3rd year computer science major at UBC. This marks her 12th cumulative month as a co-op on Billing.
Say hello on LinkedIn.