Show HN: Tier.run – Terraform for Stripe

3 years ago (github.com)

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.

      3 replies →

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

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

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?

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

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.

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.