Lessons Learned With Stripe Subscriptions

Stripe wordmark

So, you’ve got a new product or business idea and you think selling subscriptions is the way to go. After doing some research, you’ve decided that Stripe is pretty popular for handling payments, so you’re going to give it a shot.

First, that’s a good idea. Stripe is very popular for good reason. Their feature set is impressive and their API is excellent. They’ve clearly had a lot of very talented people put a lot of thought into a lot of different use cases.

Now for the bad news. Stripe having put all of that thought into different use cases is going to expose the fact that you haven’t done the same for your business yet.

We’re going to get through this though. My pain is your gain. I come with tales of pain and woe in the hope that you avoid some of the same.


One of the most important things to understand about Stripe subscriptions is that your users will never pay for them. Rather, they will pay invoices that are generated from subscriptions at some point before the payment is due. This isn’t that important of a distinction for the very straightforward use case of somebody purchasing a subscription that becomes active immediately. As we’ll see though, it’s very important in other use cases.

Stripe subscriptions involve, at minimum, the following resources

  • Subscriptions
  • Subscription items
  • Invoices
  • Invoice items

Subscriptions have many invoice items. Invoices are generated from subscriptions and contain many invoice items. An invoice can be thought of as a snapshot of the subscription at a point in time. The contents of the subscription will change over time, but past invoices will not. When an invoice is created, you will have a brief window of one hour to modify it before it gets finalized.

Luckily, the Stripe documentation is often excellent. You’ll do yourself a huge favor by carefully reading their guide to how subscriptions work multiple times. The rest of this post is going to assume that you have made yourself moderately familiar with the basics of how subscriptions work in Stripe.

That being said…

How Are You Going to Sync With Your System?

Stripe provides an extensive set of events that you can subscribe to through webhooks. Deciding what webhooks you need to care about is important. At a minimum, you need to care about the invoice.paid event. This covers the happy path of your customer choosing a subscription plan and checking out. After their card (or other payment method) is successfully charged, you will receive the invoice.paid event. You handle this event to activate their subscription in your system.

But what do you do with the rest of the information from that event? Do you toss it aside? Persist it somewhere? As with just about anything in software engineering, it depends.

Do you need to show payment history to the user? If so, do you want to query the Stripe API every time or your own database?

Do you have any reporting requirements around revenue? Is that best handled through the Stripe dashboard or your own database?

If saving information in your own database, how much? Do you need everything on the invoice and each invoice item? Just a subset?

In my case, I’ve decided to persist a subset of this information in a relational database. We have some monthly reporting requirements that are easier to manage when we can query the Stripe data alongside non-Stripe data in our database. This does introduce overhead though and I have concerns about the number of Stripe events we’ll need to subscribe to over time. Time will tell if it was the correct choice.

Do You Offer Trials?

I mentioned above that on the happy path your customer will choose a subscription plan and immediately be charged. As soon as you decide to offer trials, you’re off this happy path.

In the happy path use case, you create a subscription through the Stripe API and an invoice is immediately generated. That invoice has a payment intent associated with it. If you specifically ask for the latest invoice and it’s payment intent by using expanded responses, you can get the ID for this payment intent in the response when creating the subscription. You can then use this payment intent when the user submits their payment information.

In trials, that is no longer true. When you create the subscription, you will not get a latest invoice back with the payment intent. Stripe will initially create an invoice for $0 that it will be immediately marked as paid for the trial. The first invoice for actual payment won’t be generated until an hour before the trial expires.

I haven’t actually implemented this yet, but, from what I can tell as of writing, this means you need to collect a payment method and associate that with the customer in Stripe. When the trial expires and the next invoice needs to be paid, you can then specify that payment method.

Do You Want To Start Subscriptions on a Future Date?

Suppose trials don’t make sense for your use case. For Husmus, this is true. We sell subscriptions for insurance. A standard use case is for a tenant to buy insurance that they want to become active when they move into their new home. Trials don’t make sense here.

Solving this is actually what I’m working on now, so I don’t have a full solution yet. It involves using subscription schedules though. The documentation has a guide for starting a subscription in the future. Given how trials work, it’s safe to assume (I can’t wait to eat these words) that the same rules for invoice generation apply. You will need to collect payment information in advance and then charge the user when the subscription actually starts.

Do You Want To Remind Users of Renewal?

Thankfully, this one is a bit easier to handle. In your account billing settings, you can set the number of days in advance to send an invoice.upcoming event. You will need to handle this webhook event and remind your users in whatever way is appropriate for your app.

Stripe renewal event settings screenshot

What Taxes Do You Need to Collect?

Are you assessing sales tax or VAT? Good news! Stripe will handle that for you. Anything else though, and you’ll want to start reading up on Stripe tax rates.

Husmus currently operates in the UK, which mean means we have to deal with IPT (Insurance Premium Tax). This is a tax that applies to all insurance products sold in the UK. Stripe doesn’t attempt to handle tax rates like this itself, so we need to define the tax ourselves and make sure it is added to all insurance products that the user is subscribed to. As long as it is attached to the subscription item, then when the invoice is generated it will be attached to the corresponding invoice item and users will be charged the correct tax.

Stripe supports up to five tax rates per line item, so go nuts. Chances are Stripe can handle the tax rates in your jurisdiction.

Are You Going To Sync Up Subscription Renewal Dates?

Another issue we’re dealing with is if we should sync up subscription renewal dates. Let’s say that you purchase a subscription for one insurance product on the 3rd of the month. Then you purchase another one two weeks later on the 17th. Should these subscriptions be treated independently or not? In our case, we haven’t definitively answered this question. There is a tension between the simplicity of a single subscription vs. most insurance being sold with annual subscriptions. Should the second subscription be independent or sync up with first? Or should it be dependent of if the insurance is for the same property? Your business may have similar questions.

If you decide to sync up to existing subscriptions, then you get into proration. Luckily, this is another thing that Stripe had good support for, but it’s yet another detail you will need to handle properly.

How Do You Handle Renewal Failures?

I haven’t implemented this yet. That’s a problem for future Brian when subscriptions start to renew. However, it is something on my mind. Once again, Stripe will send you an event when this happens, invoice.payment_failed in this case. Handle the event and prod your user to update their payment method.

Of course, that opens another issue. If you’ve been handling trials or subscriptions that start in the future, chances are that you already have a way for users to enter payment methods. In this case, they probably need to update their payment method. If you haven’t care about this yet, you need to now.


Stripe is complicated because payments are complicated. I’ve only scratched the surface on use cases for Stripe subscriptions. The good news is that if you have a simple subscription product, you can probably punt on a lot of these questions. You don’t have to have firm answers to all of these questions up front, but the more you can answer, the better your Stripe experience will be for both you and your customers.

Setting Expectations With the Cone of Uncertainty

I’ve been doing a lot of planning and estimating at work the past couple weeks. The goal has been to figure out what project can get us the most bang for the buck while working around some other deadlines.

To make this happen, I’ve been going through a lot of requirements, writing a lot of stories, and doing a lot of estimating with story points. While the requirements gathering has been collaborative, the writing of stories and estimation has been a one man operation. Such is the nature of early start up life. At the end of this though, I need to provide an estimate of how long a feature will take to develop. Reaching into my bag of tricks, I’ve gone back to using the cone of uncertainty a lot.

Poorly drawn cone of uncertainty

The cone of uncertainty is a tool to provide error bars on your estimate based on where you are in the planning and implementation process. The key is to accept that your estimate is going to be wrong and to communicate an appropriate amount of uncertainty around it.

For example, during project inception the range of outcomes is very large. This makes sense because you don’t actually know a lot yet. There is probably an end business goal in mind, but nobody has started to look into the details yet. If you give a gut feel estimate of 4 weeks, the cone says that the range of uncertainty at this point is from 1-16 weeks. This range is absurd and not helpful to a product owner, but it helps communicate that even asking for an estimate at this point is unrealistic.

As you begin to gather and refine requirements, the cone narrows. The exact numbers of the curve (and even the categories) are pretty poorly defined. You’ll find varying numbers of categories and specificity, but in general you’ll find ranges like the curve above.

  • Inception – 0.25 – 4x
  • Elaboration – 0.5 – 2x
  • Construction – 0.8 – 1.25x

An Example

To make things more concrete, I just finished estimating a project that came out to 82 points. Our sustainable velocity over two week sprints is currently 30 points. We’re firmly in the elaboration part of the curve, which gives a multiplier of 0.5 – 2.0x. This gives us the following range

Lower range: 82 points * 0.5 / 30 points per sprint * 2 weeks per sprint = 2.73 weeks
Upper range: 82 points * 2 / 30 points per sprint * 2 weeks per sprint = 10.9 weeks

A range of of 2.73- 10.9 weeks is large, but I think it accurately reflects the reality of many software projects where implementation hasn’t even started yet. We’re bad at estimating and using a tool to communicate that can help.

Narrowing With Iterations Instead

In Agile Estimating and Planning, Mike Cohn suggests an alternative way to narrow your cone by using the number of completed iterations instead of stages like Inception, Elaboration, and Construction. In this model, the range of your estimate tightens with each completed sprint until you have completed 4 or more. Obviously, the usefulness of this technique can be limited if your project is too small to have that many iterations, but it can be a useful technique to fall back on for a medium sized project. I use 2 week sprints, so if the project is less then 8 weeks long, I won’t even finish 4 sprints.


Overall, the cone of uncertainty as presented here is not a formalized construct with a lot of data behind it. However, throughout my career, I have found it to be a useful tool to guide discussion and expectation with product owners. It’s an easy to understand concept that lends itself to quick explanation and provides a reasonable framework when you need an estimate for how long a project takes.

Redirects in ASP.NET Core With Both HTML and JSON endpoints

This past week I ran into the problem of retrofitting an endpoint that returns JSON into an app that up until now has returned HTML from all endpoints. It uses cookie authentication, so hitting any route requiring authentication while not logged in would redirect users to a  login page, /account/login in this case. Similarly, hitting any route where you are not authorized would redirect users to /account/forbidden. We can see below that this is done by setting LoginPath and AccessDeniedPath on CookieAuthenticationOptions.

public void ConfigureServices(IServiceCollection services) 
            options =>
                options.LoginPath = new PathString("/account/login/");
                options.AccessDeniedPath = new PathString("/account/forbidden/");

The problem with this behavior when you start mixing in JSON endpoints is that you still get redirects. The endpoint I was adding requires authentication, so unauthenticated requests were still going through the redirect. Instead of getting back a JSON response with a 401 status code, I was getting redirected to the login page and getting HTML back.

After a brief moment of panic, I figured there had to be a way to solve this. I quickly stumbled across CookieAuthenticationEvents. These let us define functions to handle the same cases we were statically defining above.

public void ConfigureServices(IServiceCollection services) 
            options => 
                options.Events = new CookieAuthenticationEvents 
                    OnRedirectToLogin = SetupRedirect("/account/login/"), 
                    OnRedirectToAccessDenied = SetupRedirect("/account/forbidden/") 

Func<RedirectContext<CookieAuthenticationOptions>, Task> SetupRedirect(string redirectPath) 
    // All requests to /api are JSON API calls and should just return a 401. 
    // All other requests are assumed to use views and the user should 
    // be redirected appropriately. 
    return redirectContext => 
        if (redirectContext.Request.Path.StartsWithSegments("/api")) 
            redirectContext.HttpContext.Response.StatusCode = 401; 
        return Task.CompletedTask; 

Here we can see that we are controlling the behavior based on a convention now. All requests to a route beginning with /api will just return a 401. Requests to any other route will continue to use the same redirect behavior as before.

This was a nice reminder that ASP.NET Core is extremely pluggable. No matter what problem you’re having, there is probably an API you can plug into to solve it.

Creating Custom Assertions With Fluent.Assertions

FluentAssertions logo

I’ve been using Fluent.Assertions for years .NET, but until this week I had never looked into the community extensions. I started adding unit tests to a new controller I was creating in an existing ASP.NET Core MVC project, so I decided to take a look at FluentAssertions.AspNetCore.Mvc.

The primary use case I was unit testing was a controller that returned a ViewResult, but with two different status codes. On the happy path it would return a 200 OK, but if the user did not have the correct role it would return an error page with 401 Unauthorized. Unfortunately, there was no built-in assertion for the status code of a ViewResult.

Luckily, with extension methods we can easily add our own! I cloned the repo for FluentAssertions.AspNetCore.Mvc locally and took a look at how ViewResultAssertions worked. For example, here’s the WithViewName assertion. It tests that the name of the view to render matches the expected view name.

public ViewResultAssertions WithViewName(
    string expectedViewName,
    string reason = "",
    params object[] reasonArgs)
    var actualViewName = ViewResultSubject.ViewName;

        .BecauseOf(reason, reasonArgs)
        .ForCondition(string.Equals(expectedViewName, actualViewName, StringComparison.OrdinalIgnoreCase))
        .FailWith(FailureMessages.CommonFailMessage, expectedViewName, actualViewName);
    return this;

Using that as an example, it was straightforward to write an extension method to test the status code on a ViewResult. The highlighted lines show how to get the actual status code and then compare it using the FluentAssertions execution API.

public static ViewResultAssertions WithStatusCode(
    this ViewResultAssertions assertions,
    HttpStatusCode expectedStatusCode,
    string reason = "",
    params object[] reasonArgs)
    var viewResultSubject = (ViewResult)assertions.Subject;
    var actualStatusCode = (HttpStatusCode)viewResultSubject.StatusCode.Value;

        .BecauseOf(reason, reasonArgs)
        .ForCondition(expectedStatusCode == actualStatusCode)
        .FailWith("Expected status code {0}, but found {1}", expectedStatusCode, actualStatusCode);
    return assertions;

Nothing about this assertion is specific to my project, so I should take the time to submit it as a PR. After writing this, I even found that other result types, like JsonResultAssertion, have almost this exact implementation to check status codes.

The most exciting part to me though was seeing how nice the FluentAssertions execution API is. I’ll definitely be keeping an eye open towards creating my own custom assertion extension methods for repeated test patterns as I continue writing tests.

Dreyfus Model

Your brain on TypeScript

I recently started reading Pragmatic Thinking & Learning: Refactor Your Wetware by Andy Hunt. Very early in the book he describes the Dreyfus model, which provides a framework to think about how people attain and master skills. I thought it would be a good exercise to think through what stage I am at in various skills, especially since I’m absorbing so many new skills all at once at my new job at Husmus.

Stage 1: Novice

Novices are very concerned about their ability to succeed; with little experience to guide them, they don’t know whether their actions will all turn out OK. Novices don’t particularly want to learn; they just want to accomplish an immediate goal. They do not know how to respond to mistakes and so are fairly vulnerable to confusion with things go awry.

Pragmatic Thinking & Learning: Refactor Your Wetware by Andy Hunt

That all sounds very familiar to me and probably does to you as well. We’ve all had to work with a tool we don’t understand at all, but something is going wrong. We don’t have time to build a mental model of how the tool works; we just want to solve our immediate problem and move on. This was me with webpack a couple of weeks ago. I had no clue what I was doing as I customized my Vue build through Vue CLI. Knowing what to change and where was pure cargo cult programming at first.

This is also going to be as we build our engineering team at Husmus. I’ve done a lot of reading on building successful teams over the years, but that is very different from applying those skills. The good news is that I do have good team communication skills, but I’m definitely going to be a novice at managing the power imbalance.

Another good example is the image at the top of this blog post. I generated that using an AI art tool that I have no understanding of and probably never will. To me, it’s just a black box that I enjoy making silly pictures with. That particular one is Your Brain on Typescript.

Stage 2: Advanced Beginner

Advanced beginners can start to break away from the fixed rule set a little bit. They can try tasks on their own, but they still have difficulty troubleshooting.

Pragmatic Thinking & Learning: Refactor Your Wetware by Andy Hunt

This is where I actually am now with webpack and Vue CLI. I know how to customize what webpack is doing under the hood through vue.config.js. I understand how to use vue inspect to see the generated webpack file and where to hook in through vue.config.js to customize as needed. There are limits to my understanding of the underlying webpack config though. Without creating my own config from scratch I probably won’t be able to get to stage 3.

Another good example is Ruby on Rails. I haven’t done deep Rails work since 2012, so my skills have atrophied back to this stage. I was probably in stage 3 back then, but definitely back to stage 2 now.

Stage 3: Competent

At the third stage, practitioners can now develop conceptual models of the problem domain and work with those models effectively. They can troubleshoot problems on their own and begin to figure out novel problems – ones they haven’t faced before. They can begin to seek out and apply advice from experts and use it effectively

Pragmatic Thinking & Learning: Refactor Your Wetware by Andy Hunt

As a jack of all trades, master of none, this stage is my bread and butter. When I need to pick up a new skill, I often level up to this stage and then plateau because I need to move on to something else. Frustratingly, I would put my Docker skills at this level. I know how to do my day-to-day tasks, but it can be a struggle when I need to dig deeper. I’m at the point where giving myself a refresher course and going much deeper is necessary for me to get to stage 4.

Stage 4: Proficient

Proficient practitioners need the big picture. They will seek out and want to understand the larger conceptual framework around this skill. They will be very frustrated by oversimplified information

Pragmatic Thinking & Learning: Refactor Your Wetware by Andy Hunt

One of the defining characteristics of proficiency is being able to reflect and feed those learnings back into your conceptual framework. This perfectly describes my .NET skills. I don’t consider myself an expert, but I have the judgement to know what will and won’t work. Sometimes I can’t explain why and rely on intuition, which I’m getting better at accepting. Being able to reflect and work backwards to discover the root of that intuition is a skill I’m constantly working on though. This is essential when advocating for certain architecture choices that feel right to me, but I can’t consciously explain why off the top of my head.

Stage 5: Expert

Experts are the primary sources of knowledge and information in any field They are the ones who continually look for better methods and ways of doing things… These are the folks who write the books, write the articles, and do the lecture circuit.

Pragmatic Thinking & Learning: Refactor Your Wetware by Andy Hunt

I don’t consider myself an expert in anything and according to the Dreyfus model, you probably shouldn’t either. Only 1 to 5 percent of a population is an expert. So, while I think I’m a proficient .NET programmer, I don’t consider myself an expert. I don’t think I’ve reached that rarified air and I’m not sure if I ever will.

One caveat to this is domain knowledge. If you’ve been working in the same domain for many years and taken a keen interest in learning its ins and outs, then there is a reasonable argument that you are an expert in that domain. As a developer though, this definitely requires learning more than necessary for your immediate job and tasks.