Support payments via Przelewy24
A comprehensive payment provider plugin that enables Przelewy24 payments on Medusa V2 projects.
Multiple Payment Methods: Supports a wide range of Przelewy24 payment methods including:
Modular Architecture: Multiple services in a single module provider for easy management.
Webhook Support: Full support for Przelewy24 webhooks for real-time payment status updates.
TypeScript Support: 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 here.
[!NOTE] > You can get your API credentials from your Przelewy24 merchant panel
yarn add p24-medusa-pluginAdd the provider to the module in your file:
1import { 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});| 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 | No | ||
| Backend URL for webhook notifications | No |
Create or update your file with the following variables:
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=falseOnce 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, you'll need to implement the payment flow according to Przelewy24's and Medusa's documentation. Here's a basic example:
BLIK payments use a two-phase flow:
Phase 1: Create Payment Session
1// 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 processingPhase 2: Process BLIK Code
1// 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});Legacy route remains as a deprecated wrapper.
Per P24 Card Payment 2.0 docs:
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;The plugin currently supports the following Przelewy24 payment methods:
| 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.
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) |
Do not add a separate store “confirm” route to replace webhooks. Production capture is webhook-driven.
Body Chief monorepo: see and .
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: → → .
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.
cd P24-package && yarn build && yalc publish --push # Body Chief: update Medusa plugin| Document | Audience |
|---|---|
| Medusa/docs/P24_PAYMENTS.md | Backend / ops runbook |
| Medusa/docs/adr/0009-p24-white-label-payments.md | Architecture decisions |
| Medusa/CART_PAYMENTS_AND_DISCOUNTS_GUIDE.md | Store API integration |
| web/docs/P24_CHECKOUT.md | Next.js storefront |
To add support for additional Przelewy24 payment methods, create a new service in that extends the class:
1import 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;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 .
In case you want to customize and test the plugin locally, refer to the Medusa Plugin docs.
MIT License