Billing Platform Migrations @ Hootsuite

how we support multiple sources of billing information

Cindy Hsu
Hootsuite Engineering
4 min readAug 3, 2018

--

¹ The front-end UX is the same, but users backed by different platforms go through different backend logic.

Monetization platforms are the often overlooked key to collecting revenue from customers. These platforms are responsible for everything from storage of customer data (like billing addresses), to invoice generation and account balance tracking. At Hootsuite we use managed third-party services which can be costly, and sometimes impact our uptime because of the extra external dependency. Our desire for better predictability has spurred the adoption of a self-hosted solution, and a lengthy migration from our existing billing platform.

We began by re-tooling the account creation flow¹, which was the simplest option for getting users onto the new platform, and also carried the least risk. Groundwork for the new integration had already been built for an investigative exercise prior, so progress was only lacking on the front-end.

The choice of which billing form to load is managed by feature flags in Consul, a key-value store. In a nutshell, the flags control when and where we acquire users on the new platform. Eligibility is determined by geolocation because additional currencies and features, such as taxes, may need to be supported beforehand.

² It is a convention to associate feature names with JIRA tickets (e.g. BILLING-123), so other developers have context accessible to them.

After the PHP web-server (aka the Dashboard) determines which billing platform to use², it adds the data to the view layer, for access from JavaScript, and also to a configuration transfer object that is passed down to the page’s React container as a prop. The component funnels the config down to its children — including the billing form. As a dumb component, its sole responsibility is rendering the correct form based on what the backend determined.

Developers use feature flags to switch between new and old billing flows, and to guard any changes made to them. Reverting modifications may consist of simply turning that flag off, bypassing the build pipeline and providing a safety net for our continuous integration. Additionally, having fine-grained controls allows billing smoke tests to cover the full gamut of platform-dependent flows. Generally, a feature is first launched on browser cookie mode before being completely enabled, so that quality assurance can be performed on the live environment without impacting customers. This is a necessary step because our test environments are linked to sandbox environments which are sometimes limited in their accuracy.

Meanwhile, the architectural distinction between the Dashboard, Billing Service, and billing platforms allows us to simultaneously offer support for both platforms from the backend. The Billing Service is written in Scala, and while it indexes all product plan variants, its other primary focus is linking a user’s Dashboard account to their billing platform account. It accomplishes this by storing their member id (the identifier used in the Dashboard), billing platform type, and corresponding platform account id.

³ How our billing service dynamically reroutes based on the user’s platform type.

To ease the addition of platform types, a CompositePlatform class is injected into the Billing Service’s internal services. It extends the trait, Platform, and relays the call to another Platform implementation corresponding to the user’s platform type. This would be SpecificPlatform in Figure 3. Billing Service can thereby handle requests dynamically, while consumers like the Dashboard can use the API without any knowledge of implementation details. Unfortunately, some billing areas have yet to emigrate from the Dashboard and still require knowledge of the user’s platform type. Events originating from platforms, such as overdue state changes, are all tagged with the platform type that generated the event, allowing the listener inside Billing Service to consume those in multiple ways as well.

Migrating existing accounts off of the old platform is a much more demanding problem. We built and honed an endpoint that scraped each bit of billing data from the old platform and added it to the newly created account in the other platform. Sometimes this required developing new features because 1-to-1 mappings did not exist between platforms. To perform migrations, users were selected through SQL queries and we fed them to our endpoint, writing results to log files and grouping any failed ones by their error code. This was effective for debugging, but lacked efficiency because it was a synchronous process. Gearman, a PHP job server, allowed for parallelization: one migration job was made per user⁴, and Gearman distributed jobs to workers. Results would instead be logged to Sumologic for analysis.

⁴ Parsing data from migration logs to make visualizations in Sumologic for monitoring.

Hootsuite users deserve a billing experience with greater reliability and consistency — and that is the goal of the shift — but there are internal benefits as well. Self-hosting empowers the business to enhance the platform to adapt to the ever-changing technological landscape, and completing the migration unlocks the possibility of purging legacy billing flows tied to the old platform, a critical step in the overarching journey to decompose our Dashboard monolith. These internal benefits are what will enable us to deliver that improved experience.

About the Author

Cindy Hsu is a Computer Science Major at UBC. Fountain pen addict, video game enthusiast, and internet pop culture buff.
Connect with her on LinkedIn.

--

--

Cindy Hsu
Hootsuite Engineering

Professional sprint namer and full-stack software developer co-op on Hootsuite's Billing Team.