Home/ Blog/ Article

Stripe or GoCardless: the billing engineering decisions that matter more than the fees

·

Most conversations about payment providers start and end with a comparison table: Stripe against GoCardless against Adyen, fees per transaction, settlement times, which one supports Direct Debit and which one supports Apple Pay. That comparison matters, but it is the easy ten percent of the decision. The other ninety percent is the billing engineering you build around whichever provider you pick, and that is where SaaS products actually break in production – failed payments that silently lock out paying customers, webhooks that arrive twice, or a subscription state that disagrees with what the provider’s dashboard says.

If you are commissioning or building billing for a SaaS product sold to UK or EU customers, the provider choice is worth an afternoon. The decisions below are worth a proper design pass before anyone writes code.

The provider choice, briefly

Stripe is the default for a reason: cards, wallets, multiple currencies and a subscriptions API that covers most billing models out of the box. Its cost climbs with card volume, and card payments carry a meaningful failure rate from expiry, fraud checks and insufficient funds.

GoCardless and other Direct Debit providers suit recurring B2B billing where the customer is a UK or SEPA bank account rather than a card – lower percentage fees at higher contract values, but slower settlement (Direct Debit collection typically takes several working days to confirm) and a different failure mode: mandates get cancelled, not declined.

Plenty of SaaS products end up running both: cards for self-serve customers who sign up and pay immediately, Direct Debit for enterprise accounts on invoiced annual contracts. That is a reasonable outcome, but it doubles the number of failure modes your system has to handle correctly, which is exactly why the engineering underneath deserves more attention than the provider logo.

Your database is the source of truth, not the provider’s dashboard

The most common mistake in early billing implementations is checking subscription status by calling the payment provider’s API on every request, or worse, trusting whatever the last webhook happened to say without a clear model of what state a subscription can be in. Build an explicit state machine in your own database: trialing, active, past_due, paused, cancelled, and the specific transitions allowed between them. The provider’s events update that state machine; they are not a substitute for having one.

This matters for a concrete reason: access control has to run against your database on every request without an external API call in the hot path, both for latency and because you need the product to keep working correctly if the provider has an outage. A subscription that is past_due should still resolve to a clear, deliberate decision in your code – full access, restricted access, or a grace period – rather than whatever falls out of an unhandled edge case.

Webhooks will arrive late, out of order, and sometimes twice

Every major provider sends billing events by webhook, and every provider’s documentation tells you the same three things, which teams routinely skip under deadline pressure:

  • Verify the webhook signature before processing anything – an unverified endpoint is an open door to fake “payment succeeded” events.
  • Make handlers idempotent. The same event ID can arrive more than once; processing it twice must not charge, email, or upgrade a customer twice.
  • Do not assume delivery order. A “subscription updated” event can arrive after a later “subscription cancelled” event. Key your state transitions off the event’s own timestamp, not off arrival order.

On top of that, plan for the endpoint being down. Both Stripe and GoCardless retry failed webhook deliveries, but on their schedule, not yours – so build a reconciliation job that polls the provider’s API periodically and corrects any subscription whose state has drifted from what the webhooks reported. Without it, a single missed webhook during a deploy can leave an account in the wrong state indefinitely.

Dunning is a product decision wearing engineering clothes

A declined card and a cancelled Direct Debit mandate are different problems and need different handling. Card failures are often temporary – an expired card, a bank’s fraud check – and providers retry them on a schedule (Stripe’s Smart Retries is one example). A cancelled mandate usually means the customer actively stopped it, and retrying silently is the wrong move; the right one is a direct, human message.

Whoever builds this needs answers to questions that are business decisions, not technical ones, before writing the retry logic:

  • How many days of grace does a past_due account get before access is restricted, and does that differ for a new customer versus one who has paid reliably for two years?
  • Who gets emailed – the billing contact, the account owner, or both – and at what point does a human on your side get involved instead of another automated email?
  • Does a failed renewal downgrade the account or suspend it outright? Suspending outright is simpler to build and worse for retention.

Get this wrong and the failure mode is invisible until a customer complains that they were locked out of a tool they use daily over a card that expired the week before, or worse, that they kept paying for months after they meant to cancel.

Keep card data out of your system entirely

This one has a simple answer: never let raw card details touch your servers. Use the provider’s hosted fields or client-side elements (Stripe Elements, GoCardless’s hosted mandate flow) so card and bank details go straight from the customer’s browser to the provider. Done properly, this keeps you on the simplest PCI DSS self-assessment questionnaire rather than the much heavier scope that applies to anyone who handles card data directly. There is no good reason for a SaaS product outside payments infrastructure itself to take on that scope.

VAT and Making Tax Digital do not disappear because a provider handles payments

A payment provider processes the payment; it does not automatically produce a compliant VAT invoice or feed your accounting records correctly. If you are charging UK VAT, selling to EU consumers under the VAT MOSS-successor rules, or need invoice records that satisfy HMRC’s Making Tax Digital requirements, that logic – VAT rate determination by customer location, invoice numbering, credit note handling – has to be designed deliberately, whether it lives in your billing code, your accounting software, or a specialised tax tool like Stripe Tax. Decide who owns this before launch; retrofitting correct VAT handling onto live subscriptions is far more painful than building it in from the start.

Deciding what to build versus what to configure

None of this is an argument for building a billing engine from scratch. Stripe Billing and GoCardless’s subscription tooling cover trial periods, proration, coupons and most retry logic without custom code, and reimplementing that is rarely a good use of a small engineering team’s time. The argument is narrower: treating billing as “just an integration” and delegating it to whoever has a spare afternoon is how subscription state gets out of sync with reality, usually discovered when a customer who cancelled six months ago asks why they are still being charged.

Before billing goes live, it is worth being able to answer these directly:

  • Is subscription state modelled explicitly in our own database, with defined transitions, rather than inferred from the provider’s dashboard on demand?
  • Are our webhook handlers idempotent, signature-verified, and safe to receive out of order?
  • Is there a reconciliation job that catches drift if a webhook is ever missed?
  • Do we have separate, deliberate handling for card failures versus Direct Debit mandate cancellations?
  • Have we agreed the grace period and access-restriction rules with whoever owns customer retention, not just with engineering?
  • Does raw card or bank data ever touch our servers – and if the honest answer is yes, why?
  • Who owns VAT determination and invoice compliance, and has that been tested against a real cross-border sale?

If most of those have clear owners and clear answers, the choice between Stripe and GoCardless is genuinely the easy part.

Filed under: