How do you guys handle past due / grace period if there are no webhooks? Like, do you notify the end-user that their card failed and they need to update their card? Or do you let Stripe send emails?
What's your approach to 3DS and related race-conditions for subscriptions? In my experience 3DS is by far the biggest problem for subscription integrations - not the webhooks.
Grace periods and past due - our current default behavior is:
- when the first charge fails, we block access
- when the n+1th charge fails, we continue grant access until you cancel their subscription
There's a lot of work to do to make that behavior more configurable, because every merchant has a different policy.
For 3DS we're working on getting more explicit coverage for. Not quite 3DS but related: we currently have it set up so that if your customer successfully adds a payment method but the first charge fails, we also won't grant entitlements in that case.
Lots of weird payments edge cases to work through still.
This used to be the case back in 2015, but not anymore. The financial compliance is more strict now. You have to charge taxes. EU enforced SCA / 3DS in 2019. All of these are hard to implement (correctly) on their own - almost impossible together.
Source: I run (paid) Ruby on Rails library for Stripe subscriptions integrations. I also do billing audits. Here's an example audit where I pay $30, get ~$2000 https://www.youtube.com/watch?v=YuXp7V4nanU
Article author here - thank you for putting it this way. This is exactly the attitude I wanted to convey: it's something I tried and really liked for this specific use case. I shared because I hope it might inspire others.
"Friendly Attributes" is not the "new way", not to be used "everywhere now", does not "apply to all scenarios".
If you like it, maybe you'll use it once in the next five years when the opportunity arises.
Your input would work exactly as you wrote it if passed to `Billing::Plan.find_or_create_all_by_attrs!`, just add commas at the end of lines.
If you want to make it even shorter, you have a few options - it really just comes down to preference:
# Option 1. my personal favorite, follows structure of
# intervals and plans on a pricing page.
1.month => {red: 10, blue: 120},
1.year => {red: 120, blue: 300}
# Option 2. this is fine too
red: {1.month => 10, 1.year => 120},
blue: {1.month => 120, 1.year => 300}
# Option 3. possible and works, but hurts my brain, NOT recommended
10 => {red: 1.month},
120 => {red: 1.year, blue: 1.month},
300 => {blue: 1.year}
> there is always at least one attribute that serves as a "discriminator" between the billing plans, right
Just a note: if you try to create two plans with the same attributes, that would error because of ActiveRecord uniqueness validations (and DB constraints). No point in having multiple identical plans.
> I would consider monthly Enterprise & yearly Enterprise the same plan, with modified cost & billing frequency.
How would you then call the objects that store costs and billing frequency? :)
Here's what Stripe uses:
- Product: "describes the goods or services". This is where you define a (plan) name and features.
- Price: defines the amount, currency, and (optional) billing interval. Since interval is optional, Prices can be used to define both recurring, and one-off purchases.
Technically, using Prices for recurring, and one-off payments is a brilliant idea. The problem is, no one refers to recurring payments as "prices". Everyone calls a "$50 per year" option a "plan".
That link describes billing problems of a neobank... I mean, yes, there's a big gap between my test helpers and financial institution's problems - to the point it's not related at all.
But, in principle I agree billing, even the simple SaaS stuff, is much harder than most people expect it to be in 2025. My product (linked in the original article) is based completely on Stripe Billing - and it is still very hard to avoid all the footguns.
For people wondering, I even have an example how wrong it can go: I "audited" a successful SaaS I know uses custom Stripe billing. I paid $30 for a starter plan, but was able to "upgrade" to $2k plain for free. Here's the full video: https://www.youtube.com/watch?v=YuXp7V4nanU
The usual response to this complaint in the Ruby/Rails community is that optimizing for nanoseconds, or even milliseconds doesn't matter when the same operation also involves multiple database queries or API calls.
> The usual response to this complaint in the Ruby/Rails community is that optimizing for nanoseconds, or even milliseconds doesn't matter when the same operation also involves multiple database queries or API calls
The problem with that logic is that it’s pervasive: people have that same attitude everywhere even if no IO is being done. That’s how we get multi gigabyte processes.
The whole language (and Rails) also pushes you towards a less efficient path. For instance you’re probably iterating over those six plans and inserting them individually in the DB. Another approach would’ve been to accumulate all of them in memory then build and perform a single query. That’s not something people really consider because it’s “micro” optimization and makes the code look worse. But if you miss out on hundreds of these micro optimizations then you get a worse system.
In a general sense optimizing Ruby is indeed futile: any optimization is dwarfed by just choosing a different language.
I say all this as someone who has worked with it for two decades, I like the language, it’s just laughably inefficient.
Have you used it over the last few years? It has it been rapidly improving, mainly because Shopify put a team full time on it. It doesn’t take a lot of people to optimize a VM/interpreter it just has to be the right people.
And the question is always “fast enough for what?” Different languages are more suitable for different types of projects. I wouldn’t code a rendering engine in Ruby but for web apps it’s amazing.
Yes, every web app I’ve worked on the past ~18 years has been with Rails. I’ve seen it all except an efficient app. Sure, Ruby and Rails never bankrupted these companies but they’d all have been better off with something else. Certain cloud bills would’ve been much smaller for sure.
Those optimizations to the VM are just very workload specific and become less relevant today when you’re using containers and fractional CPU/mem. It also doesn’t take much for a dev to write the wrong code and make them irrelevant again. Even if you get everything right you’re leaving so much performance on the table it feels like crumbs.
For small web apps Rails is fine though. I just never worked on one. The issue is perhaps no one threw the code away when it got big.
Rails apps can get very expensive server wise because the “IO is slow anyways” attitude means more servers will be needed to serve the same amount of requests. For a specific bad case I worked at, the cloud bill was the same cost of 15 senior developers. And it was an app without external users (I was actually responsible for the external parts of it, it was isolated and not in Rails).
Excessive abstraction at the ORM can also make it extremely difficult to optimize db queries, so each user request can trigger way more DB queries than necessary, and this will require more db power. I have seen this happening over and over due to abstraction layers such as Trailblazer, but anything that is too layered clean-code style will cause issues and requires constant observation. And refactoring is made difficult due to “magic”. Even LLMs might find it too much.
Another problem with the slowness is that it slows down local development too. The biggest test suite I ever saw took 2 hours to run in a 60-machine cluster, so 120 hours of CI. Impossible to run locally, so major refactoring was borderline impossible without a huge feedback cycle.
The solution for the slow development ends up being hiring more developers, of course, with each one responsible for a smaller part of the app. In other companies these kind of features I saw would be written by people over days, not by team over months.
The terseness of both Ruby and Rails is also IMO countered by the culture of turning 10-line methods into bigger classes and using methods and instance variables instead of local variables. So it also hurts both readability (because now you have 5x more lines than needed) but also hurts optimization and stresses the garbage collection. If you know this, you know. I have seen this in code from North+Latin American, European and Japanese companies, so it’s not isolated cases. If you don’t know I can provide examples.
I have seen this happening with other tech too, of course, but with Rails it happens much much faster IME.
It is also 100% preventable, of course, however a lot of advice on how to prevent these problems will clash with Ruby/Rails traditions and culture.
These are just examples out of personal experience, but definitely not isolated cases IMO.
Just want to reiterate what the sibling commenter said, it's dead on with my experience.
Static typing would be the main thing teams would've been better off with. I was big on dynamic languages, love Clojure/LISPs and still work with Ruby and JS today, but you just can't trust 100 developers with it. Last company I worked for I ran the dev team and did some bug analysis: conservatively 60% of bugs were things a simple static type system would've caught.
Very few business logic bugs. We had loads of tests but these simple bugs still popped up. Someone in team A would change a method's return type, find and replace in the codebase, but miss some obscure case from team D. Rinse and repeat. Nothing complicated, just discipline but you can't trust discipline on a 500k LoC codebase and a language with no guardrails.
Performance would've been the other main advantage of static typing. While most people think their Rails app will be IO-bound forever that's really downplaying their product. In actuality every company that mildly succeeds will start to acquire CPU-bound workloads and it'll come a point where they are the bottleneck. One might argue that it is at this point you ditch Ruby but in reality no one really wants to run a polyglot company: it's hard to hire, hard to fill in gaps, hard to manage and evaluate talent.
People underestimate the impact of performance on the bottom line these days with phrases like "memory is cheap; devs are not". Like the sibling commenter put it the monthly cloud bill on that last company would've paid about 20 dev salaries. Most of that was for the app servers. That for an app that served about 500 req/sec at peak. You can imagine the unnecessary pressure that puts on the company's finances.
Better choices would've been Go, Rust, even something on the JVM.
Static typing has come, there's RBS, which has (finally) coalesced (after adding support for inlining in code) as the blessed type notation, supported by both steep and sorbet. Considering that big companies have adopted it, I'd say that the community agrees and has done something about it. But as you can imagine, many ruby apps have been stuck in legacy for years.
About performance, not sure how you think static typing could solve it, but considering the significant investment recently in JITs, in particular YJIT and ZJIT, again, the big apps seem to agree with you and have done something about it?
Even if you ditch CRuby for the JVM, you can still use JRuby, and still leverage the language while not being pulled down by the runtime.
The thing about "it's IO bound anyway so who cares" is that it forces you to scale the app much earlier.
At a company I worked, a single Golang instance (there was backup) was able to handle every single request by itself during peak hours, and do authentication, partial authorization, request enrichment, fraud detection, rate limiting and routing to the appropriate microservice. I'm not saying it was a good idea to have a custom Ingress/Proxy app but it's what we had.
By contrast, the mesh of Rails applications required a few hundred machines during peak time to serve the same number of requests, and none of it was CPU-heavy. It was DB heavy!
If it had been a Golang or JVM or Rust app it would require a much smaller fleet to serve.
> For instance you’re probably iterating over those six plans and inserting them individually in the DB. Another approach would’ve been to accumulate all of them in memory then build and perform a single query. That’s not something people really consider because it’s “micro” optimization and makes the code look worse.
This same pitfall exists in every language. This has nothing to do with Ruby.
This attitude towards wastefulness is how you have web apps that could run in a single machine but struggle to run in a server cluster.
And after a couple years even Postgres is struggling because the amount of queries is too massive because of abstractions that don’t lend themselves to optimization.
Also it’s how you have codebases that could be maintained by two or three suddenly needing dozens because the testing suite needs hours to run and people even celebrate when there’s no tests in sight.
Just anecdotal personal experience. But I saw this happening inside at least 4 successful companies that started with Rails but didn’t care about those problems, and ended up wanting/having to move to something else.
The reality is most companies and products never blow bast the point of needed to ditch Rails. The argument made at the time was scaling horizontally is cheaper than hiring new devs, and you probably will never need to scale that much horizontally.
Test suite bloat is a different problem that stems from the lack of incremental typing which I think is what ultimately killed Ruby and Rails.
Any big Rails codebase can be a nightmare to grok unless people have been diligent about documenting what different methods return.
We have 3 young kids in the range from 2 to 5 years old. We use white noise heavily for our kids sleep. It isolates them from the noises we make around the house.
For this purpose I recommend "LectroFan" devices. We have like 5 "LectroFan Evos" in our's and our kids' bedrooms. They just work, no apps, no upgrades, no LEDs blinking, and thank god, no screens. These devices are worth their amount in gold.
On the phone I recommend using iOS built-in white noise feature. We use it when travelling by car, and our kids sleep in their seats. When we turn the white noise on they stop talking and it quickly lulls them into sleep.
Seconded on LectroFan. I've been using one for years for sleeping all night. No looping at all. Inside it only has 16K of flash, not enough to store samples. I once recorded it for 8 hours and looked for any repeating dips in the waveform, but found nothing. I used the old analog Marsona units for decades before this, so it had some good competition. Customer service was great when I contacted them years ago with a rattling problem with one of mine.
Same here, we have a lot of noise in our area. I have a LectroFan, and it's invaluable. It's simplicity is what makes it so great. We don't need apps for everything. In fact, I prefer to avoid them when possible.
It's kinda funny these things exist to me. I actually used to keep a window AC unit when I could have upgrade to something much nicer because the noise helped me sleep.
Killer feature is multiple plans per customer.
https://railsbilling.com