Related: Rewrite or Refactor? How to Decide Without Guessing
Taking one payment is a day of work. Running billing properly is a lot more. The gap between those two is where most first attempts get into trouble.
This covers what to pick, what it costs, and the cases that break naive implementations.
Never handle card numbers yourself
Start here, because it determines everything else.
If card details touch your server, you fall into the strictest level of PCI DSS compliance. That means audits, scanning, segmented networks and real ongoing cost. No small business wants this.
The way around it is simple. Let the payment provider collect the card:
- Hosted checkout. You redirect to the provider's page. They handle the card. They send the customer back. Lowest effort, lowest risk, least control over the look.
- Embedded fields. The provider's form is placed inside your page in an isolated frame. Looks native, but the card data goes straight to them and never through your code.
Both keep you in the light compliance tier. Use one of them. The only reason to do otherwise is if you are building a payment company.
What it costs
Roughly, for card payments:
- Around 2.9% plus a fixed fee per transaction is typical for major providers
- International cards usually add about 1%
- Currency conversion adds about 1% more
- Disputes cost a fixed fee, often around $15, and you pay it even if you win
Two things worth knowing:
- Percentage pricing is fine at low volume and expensive at high volume. Once you are processing serious money, negotiate. Published rates are not the only rates.
- Fees come out before you see the money. If your margins are thin, model this properly before you price your product.
Webhooks are the source of truth
This is the single most common mistake.
A naive flow marks an order paid when the customer lands back on your success page. That page is a browser redirect. It can fail. The customer may close the tab. The network may drop. Meanwhile the payment succeeded and your system does not know.
The provider also sends a webhook: a server to server message saying what happened. That is authoritative. Build on it.
Rules for webhook handling:
- Verify the signature. Otherwise anyone who finds your endpoint can mark orders paid.
- Make handlers idempotent. Providers retry. You will receive the same event more than once. Processing it twice must not grant two subscriptions or send two receipts. Store the event id and skip duplicates.
- Return 200 quickly, then do the work. If you do slow work before responding, the provider times out and retries, and now you have a pile-up.
- Expect events out of order. Do not assume the created event arrives before the updated one.
- Log every event. When a customer says they paid and have no access, the log is how you find out what happened.
See webhooks versus APIs for the general pattern.
Subscriptions have their own failure cases
One off payments are simple. Recurring billing is not.
- Cards fail routinely. Expiry, insufficient funds, bank declines. A meaningful share of renewals fail every month. Without a retry and reminder flow you are silently losing paying customers.
- Plan changes need proration. Upgrading mid cycle should charge the difference, not a full new period. Let the provider calculate this. Do not write the arithmetic yourself.
- Cancellation has to be real. Cancel in your database and not at the provider, and you keep charging someone who believes they stopped. That is the worst possible bug to ship. Always cancel at the provider first and let the webhook update your side.
- Decide what happens at the end. Does access stop immediately or at period end? Is data kept, and for how long? Write it down before you build it.
- Dunning. The emails that tell someone their payment failed. Most providers can send these. Turn them on.
Tax is decided by where the buyer is
For digital goods and services in most of the world, tax is owed based on the customer's location, not yours. Selling to the EU generally means VAT at the buyer's national rate. Many other countries have equivalent rules.
Two ways to handle it:
- A merchant of record. They sell to the customer and you sell to them. They take on the tax obligation entirely. Higher fees, far less work. For a small team selling internationally this is usually the right trade.
- Do it yourself with a tax service wired into checkout, plus registration and filing wherever you cross a threshold. Cheaper per transaction, real administrative overhead.
Decide early. Retrofitting tax handling after a year of sales is unpleasant and sometimes expensive.
Before you launch
Test all of these against the provider's sandbox:
- Successful payment
- Declined card
- Card requiring extra authentication
- Customer closes the tab after paying but before returning
- Webhook arriving twice
- Webhook arriving while your server is down, then retried
- Full refund and partial refund
- Failed renewal, then a successful retry
- Cancellation, then checking access actually ends when it should
Most first builds handle case one and nothing else. Cases four, five and nine are the ones that generate angry support emails.
We build billing into SaaS products regularly, including the unglamorous parts. If you are planning one, tell us what you are selling and where.
Comments