Overview
Icon for Pay.

Pay.

Accept credit cards, digital and buy now pay later

Pay. payments for Medusa V2

Get access to 50+ payment options for online and in-store payments.


Getting Started

Donโ€™t have an account with Pay. yet? Register now!

[!CAUTION] If you have subscribers listening to the Copy to clipboardorder.placed event, update them to listen to the Copy to clipboardpayment.captured event instead. See below.
[!WARNING] This plugin creates orders in Medusa immediately, even if the payment has not yet been captured. If a payment expires, the associated order will be automatically canceled.



This change in flow is required to support asynchronous payment methods (e.g., SprayPay), where payment confirmation can take hours depending on customer input.

Table of Contents

  • Demo store
  • Pay. Payment Methods
  • Features
  • Prerequisites
  • Installation
  • Configuration
    • Configuration Options
    • Environment Variables
  • Usage
  • Supported Payment Methods
  • Client-Side Integration
    • Duplicate cart endpoint
    • Adding payment method icons
  • Extending the Plugin
  • Medusa v1 Support

Demo store

Visit the demo store here: https://pay-demo.webbers.com

Pay. payment methods

Card payment methods

  • Mastercard
  • VISA
  • American Express
  • Carte Bancaire
  • Maestro
  • PostePay
  • DanKort
  • Nexi
  • Visa Mastercard

Digital wallets

  • Apple Pay
  • Google Pay

Afterpay methods / Buy now pay later

  • iDEAL IN3
  • Billink
  • SprayPay
  • Riverty
  • Mondu
  • AlmaPAY
  • Klarna

Other

  • PayPal

Recurring payments

  • SEPA Direct Debit
  • Card-on-file / recurring card payments (tokenisation) [Coming soon]

Region specific payment methods

Payment Method Region(s) iDEAL ๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands Bancontact ๐Ÿ‡ง๐Ÿ‡ช Belgium Vipps ๐Ÿ‡ณ๐Ÿ‡ด Norway
๐Ÿ‡ธ๐Ÿ‡ช Sweden Bizum ๐Ÿ‡ช๐Ÿ‡ธ Spain Payconiq ๐Ÿ‡ง๐Ÿ‡ช Belgium
๐Ÿ‡ฑ๐Ÿ‡บ Luxembourg Twint ๐Ÿ‡จ๐Ÿ‡ญ Switzerland MB Way ๐Ÿ‡ต๐Ÿ‡น Portugal Pay By Bank ๐Ÿ‡ง๐Ÿ‡ช Belgium
๐Ÿ‡ฉ๐Ÿ‡ช Germany
๐Ÿ‡ฎ๐Ÿ‡น Italy
๐Ÿ‡ฑ๐Ÿ‡บ Luxembourg
๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands
๐Ÿ‡ช๐Ÿ‡ธ Spain WeChat Pay ๐Ÿ‡จ๐Ÿ‡ณ China Payment Method Region(s) Alipay Plus ๐Ÿ‡จ๐Ÿ‡ณ China
๐Ÿ‡ญ๐Ÿ‡ฐ Hong Kong MultiBanco ๐Ÿ‡ต๐Ÿ‡น Portugal Swish ๐Ÿ‡ธ๐Ÿ‡ช Sweden Satispay ๐Ÿ‡ฎ๐Ÿ‡น Italy Blik ๐Ÿ‡ต๐Ÿ‡ฑ Poland EPS ๐Ÿ‡ฆ๐Ÿ‡น Austria Przelewy24 ๐Ÿ‡ต๐Ÿ‡ฑ Poland MobilePay ๐Ÿ‡ฉ๐Ÿ‡ฐ Denmark
๐Ÿ‡ซ๐Ÿ‡ฎ Finland

InStore / SoftPOS payments

Would you like to integrate Pay. (Soft)P0S? Get in touch!

Features

  • Easily Extendable: The modular architecture makes it easy to add support for additional Pay payment methods.
  • Webhook Support: Full support for Pay webhooks for real-time payment status updates.
  • Automatic Capture: Configurable automatic capture of payments.

Prerequisites

  • Medusa server v2.20.1 or later
  • Node.js v20 or later
  • A Pay account and token & secret with payment methods enabled.

Installation

pnpm add @webbers/pay-payments-medusa

Configuration

Add the provider to the Copy to clipboard@medusajs/payment module in your Copy to clipboardmedusa-config.ts file & add it as plugin:

module.exports = defineConfig({
projectConfig: {
// ...
},
plugins: [
// ... other plugins
'@webbers/pay-payments-medusa'
],
modules: [
// ... other modules
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve: "@webbers/pay-payments-medusa/providers/pay",
id: "pay",
options: {
paymentDescription: "Your description", // optional
atCode: process.env.PAY_AT_CODE,

Configuration Options

[!NOTE] You can get the API token & secret from your Pay dashboard: click Settings > Click sales channel > Copy api tokens

Option Description Default Copy to clipboardatCode Your Pay AT code Required Copy to clipboardapiToken Your Pay API token Required Copy to clipboardslCode Your Pay sales channel code Required Copy to clipboardslSecret Your Pay sales channel secret Required Copy to clipboardreturnUrl The URL to return to after payment Required Copy to clipboardmedusaUrl The URL of your Medusa server Required Copy to clipboardtestMode Whether to enable test payments Optional Copy to clipboardtguApiUrl Pay TGU API Url Optional, use if you want to use a specific or private TGU, see here. Copy to clipboardotherSlCodes Your other Pay sales channel code and secrets Optional, used for webhook signature validation when using multiple Pay. sales channels. Format Copy to clipboard'{"SL-CODE-X":"secretX","SL-CODE-Y":"secretY"}'

Environment Variables

Create or update your Copy to clipboard.env file with the following variables:

PAY_AT_CODE="<your-pay-at-code>"
PAY_API_TOKEN="<your-pay-api-token>"
PAY_SL_CODE="<your-pay-sl-code>"
PAY_SL_SECRET="<your-pay-sl-secret>"
#PAY_TEST_MODE="true"
PAY_EXCHANGE_URL="https://your-store.com/checkout/payment"

Usage

Once installed and configured, the Pay payment methods will be available in your Medusa admin. To enable them, log in to you Medusa Admin, browse to Settings > Regions, add or edit a region and select the desired Pay providers from the dropdown.

Make sure that the selected payment methods are enabled in your Pay origanization settings as well.

Testing direct debits locally

Pay.'s test environment does not process SEPA direct debits. With Copy to clipboardtestMode enabled the plugin therefore does not create a mandate at Pay. when a direct debit order is placed. It stores a simulated mandate on the payment session instead (code Copy to clipboardTEST-<payment session id>, logged as Copy to clipboardPay. direct debit test mode: storing simulated mandate ...), and the collection flow can be driven by posting the exchanges Pay. would normally send to the webhook route yourself. Exchanges for a simulated mandate are not re-fetched from Pay., the body is taken as the direct debit state. This only happens while Copy to clipboardtestMode is on and only for mandates that were created in test mode.

# 1. Place an order with SEPA Direct Debit, the payment collection is now "awaiting".
# Copy the simulated mandate code from the server log.
MANDATE="TEST-payses_01JXXXXXXXXXXXXXXXXXXXXXXX"
# 2. The bank collected the money: the payment is captured and the order is paid.
curl -X POST http://localhost:9000/hooks/pay/pay-direct-debit_pay \
-H "Content-Type: application/json" \
-d "{\"action\":\"incassocollected\",\"mandateId\":\"$MANDATE\"}"
# 3. The customer reversed the debit (storno): the payment is refunded in Medusa only, the order
# shows the outstanding amount again and the admin offers "Copy payment link" / "Mark as paid".
curl -X POST http://localhost:9000/hooks/pay/pay-direct-debit_pay \
-H "Content-Type: application/json" \
-d "{\"action\":\"incassostorno\",\"mandateId\":\"$MANDATE\"}"

Other legacy actions (Copy to clipboardincassopending, Copy to clipboardincassosend) and an explicit Copy to clipboardstatus: {"code": 106} (failed) or Copy to clipboarddeclined: true work the same way. Add Copy to clipboard"reference": "<order display id>" to the body when the order can not be resolved from the mandate code. The webhook route processes exchanges asynchronously (5 seconds by default), so give the server a moment before checking the order. The payment link opens the regular Pay. hosted checkout, so the outstanding amount can be paid with any test payment method.

Reversed payments in the admin

When a chargeback or storno reverses a captured payment, the plugin refunds the payment in Medusa (without contacting Pay.), books the amount as outstanding again and opens a new payment collection, so the order offers "Copy payment link" and "Mark as paid". Medusa's own payment status can only call that "Refunded", so the plugin adds a "Payment reversed, action required" banner on top of the order page that names the cause (chargeback, storno, failed collection), the amount that went back to the customer and what is still outstanding.

Simulator widget in the admin

The same exchanges can be triggered from the order detail page. Switch on SEPA testing in Settings > Pay (off by default, meant for staging servers) and a "Pay. direct debit simulator" panel appears in the sidebar of orders that hold a simulated mandate, with a button per exchange (pending, sent, collected, storno, failed, declined). The exchanges are processed synchronously, so the order refreshes right away. The switch is stored in the store's metadata (Copy to clipboardpay_sepa_testing), so it applies to every admin user of that server. The panel is backed by Copy to clipboardGET /admin/pay/orders/:id/direct-debit and Copy to clipboardPOST /admin/pay/orders/:id/direct-debit/exchange (Copy to clipboard{"action": "storno"}), which only work while the switch and Copy to clipboardtestMode are on; the switch itself is read and written through Copy to clipboardGET/Copy to clipboardPOST /admin/pay/settings (Copy to clipboard{"sepaTesting": true}).

Supported Payment Methods

The plugin currently supports the following Pay payment methods:

Payment Method Provider ID Pay. hosted checkout Copy to clipboardpp_pay-hosted-checkout_pay Creditcards
Mastercard / VISA / American Express/ Carte Bancaire /
Maestro / PostePay / DanKort / Nexi / Visa Mastercard Copy to clipboardpp_pay-creditcard-group_pay Card-on-file / recurring card payments (tokenisation) Coming soon Apple Pay Copy to clipboardpp_pay-apple-pay_pay Google Pay Copy to clipboardpp_pay-google-pay_pay iDEAL IN3 Copy to clipboardpp_pay-ideal-in3_pay Billink Copy to clipboardpp_pay-billink_pay SprayPay Copy to clipboardpp_pay-spraypay_pay Riverty Copy to clipboardpp_pay-riverty_pay Mondu Copy to clipboardpp_pay-mondu_pay AlmaPAY Copy to clipboardpp_pay-almapay_pay Klarna Copy to clipboardpp_pay-klarna_pay PayPal Copy to clipboardpp_pay-paypal_pay SEPA Direct Debit Copy to clipboardpp_pay-direct-debit_pay iDEAL Copy to clipboardpp_pay-ideal_pay Bancontact Copy to clipboardpp_pay-bancontact_pay Vipps Get in touch Bizum Get in touch Payconiq Copy to clipboardpp_pay-payconiq_pay Twint Copy to clipboardpp_pay-twint_pay MB Way Get in touch Pay by Bank Copy to clipboardpp_pay-paybybank_pay WeChat Pay Copy to clipboardpay-wechatpay AliPay Plus Get in touch MultiBanco Get in touch Swish Get in touch Satispay Get in touch Blik Copy to clipboardpp_pay-blik_pay EPS Copy to clipboardpp_pay-eps_pay Przelewy24 Copy to clipboardpp_przelewy24_pay MobilePAY Copy to clipboardpp_pay-mobilepay_pay SoftPOS Copy to clipboardpp_pay-softpos_pay Gift Card Copy to clipboardpp_pay-giftcard_pay

Client-Side Integration

To integrate with your storefront, you'll need to implement the payment flow according to Pay's and Medusa's documentation. Here's a basic example:

  1. Create a payment session in your checkout flow
  2. Redirect the customer to the Pay payment page
  3. Handle the webhook notifications to update the payment status

Example integration using the Medusa Next.js Starter:

https://github.com/user-attachments/assets/742ee261-5e41-4e33-9a72-faf1a424fc52

Duplicate cart endpoint

[!TIP] Use the duplicate cart endpoint in your storefront



When a customer cancels a payment or returns to the storefront without completing the Pay. checkout, a new duplicate cart should automatically be created. This allows the customer to easily start a new transaction without losing the items they had selected.

API Route: Copy to clipboardGET /store/carts/:id/duplicate

Alter your storefront retrieve cart function(s) and check if the returned cart.completed_at value is set. If so request a new cart with the duplicate cart endpoint and update cart id in cookies accordingly.

The duplicate cart endpoint is idempotent, so it can be called multiple times with the same cart id.

Adding payment method icons

  1. Download the latest payment images from here: https://github.com/paynl/payment-images
  2. Add these to your storefront public assets
  3. In your checkout, create the mapping from the provider id to the icon.
  4. You can also utilize the exported Copy to clipboardpayPaymentMethods from this plugin to find the corresponding ID.
  5. I.e. Copy to clipboardconst paymentMethodData = payPaymentMethods.find(method => `pp_${method.value}_pay` === provider_id)

Extending the Plugin

To add one of the missing Pay payment methods, create a new service in Copy to clipboardsrc/providers/Pay/services that extends the Copy to clipboardPayBase class:

import {PaymentMethod} from "@Pay/api-client";
import PayBase from "../core/Pay-base";
import {PaymentOptions, PaymentProviderKeys} from "../types";
class PayNewMethodService extends PayBase {
static identifier = "Pay-new-method";
get paymentCreateOptions(): PaymentOptions {
return {
method: PaymentMethod.newMethod,
};
}
}
export default PayNewMethodService;

Make sure to replace Copy to clipboardnew method with the actual Pay payment method ID.

Export your new service from Copy to clipboardsrc/providers/Pay/services/index.ts. Then add your new service to the list of services in Copy to clipboardsrc/providers/Pay/index.ts.

We will be working on providing all the available Pay. options in the near future.

Medusa v1 Support

Searching for support for Medusa v1, we have a legacy plugin available. Get in touch

You may also like

Browse all integrations

Build your own

Develop your own custom integration

Build your own integration with our API to speed up your processes. Make your integration available via npm for it to be shared in our Library with the broader Medusa community.

gift card interface

Ready to build your custom commerce setup?