Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Tier.run – Terraform for Stripe (github.com/tierrun)
140 points by jmacd on Nov 2, 2022 | hide | past | favorite | 46 comments
Hi HN, we are Jevon, Blake and Isaac, we've been working on Tier for a little while ( http://github.com/tierrun/tier )

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.




I'm not in that problem domain in order to know: why make "like terraform, but for stripe" when you could have made "terraform-provider-stripe" and then been _actual_ terraform for stripe?

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


Perhaps I will regret using that tagline. It's an analogy covers half the functionality.

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.


There are terraform providers for DNS services, Database configuration, CI services and all sorts of other things. It's actually not that hard to write a something that wraps configuration state of various APIs into a terraform module.

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.


And combine it with cdktf you can use actual code, shared consts, and have a reliable setup in a common language rather than a JSON DSL.


Use HCL and you can use actual code, shared consts, and have a reliable setup in a fit-for-purpose (declarative) language rather than a JSON DSL.


Not sure why his comment was downvoted, but I actually think his comment is accurate. HCL is pretty limited in its primitives. In my opinion cdktf's two fundamental issues are:

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.


Because controlling the stack allows you to monetize it later.


Isn't stripe "like terraform, but for online payment methods" ?

But I guess everything is a provider depending on how you look at it.


How is this better than the Stripe Terraform provider? https://registry.terraform.io/providers/franckverrot/stripe/...


I like that provider and it's really useful in a lot of situations. Configuring Stripe as it is currently designed doesn't always help as much for SaaS and infrastructure billing models. We wrote here about how we map to Stripe https://www.tier.run/docs/mapping/

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://news.ycombinator.com/item?id=33431567


This looks very cool! Stripe is very hard in 2022. We just integrated such for our VS Code extension.

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!!


Hi! Tier co-founder here.

> 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?


> 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.

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.


Very interesting approach, and nice to see someone tackling that problem area! You see a hundred blog posts of "why you should experiment with your pricing", but actually doing that is often such a pain that nobody ends up doing that (and admittedly the reason why I would have shied away from such projects in the past).

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/


> You see a hundred blog posts of "why you should experiment with your pricing", but actually doing that is often such a pain that nobody ends up doing that (and admittedly the reason why I would have shied away from such projects in the past).

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


Just to clarify:

> 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.


(a) >> Adjusting price is the single most effective lever a business can use to achieve product/market fit,

(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?


> Might be wrong, but brief review of this appears to show the “thesis” of PriceOps is yield-based metered feature pricing

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...


Thanks, appreciate links, clearly spent time researching topic, curious what you found of use, and will check out all the links.

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.


Ah, I somewhat misunderstood your previous comment, my apologies. This could certainly be clearer in documentation and the priceops.org presentation, I'll take the note on that and think about how to make it more clear.

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.


This is pretty cool! Do you have ways for elegantly handling grandfathering old customer plans, scheduled rollouts of new pricing, etc? I think more than testing/changing the value of the $tier_2_price variable is how you communicate and/or apply it to existing customers.


Grandparenting in old accounts is baked into the system. Plans are immutable[1] once pushed live, so if you add a new plan and put it on your signup page, new customers will use the new plan, but existing customers will keep on with the plan they signed up with. You can of course upgrade them at any time using `tier.subscribe()`

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.


If I'm thinking about pricing, I'm likely also highly interested in a nice UI/Design for my page. IMHO Your https://priceops.org/ needs some major design TLC. Personally, when I see a gray background, or a especially a serif font on a web page trying to sell me something I'll be pretty skeptical. It doesn't need to be wizz-bang amazing, but something cleaner is in order. I'd just grab something from one of the many bootstrap css template stores that are out there. The tier.run site looks a million times better.

It lookslike a cool product, good luck to you!


I completely agree. Whoever[1] made that page should probably not be allowed to make web pages without the assistance of a real designer.

[1]: me. I'm talking about me.


From the Pricing JSON page, on the 'Why Each Plan Must Have At Least One Feature' question, it says:

> 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?


It's a fairly common occurrence (I think) that when you are running a query against some store and there is nothing to filter by, you get all the results back. Now the client has to sift through the result set and find products with an empty feature list on the receiving end.

The fix is to add a "no features" placeholder feature and look for it when you are trying to find plans without any features.


I don't think that's 100% accurate implementation-wise, but the gist is definitely correct. Basically, yes, owing to the way that these things have to get mapped into Stripe's objects, having a plan without any features would make it really costly to reconstruct the model later, and we wouldn't be able to create a subscription to it anyway.

You can create a plan like:

    "plan:nofeatures@0": {
      "feature:donotuse": {}
    }
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.


congrats guys, i've been interested in UBB for a while and i think an "as code" type of approach makes a ton of sense (and maybe can build a low code layer on top). this is the killer line:

> 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 love that!

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).


Very interesting. I‘m not very knowledgeable in the payment space, unfortunately. Do you think there is a way to abstract the concepts over multiple, pluggable payment providers (other than Stripe) in the future?


We don't have SaaS apps but having something like that for in-app purchases that will deploy over App Store, Google Play and PayPal at once would be appreciated. Just a suggestion.


I love that idea. I'm not very experienced with the mobile app stores, but will take a closer look. We've had requests for AWS marketplace as well, which is different from the mobile app stores but the concept would be the same.


Has Stripe gotten complex enough that there is again room in the market for a simple "one line of code" product to accept credit cards on your website?


Small heads-up: The "Read docs" button on your website isn't clickable directly after page load (only after scrolling down, when it moves to the header).


Do you have plans to add other subscription vendors like Zuora in the future?


very cool!

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


As a Stripe customer, I think that the principles on which Tier were built constitute best practices that could have been taught to me by Stripe docs.

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.


Where does Terraform fit in here? Title is very misleading.


"Pricing as Code" would be a better tagline I guess?


Good suggestion! That's what we've been using on https://www.tier.run


Hey, Tier team, this looks promising! Is it possible to run Tier in NextJs API routes?


I am not super familiar with NextJS, but I'll take a look.

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.


I just played around with the NextJS starter demo app, and it seems like you can `import tier from 'tier'` and call `tier.subscribe()`, `tier.report()`, and `tier.limits()` without any issue in a NextJS API route.


not sure why this needs to be a product... stripe already facilitates price versioning. shouldn't be too hard to sync with my app. my app is tiny right now so I'm doing it by hand but building a little sync command wouldn't take me more than a day.


That’s exactly why this is needed


Ehh.. I'm weary of bringing in extra deps (especially paid deps) to solve iddy bitty problems. They just add complexity and new places to break.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: