Why I'm no longer using Stripe

Why I'm No Longer Using Stripe (And What I'm Using Instead)
When it comes to building SaaS products, one of the most important components is the ability to accept payments. While you could roll your own payment provider, this is in fact a terrible idea. Instead, it's much better to use a third-party service with some of the more popular ones being PayPal, Square, and of course, Stripe.
Stripe is perhaps the most popular payment provider out there, and for good reason. Not only does it have a fantastic developer experience, but I would argue it pretty much sets the trend on how a payment provider should work. Because Stripe has both high reliability and fantastic service, it's pretty much the de facto standard payment provider most developers will use.
This was certainly the case when it came to my own personal experience, having used Stripe now for almost 10 years. That is until I ran into a few challenges about a couple of months ago. These challenges ended up causing me to move over to a new payment provider—one that has not only managed to help me solve these issues, but I think I'm going to be using for the foreseeable future.
This provider is Polar.sh, which has been recently growing in popularity with more and more people migrating over. But this begs the question: why?
Well, ultimately, like most things in software development, it depends. For some people, like myself, there's a good reason to move over, but for others, migration might not actually be the best choice. Therefore, in order to answer the question of why people are migrating over, let's take a moment to talk about what Stripe provides, as well as perhaps more importantly, what it does not.
What is a Payment Provider?
As I mentioned before, Stripe is likely the most popular payment provider out there. But what exactly does a payment provider do?
A payment provider is the service that sits between your business, your customer, and the financial institutions (aka a bank). They handle all of the heavy lifting of securely processing credit cards, debit cards, and digital wallets, making sure the money leaves your customer's account and lands in yours.
In practice, this means they deal with things like:
- Fraud protection
 - Encryption
 - PCI compliance
 - Settlement
 
This way you don't have to build all of that infrastructure yourself, which would not only take a lot of time, but would be incredibly expensive.
In addition to this, Stripe also provides a wide range of features and functionality that go beyond raw payment processing, leaning towards more business logic. These include:
- Managing customers
 - Setting up and handling subscriptions
 - Generating invoices
 - Sending receipts
 - Issuing refunds
 
Not only this, but there's also some really amazing integrations when it comes to getting Stripe added to your project, with perhaps my favorite one being the plug-in for Better Auth, enabling you to easily add payments into your products through Stripe in a matter of minutes rather than hours.
All of this makes Stripe more than just a payment processor. Instead, you can consider it more as a financial infrastructure platform, enabling us developers to focus more on building and shipping our products without needing to worry about the complexities of handling payments.
The Challenge: Sales Tax
So, given all this, why then did I decide to move away?
While Stripe does provide an awful lot of functionality, there are a couple of important things that it doesn't handle, which when done by oneself can take up quite a lot of time—with perhaps the biggest one, at least for myself, being related to sales tax.
Disclaimer
Before I go on, this probably needs to be said: I'm not a tax professional and I'm not a legal expert. Instead, I'm just a software developer and YouTuber sharing my own experience. If you need legal advice or tax advice, then I recommend speaking to an actual tax expert.
The Sales Tax Problem
To be fair, Stripe does provide you the ability for automatic tax collection when it comes to the relevant sales tax for your customers, which can be both enabled and configured, provided you've registered for sales tax in that jurisdiction and have provided Stripe the relevant tax ID.
Additionally, Stripe will also let you know when you need to register for sales tax for a specific jurisdiction—typically whenever you've exceeded a certain sales threshold for that location, which in some places is quite high, but in others, not so much.
Despite providing both of these useful features when it comes to sales tax, there's unfortunately one feature that Stripe doesn't provide: tax remittance. This is the process of reporting and paying these collected taxes to the relevant tax authority.
When it comes to selling online digital products, sales tax can be a little complicated. This is because the rules for sales tax typically apply based on where your customer is located rather than where your business is based. So, for example, if someone in Germany makes a purchase, then you need to comply with the German/EU VAT laws.
In many jurisdictions, you only need to register and remit once you pass that country's sales threshold, which can be in the hundreds of thousands. But in places like the EU and the UK, if you're located outside of them, this threshold is effectively zero, meaning that tax applies from your very first sale.
Once this threshold has been crossed for a jurisdiction (or immediately when it comes to the EU/UK), then you need to:
- Register with the relevant tax authority
 - Report and pay taxes periodically (typically every quarter)
 - Fill out the sales information for that location in a timely manner
 - Set aside the relevant amount in tax for each sale you make
 
Missing these deadlines results in penalties, and in some jurisdictions, there are additional rules to abide by. For example, in the EU, you have to provide a VAT invoice for every sale that you make, which Stripe doesn't do for you (at least not for free).
If all of this sounds like a hassle, then you're correct. It absolutely is.
The Solution: Merchant of Record (MoR)
So much so that many solo developers like myself would rather not do this. Fortunately for those who don't, there is a solution through services like Lemon Squeezy, Paddle, or Polar.sh. All three of which are known as a merchant of record or MoR for short.
What is a Merchant of Record?
MoRs are services that work in a similar way to Stripe—basically allowing you to manage payments, customers, products, subscriptions, etc. However, unlike Stripe, they have one major difference: these services act as the official seller or merchant when it comes to any sales (i.e., the merchant of record, which is what gives them their name).
This basically means that your customers are legally buying from this service rather than buying from your own business, and the platform then pays you the sale amount minus a service fee. Basically, you can think of it like a proxy for any of the sales that you make.
While this might seem like a weird double hop, it actually provides some benefits when it comes to selling digital products. This is because the platform itself assumes the legal responsibility of the sale, which means they're responsible for handling:
- Refunds
 - Credit card disputes
 - Most relevant to my needs: collecting and remitting sales tax for different jurisdictions
 
This is what makes them so appealing for anybody who wants to outsource this tedious task.
For more detailed information about MoRs, check out What is an MoR.
MoR Drawbacks
Of course, these platforms don't do this out of the kindness of their heart, and they take a percentage of the sale as their operating fee. This percentage amount varies depending on each provider, but can range anywhere from 4% all the way up to 10%.
Now, this may seem like a lot, and yeah, to be fair, the 10% fee is kind of high, but when you consider that Stripe's fee is 2.9%, then if you're on the lower end of this spectrum (say 4%), this only ends up becoming about a 1.1% additional fee. For some people, this additional fee to not have to worry about tax compliance is going to be worth the cost.
However, there are some other drawbacks of using an MoR to be aware of beyond this additional fee, with the most major one being that there's a higher chance it'll cost your customers more.
This is because, as I mentioned before, you only need to collect sales tax in a jurisdiction once you've passed a certain sales threshold, which in some places can be quite high. This means until you exceed those thresholds, you don't have to charge tax to your customers in those locations, which means they'll technically end up paying less for your product and therefore it'll likely increase your sales.
However, by using an MoR, there's a greater likelihood that the sales threshold for a location has been exceeded due to the aggregate of sales that they make, which means your product is either going to have to cost more or you yourself are going to have to eat those additional costs.
This means depending on where you're selling, by using an MoR straight away, it may actually be a net negative, especially if you're willing to put in the effort to just handle sales tax in the few jurisdictions that you need to initially.
My Experience: Why I Finally Made the Switch
This was actually one of the main reasons I decided to forego using an MoR initially and instead decided to just use Stripe and handle tax remittance by myself. However, this ended up being quite a lot more work than I originally thought it would.
Initial Challenges
For starters, it often took weeks or sometimes months to register with a new jurisdiction. And once I was registered, deadlines to remit taxes came about pretty quickly, which given my time management issues, I would often end up missing and would be subsequently fined.
Perhaps the biggest thing I was concerned about, however, is I was starting to cross even more thresholds, which meant that the amount of work I would need to do to remain tax compliant was only going to increase.
Therefore, I decided that this sort of paperwork really isn't my cup of tea, and instead, I'd much rather spend my time both building and creating. So, I decided to outsource this and migrate over to another solution that would allow me to buy back some time.
Stripe Tax Complete
One thing to note is that Stripe actually does provide a paid tax service called Stripe Tax Complete, which is fulfilled by their partner Taxually. This service does handle some tax registrations and filings for you, although it's not exactly what I would call comprehensive.
The pricing structure:
- $90/month plan: Only covers two global registrations per year (with additional fees) and provides four global filings
 - $430/month plan: The next tier up
 
Remember, in the EU you have to file four times a year anyway. So unless I'm misunderstanding what this provides, the $90 plan wouldn't cover all of the EU and the UK—you would still have to do some of this yourself.
I determined that this option was a non-starter and instead I would have to use an MoR.
Choosing the Right MoR
Therefore, I decided to do some preliminary research on the three services that I mentioned before: Lemon Squeezy, Paddle, and Polar.sh. Each of which come with their own percentage fee and onboarding requirements.
In the end, the one that I decided to go with was Polar.sh for a few different reasons:
Why I Chose Polar.sh
Lowest Fee: Out of the three, Polar has the lowest fee at only 4%. In fact, they describe themselves as the cheapest MoR on the market.
Easy Onboarding: Polar, in my opinion, has the easiest onboarding process, allowing you to get up and running accepting payments before needing to be reviewed, which will come after your first few sales.
Quality of Life Features: It provides many quality of life features such as:
- Great integrations with the languages and frameworks that I use
 - Fantastic developer documentation
 - The ability to set benefits such as Discord invites, GitHub repository access, and even file downloads
 
The Migration Process
So, I decided to migrate over, which ended up being a lot easier than I originally thought it would.
Database Schema Changes
In order to do so, I initially decided to do a proof of concept using Go, adding in Polar.sh alongside Stripe so that I could easily switch between the two when testing. To do so, I needed to make a few changes to my database schema.
In order to do so without accidentally breaking prod, I needed a way to be able to fork my production data into a new database branch so that I could test it properly. Fortunately, I was able to easily achieve this thanks to my Postgres provider, Neon.
Setting Up the Migration
The first change that I needed to make was to migrate all of my existing tables and columns to have a prefix of stripe_. This enabled me to segment the existing columns by platform, which meant I could add new ones in and they wouldn't be conflicting.
Because when it comes to Go, I like to use SQLC, this was easy enough for me to make the change throughout the code, as I had compile-time checking to make sure that I hadn't left any previous references to the non-prefixed columns.
Once I had confirmed that the changes were working, I then merged these in. Next, it was time to add in support for Polar.sh.
Key Differences: Polar vs Stripe
Fortunately, for the most part, Polar works very similar to Stripe. So, the majority of the changes were setting up a similar schema and business logic as what I had already defined. However, there are a few key differences between the two platforms to be aware of:
1. Products and Prices
Both Stripe and Polar provide an entity type called a product which is used to represent what it is that the customer is purchasing. This product can be anything you want from a simple course all the way up to a different product tier such as a pro plan.
Each product then has an associated price, which on both platforms can either be:
- A one-off fee (one-time purchase)
 - A periodic payment plan (subscription)
 
The Key Difference:
- Stripe: A single product can have multiple prices
 - Polar: A product can only have a single associated price
 
Personally, I actually found that I preferred the way that Polar worked when it came to my database schema. This is because when using Stripe, I was having to store both a product ID and a price ID column for each of my products. Whereas with Polar, I could just reduce this down to a single product ID, which made my schema simpler.
That being said, one caveat with Polar's approach of having a one-to-one mapping of product and price does mean that if you like to use prices to represent multiple tiers of a product, then this doesn't directly translate over. Instead, you need to create multiple products in Polar—one to represent each pricing tier.
2. Customer References
Both platforms provide an entity type called a customer, which is basically the financial representation of a user that you would find in your own platform.
Stripe Approach: To map your own user with the Stripe customer, you'll typically need to use either a lookup table or add a column to your user table, which means you can then reference the customer entity in Stripe for your user later on.
Polar Approach: There's no need to do this as Polar allows you to set a field on the customer called external_id, which you can use to reference the customer instead of the ID that's generated by Polar. By setting a customer's external ID to the ID of your own internal user, you don't need to store the Polar customer UUID inside of a lookup table, which can help simplify your database schema.
3. ID Generation
- Polar: Makes use of UUIDs
 - Stripe: Makes use of prefixed object IDs (which I actually prefer)
 
While this is a minor implementation detail, it could potentially have an impact when it comes to any foreign key references in your database. However, given the fact that you can store a UUID inside of a varchar, it's likely not going to be too much of an issue.
Implementation Details
Other than these three differences, the rest of the migration was pretty straightforward and mostly involved:
- Changing to use the Polar SDK rather than the Stripe one
 - Setting up a dedicated webhook handler to handle any Polar.sh events
 
To make the implementation easier, I made use of Go interfaces to define any custom business logic methods I was using with the Stripe SDK and therefore what I would need to reimplement in order to make it work with Polar. Basically, it acted like a blueprint for what I was needing to build.
Testing
To make testing easier, Polar comes with its own sandbox environment which you can find at sandbox.polar.sh and is able to be toggled between in the SDK. This allows you to test that everything is working before going live, which is rather important when it comes to integrating with a payment provider and dealing with both credit cards and real money.
Once I was confident that the migration was working in dev, I pushed up the changes to prod and saw my first successful Polar transaction come through, letting me know I had successfully migrated over.
Easy Integration with Better Auth
All in all, migrating over my existing project wasn't too difficult. And as I mentioned before, I'm likely going to be using Polar on any future projects for the foreseeable future.
Fortunately, because I use Better Auth as my auth provider, it's incredibly simple to integrate with Polar thanks to its fantastic Better Auth plugin, which can get you up and running accepting payments in just a couple of minutes.
The plugin documentation on the Better Auth web page is pretty comprehensive on how to achieve this. But if you'd like to see a full video where I implement Polar into an existing Better Auth project, then let me know in the comments and I'll draft up a dedicated walkthrough showing how to do this.
Conclusion: Focus on What Matters
All in all, for myself, migrating away from Stripe to Polar has been a great decision as it's prevented me from needing to spend time dealing with some of the more tedious tasks of shipping software. Instead, it's allowed me to focus more on creating and building, which really is what I want to be doing.
Personally, this is something that I want to be focusing more and more on in the future—opting to have a low-maintenance lifestyle where instead of spending time trying to do everything, I'm instead investing more in solutions that give me time back.
This includes using platforms like Polar and of course, database solutions like Neon, who have allowed me to not need to worry when it comes to setting up and deploying databases to use with all of my projects.
Useful Links
This article is based on my personal experience migrating from Stripe to Polar.sh. Your mileage may vary depending on your specific use case and requirements. Always consult with tax and legal professionals for advice specific to your situation.