Overview
Icon for Przelewy24

Przelewy24

Support payments via Przelewy24

A comprehensive Przelewy24 payment provider plugin for Medusa.js 2.0 — BLIK, cards, Visa Mobile, and general P24 redirect flows.

Features • Installation • Configuration • Usage • Payment Flows • Webhooks

Features

  • ✅ Multiple Payment Methods — BLIK (regular and one-click), credit/debit cards, Visa Mobile, bank transfers, and general P24 redirect
  • ✅ White-Label Integration — Card 2.0 iframe and BLIK charge flows without leaving your storefront
  • ✅ Webhook Support — Real-time payment status updates with signature verification
  • ✅ Modular Architecture — Multiple services in a single module provider for easy management
  • ✅ TypeScript — Full TypeScript implementation with proper types
  • ✅ Sandbox Mode — Built-in sandbox support for testing
[!WARNING] This plugin has not been tested on a live store. Please conduct thorough testing before using it in a production environment. gmi.software is not responsible for any missed or failed payments resulting from the use of this plugin. If you encounter any issues, please report them on the GitHub issues tracker.

Prerequisites

  • Medusa server v2.4.0 or later
  • Node.js v20 or later
  • A Przelewy24 merchant account with API credentials
[!NOTE] You can get your API credentials from your Przelewy24 merchant panel.

Installation

yarn add @gmisoftware/przelewy24-payments-medusa

Configuration

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

import { Modules } from "@medusajs/framework/utils";
module.exports = defineConfig({
modules: [
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve:
"@gmisoftware/przelewy24-payments-medusa/providers/przelewy24",
id: "przelewy24",
options: {
api_key: process.env.P24_API_KEY,
merchant_id: process.env.P24_MERCHANT_ID,
pos_id: process.env.P24_POS_ID,
crc: process.env.P24_CRC,
sandbox: process.env.P24_IS_SANDBOX,
frontend_url: process.env.MEDUSA_STORE_URL,
backend_url: process.env.MEDUSA_BACKEND_URL,

Configuration Options

Option Description Required Default Copy to clipboardmerchant_id P24 Merchant ID Yes - Copy to clipboardpos_id P24 POS ID Yes - Copy to clipboardapi_key P24 API Key Yes - Copy to clipboardcrc P24 CRC Key for signature verification Yes - Copy to clipboardsandbox Enable sandbox mode (Copy to clipboardtrue/Copy to clipboardfalse or Copy to clipboard"true"/Copy to clipboard"false") No Copy to clipboardfalse Copy to clipboardcard_channel P24 channel for card-only registration No Copy to clipboard4096 Copy to clipboardvisa_mobile_method_id P24 method id for Visa Mobile No Copy to clipboard198 Copy to clipboardfrontend_url Frontend URL for customer redirects Yes - Copy to clipboardbackend_url Backend URL for webhook notifications Yes -

Environment Variables

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

# P24 Configuration
P24_MERCHANT_ID=your_merchant_id
P24_POS_ID=your_pos_id
P24_API_KEY=your_api_key
P24_CRC=your_crc_key
# URL Configuration (use the same names as medusa-config.ts)
MEDUSA_STORE_URL=https://your-frontend-domain.com
MEDUSA_BACKEND_URL=https://your-backend-domain.com # public HTTPS — P24 webhooks hit this host
P24_IS_SANDBOX=false

Usage

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

Make sure that the selected payment methods are enabled in your Przelewy24 merchant panel as well.

Client-Side Integration

To integrate with your storefront, implement the payment flow according to Przelewy24's and Medusa's documentation. Here are basic examples:

BLIK Payment

BLIK payments use a two-phase flow:

Phase 1: Create Payment Session

// Create payment session for BLIK
const paymentSession = await medusa.payment.createPaymentSession({
provider_id: "pp_p24-blik_przelewy24",
amount: 10000, // 100.00 PLN in grosze
currency_code: "PLN",
data: {
country: "PL", // Country code (defaults to "PL" if not provided)
language: "pl", // Language code (defaults to "pl" if not provided)
},
context: {
email: "customer@example.com",
},
});
// Response includes session_id and token, but no redirect_url
console.log(paymentSession.data.session_id); // Use this for BLIK processing

Phase 2: Process BLIK Code

// Store API (publishable key + cart context)
const blikResponse = await fetch("/store/payments/blik/charge", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-publishable-api-key": publishableKey,
},
body: JSON.stringify({
token: paymentSession.data.token,
blikCode: "123456",
payment_session_id: paymentSession.id,
}),
});
Legacy route Copy to clipboardPOST /payments/blik remains as a deprecated wrapper.

Card Payment 2.0 (white-label iframe)

Per P24 Card Payment 2.0 docs:

// 1) Create payment session — returns tokenization fields (no transaction register yet)
const { payment_session } = await medusa.store.cart.createPaymentSession(cartId, {
provider_id: "pp_p24-cards_przelewy24",
});
const { merchant_id, session_id, card_tokenization_sign } =
payment_session.data;
// 2) Load P24 Card 2.0 SDK and render iframe
// https://{sandbox|secure}.przelewy24.pl/js/cardTokenizationIframe.min.js
// new Przelewy24CardTokenization(merchant_id, session_id, card_tokenization_sign)
// .render("form", "#container", { lang: "pl", ... })
// On Pay: .tokenize("temporary") → listen for postMessage success with data.refId
// 3) Register with refId, then run P24 whitelabel charge script in the browser
const cardResponse = await fetch("/store/payments/card/charge", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-publishable-api-key": publishableKey,

Visa Mobile (redirect with pre-selected method)

// 1) Create payment session — returns redirect_url with method 198 pre-selected
const { payment_session } = await medusa.store.cart.createPaymentSession(cartId, {
provider_id: "pp_p24-visa-mobile_przelewy24",
});
// 2) Redirect customer to P24 Visa Mobile flow
window.location.href = payment_session.data.redirect_url;
// 3) Customer completes payment on P24 hosted page
// 4) Return to frontend_url / return_url
// 5) Capture happens via P24 webhook to urlStatus (not via a separate confirm API)

Transaction status (poll timeout fallback)

await fetch("/store/payments/transaction/status", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
session_id: payment_session.data.session_id,
provider_id: payment_session.provider_id,
}),
});

General P24 Payment

// Create payment session for general P24
const paymentSession = await medusa.payment.createPaymentSession({
provider_id: "pp_p24-provider_przelewy24",
amount: 10000,
currency_code: "PLN",
data: {
country: "PL", // Country code (defaults to "PL" if not provided)
language: "pl", // Language code (defaults to "pl" if not provided)
},
context: {
email: "customer@example.com",
},
});
// Redirect user to P24 payment selection
window.location.href = paymentSession.data.redirect_url;

Supported Payment Methods

Payment Method Provider ID Notes BLIK Copy to clipboardpp_p24-blik_przelewy24 White-label, channel 64 Cards Copy to clipboardpp_p24-cards_przelewy24 White-label iframe, channel 4096 Visa Mobile Copy to clipboardpp_p24-visa-mobile_przelewy24 Redirect with method Copy to clipboard198 General P24 Copy to clipboardpp_p24-provider_przelewy24 Redirect to P24 method picker

Payment Flows

1. BLIK (white-label)

  1. Create payment session (Copy to clipboardinitiatePayment) → Copy to clipboardtoken, Copy to clipboardsession_id
  2. Customer enters 6-digit BLIK code
  3. Copy to clipboardPOST /store/payments/blik/charge → P24 Copy to clipboardchargeByCode
  4. Customer approves in banking app
  5. Storefront polls Copy to clipboardPOST /store/carts/{id}/complete until order is created
  6. P24 sends webhook to Copy to clipboardurlStatus → verify + capture → Copy to clipboardpayment_collection.completed_at

BLIK does not use redirect URLs.

Copy to clipboardurlStatus is only called for paid transactions. When the customer rejects the payment in the banking app (or the bank declines it), P24 sends the additional BLIK notification to Copy to clipboard{backend_url}/hooks/p24/blik-notification; the plugin then marks the payment session Copy to clipboarderror, so Copy to clipboardcomplete fails and Copy to clipboardPOST /store/payments/transaction/status returns Copy to clipboardstatus: "error" instead of a pending payment. A BLIK token is single-use: Copy to clipboardPOST /store/payments/blik/charge rejects sessions that were already charged or are no longer Copy to clipboardpending/Copy to clipboardrequires_more — create a new payment session to retry. Charges and rejection notifications for one session are serialised through the Medusa Locking Module; with several Medusa instances, configure a shared locking provider such as Copy to clipboard@medusajs/medusa/locking-redis.

2. Cards (P24 Card 2.0 white-label)

  1. Create payment session → tokenization fields only (Copy to clipboardmerchant_id, Copy to clipboardsession_id, Copy to clipboardcard_tokenization_sign, Copy to clipboardamount_grosze) — no Copy to clipboardtransaction/register yet
  2. Load Copy to clipboardcardTokenizationIframe.min.js, render iframe; regulation checkbox is inside the P24 widget
  3. On Pay: Copy to clipboardtokenize("temporary") → Copy to clipboardrefId via postMessage
  4. Copy to clipboardPOST /store/payments/card/charge with Copy to clipboardref_id → Copy to clipboardtransaction/register + returns Copy to clipboardchargeScriptUrl
  5. Browser runs Copy to clipboardPrzelewy24CardWhileLabelHandler (3DS in merchant UI)
  6. Storefront polls Copy to clipboardPOST /store/carts/{id}/complete until order is created
  7. P24 webhook → verify + capture

See P24 Card 2.0 docs.

3. Visa Mobile (redirect with pre-selected method)

  1. Create payment session → Copy to clipboardredirect_url (P24 Copy to clipboardtransaction/register includes Copy to clipboardmethod: 198)
  2. Customer pays on P24 hosted Visa Mobile page
  3. Return to Copy to clipboardfrontend_url / Copy to clipboardreturn_url
  4. Webhook confirms payment

4. General P24 (redirect)

  1. Create payment session → Copy to clipboardredirect_url
  2. Customer pays on P24 hosted page
  3. Return to Copy to clipboardfrontend_url / Copy to clipboardreturn_url
  4. Webhook confirms payment

Completion model (all white-label methods)

Step Mechanism Order creation Storefront polls Copy to clipboardcomplete; Medusa Copy to clipboardauthorizePayment queries P24 API; or Copy to clipboardcomplete-captured-carts-backstop (Medusa, every 2 min) when payment is already captured Capture + Copy to clipboardcompleted_at P24 webhook to Copy to clipboardurlStatus, or Copy to clipboardreconcile-p24-payments job (every 5 min)

Copy to clipboardauthorizePayment rejects a transaction with Copy to clipboardamount_mismatch when the amount P24 reports for it differs from the registered Copy to clipboardamount_grosze. Copy to clipboardreconcile-p24-payments skips such sessions.

Do not add a separate store “confirm” route to replace webhooks. Production capture is webhook-driven.

Webhook Configuration

Webhook URLs (auto-registered)

On each Copy to clipboardtransaction/register, the plugin sets:

urlStatus: {backend_url}/hooks/payment/{service-identifier}_{payment-module-id}

With Copy to clipboardpayment-module config Copy to clipboardid: "przelewy24" (recommended):

Method Webhook URL Cards Copy to clipboard{backend_url}/hooks/payment/p24-cards_przelewy24 BLIK Copy to clipboard{backend_url}/hooks/payment/p24-blik_przelewy24 Visa Mobile Copy to clipboard{backend_url}/hooks/payment/p24-visa-mobile_przelewy24 General Copy to clipboard{backend_url}/hooks/payment/p24-provider_przelewy24

Medusa route: Copy to clipboardPOST /hooks/payment/:provider → Copy to clipboardgetWebhookActionAndData → Copy to clipboardprocessPaymentWorkflow.

A notification whose paid Copy to clipboardamount differs from the registered Copy to clipboardoriginAmount is not verified with P24 and returns Copy to clipboardFAILED (logged with both amounts). The customer keeps the money, because P24 does not settle unverified transactions.

BLIK transactions additionally register:

urlCardPaymentNotification: {backend_url}/hooks/p24/blik-notification

The route accepts only P24 webhook IPs (plus localhost in sandbox) and a valid Copy to clipboardsign, and acts only on rejections for the session's current P24 registration while it is still pending and P24 does not report it as paid. Successful payments keep going through Copy to clipboardurlStatus.

  • Copy to clipboardbackend_url must be the Medusa server URL (public HTTPS in production), not the storefront.
  • Webhooks are registered per transaction; you do not paste these into the P24 panel for white-label flows.
  • Return URL for redirects: Copy to clipboard{frontend_url} (and cart-specific Copy to clipboardreturn_url in session data).

Production checklist

  • Copy to clipboardbackend_url reachable from the internet (P24 cannot call Copy to clipboardlocalhost)
  • Copy to clipboardcrc matches merchant panel (signature verification)
  • P24 webhook source IPs allowed (see Copy to clipboardP24_WEBHOOK_ALLOWED_IPS; sandbox allows localhost)
  • Region enables correct Copy to clipboardpp_p24-*_przelewy24 providers

Local development

Without a tunnel, webhooks will not arrive. Orders may still be created via poll Copy to clipboardcomplete, but Copy to clipboardpayment_collection can remain Copy to clipboardnot_paid until the reconcile job runs (~5 min). Use ngrok + public Copy to clipboardbackend_url for full webhook testing.

yarn build && yalc publish --push

Extending the Plugin

To add support for additional Przelewy24 payment methods, create a new service in Copy to clipboardsrc/providers/przelewy24/services that extends the Copy to clipboardP24Base class:

import P24Base from "../core/p24-base";
import { PaymentOptions } from "../types";
class P24NewMethodService extends P24Base {
static identifier = "p24-new-method";
get paymentCreateOptions(): PaymentOptions {
return {
method: "new_method",
};
}
}
export default P24NewMethodService;

Make sure to replace Copy to clipboardnew_method with the actual Przelewy24 payment method ID.

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

Local Development

To customize and test the plugin locally, refer to the Medusa Plugin docs.

yarn install
yarn build
yarn test

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

License

MIT © gmi.software

Made with ❤️ by gmi.software

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?