# Medusa

> Add Mintage Pay to a Medusa v2 backend, test it with test money and go live.

Source: https://mintagepay.com/docs/integrations/medusa/ (last updated 2026-10-25)

A payment provider for **Medusa v2**. Your storefront sends the customer to the Mintage Pay payment page, and Medusa
completes the cart and captures the payment by itself when the payment is final.

Tested on 5 October 2026 with Medusa 2.21 on Node 22 and a region priced in euros, against the sandbox: connect,
payment session, payment, cart completed into an order with a captured payment, and removal. The run used the Store
API. The Next.js starter storefront and its return page have not been run yet, so test your storefront with a sandbox
key first.

## What you need

- Medusa v2 with `@medusajs/framework` **2.21 or later**, on Node 20.19 or later. The package does not install on an
  older backend: upgrade Medusa first. The official starter may still pin an older version.
- The backend repository and the right to deploy it, and an admin user who can edit regions.
- The storefront repository: it needs one redirect and a return page.
- Region currencies Mintage Pay supports: USD, EUR, GBP, CAD, AUD, CHF, JPY, MXN, BRL, INR, SGD, AED, PLN, SEK, NOK, DKK,
  NZD, ZAR, TRY, KRW, HKD, NGN, ARS, PHP, IDR, VND, PKR, EGP, BDT, KES, GHS, TZS, COP, CLP, PEN, UYU, PYG, BOB, DOP, GTQ, CRC, THB, MYR, TWD, LKR, NPR, KZT, UAH, ILS, SAR, QAR, CZK, HUF, RON, ISK, XAF or XOF. Your customers always pay in US dollar stablecoins: a price in another currency is
  converted when they check out, and the payment page shows both amounts.
- Your backend on **https**, reachable from the internet. Mintage Pay tells your backend about payments by calling
  `https://your-backend/hooks/payment/mintagepay_mintagepay`.
- A Mintage Pay account with at least one payout address set.

## Install

1. Download [medusa-payment-mintagepay.tgz](https://app.mintagepay.com/downloads/medusa-payment-mintagepay.tgz) and
   put it in your backend repository, for example in `vendor/`.
2. In the backend, run:

   ```
   npm install ./vendor/medusa-payment-mintagepay.tgz
   ```
3. Add the provider to `medusa-config.ts`:

   ```ts
   module.exports = defineConfig({
     // ...
     modules: [
       {
         resolve: "@medusajs/medusa/payment",
         options: {
           providers: [
             {
               resolve: "medusa-payment-mintagepay",
               id: "mintagepay",
               options: {
                 api_key: process.env.MINTAGEPAY_API_KEY,
                 webhook_secret: process.env.MINTAGEPAY_WEBHOOK_SECRET,
                 redirect_url: process.env.MINTAGEPAY_REDIRECT_URL,
               },
             },
           ],
         },
       },
     ],
   })
   ```
4. In the Mintage Pay dashboard, open **Developers > API keys** and create a key. Copy it: it is shown only once. Set it
   on the backend as `MINTAGEPAY_API_KEY`. Set `MINTAGEPAY_REDIRECT_URL` to your storefront's return page.

The key decides where the provider connects: a live key (`mp_live_...`) uses the live service and a test key
(`mp_test_...`) uses the [sandbox](https://mintagepay.com/docs/sandbox/).

## Connect the webhook

Medusa has no settings screen for payment providers, so a small command does this step. Run it in the backend folder:

```
MINTAGEPAY_API_KEY=mp_test_... npx mintagepay-medusa-webhook connect \
  --url https://your-backend/hooks/payment/mintagepay_mintagepay --out .env.mintagepay
```

The command checks the key, prints your business name and writes `MINTAGEPAY_WEBHOOK_SECRET` and the endpoint id to
`.env.mintagepay`. It never prints the secret. Copy the secret into your backend's secret store, keep the endpoint id
and delete the file. Then deploy the backend.

Run the command again for each key: the sandbox and the live service each have their own endpoint and secret.

## Turn it on in your regions

In Medusa Admin, go to **Settings > Regions > (your region) > Payment providers** and add **Mintage Pay**
(`pp_mintagepay_mintagepay`). Do this for every region that should offer it.

## Update the storefront

Start the payment session with the provider and send the customer to the payment page:

```ts
const { payment_collection } = await sdk.store.payment.initiatePaymentSession(cart, {
  provider_id: "pp_mintagepay_mintagepay",
  data: {
    redirect_url: `${origin}/checkout/mintagepay-return?cart_id=${cart.id}`,
    cancel_url: `${origin}/cart`,
  },
})
const session = payment_collection.payment_sessions.find((s) => s.provider_id === "pp_mintagepay_mintagepay")
window.location.href = session.data.checkout_url as string
```

On the return page, call `sdk.store.cart.complete(cartId)`.

- **An order comes back:** done.
- **"Not authorized" comes back:** the payment is still confirming. Show "payment confirming" and try again a little
  later. Medusa also completes the cart by itself when Mintage Pay reports the payment, so the order is not lost if the
  customer closes the page.

The session data your storefront can read holds no secret: the invoice id, the payment page address, the status and
the amounts.

## Try it first with test money

1. Create a free account at [sandbox.mintagepay.com](https://sandbox.mintagepay.com), set a payout address and
   create a key there.
2. Set the sandbox key on the backend, run the connect command with it and deploy.
3. Place an order in your storefront and choose Mintage Pay. You land on a payment page with a **Test mode** banner.
4. Pay on the payment page with free test money. The page has a button to get some.
5. Within a few minutes the cart is completed and the order has a captured payment, whether or not you returned to the
   storefront.
6. When you are happy, set your live key, run the connect command again with it, set the live webhook secret and
   deploy.

## What you will see

| What happened | In Medusa |
|---|---|
| Customer sent to pay | Payment session waiting for the customer |
| Payment seen, not final yet | Session pending, with a "payment detected" note |
| Paid in full | Payment captured, cart completed into an order |
| Paid, but the amount does not match the order | Not captured, with a note. Check it by hand |
| Part paid | Not captured, with a note saying how much is still owed |
| Payment window closed with nothing paid | Session in error. No order is created |
| Customer cancelled on the payment page | Session cancelled |
| Payment window closed with part paid | Not captured, with a note. Refund the customer or collect the rest |

A refund started in Medusa Admin is refused: Mintage Pay never holds your money, so it cannot send a refund for you.
Send the refund from your own wallet and record it in the Mintage Pay dashboard.

## If something is wrong

- **The backend log says `rejected: no_secret`:** `webhook_secret` is not set in the provider options.
- **The backend log says `rejected: invalid_signature`:** the secret is wrong (a sandbox secret with a live key, or the
  other way round), or something in front of the backend changed the request body.
- **The cart is not completed after a payment:** look at the webhook delivery in the Mintage Pay dashboard, then at the
  backend log. Medusa answers every webhook with 200, so the reason is in its log.
- **`npm install` is refused with ERESOLVE:** your Medusa is older than 2.21. Upgrade the backend first.

Your key and webhook secret are never written to the log.

## Remove the provider

1. Switch off the connection at Mintage Pay:

   ```
   MINTAGEPAY_API_KEY=... npx mintagepay-medusa-webhook disable --id (endpoint id)
   ```
2. Remove **Mintage Pay** from your regions and from `medusa-config.ts`, then deploy.
