Turnkey Payment Gateway Integration in Medusa.js with Guarantee

Payments in a Medusa.js store often fail: orders get stuck, customers are charged twice, and the successful payment rate drops. We develop and configure the payment pipeline turnkey — from gateway selection to idempotent webhook processing. Our engineers bring the successful payment rate to a stable level and eliminate manual troubleshooting for support.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Frequently Asked Questions

Why payment gateway integration in Medusa.js breaks in production

Payment gateway integration in Medusa.js almost always goes smoothly in the test environment and breaks in production — because the test provider responds instantly and never retries requests.

In reality, payment webhooks arrive repeatedly, with delays, sometimes out of order, and the provider may send a success event after the order has already been canceled due to a timeout.

Medusa.js stores order status separately from the payment provider's transaction, and if you link them "by eye" — by time or by amount — you get order status desynchronization: the customer sees "paid," while in the system the order remains unpaid, or vice versa.

The second typical failure is duplicate charges. The provider does not guarantee exactly-once webhook delivery: on a network failure it will retry the event, and without payment idempotency the handler will process the payment twice.

This is not fixed by "a check in the code," but by an idempotency key on the provider's side plus a log of processed events in the Medusa database: a repeated webhook with the same identifier must be recognized and ignored before any business logic.

The third failure is payment loss on timeout. The user went to the provider's page, paid, but the redirect never came back — and the order remains in a suspended state, even though the funds were charged.

Here only asynchronous reconciliation works: a background process periodically polls the provider for "stuck" transactions and brings the status to a final one, rather than relying on the browser returning.

All three failures are about one thing: the stream of events from the provider and the order state in Medusa.js must converge deterministically, not "usually work." Let's break down how this is closed at the architecture level, rather than with patches in the handler.

What a correctly assembled payment loop delivers

A correctly assembled payment loop in Medusa.js is not "connect a provider and forget about it," but a managed layer between the order, the provider, and the warehouse.

When it is built properly, the share of successful payments stops depending on the whims of a single gateway: routing by card type and region sends the payment where it will go through on the first attempt, rather than where it is configured by default.

Resilience to provider failures is built in at the webhook handling level.

We implement idempotent event processing with a key based on the payment identifier and a state log — a repeated webhook from the provider does not create a second order and does not charge the money twice.

This eliminates an entire class of double charges, which are usually the reason people come to investigate: the customer sees two holds, support manually reconciles statements, and the money is returned days later.

The speed of refunds is a direct consequence of the same log. Cancellation and refund are initiated from the admin panel in a single operation, the status is synchronized with the provider automatically, and discrepancies end up in a report rather than surfacing in complaints.

The response to a provider failure is reduced to switching to a backup route without stopping sales.

The loop can be verified by several signs: the share of successful payments holds at a stable level without manual tweaks, repeated webhooks do not break orders, refunds proceed predictably, and a failure of one provider does not bring down checkout entirely.

If even one point is not met, the loop is only partially assembled, and this is visible in the load on support.

Payment Integration Methods: What to Choose for the Task

In Medusa.js, a payment provider is a module with a fixed set of methods: payment initialization, authorization, capture, refund, and processing notifications from the bank.

The integration method is chosen not by convenience of integration, but by how much the business logic of payment differs from the typical scenario "cart → payment → order".

If the scenario is standard, it is more reasonable to take a ready-made provider module: it already covers holding, partial refund, and re-authorization, and updates come along with the core.

A custom provider module is justified when non-standard steps are needed — installment payments, multi-currency splits, card binding to an account without charging, custom retry rules.

An external payment layer (a custom service between the store and the bank) adds another node, but isolates the store from changes in acquiring: the provider is changed in one place, the storefront and order logic are untouched.

Option When it fits Limitations
Ready-made provider module Typical payment scenario, one or two acquiring providers, fast launch needed Logic is limited to the module's methods; non-standard steps will have to be worked around externally
Custom provider module Non-standard scenarios: splits, partial charges, card binding, custom retries Development and maintenance on our side; during major core updates the module needs to be checked
External payment layer Multiple acquiring providers, frequent provider changes, unified rules for several storefronts An additional node in the payment chain; monitoring and reconciliation of status discrepancies are needed

In practice, the choice almost always comes down to how many payment scenarios live longer than a year. If there is one — we take a ready-made module and do not multiply entities.

If payment becomes part of the product, we weigh the cost of ownership of a custom module against the layer: the former is cheaper to launch, the latter is cheaper to operate when acquiring changes.

Let's analyze your payment scheme and propose an integration method with an estimate for timelines and support.

Stages of payment gateway implementation in Medusa.js

The work starts from an audit of the current payment loop, not from installing a module: first we establish how orders, carts, and payment statuses live in your Medusa.js installation, which providers are already connected, and where the link between payment and order status change breaks. Then comes the sequence in which each subsequent step builds on the result of the previous one.

How the process is structured

  1. Payment loop audit. We analyze the current payment modules, the orders and carts schema, refund and partial payment handling. The output is a map of places where order status diverges from the actual payment state.
  2. Provider selection and connection. We determine whether the integration goes through a ready-made module or through a custom payment service, and configure it for your scenario: single-stage payment, capture separate from authorization, recurring charges.
  3. Webhook setup. We set up receiving notifications from the gateway, signature verification, and idempotent event handling — a repeated webhook must not create a second payment or duplicate an order.
  4. Payment scenario testing. We run successful payment, decline, cancellation, full and partial refund, and payment with delayed confirmation. Each scenario is verified against order status and the payment journal entry.
  5. Failure handling. We configure behavior when the gateway is unavailable: a retry queue, a clear status for the customer, and no "stuck" orders in an intermediate state.
  6. Test and production loops. We run the full set of scenarios in the provider's test mode, then switch to production keys and verify the first real operations under supervision.
  7. Handover of regulations. We provide configuration documentation, the payment flow diagram, and instructions for actions in case of failure — so that support can resolve incidents without us.

The order of the stages is not rearranged: setting up webhooks without an audit produces duplicate payments, and testing scenarios without a test loop transfers errors onto real money.

If you already have a partially working integration, we will start with an audit and show which steps need to be redone and which can be left as is.

What the client receives upon completion

After implementation, the team is left not with "working code on our laptop," but with a set of artifacts that allows payments to be developed and maintained without us.

Each item is something that is actually handed over to the repository and to the client's internal documentation.

  • Provider configuration — payment modules in Medusa.js with separate profiles for test and production environments, keys only via environment variables, provider switching without changes to business logic.
  • Payment flow diagram — diagrams for each scenario: regular payment, confirmation, partial refund, cancellation, and retry. It shows where the cart is created, where the order is recorded, and at what point the order is considered paid.
  • Webhook handling policy — documented rules for receiving events from the gateway: signature verification, idempotency by event ID, the order of processing repeated deliveries, behavior when the service is unavailable, and a queue for catching up on missed notifications.
  • Failure and timeout handling — scenarios where the gateway did not respond or returned an error: retries with delay, recording the "processing" status, manual confirmation, and a clear log for incident investigation.
  • Payment documentation — the procedure for adding a new provider, a description of configuration fields, an order status matrix, and a support guide: what to check when there is a status discrepancy between the gateway and the system.
  • Reconciliation procedure — the process for matching gateway transactions with orders, the frequency of checks, and the list of signals that trigger a discrepancy investigation.
  • Access and roles — the separation of rights for keys and payment settings, the procedure for rotating secrets, and transferring access when the responsible person changes.

This set is sufficient to connect a new provider or investigate a disputed transaction using the internal team, without going back to the authors of the integration.

Case Study: How We Eliminated Double Charges and Increased the Share of Successful Payments

The client — a subscription service built on Medusa.js — came with a classic growth problem: the share of successful payments held at 71%, and every third support request was about "money was charged, but the order wasn't placed." The cause turned out to be not in a single gateway, but in the architecture: payment confirmation was processed synchronously within the user's request, and when the provider's response timed out, the client retried the request — hence the double charges.

What We Changed

We split the payment flow into two independent loops.

Payment initialization now only creates a record with a "pending" status and returns the operation identifier, while the final status arrives as a separate notification from the provider and is processed idempotently — a repeat of the same event does not create a second charge.

The idempotency key became the provider's operation identifier rather than the user session, so client-side retries and repeated webhooks collapse into a single transaction.

The second loop is reconciliation. Every few minutes, a background process reconciles "stuck" operations against the provider's status and brings them to a final state if the notification was lost.

This eliminated the class of failures where the payment went through but the order remained unplaced.

Metrics Before and After

Indicator Before After
Share of successful payments 71% 96%
Support requests about payments ~40 per week isolated cases
Response time to provider failure manual investigation, hours automatic resolution, minutes
Double charges regular eliminated

We also set up routing separately: when the primary gateway is unavailable, traffic goes to the backup one instead of bringing down order placement.

The response time to a provider failure was reduced from manual investigation to automatic operation resolution — an engineer only gets involved for anomalies that reconciliation couldn't close on its own.

How idempotent webhook processing differs from a duplicate charge

A payment gateway delivers a webhook on a "until it gets confirmation" basis — on a timeout, network error, or service restart, the same notification arrives again.

This is standard behavior, not a failure: the gateway doesn't know whether you've processed the event or not, so it repeats delivery. A repeated webhook delivery and a duplicate charge are different events.

The first means that a notification about an already-completed payment arrived a second time; the second means that the customer was actually charged again. Confusing these concepts in code is what produces duplicate orders, duplicate emails, and duplicate charges.

Idempotent processing is a property of a handler whereby a repeated call with the same event identifier does not change the state of the system.

In a payment processing architecture, the idempotency key is the event identifier from the gateway, not the order number: a single order can generate several legitimate events (authorization, confirmation, refund).

The handler, within a single transaction, checks whether the event has been recorded before, and only then changes the order status and triggers side effects.

The practical takeaway for Medusa.js: the webhook handler must be atomic and rely on a unique index on the event identifier in the database.

Then a repeated delivery will simply return "already processed" and won't touch the order, and a duplicate charge becomes impossible at the level of the data schema rather than at the level of checks in code.

We also separately pin down event ordering: notifications may arrive out of order, so we update the order status according to transition rules, not "as it arrived."

If this layer is missing, protection is built on manual reconciliations and handling support requests — and every repeated delivery turns into a potential incident.

We design processing so that a repeat becomes safe by default: event uniqueness, an atomic transaction, a log of processed notifications, and correct status transitions.

Common technical concerns about payment integration

Payment integration is rarely a "set it and forget it" affair: providers change requirements, new payment methods appear, and the business asks for refunds and recurring charges. Below are the technical concerns that most often come up on calls, and how we address them in code.

How difficult is it to change a payment provider if the integration is already working?

The payment layer in Medusa.js is isolated: the provider is connected as a separate module, while the order, cart, and status logic remains untouched. Switching comes down to registering a new module, migrating access keys, and mapping payment statuses to internal order states.

We design adapters so that business logic knows nothing about the specific gateway — then the transition takes days, not weeks, and old transactions remain available for reconciliation and refunds.

How do you test payments without making real charges?

Every provider offers a test environment with separate keys and a sandbox. We run the full scenario: successful payment, bank decline, timeout, retry, cancellation on the client side.

We separately test asynchronous notifications — that's where status handling most often breaks, when a response arrives with a delay or is duplicated.

How are refunds and partial refunds handled?

A refund is initiated from the admin panel or via an order event, then a request is sent to the provider and the result is recorded. We handle full and partial refunds, keep the link to the original transaction, and prevent refunding more than was paid.

If the provider supports asynchronous refunds, the status is updated via notification rather than polling — the order doesn't get stuck in an intermediate state.

What about security and access to payment data?

Access keys are stored in environment variables and never end up in the repository, webhooks are verified by signature, and card data never passes through our code — it's handled by the provider itself. This removes most of the payment information protection requirements on your side.

If your case isn't on the list — describe the task, and we'll come back with an estimate and a work plan.

Let's discuss your payment gateway integration task in Medusa.js

To make the estimate for payment gateway integration in Medusa.js concrete rather than a rough guess, we need to clarify a few inputs at the start.

Medusa is a headless platform: payments here are connected as a separate provider module, so the total scope of work depends not only on the number of gateways but also on how your orders, refunds, and payment statuses are structured.

What we clarify in the first conversation:

  • which gateways and payment methods are needed (cards, wallets, installments, invoice payment) and in which countries you accept payments;
  • scenarios: single-stage or two-stage payment (authorization + capture), partial refunds, cancellation, retries;
  • how webhooks are currently handled and what happens to the order if a payment hangs or fails;
  • requirements for 3-D Secure and payer identity verification;
  • whether accounts in the payment systems already exist or need to be set up;
  • how accounting is structured: whether payments need to be synchronized with accounting and inventory;
  • what environments and access exist now and who on your side makes decisions about payments.

Based on this data, we come back with an estimate and a plan: the composition of provider modules, the payment flow scheme, the scope of modifications for orders and refunds, the implementation timeline by stages, and the points where your confirmations are needed.

If non-obvious scenarios come up along the way, we document them separately so they don't surface in production.

Describe the task in free form: which gateways, which payment scenarios, what already works. That's enough for us to come back with an estimate, a plan, and implementation timelines.

Our competencies:

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1393
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1368
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1139
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1152
  • Website development for SBH Partners
    Website development for SBH Partners
    1231
  • Website development for Red Pear
    Website development for Red Pear
    641