<!-- canonical: https://docs.costplus.io/docs/plugins/medusa -->

> Integrate Cost+ with your Medusa.js v2 store using the official costplus-medusa plugin
<div style={{background: 'white', borderRadius: '8px', padding: '1rem', display: 'inline-block', marginBottom: '1rem'}}>
  <img src="/images/logos/medusa.png" alt="Medusa.js" style={{height: '48px', width: 'auto'}} />
</div>

Integrate Cost+ as a payment provider in your Medusa.js v2 store. The official **`costplus-medusa`** plugin registers a separate Medusa payment provider for each Cost+ payment method, uses the Hosted Payment Page flow, and verifies every status change against the Cost+ API — fully PCI DSS compliant.

## Prerequisites

- Active Cost+ merchant account
- Medusa.js `2.14.2` or later
- Node.js `22`
- PostgreSQL access through Medusa's `DATABASE_URL`
- A public HTTPS Medusa backend URL (required for webhooks)
- A storefront checkout that passes `cart_id` when initiating the payment session

## Supported Payment Methods

| Checkout Label | Medusa Provider Id | Cost+ Identifier |
|---|---|---|
| Credit / Debit Card | `pp_costplus-credit-card_costplus` | `credit-card` |
| Apple Pay | `pp_costplus-apple-pay_costplus` | `apple-pay` |
| Google Pay | `pp_costplus-google-pay_costplus` | `google-pay` |
| Vipps MobilePay | `pp_costplus-vipps-mobilepay_costplus` | `vipps-mobilepay` |

Each provider creates a Cost+ order for exactly one payment method, so the storefront checkout options stay aligned with the Cost+ hosted payment redirect flow.

## 1. Install the Plugin

Install the package in your Medusa backend project:

```bash
npm install costplus-medusa
```

> [!TIP]
> You can also install from a tagged Git release: `npm install git+ssh://git@github.com:NoPayn/costplus-medusa.git#vX.Y.Z`

## 2. Register the Plugin and Provider

Add the plugin and payment provider to your `medusa-config.ts`:

```ts

module.exports = defineConfig({
  plugins: [
    {
      resolve: "costplus-medusa",
      options: {},
    },
  ],
  modules: [
    {
      resolve: "@medusajs/medusa/payment",
      options: {
        providers: [
          {
            resolve: "costplus-medusa/providers/costplus",
            id: "costplus",
            options: {},
          },
        ],
      },
    },
  ],
})
```

Restart the Medusa backend so the new plugin and provider are loaded.

## 3. Configure in Admin

In the Medusa Admin, open **Cost+** (route `/app/costplus`) and configure:

- **API Key** — your merchant API key from the [Merchant Portal](https://dashboard.costplus.io/) (**Websites → your website → Integration**)
- **Checkout Expiry** — timeout in minutes (sent to Cost+ as ISO-8601 duration, e.g. `5` → `PT5M`, default is 5 minutes)
- **Manual Capture** — optional, authorize card payments without immediate capture
- **Debug Logging** — optional, log API requests and responses for troubleshooting

On the same **Cost+** Admin page, enable the checkout methods you want to expose for each region:

- `Cost+ Credit / Debit Card`
- `Cost+ Apple Pay`
- `Cost+ Google Pay`
- `Cost+ Vipps MobilePay`

> [!WARNING]
> Only activate the payment methods you have been approved for and received confirmation for.

> [!NOTE]
> Toggling methods on this page updates Medusa's region payment-provider links for Cost+ providers, so the standard Store API endpoint `/store/payment-providers?region_id=...` returns only the enabled Cost+ methods. If all Cost+ methods are disabled for a region, the Store API returns no Cost+ providers for that region.

## 4. Storefront Integration

When initiating a Cost+ payment session, pass the Medusa `cart_id` in `data`:

```ts
await sdk.store.payment.initiatePaymentSession(paymentCollection.id, {
  provider_id: "pp_costplus-credit-card_costplus",
  data: {
    cart_id: cart.id,
    return_url: `${origin}/${countryCode}/checkout/costplus/return?cart_id=${cart.id}`,
    failure_url: `${origin}/${countryCode}/checkout?step=payment&costplus_cancelled=1&cart_id=${cart.id}`,
  },
})
```

After the session is created, redirect the shopper to `payment_session.data.costplus_payment_url`.

The default success route is `/checkout/costplus/return?cart_id={cart_id}` — that page should call Medusa's complete-cart endpoint and then show the order confirmation page. The failure URL should return the shopper to the payment step without completing the cart.

> [!NOTE]
> Return and webhook handling always verifies the Cost+ order with `GET /orders/{id}/` before mapping the payment state back to Medusa.

### List Payment Methods from the Store API

List payment methods from Medusa's Store API instead of hardcoding the Cost+ provider IDs in your storefront. If the storefront caches `/store/payment-providers`, use `no-store` or invalidate the cache after Admin method changes so disabled methods disappear from checkout immediately.

> [!TIP]
> For a reference implementation, see `examples/nextjs/costplus-return/` in the [costplus-medusa repository](https://github.com/NoPayn/costplus-medusa) — a Next.js DTC starter return page that pairs a client handler with a server action so cart completion runs outside the page render and reliably redirects to `/order/{id}/confirmed`.

## 5. Webhooks

The plugin automatically registers a webhook endpoint at `/hooks/payment/{identifier}_costplus`. The Medusa backend URL must be publicly reachable over HTTPS so Cost+ can deliver asynchronous status updates.

If orders remain pending long after the configured expiry, that points to a webhook delivery or return-flow problem rather than the expiry setting itself.

## 6. Security & Settings Storage

The API key is encrypted before it is stored in the Medusa database. Set `COSTPLUS_SETTINGS_SECRET` in production:

```bash
COSTPLUS_SETTINGS_SECRET=your-strong-secret
```

If `COSTPLUS_SETTINGS_SECRET` is not set, the plugin falls back to Medusa's `COOKIE_SECRET` or `JWT_SECRET`.

> [!TIP]
> Set `COSTPLUS_API_KEY` in the environment to lock the API key source — when set, the Admin page will not overwrite it. Leave it empty when merchants should manage the key in Admin.

## 7. Test and Launch

The Cost+ environment is determined by the API key — a sandbox website key runs in test mode, a production website key runs in live mode (no separate sandbox URL). Place a few test transactions to verify successful, cancelled, and expired flows before going live.

## Support

Need help? Reach out to our support team at **support@costplus.io**.
