Limitless Wealth
Article

The gateway you integrate is the gateway you keep

Every payment integration starts the same way. One provider, their SDK, their webhooks, their field names, straight into the application. It works, it ships, and nobody thinks about it again until the day you need to move - at which point you discover the integration was never the hard part.

Three things that do not come with you

Rewriting API calls is a week of work. That is not what makes migrations expensive. These are.

1. Stored card tokens

If you take repeat payments, your customers' cards are held by your provider and referenced by a token that only they can resolve. Those tokens are worthless to anyone else. Your subscription billing, your one-click checkout, your saved payment methods - all of it runs on references that belong to a company you are trying to leave.

Card data migration between providers does exist. Both sides are PCI-compliant, the data can move under a controlled transfer, and the major providers have processes for it. But it is a project with paperwork, scheduling and cooperation from a provider who has no commercial interest in helping you leave quickly. Weeks, not afternoons.

The alternative is asking every customer to re-enter their card, and you will not get them all back. For a subscription business that is not a migration cost, it is a churn event.

2. Webhook handling

Providers do not agree on what to call things, when to send them, or how hard to try. Event names differ. Delivery guarantees differ - at-least-once for some, best-effort for others. Retry schedules, signature schemes and ordering guarantees all differ. Some send you the full object, some send an ID you must go and fetch.

If you handled those events inline - updating orders directly in the webhook controller - then that logic is shaped around one provider's vocabulary and timing assumptions. Swapping the provider means rewriting your order lifecycle, which is usually the part of the system nobody wants to touch.

3. The vocabulary in your database

This is the subtle one and the most expensive to unpick. Payment states are not standard. authorised, captured, settled, pending and completed mean materially different things across providers, and some providers collapse states that others separate.

If you stored their status strings in your payments table, your schema is now a description of their system rather than yours. Every report, every state machine and every conditional in your codebase encodes assumptions from a provider you are leaving.

The integration is a week. The tokens, the webhook semantics and the vocabulary in your schema are the quarter.

The exception: network tokens

There is a route out of the token problem, and it is worth knowing about before you pick a provider rather than after.

Visa and Mastercard both run their own tokenisation services - Visa Token Service and Mastercard Digital Enablement Service. A network token replaces the card number at the scheme level rather than the gateway level, and it carries real advantages: measurably better authorisation rates, and automatic lifecycle updates so a reissued or expired card keeps working without a failed payment and a dunning email.

The part that matters here is portability, and it hinges on one detail most merchants never look at: who is the token requestor. Tokens are provisioned against a Token Requestor ID. If your gateway holds it - which is the default - the tokens are still theirs and you are no better off. If you hold it, or you use a provider that supports merchant-owned tokens, the same tokens work across acquirers.

Same technology, opposite outcome. Whose name is on the Token Requestor ID decides whether tokenisation is a lock-in mechanism or an exit route.

Becoming a token requestor yourself means scheme enrolment and is not trivial. The practical middle path is to choose a provider that supports merchant-owned network tokens and to ask the question during procurement, when you still have leverage, rather than during a migration when you have none.

See network tokens and who owns your cards for how this works in practice.

What to do instead

The fix is unglamorous and cheap if you do it early: own your own payment model, and treat every provider as a translation layer into it.

Concretely, three rules.

Your states are yours. Define the payment lifecycle your business actually has, not the one your provider exposes. Store your state. Store theirs alongside it as a reference if you like, but never branch on it outside the adapter.

Webhooks normalise before they touch anything. The controller's only job is to verify the signature, translate the event into your own internal event, and enqueue it. Nothing downstream should know which provider it came from. Idempotency belongs here too, keyed on your identifier rather than theirs.

The adapter is the only provider-aware code. One interface - authorise, capture, refund, tokenise, void - with a concrete implementation per provider. If provider names appear anywhere outside that directory, the abstraction has leaked.

app/payments/
  gateway.rb            # the interface your app talks to
  adapters/
    stripe_adapter.rb
    worldpay_adapter.rb
  events/
    normaliser.rb       # provider event -> your event
  payment.rb            # your states, your vocabulary

That is a few days of work on a new project, and it is the difference between adding a second provider in a sprint and rebuilding checkout over a quarter.

If you are already integrated directly

Most systems we are called into are. It is recoverable, and the order matters:

  1. Introduce your own state column alongside the provider's and dual-write. Migrate reads across gradually. This is safe and can ship incrementally.
  2. Move webhook handling behind a normaliser. Same handlers, one translation step in front. Nothing downstream changes.
  3. Extract the adapter last, once nothing outside it depends on provider vocabulary. By this point it is mechanical.
  4. Deal with tokens separately. They are their own project, and knowing that early lets you plan around it rather than discovering it mid-migration.

None of this requires a rewrite, and each step is independently useful even if you never change provider. Which is the point: the abstraction earns its keep as soon as you need a second provider for redundancy, geography or cost - and that need arrives sooner than most teams expect.

We build payment abstraction layers and run gateway migrations, including the token transfer. If you are integrated directly and starting to feel it, we can tell you what it would actually take.

contact@limitlesswealth.xyz