Поддержка платежей через 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
[!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.
[!NOTE] You can get your API credentials from your Przelewy24 merchant panel.
Add the provider to the module in your file:
| Option | Description | Required | Default |
|---|---|---|---|
| P24 Merchant ID | Yes | - | |
| P24 POS ID | Yes | - | |
| P24 API Key | Yes | - | |
| P24 CRC Key for signature verification | Yes | - | |
| Enable sandbox mode (/ or /) | No | ||
| P24 channel for card-only registration | No | ||
| P24 method id for Visa Mobile | No | ||
| Frontend URL for customer redirects | Yes | - | |
| Backend URL for webhook notifications | Yes | - |
Create or update your file with the following variables:
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.
To integrate with your storefront, implement the payment flow according to Przelewy24's and Medusa's documentation. Here are basic examples:
BLIK payments use a two-phase flow:
Phase 1: Create Payment Session
Phase 2: Process BLIK Code
Legacy route remains as a deprecated wrapper.
Per P24 Card Payment 2.0 docs:
| Payment Method | Provider ID | Notes |
|---|---|---|
| BLIK | White-label, channel 64 | |
| Cards | White-label iframe, channel 4096 | |
| Visa Mobile | Redirect with method | |
| General P24 | Redirect to P24 method picker |
BLIK does not use redirect URLs.
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 ; the plugin then marks the payment session , so fails and returns instead of a pending payment. A BLIK token is single-use: rejects sessions that were already charged or are no longer / — 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 .
See P24 Card 2.0 docs.
| Step | Mechanism |
|---|---|
| Order creation | Storefront polls ; Medusa queries P24 API; or (Medusa, every 2 min) when payment is already captured |
| Capture + | P24 webhook to , or job (every 5 min) |
rejects a transaction with when the amount P24 reports for it differs from the registered . skips such sessions.
Do not add a separate store “confirm” route to replace webhooks. Production capture is webhook-driven.
On each , the plugin sets:
urlStatus: {backend_url}/hooks/payment/{service-identifier}_{payment-module-id}With config (recommended):
| Method | Webhook URL |
|---|---|
| Cards | |
| BLIK | |
| Visa Mobile | |
| General |
Medusa route: → → .
A notification whose paid differs from the registered is not verified with P24 and returns (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-notificationThe route accepts only P24 webhook IPs (plus localhost in sandbox) and a valid , 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 .
Without a tunnel, webhooks will not arrive. Orders may still be created via poll , but can remain until the reconcile job runs (~5 min). Use ngrok + public for full webhook testing.
To add support for additional Przelewy24 payment methods, create a new service in that extends the class:
Make sure to replace with the actual Przelewy24 payment method ID.
Export your new service from . Then add your new service to the list of services in .
To customize and test the plugin locally, refer to the Medusa Plugin docs.
MIT © gmi.software
Made with ❤️ by gmi.software
yarn add @gmisoftware/przelewy24-payments-medusa1import { Modules } from "@medusajs/framework/utils";2
3module.exports = defineConfig({4 modules: [5 {6 resolve: "@medusajs/medusa/payment",7 options: {8 providers: [9 {10 resolve:11 "@gmisoftware/przelewy24-payments-medusa/providers/przelewy24",12 id: "przelewy24",13 options: {14 api_key: process.env.P24_API_KEY,15 merchant_id: process.env.P24_MERCHANT_ID,16 pos_id: process.env.P24_POS_ID,17 crc: process.env.P24_CRC,18 sandbox: process.env.P24_IS_SANDBOX,19 frontend_url: process.env.MEDUSA_STORE_URL,20 backend_url: process.env.MEDUSA_BACKEND_URL,21 },22 },23 ],24 },25 },26 ],27 plugins: ["@gmisoftware/przelewy24-payments-medusa"],28});1# P24 Configuration2P24_MERCHANT_ID=your_merchant_id3P24_POS_ID=your_pos_id4P24_API_KEY=your_api_key5P24_CRC=your_crc_key6
7# URL Configuration (use the same names as medusa-config.ts)8MEDUSA_STORE_URL=https://your-frontend-domain.com9MEDUSA_BACKEND_URL=https://your-backend-domain.com # public HTTPS — P24 webhooks hit this host10P24_IS_SANDBOX=false1// Create payment session for BLIK2const paymentSession = await medusa.payment.createPaymentSession({3 provider_id: "pp_p24-blik_przelewy24",4 amount: 10000, // 100.00 PLN in grosze5 currency_code: "PLN",6 data: {7 country: "PL", // Country code (defaults to "PL" if not provided)8 language: "pl", // Language code (defaults to "pl" if not provided)9 },10 context: {11 email: "customer@example.com",12 },13});14
15// Response includes session_id and token, but no redirect_url16console.log(paymentSession.data.session_id); // Use this for BLIK processing1// Store API (publishable key + cart context)2const blikResponse = await fetch("/store/payments/blik/charge", {3 method: "POST",4 headers: {5 "Content-Type": "application/json",6 "x-publishable-api-key": publishableKey,7 },8 body: JSON.stringify({9 token: paymentSession.data.token,10 blikCode: "123456",11 payment_session_id: paymentSession.id,12 }),13});1// 1) Create payment session — returns tokenization fields (no transaction register yet)2const { payment_session } = await medusa.store.cart.createPaymentSession(cartId, {3 provider_id: "pp_p24-cards_przelewy24",4});5
6const { merchant_id, session_id, card_tokenization_sign } =7 payment_session.data;8
9// 2) Load P24 Card 2.0 SDK and render iframe10// https://{sandbox|secure}.przelewy24.pl/js/cardTokenizationIframe.min.js11// new Przelewy24CardTokenization(merchant_id, session_id, card_tokenization_sign)12// .render("form", "#container", { lang: "pl", ... })13// On Pay: .tokenize("temporary") → listen for postMessage success with data.refId14
15// 3) Register with refId, then run P24 whitelabel charge script in the browser16const cardResponse = await fetch("/store/payments/card/charge", {17 method: "POST",18 headers: {19 "Content-Type": "application/json",20 "x-publishable-api-key": publishableKey,21 },22 body: JSON.stringify({23 ref_id: refIdFromTokenization,24 payment_session_id: payment_session.id,25 }),26});27
28const { token, chargeScriptUrl } = await cardResponse.json();29// 4) Load chargeScriptUrl and run Przelewy24CardWhileLabelHandler (3DS in merchant modal)30// 5) Poll POST /store/carts/{cartId}/complete until type === "order"31// 6) Capture happens via P24 webhook to urlStatus (not via a separate confirm API)1// 1) Create payment session — returns redirect_url with method 198 pre-selected2const { payment_session } = await medusa.store.cart.createPaymentSession(cartId, {3 provider_id: "pp_p24-visa-mobile_przelewy24",4});5
6// 2) Redirect customer to P24 Visa Mobile flow7window.location.href = payment_session.data.redirect_url;8
9// 3) Customer completes payment on P24 hosted page10// 4) Return to frontend_url / return_url11// 5) Capture happens via P24 webhook to urlStatus (not via a separate confirm API)1await fetch("/store/payments/transaction/status", {2 method: "POST",3 headers: { "Content-Type": "application/json" },4 body: JSON.stringify({5 session_id: payment_session.data.session_id,6 provider_id: payment_session.provider_id,7 }),8});1// Create payment session for general P242const paymentSession = await medusa.payment.createPaymentSession({3 provider_id: "pp_p24-provider_przelewy24",4 amount: 10000,5 currency_code: "PLN",6 data: {7 country: "PL", // Country code (defaults to "PL" if not provided)8 language: "pl", // Language code (defaults to "pl" if not provided)9 },10 context: {11 email: "customer@example.com",12 },13});14
15// Redirect user to P24 payment selection16window.location.href = paymentSession.data.redirect_url;yarn build && yalc publish --push1import P24Base from "../core/p24-base";2import { PaymentOptions } from "../types";3
4class P24NewMethodService extends P24Base {5 static identifier = "p24-new-method";6
7 get paymentCreateOptions(): PaymentOptions {8 return {9 method: "new_method",10 };11 }12}13
14export default P24NewMethodService;1yarn install2yarn build3yarn test