Test clocks: How we made it easier to test Stripe Billing integrations
Stripe Billing allows businesses to manage customer relationships with recurring payments, usage triggers, and other customizable features.
These are key processes for any business, and for that reason businesses need to validate that their Stripe Billing integrations behave as they expect. But integrations are often error prone due to common misconceptions about time: days always have 24 hours (not when we change the clocks twice a year), February always has 28 days (true, except for leap years), timestamps will always be in the same format (yyyy-mm-dd hh:mm:ss is the default, but it’s not universal), system clocks are always set to the right time, and so on.
Billing integrations are also difficult to test. Historically, the only way to run a test was to wait for time to pass—typically by creating test configurations with shorter subscription cycles than real production systems, or running 10-second trials to force subscriptions to cycle—and then look for any bugs that might surface in the course of normal business. Of course, doing anything that is not a perfect mirror of your production system is a shaky foundation to build on.
We sought to address these challenges with the launch of test clocks, which allow users to simulate the passage of time in Billing scenarios without waiting for actual seconds to tick by in the real world. A test clock, when associated with Customer
objects, allows users to associate a time reference with each Customer
and its associated Billing resources. When the test clock runs, the Subscription
and Invoice
objects will behave as if time has actually passed, changing states and triggering webhooks. With a test clock, users can—for example—perform the leap-year test with just a few API calls or clicks in the Dashboard.
This blog discusses the technical details of how we built test clocks in Billing, and how we updated Stripe systems to account for the different ways that time passes.
Conceptualizing a hybrid logical clock
We often think about time as we see it on a real-world clock or a calendar—seconds, minutes, days, months, and years pass by in steady succession. We say that time “flows,” much like a boat down a river. But we could also think of time as advancing through a sequence of events, each of which happens to occur at a particular timestamp. This combination of physical timestamps from real-world clocks with ordered and meaningful events from logical clocks creates a hybrid logical clock.
This hybrid logical clock is based on a concept of time that is useful for any system in a test environment, because it makes it easy to fast-forward to the most important future moments in a billing system. Instead of advancing through 30 days of normal clock time, which requires traversing all the seconds in between the current time and that future time, you can just advance your clock to the next “event”—the next monthly billing date.
Rather than flowing down the river, the boat can teleport directly to a meaningful event. As a result, the computational cost of advancing the clock is significantly reduced.
Building a hybrid logical clock
One challenge in building a hybrid logical clock is that it’s difficult to know beforehand the total set of meaningful events, and the order in which they will occur, because any event can trigger state changes.
For example, in a scenario where customers are on a monthly recurring plan, and the business suddenly decides to credit the first 10 days free, the system needs to be flexible enough to accommodate this change in state. It is equally possible that the business may want to maintain the subscription cycle, or they may want to advance the subscription cycle by 10 days—and change the time for issuing invoices.
This needs to be taken into account when figuring out the next meaningful event. So when a user advances the time of a test clock, we repeat an “advance” function under the hood:
- Given a test clock, compute the
next meaningful time
for all of the objects that use it. For example, this might be the next date toInvoice
for eachSubscription
associated with the clock. - If the
next meaningful time
is after thetarget time
, update thefrozen time
of the test clock to thetarget time
, and stop advancing. - If the
next meaningful time
is before thetarget time
, execute the actions that occur at thenext meaningful time
and update thefrozen time
of the test clock to thenext meaningful time
. Then return to step 1.
In the above diagram, the test clock starts at 19:00 (the “frozen time”) with a target time of 06:30 the next day. We start by taking action on Event 2, and set the time to 00:00—processing Event 2 also causes the new Event 2A to be scheduled for 01:30, and we process this as a normal event at 01:30. Since we haven’t reached the target time, we go through the loop again, take action on Event 3, and set the time to 03:00. In the final loop, we notice that Event 4 is past the target time, so we set the clock to 06:30, and stop advancing.
Since test clocks affect only an object’s understanding of the “current” time, they execute the same exact logic that would occur in real time for each meaningful action. Test clocks can thus be used to safely and rigorously test integrations—and we use them internally to test all new features on Billing.
Updating Stripe systems to understand test clocks
We made a no-op change to our internal logic to remove dependencies on real-world time, and instead retrieve timestamps from an abstract “time provider” backed either by a real-world clock or a test clock. This approach means that there is no semantic change in the presentation of Billing objects in the API, and it allows both developers and internal systems to continue relying on existing business logic without changes in behavior.
After we changed the basis for retrieving timestamps, we needed to ensure that time-dependent operations were not triggered by the passage of real time for objects with test clocks attached. To do that, we used a scheduling service that looks for database records meeting certain criteria, and which triggers certain events when it finds them.
Consider the example of generating an Invoice
for a Subscription
that cycles at the start of the month. When a new month begins, the Subscription
gets picked up by a part of the scheduling service that looks for Subscriptions
whose billing periods have just ended, and triggers the creation of a new Invoice
. Objects with an associated test clock, on the other hand, are explicitly filtered out from any database scans done by the asynchronous scheduling service. Instead, we give the test clock full control over orchestration and scheduling.
Using test clocks in Billing
With test clocks, you can confidently validate and deploy your business model in a much shorter time, allowing you to get to market faster. Test clocks allow you to safely and quickly validate integrations and can be used for any combination of scenarios within Billing: recurring subscriptions, trials that convert into paid subscriptions, prorations, renewal-payment failures, past-due subscriptions, timed discounts, subscription schedules, and so on.
To get started, simply create a test clock through the API and attach customers to it. (You can also work with test clocks in the Dashboard.) Any Billing object created under that customer will then be controlled by the test clock, and you can advance time to observe any effects on Billing objects. For more details, check out our documentation.
And if you’re interested in building financial infrastructure at Stripe—including products such as Billing—consider joining our engineering team.