# BLIK payments Learn how to accept BLIK, a common payment method in Poland. # Direct API > We recommend that you follow the [Accept a payment](https://docs.stripe.com/payments/accept-a-payment.md) guide unless you need to use manual server-side confirmation, or your integration requires presenting payment methods separately. If you’ve already integrated with Elements, see the [Payment Element migration guide](https://docs.stripe.com/payments/payment-element/migration.md). BLIK is a [single use](https://docs.stripe.com/payments/payment-methods.md#usage) payment method that requires customers to [authenticate](https://docs.stripe.com/payments/payment-methods.md#customer-actions) their payments. When customers want to pay online using BLIK, they request a six-digit code from their banking application and enter it into the payment collection form. The bank sends a push notification to your customer’s mobile phone asking to authorize the payment inside their banking application. The BLIK code is valid for 2 minutes; customers have 60 seconds to authorize the payment after starting a payment. After 60 seconds, it times out and they must request a new BLIK code. Customers typically approve BLIK payments in less than 10 seconds. ## Set up Stripe [Server-side] First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register). Use our official libraries for access to the Stripe API from your application: #### Ruby ```bash # Available as a gem sudo gem install stripe ``` ```ruby # If you use bundler, you can add this line to your Gemfile gem 'stripe' ``` ## Create a PaymentIntent [Server-side] A [PaymentIntent](https://docs.stripe.com/api/payment_intents/object.md) is an object that represents your intent to collect a payment from a customer and tracks the lifecycle of the payment process through each stage. First, create a `PaymentIntent` on your server and specify the amount to collect and the `pln` currency, because BLIK only supports `pln`. ```curl curl https://api.stripe.com/v1/payment_intents \ -u "<>:" \ -d amount=1099 \ -d currency=pln \ -d "automatic_payment_methods[enabled]=true" \ --data-urlencode "description=A description of what you're selling" \ -d statement_descriptor=ORDER_123 ``` ### Retrieve the client secret The PaymentIntent includes a *client secret* (The client secret is a unique key returned from Stripe as part of a PaymentIntent. This key lets the client access important fields from the PaymentIntent (status, amount, currency) while hiding sensitive ones (metadata, customer)) that the client side uses to securely complete the payment process. You can use different approaches to pass the client secret to the client side. #### Single-page application Retrieve the client secret from an endpoint on your server, using the browser’s `fetch` function. This approach is best if your client side is a single-page application, particularly one built with a modern frontend framework like React. Create the server endpoint that serves the client secret: #### Ruby ```ruby get '/secret' do intent = # ... Create or retrieve the PaymentIntent {client_secret: intent.client_secret}.to_json end ``` And then fetch the client secret with JavaScript on the client side: ```javascript (async () => { const response = await fetch('/secret'); const {client_secret: clientSecret} = await response.json(); // Render the form using the clientSecret })(); ``` #### Server-side rendering Pass the client secret to the client from your server. This approach works best if your application generates static content on the server before sending it to the browser. Add the [client_secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret) in your checkout form. In your server-side code, retrieve the client secret from the PaymentIntent: #### Ruby ```erb
``` ```ruby get '/checkout' do @intent = # ... Fetch or create the PaymentIntent erb :checkout end ``` ## Collect payment method details [Client-side] To submit the payment to Stripe, you need to confirm the PaymentIntent with your customer’s 6-digit BLIK code. You can get this code by creating a form on your client to collect the required code from the customer. #### HTML + JS ```html
``` #### React #### npm Install [React Stripe.js](https://www.npmjs.com/package/@stripe/react-stripe-js) and the [Stripe.js loader](https://www.npmjs.com/package/@stripe/stripe-js) from the npm public registry. ```bash npm install --save @stripe/react-stripe-js @stripe/stripe-js ``` #### umd We also provide a UMD build for sites that don’t use npm or modules. Include the Stripe.js script, which exports a global `Stripe` function, and the UMD build of React Stripe.js, which exports a global `ReactStripe` object. Always load the Stripe.js script directly from **js.stripe.com** to remain PCI compliant. Don’t include the script in a bundle or host a copy of it yourself. ```html ``` > The [demo in CodeSandbox](https://codesandbox.io/s/react-stripe-official-q1loc?fontsize=14&hidenavigation=1&theme=dark) lets you try out React Stripe.js without having to create a new project. ### Add Stripe.js and Elements to your page To integrate Stripe.js and (optionally) use Elements components for other payment methods, wrap the root of your React app in an [Elements provider](https://docs.stripe.com/sdks/stripejs-react.md#elements-provider). Call `loadStripe` with your publishable key and pass the returned `Promise` to the `Elements` provider. ```jsx import React from 'react'; import ReactDOM from 'react-dom'; import {Elements} from '@stripe/react-stripe-js'; import {loadStripe} from '@stripe/stripe-js'; import CheckoutForm from './CheckoutForm'; // Make sure to call `loadStripe` outside of a component's render to avoid // recreating the `Stripe` object on every render. const stripePromise = loadStripe('<>'); function App() { return ( ); }; ReactDOM.render(, document.getElementById('root')); ``` ### Create a form to accept payments If you intend to use the [useStripe](https://docs.stripe.com/sdks/stripejs-react.md#usestripe-hook) hook to access Stripe.js features, you can create a normal React component. If you prefer traditional class components over hooks, you can instead use an [ElementsConsumer](https://docs.stripe.com/sdks/stripejs-react.md#elements-consumer). #### Hooks ```jsx import React from 'react'; import {useStripe} from '@stripe/react-stripe-js'; export default function CheckoutForm() { return (
); } ``` #### Class Components ```jsx import React from 'react'; import {ElementsConsumer} from '@stripe/react-stripe-js'; class CheckoutForm extends React.Component { render() { const {stripe} = this.props; return (
); } } export default function InjectedCheckoutForm() { return ( {({stripe, elements}) => ( )} ); } ``` ## Submit the payment to Stripe [Client-side] Use the PaymentIntent object from [step 2](https://docs.stripe.com/payments/blik/accept-a-payment.md#web-create-payment-intent) and the 6-digit BLIK code you collected in [step 3](https://docs.stripe.com/payments/blik/accept-a-payment.md#web-collect-payment-method-details) to *confirm* (Confirming an intent indicates that the customer intends to use the current or provided payment method. Upon confirmation, the intent attempts to initiate the portions of the flow that have real-world side effects) the PaymentIntent; this declares that the customer intends to pay with the specified *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs). After the PaymentIntent is confirmed, it initiates a payment. Rather than sending the entire PaymentIntent object to the client, use its [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret). This client secret is different from your API keys that authenticate Stripe API requests. Handle the client secret carefully, because it can complete the charge. Don’t log it, embed it in URLs, or expose it to anyone but the customer. Also, because the 6-digit BLIK code is only valid for 2 minutes, you can only pass it into the payment method options when confirming the PaymentIntent. In a *sandbox* (A sandbox is an isolated test environment that allows you to test Stripe functionality in your account without affecting your live integration. Use sandboxes to safely experiment with new features and changes), any 6-digit code works. #### HTML + JS When a customer clicks to pay with Blik, use Stripe.js to submit the payment to Stripe. [Stripe.js](https://docs.stripe.com/payments/elements.md) is the foundational JavaScript library for building payment flows. It automatically handles complexities like the redirect described below, and enables you to extend your integration to other payment methods. Include the Stripe.js script on your checkout page by adding it to the `head` of your HTML file. ```html Checkout ``` Create an instance of Stripe.js with the following JavaScript on your checkout page. ```javascript // Set your publishable key. Remember to change this to your live publishable key in production! // See your keys here: https://dashboard.stripe.com/apikeys const stripe = Stripe('<>'); ``` Use [stripe.confirmBlikPayment](https://docs.stripe.com/js/payment_intents/confirm_blik_payment) to confirm the payment. `confirmBlikPayment` doesn’t return until the confirmation succeeds or fails. If you want to implement your own logic to update payment status, you can disable Stripe.js polling of the API by setting the `handleActions` parameter to `false`. ```javascript const form = document.getElementById('payment-form'); const codeInput = document.getElementById('code'); form.addEventListener('submit', (event) => { event.preventDefault(); const {error} = await stripe.confirmBlikPayment( '{{PAYMENT_INTENT_CLIENT_SECRET}}', { payment_method: { blik: {}, }, payment_method_options: { blik: { code: code.value, }, }, } ); }); ``` #### React When a customer clicks to pay with Blik, use Stripe.js to submit the payment to Stripe. [Stripe.js](https://docs.stripe.com/payments/elements.md) is the foundational JavaScript library for building payment flows. It automatically handles complexities like the redirect described below, and enables you to extend your integration to other payment methods. Include the Stripe.js script on your checkout page by adding it to the `head` of your HTML file. ```html Checkout ``` Create an instance of Stripe.js with the following JavaScript on your checkout page. ```javascript // Set your publishable key. Remember to change this to your live publishable key in production! // See your keys here: https://dashboard.stripe.com/apikeys const stripe = Stripe('<>'); ``` Use [stripe.confirmBlikPayment](https://docs.stripe.com/js/payment_intents/confirm_blik_payment) to confirm the payment. `confirmBlikPayment` doesn’t return until the confirmation succeeds or fails. You can disable the polling of the API by setting the `handleActions` parameter to `false`. #### Hooks To call `stripe.confirmBlikPayment` from your payment form component, use the [useStripe](https://docs.stripe.com/sdks/stripejs-react.md#usestripe-hook) hook. ```jsx import React from 'react'; import {useStripe} from '@stripe/react-stripe-js'; export default function CheckoutForm() { const stripe = useStripe(); const handleSubmit = async (event) => { // We don't want to let default form submission happen here, // which would refresh the page. event.preventDefault(); if (!stripe) { // Stripe.js hasn't yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } // For brevity, this example is using uncontrolled components for // the code input. In a real world app you will // probably want to use controlled components. // https://reactjs.org/docs/uncontrolled-components.html // https://reactjs.org/docs/forms.html#controlled-components const code = event.target['code']; const {error} = await stripe.confirmBlikPayment('{{PAYMENT_INTENT_CLIENT_SECRET}}', { payment_method: { blik: {}, }, payment_method_options: { blik: { code, }, }, }); if (error) { // Show error to your customer. console.log(error.message); } // Otherwise the customer will be redirected away from your // page to complete the payment with their bank. }; return (
); } ``` #### Class Components ```jsx import React from 'react'; import {ElementsConsumer} from '@stripe/react-stripe-js'; class CheckoutForm extends React.Component { handleSubmit = async (event) => { // We don't want to let default form submission happen here, // which would refresh the page. event.preventDefault(); const {stripe} = this.props if (!stripe) { // Stripe.js hasn't yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } // For brevity, this example is using uncontrolled components for // the accountholder's name. In a real world app you will // probably want to use controlled components. // https://reactjs.org/docs/uncontrolled-components.html // https://reactjs.org/docs/forms.html#controlled-components const code = event.target['code']; const {error} = await stripe.confirmBlikPayment('{{PAYMENT_INTENT_CLIENT_SECRET}}', { payment_method: { blik: {}, }, payment_method_options: { blik: { code, }, }, }); if (error) { // Show error to your customer. console.log(error.message); } // Otherwise the customer will be redirected away from your // page to complete the payment with their bank. }; render() { const {stripe} = this.props; return (
); } } export default function InjectedCheckoutForm() { return ( {({stripe, elements}) => ( )} ); } ``` After confirming the PaymentIntent with valid parameters, the PaymentIntent’s `status` becomes `requires_action`. The `next_action` has the `blik_authorize` type: this means that the next step is completed by your customer, who has 60 seconds to confirm the payment inside their mobile banking app. We recommend showing a timer or a dialog to your customer to prompt them to authorize the payment within the time. ### What customers see Inside their Banking app, customers see four lines related to each BLIK transaction: - If you provided a value for `description` when creating the PaymentIntent, the first two lines display it (max 70 characters). - If you provided a value for `statement_descriptor` (typically, an order ID), line 3 displays it (max 22 characters). - The fourth line automatically populates with the URL of your website. ## Fulfill the order [Server-side] [Use a method such as webhooks](https://docs.stripe.com/payments/payment-intents/verifying-status.md#webhooks) to handle order *fulfillment* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected). When a customer completes payment, the `PaymentIntent`’s status transitions to `succeeded` and sends the [payment_intent.succeeded](https://docs.stripe.com/api/events/types.md#event_types-payment_intent.succeeded) *webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) event. If a customer doesn’t pay, the `PaymentIntent` sends the [payment_intent.payment_failed](https://docs.stripe.com/api/events/types.md#event_types-payment_intent.payment_failed) webhook event and returns to a status of `requires_payment_method`. ## Optional: Simulate failures in a sandbox BLIK payments can fail for different reasons. There are immediate failures (for example, the code is expired or invalid), delayed errors (the bank declines) or timeouts (the customer didn’t respond in time). You can simulate each failure scenarios by passing `email` values matching certain patterns (documented below) when creating the `PaymentIntent`, as part of the [billing details](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-payment_method_data-billing_details), with any 6-digit as BLIK code. When confirming the `PaymentIntent` server side, a request simulating an invalid BLIK code would look like: #### curl ```bash curl https://api.stripe.com/v1/payment_intents/{{PAYMENT_INTENT_ID}}/confirm \ -u <>: \ -d "payment_method_data[type]"="blik" \ -d "payment_method_options[blik][code]"="{{CODE}}" \ -d "payment_method_data[billing_details][email]"="invalid_code@example.com" ``` ### Immediate Failures | Failure code | Explanation | Email pattern | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------- | | `payment_method_invalid_parameter` | The code passed wasn’t a valid BLIK code. | `.*invalid_code@.*` | | `payment_method_invalid_parameter` | The code passed has expired. BLIK codes expire after 2 minutes, please request another code from the customer. | `.*expired_code@.*` | ### Declines (8 second delay) | Failure code | Explanation | Email pattern | | --------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------- | | `payment_method_provider_decline` | The payment has been declined because the payment limit on this bank account has been reached. | `.*limit_exceeded@.*` | | `payment_method_provider_decline` | The bank account has insufficient funds to complete the purchase. | `.*insufficient_funds@.*` | | `payment_method_provider_decline` | The customer declined this payment. | `.*customer_declined@.*` | | `payment_method_provider_decline` | The payment has been declined by the customer’s bank for an unknown reason. | `.*bank_declined@.*` | | `payment_method_provider_decline` | The payment has been declined for an unknown reason. | `.*blik_declined@.*` | ### Timeouts (60 second delay) | Failure code | Explanation | Email pattern | | --------------------------------- | ------------------------------------------------------------------------- | ----------------------- | | `payment_method_provider_timeout` | The customer didn’t approve this payment within the allocated 60 seconds. | `.*customer_timeout@.*` | | `payment_method_provider_timeout` | The request to the customer’s bank timed out. | `.*bank_timeout@.*` | | `payment_method_provider_timeout` | The request to the BLIK network timed out. | `.*blik_timeout@.*` |