Tier is "Terraform for Stripe" but it goes further and gives you feature flag style access checks, and allows you to count/report usage which can be used for metered billing.
When we started Tier, we knew that there was something interesting in the SaaS pricing and packaging space. Adjusting price is the single most effective lever a business can use to achieve product/market fit, and there's a strong correlation price nimbleness and market success.
In spite of overwhelming evidence of this, most startups pick the price for their product once and then never change it, opting instead to invest in less effective levers like CAC, sales efficiency, "virality", churn, etc. Why?
It's just too hard. Any change you make to the pricing model means refactoring not just the entire product, but sometimes the entire company. The path of least resistance leads to a place where there's no single source of truth, and changes anywhere require changes everywhere.After over 50 or so customer conversations and user research chats, this represents our third or fourth implementation (depending on how you count them), and our conception of how best to solve it has been refined and adjusted along the way.
The concept of "PriceOps" came out of those conversations, looking at where mature companies end up after several expensive rounds of iterating on how they implement their prices for flexibility and order. https://priceops.org
What we're releasing now is an open source tool you can use to set up your Stripe system that keeps everything organized around a single source of truth. With this, changes to your pricing model don't require changes to your application code or business processes.
As a bonus, I think it's actually easier to integrate with than integrating with Stripe the "normal" way. Use the identifiers for your customers and features that you already have. Define plans and subscribe customers to them. No ever-growing pile of object ids to manage.
If you are just starting to think about adding pricing to your product, or if you've built something custom but would like something less maintenance intensive, then please give Tier a try and we'd love your feedback.
Because the recipes still look like they're targeting a very technical user, which was my mental model for why one would build a whole new DSL
Tier does set up and manage your stripe account based on your pricing.json config, but we go further than that with metering, feature flag style entitlement checks, and other things we plan to provide that you couldn't do as just a provider.
Now, I don't really know the Stripe API besides a quick skim a few years ago, but I think that it's a fully reasonable comment and expectation that when someone says "Terraform for random API" one expects a terraform provider for that.
1. the generated HCL contains plain text passwords if you use any sort of secrets access inside of cdktf
2. because you can use any programming language for cdktf, every project will look different. But then again terragrunt and terraform also have a bunch of differences.
But I guess everything is a provider depending on how you look at it.
Even after Stripe has been set up, you then get our SDK (node available, Go shortly) which gives you things like .subscribe(), .report(), .whois() (gets stripe ids) and .limits() that you can use to tool your app. The benefit is that when you change your plans or pricing, you don't need to refactor your front end. These are the benefits of the pillars on https://priceops.org
The CLI also acts as a sidecar in your network to do things like rollups and it manages Stripe pretty gracefully (Stripe can often have rate limits or requirements that aren't always clear). It also handles metering for you via the above referenced methods.
Someone made another comment on this thread that outlines some of the benefits vs Stripe raw as well. https://www.hackerneue.com/item?id=33431567
With our current architecture (a very simple one) a simple thing such as iterating on the price of our product requires changing an env var on Vercel. Not complicated, but feels cumbersome for changing a price. Iterating this on a pricing.json file feels easier.
Things get more complex if we decide to do something like giving the user the possibility of choosing between a monthly or a yearly subscription (with a variable quantity, because we charge per seat). That would imply a code refactor.
Would tier.run help us with that????
Really excited about feature flags as well. How would this work exactly? Do I set up which features belong to every plan on the pricing.json file, and then consume your SDK on the frontend to only show the tier to a certain category of paying user?
We currently handle that by querying a SQL table of paying users, and if the email address of the authed user belongs to the table, we show the feature, else not.
I also saw you want to build your product as an Auth0 action. Would that be used for this use case?
Anways, congrats on the launch!!
> Iterating this on a pricing.json file feels easier.
Agreed. :)
> if we decide to do something like giving the user the possibility of choosing between a monthly or a yearly subscription (with a variable quantity, because we charge per seat). That would imply a code refactor. Would tier.run help us with that????
Currently, plans that are similar but with different billing intervals can best be expressed as two plans like: `plan:pro:annual@0` and `plan:pro:monthly@0`. We're very open to feedback on this approach though.
> Do I set up which features belong to every plan on the pricing.json file, and then consume your SDK on the frontend to only show the tier to a certain category of paying user?
This is correct, limits (if any) are defined in the pricing.json using tiers. These limits may be reported to a browser session via a backend that proxies the limits from the sidecar back out.
> I also saw you want to build your product as an Auth0 action. Would that be used for this use case?
It seems possible. How do you envision the Auth0 action behaving with Tier?
I think this makes sense because switching between them can be done very easily, just change the string from `plan:pro:annual@0` to `plan:pro:monthly@0`. As long as I can write some client side code that allows me to change if the end user is getting access to `plan:pro:annual@0` or `plan:pro:monthly@0` based on what they select on a UI.
> It seems possible. How do you envision the Auth0 action behaving with Tier?
Sorry, I'm not familiar with Auth0 actions. It was just an open-ended question because I saw someone proposed this on your Slack group.
Two things that come to mind:
- What are your plans like regarding the integration of entitlement checking in the service and SDK? As far as I can tell, that's still a missing piece? I really love how oso[0] has managed to separate the authorization checking from the authorization definition via their DSL and SDKs. Ever since I saw that, I was wondering if a very similar system could work for entitlement checking.
- I think where your system could provide a lot of benefit would be in help tracking usage metrics that can be harder to calculate (and with that more of a pain to use). It's already nice not having to implement a simpler "number of action N taken the last billing period" metric. However something like a "N number of active users last billing period", where you now would also have to keep track of which users you have already seen before to prevent double-counting them becomes increasingly annoying to implement.
[0]: https://www.osohq.com/
Yes, exactly. Especially when those same blog posts tell you to plan to basically have your whole team focus on it for 3 months, as if you have nothing else to do!
> - What are your plans like regarding the integration of entitlement checking in the service and SDK? As far as I can tell, that's still a missing piece? I really love how oso[0] has managed to separate the authorization checking from the authorization definition via their DSL and SDKs. Ever since I saw that, I was wondering if a very similar system could work for entitlement checking.
The SDK does expose a `tier.limit(org, feature)` method[1]. This is reasonably fast, but if it's uncached, it is an API call to Stripe, so it can be beneficial to have that drive a proper feature-flagging system like Launch Darkly, or whatever else you use to manage authorization and feature availability.
> - I think where your system could provide a lot of benefit would be in help tracking usage metrics that can be harder to calculate (and with that more of a pain to use). It's already nice not having to implement a simpler "number of action N taken the last billing period" metric. However something like a "N number of active users last billing period", where you now would also have to keep track of which users you have already seen before to prevent double-counting them becomes increasingly annoying to implement.
For seats you could use an `"aggregate": "perpetual"` setting on the feature. See: https://www.tier.run/docs/recipes/#simple-per-seat-pricing (that page also has some other examples that might be helpful.) With that, you'd just report the increase or decrease every time the seat count changes, and the counter would never be reset. (There's a few other ways to approach it, but that's what I'd do, as it's probably the simplest.)
You're right, though, if you want to charge based on active users, then it gets a bit more tricky, because you do have to avoid double-counting. And the precise definition of "active" becomes really important. I'd probably approach it by putting a "lastSeen" timestamp on each user account, and then periodically calling `tier.report(org, "feature:activeusers", numberActiveSinceFirstOfMonth, Date.now(), true)` to clobber any previous value. (`clobber` is not the default, since usually you want Tier to count it for you.) I'll add an "active users" example to that recipes page.
[1]: https://www.npmjs.com/package/tier#user-content-limitsorg
> This is reasonably fast, but if it's uncached, it is an API call to Stripe
It will be cached after the first request you make with the sidecar/SDK, and unless you have a truly impressive number of users, it'll be able to fit your entire data set in local cache and update only when needed, even on a pretty tiny VM.
I just tend to be probably over-cautious when relying on caches to be fast. Fast 99% of the time is slow 1% of the time. Still good to do it, of course, but always a good idea to at least consider what your worst-case will be.
(b) >> and there's a strong correlation price nimbleness and market success.
Would it be possible to expand on and/or provide additional sources for each of these claims, since while I get that given all other aspects being equal that price is frequently a defining factor, but my understanding is that even then loss aversion [1] means it’s economically a bad strategy to a market.
- [1] https://wikipedia.org/wiki/Loss_aversion
______
(c) >> https://priceops.org/
Might be wrong, but brief review of this appears to show the “thesis” of PriceOps is yield-based metered feature pricing, but again not seeing any research to backup this approach and/or measure of fitness of this approach to a given situation. Do you have an related research to link to?
The "thesis" of PriceOps is less "you must have metered feature pricing", and more "you should design your infrastructure so that you can learn from and adjust pricing in the future". In other words, expect that your product, customers, and landscape will change, and understand that the pricing scheme (ie, what you charge for, how much you charge, and how you bundle prices into a packaged plan) will almost certainly need to change as well.
> Do you have an related research to link to?
Here's a selection of links to check out if you're interested in learning more about this subject. I haven't carefully reviewed each one of these to make sure they're 100% what you're asking for, but they're all things I've read and found helpful studying this topic, and many have links to more research and primary sources. Hope you find it helpful :)
On unlocking growth with pricing:
https://techcrunch.com/2022/07/11/turn-your-startups-pricing...
https://www.nfx.com/post/the-hidden-world-of-pricing
https://www.lennysnewsletter.com/p/saas-pricing-strategy
https://www.bvp.com/atlas/why-pricing-deserves-as-much-itera...
More from BVP, this pricing course is amazing btw, I highly recommend checking it out:
https://www.bvp.com/pricing-course
https://www.bvp.com/assets/media/the-startup-pricing-journey...
https://www.bvp.com/atlas/five-pros-and-four-cons-of-usage-b...
(They led npm's series A, I saw first hand the Bessemer brain trust is really impressive.)
"How to price" resources:
https://databox.com/how-to-price-saas-product
https://www2.deloitte.com/xe/en/insights/focus/industry-4-0/...
https://a16z.com/2021/03/11/bottom-up-pricing-packaging-let-...
On benefits/pitfalls of usage-based pricing specifically:
https://review.firstround.com/dont-let-growth-hurt-your-marg...
https://openviewpartners.com/blog/usage-based-pricing-playbo...
https://www.scalevp.com/blog/the-opportunity-in-usage-based-...
https://openviewpartners.com/blog/saas-pricing-and-packaging...
https://adilaijaz.medium.com/6-questions-to-ask-before-adopt...
Some more lessons and case studies:
https://zimtik.com/en/posts/lessons-learned-on-saas-pricing
https://twitter.com/Suhail/status/1418457605437943811
https://arnon.dk/5-things-i-learned-developing-billing-syste...
That said, to me feels like you’re making claims, then when confronted about them not actually addressing them.
Yes, there are situations systems like this work, for example, when there’s finite volume of inventory and market is willing to pay more to gain access to that inventory as the availability of that inventory shrinks; hotels, taxis, tickets, AD inventory, etc.
On flip side, long list of businesses that adding your code would provide provide no significant value, but instead just add technically debt.
In my experience, even in situations where yield management makes sense, it’s rarely a defining path to product market fit, but an opportunity to optimization revenue.
When we say "change prices", we're not necessarily suggesting "change prices for each customer/transaction, based on demand/time/demographics/etc" in an aggressive yield management approach. In fact, doing this too aggressively in SaaS products (even being too blatant or careless with A/B testing prices and plans) can lead to customer backlash. See for example common reactions to online games selling virtual goods for different prices in different locales. I'm not saying it's never a viable tactic, but it is not a silver bullet, as you point out.
However, it is the case that:
1. Your product will change over time (you keep adding features, decide to focus on some or abandon others, etc.)
2. Your target market may change (features formerly thought of as "pro" become expected at the "basic" level, for example.)
3. Your competitors will change (new entrants to the space, existing players changing their offerings, thus changing customer expectations of your product.)
4. Thus, you're very unlikely to pick the "correct" price and packaging on day 1, and it's virtually impossible to predict what will be "correct" in the future.
So, being able to adjust prices without refactoring your entire application and company is really essential, as many SaaS companies learn the hard way, and as is shown in the resources I shared in my previous comment. Eventually, your prices and packaging will most likely have to change (or at least, changing them will be beneficial, even if not existentially necessary), and making that easy is very valuable.
Scheduling rollouts of new pricing can be done by modifying the effective date of a `tier.subscribe()` call. So you could do something like `tier.subscribe('org:' + customerID, 'plan:whatever@123', someFutureDate)`.
For communicating it to customers, yeah, you'll probably want to tell them their price is going to change ;) It might even be good to have an email campaign where they can check out the new price and decide to upgrade/downgrade/whatever with some time to make a decision. Customers usually don't love being forced into things without notice. Tier doesn't handle this part of it for you, but it does give you the tools to make the actual subscription change pretty easily.
[1] "Mostly" immutable. You can of course go into your Stripe account and edit anything they let you. But Tier makes it easier to just add new plans and leave the old ones there.
It lookslike a cool product, good luck to you!
[1]: me. I'm talking about me.
> Without this restriction, it would be significantly more expensive to fetch your pricing model from Stripe's data.
I didn't quite get that. I understood that application code should reference features (and not plans directly) so it becomes easier to change plan structure (via https://priceops.org/4-entitlement), but that's not the reasoning laid out here, is it?
The fix is to add a "no features" placeholder feature and look for it when you are trying to find plans without any features.
You can create a plan like:
and then the customer subscribed to that plan will have a single $0 item in that subscription phase. (Tier could in theory do something like this automatically if there's a plan with no features, but figured since an empty plan is likely a mistake anyway, better to just let it be explicit.)To the parent comment, there's nothing in PriceOps that theoretically says a plan must have features, this is just an implementation detail that's somewhat unavoidable.
> With this, changes to your pricing model don't require changes to your application code or business processes.
i would go so far as to produce a scenario or skit or whatever to use this all over your marketing materials as any growth PM or founder would immediately understand this one
I'm currently writing "PriceOps for Product Managers" and "PriceOps for Growth" as well. We hear a lot from PMs and PMMs who are eager for something that can allow them to make pricing and packaging changes safely and reliably (and often).
Since you’re well within the power user category, any feedback on how to make the Stripe APIs better? Either for customers’ uses or for your own. I hear you on the object ids :)
Happy to hear here or maddox@stripe.com
For instance, the idea that plans should be treated as immutable (and versioned when changes are desired) and that we should attach application code to features (and not directly to plans), having a lawyer of abstraction that links features to plans, could have been laid out to us at the very beginning of our integration.
They seem obvious in retrospect, but one is not guaranteed to derive them on first try; the fact that they wrote https://priceops.org/4-entitlement is telling.
The Node SDK is a pretty standard Node library (not clientside JS, for probably obvious reasons). It does spawn a child process if it isn't given a TIER_SIDECAR environment variable, though, which is going to be pretty slow if you have stateless route handlers at the edge.
I'll poke at it today, but I think the way to go is to spin up a sidecar running somewhere with `tier serve`, and then give your NextJS API handlers an environment variable to know where to hit it.