Skip to content

P24 Bank Element

The P24 Bank Element displays a dropdown of Polish banks for Przelewy24 payments. Przelewy24 (P24) is one of the most popular payment methods in Poland, supporting online bank transfers and BLIK.

Poland Only

Przelewy24 is exclusively for Polish bank customers. For other European countries, see iDEAL (Netherlands), EPS (Austria), or IBAN Element (SEPA).

Why Use Przelewy24?

FeatureBenefit
Wide CoverageSupports 24+ Polish banks
BLIK SupportIncludes BLIK mobile payments
Multi-CurrencySupports both EUR and PLN
Instant ConfirmationReal-time payment notification

When to Use P24 Element

ScenarioDescription
Polish customersPrimary payment method in Poland
E-commerceOnline purchases by Polish shoppers
PLN or EURSupports both Polish Złoty and Euro

How It Works

mermaid
flowchart TD
    A["P24BankElement mounts inside StripeElements"] --> B["Renders bank dropdown<br/>Emits @ready when mounted"]
    B --> C["Customer selects their bank<br/>• mBank, PKO, ING, BLIK, etc."]
    C --> D["Emits @change with selected bank code<br/>{ complete, value }"]
    D --> E["On submit: Create P24 PaymentIntent"]
    E --> F["stripe.confirmP24Payment()"]
    F --> G["Customer redirected to bank<br/>to authorize payment"]
    G --> H["Return to your site with result"]

Required Components

ComponentRole
VueStripeProviderLoads Stripe.js and provides stripe instance
VueStripeElementsCreates Elements instance
VueStripeP24BankElementRenders the Polish bank dropdown

Basic Implementation

Step 1: Set Up the Component

vue
<script setup>
import {
  VueStripeProvider,
  VueStripeElements,
  VueStripeP24BankElement
} from '@vue-stripe/vue-stripe'

const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY
</script>

<template>
  <VueStripeProvider :publishable-key="publishableKey">
    <VueStripeElements>
      <VueStripeP24BankElement
        @ready="onReady"
        @change="onChange"
      />
    </VueStripeElements>
  </VueStripeProvider>
</template>

Step 2: Handle Bank Selection

vue
<script setup>
import { ref } from 'vue'

const selectedBank = ref('')
const isComplete = ref(false)

const onChange = (event) => {
  isComplete.value = event.complete
  selectedBank.value = event.value || ''
  console.log('Selected bank:', selectedBank.value)
}
</script>

Supported Polish Banks

Bank CodeBank NameDescription
blikBLIKMobile payment system
mbankmBankMajor Polish bank
ingING Bank ŚląskiMajor Polish bank
pkoPKO Bank PolskiLargest Polish bank
santanderSantander Bank PolskaMajor bank
alior_bankAlior BankDigital bank
bank_millenniumBank MillenniumMajor bank
bnp_paribasBNP ParibasInternational bank
citi_handlowyCiti HandlowyInternational bank
credit_agricoleCredit AgricoleMajor bank
getin_bankGetin BankRetail bank
idea_bankIdea BankBusiness bank
inteligoInteligoPKO digital banking
plus_bankPlus BankRetail bank
velobankVeloBankDigital bank

Confirming P24 Payments

Przelewy24 uses a redirect flow - customers are sent to their bank to authorize the payment:

Backend Endpoint

typescript
// POST /api/p24-intent
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)

export async function POST(request: Request) {
  const { amount, currency = 'pln' } = await request.json()

  const paymentIntent = await stripe.paymentIntents.create({
    amount,
    currency, // 'pln' or 'eur'
    payment_method_types: ['p24'],
  })

  return Response.json({
    clientSecret: paymentIntent.client_secret
  })
}

Frontend Confirmation

vue
<script setup>
import { useStripe, useStripeElements } from '@vue-stripe/vue-stripe'

const { stripe } = useStripe()
const { elements } = useStripeElements()

const handleSubmit = async (clientSecret: string) => {
  const p24Element = elements.value?.getElement('p24Bank')

  const { error } = await stripe.value.confirmP24Payment(
    clientSecret,
    {
      payment_method: {
        p24: p24Element,
        billing_details: {
          email: 'customer@example.com' // Required for P24
        }
      },
      return_url: `${window.location.origin}/payment-complete`
    }
  )

  if (error) {
    console.error(error.message)
  }
  // Customer is redirected to their bank
}
</script>

Email Required

P24 payments require a billing_details.email - this is mandatory for the redirect flow and customer communication.

Customization

Custom Styling

vue
<script setup>
const p24Options = {
  style: {
    base: {
      fontSize: '16px',
      color: '#424770',
      fontFamily: '-apple-system, BlinkMacSystemFont, sans-serif',
      padding: '10px 12px'
    }
  }
}
</script>

<template>
  <VueStripeP24BankElement :options="p24Options" />
</template>

Complete Example

vue
<script setup lang="ts">
import { ref } from 'vue'
import {
  VueStripeProvider,
  VueStripeElements,
  VueStripeP24BankElement,
  useStripe,
  useStripeElements
} from '@vue-stripe/vue-stripe'

const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY

const selectedBank = ref('')
const isComplete = ref(false)
const email = ref('')
const processing = ref(false)
const error = ref('')

const p24Options = {
  style: {
    base: {
      fontSize: '16px',
      color: '#424770'
    }
  }
}

const handleChange = (event: any) => {
  isComplete.value = event.complete
  selectedBank.value = event.value || ''
}

const handleSubmit = async () => {
  if (!email.value) {
    error.value = 'Email is required for P24 payments'
    return
  }

  processing.value = true
  error.value = ''

  try {
    // Fetch clientSecret from backend
    const response = await fetch('/api/p24-intent', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ amount: 1000, currency: 'pln' })
    })
    const { clientSecret } = await response.json()

    // Confirm payment (would be in child component)
    // const { stripe } = useStripe()
    // ... confirm P24 payment with redirect
  } catch (e) {
    error.value = 'Failed to process payment'
  } finally {
    processing.value = false
  }
}
</script>

<template>
  <div class="p24-form">
    <VueStripeProvider :publishable-key="publishableKey">
      <VueStripeElements>
        <form @submit.prevent="handleSubmit">
          <div class="field">
            <label>Email (required)</label>
            <input
              v-model="email"
              type="email"
              placeholder="customer@example.com"
            />
          </div>

          <div class="field">
            <label>Select your bank</label>
            <VueStripeP24BankElement
              :options="p24Options"
              @change="handleChange"
            />
          </div>

          <div v-if="selectedBank" class="selected-bank">
            Selected: {{ selectedBank }}
          </div>

          <div v-if="error" class="error">{{ error }}</div>

          <button
            type="submit"
            :disabled="!isComplete || !email || processing"
          >
            {{ processing ? 'Processing...' : 'Pay with Przelewy24' }}
          </button>

          <p class="note">
            You will be redirected to your bank to authorize the payment.
          </p>
        </form>
      </VueStripeElements>
    </VueStripeProvider>
  </div>
</template>

<style scoped>
.p24-form {
  max-width: 400px;
  margin: 0 auto;
}

.field {
  margin-bottom: 16px;
}

.field label {
  display: block;
  margin-bottom: 8px;
  font-weight: 500;
}

.field input {
  width: 100%;
  padding: 12px;
  border: 1px solid #e0e0e0;
  border-radius: 4px;
  font-size: 16px;
}

.selected-bank {
  margin-bottom: 16px;
  padding: 8px 12px;
  background: #f0f9ff;
  border-radius: 4px;
  font-size: 14px;
}

button {
  width: 100%;
  padding: 12px;
  background: #d32d27;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.error {
  color: #9e2146;
  margin-bottom: 16px;
}

.note {
  margin-top: 16px;
  font-size: 12px;
  color: #666;
  text-align: center;
}
</style>

BLIK Payments

BLIK is a mobile payment system popular in Poland. When a customer selects BLIK from the bank dropdown, they'll be prompted to enter a 6-digit code from their banking app:

mermaid
flowchart LR
    A["Select BLIK"] --> B["Enter 6-digit code"]
    B --> C["Confirm in app"]
    C --> D["Payment complete"]

Next Steps

Released under the MIT License.