Integrate BLIK Pya Later with JavaScript SDK
Last updated: Apr 16th, 1:34pm
BLIK - jssdk
Prerequisites
- You have a PayPal sandbox account with BLIK Pay Later enabled for Poland (
PL). - Your server exposes an auth endpoint that returns either a
clientTokenorclientId— credentials must never be hardcoded client-side. - You have the
@paypal/paypal-server-sdknpm package installed on your server. - You have a webhook endpoint configured and a
verifyWebhookSignatureutility in place. - You have
returnUrlandcancelUrlvalues ready for the payment experience context.
Client-side integration with the JavaScript SDK
If you use the JavaScript SDK instead of a server-side redirect, follow this approach.
Load the SDK
Load the core bundle via a script tag. The blikpaylater-payments component bundle is loaded automatically by the SDK when you pass it in the components array.
1<script2 async3 src="https://www.sandbox.paypal.com/web-sdk/v6/core"4 onload="onPayPalWebSdkLoaded()"5></script>
Initialize and check eligibility
The auth object is fetched from your server and contains either a clientToken or clientId. Do not hardcode credentials client-side. Pass "blikpaylater-payments" in the components array so the SDK loads the bundle automatically during initialization.
1async function onPayPalWebSdkLoaded() {2 const auth = await fetchAuthFromServer();34 const sdkInstance = await window.paypal.createInstance({5 ...auth,6 testBuyerCountry: "PL",7 components: ["blikpaylater-payments"],8 });910 const paymentMethods = await sdkInstance.findEligibleMethods({11 currencyCode: "PLN",12 });1314 if (paymentMethods.isEligible("blik_pay_later")) {15 setupBlikPayLaterPayment(sdkInstance);16 } else {17 console.error("BLIK Pay Later is not eligible for this configuration.");18 }19}
Set up the payment session
The <blikpaylater-button> web component starts hidden and is revealed only after eligibility is confirmed. Because ORDER_COMPLETE_ON_PAYMENT_APPROVAL is set, the payment is captured automatically on buyer approval — onApprove should verify the final order status, not call a separate capture endpoint.
1function setupBlikPayLaterPayment(sdkInstance) {2 const blikpaylaterCheckout = sdkInstance.createBlikPayLaterOneTimePaymentSession({3 onApprove: async (data) => {4 // Payment is auto-captured — verify the order status5 const response = await fetch(`/api/orders/${data.orderId}`, {6 method: "GET",7 });8 const orderDetails = await response.json();9 console.log("Order details:", orderDetails);10 },11 onCancel: (data) => {12 console.log("Payment cancelled:", data);13 },14 onError: (error) => {15 console.error("Payment error:", error);16 },17 });1819 const nameField = blikpaylaterCheckout.createPaymentFields({20 type: "name",21 value: "",22 });2324 document.querySelector("#blikpaylater-full-name").appendChild(nameField);2526 const blikpaylaterButton = document.querySelector("#blikpaylater-button");27 blikpaylaterButton.removeAttribute("hidden");28 blikpaylaterButton.addEventListener("click", async () => {29 const isValid = await blikpaylaterCheckout.validate();30 if (isValid) {31 await blikpaylaterCheckout.start(32 { presentationMode: "popup" },33 createOrder()34 );35 }36 });37}
HTML structure
1<div id="blikpaylater-full-name"></div>2<blikpaylater-button id="blikpaylater-button" hidden></blikpaylater-button>
Create the order from your server
1async function createOrder() {2 const response = await fetch("/api/orders/create", {3 method: "POST",4 headers: { "Content-Type": "application/json" },5 body: JSON.stringify({ currencyCode: "PLN" }),6 });7 const { id } = await response.json();8 return { orderId: id };9}
Server-side order creation (Node.js)
1const { ordersController } = require("@paypal/paypal-server-sdk");2const { randomUUID } = require("crypto");34async function createBlikPayLaterOrder(req, res) {5 const orderRequestBody = {6 intent: "CAPTURE",7 processingInstruction: "ORDER_COMPLETE_ON_PAYMENT_APPROVAL",8 purchaseUnits: [9 {10 amount: {11 currencyCode: "PLN",12 value: req.body.totalAmount,13 breakdown: {14 itemTotal: {15 currencyCode: "PLN",16 value: req.body.totalAmount,17 },18 },19 },20 items: req.body.items,21 },22 ],23 paymentSource: {24 blikPayLater: {25 name: {26 fullName: req.body.buyerName,27 },28 countryCode: "PL",29 email: req.body.buyerEmail,30 experienceContext: {31 locale: "pl-PL",32 returnUrl: req.body.returnUrl,33 cancelUrl: req.body.cancelUrl,34 },35 },36 },37 };3839 const { result, statusCode } = await ordersController.createOrder({40 body: orderRequestBody,41 paypalRequestId: randomUUID(),42 prefer: "return=minimal",43 });4445 res.status(statusCode).json(result);46}
Webhook handler (Node.js)
1const crypto = require("crypto");23async function handleWebhook(req, res) {4 // Verify webhook signature5 const isValid = await verifyWebhookSignature(req);67 if (!isValid) {8 return res.status(401).json({ error: "Invalid signature" });9 }1011 const event = req.body;1213 switch (event.event_type) {14 case "PAYMENT.CAPTURE.COMPLETED":15 await updateOrderStatus(16 event.resource.supplementary_data.related_ids.order_id,17 "COMPLETED"18 );19 break;2021 case "PAYMENT.CAPTURE.DENIED":22 await updateOrderStatus(23 event.resource.supplementary_data.related_ids.order_id,24 "DENIED"25 );26 break;2728 case "PAYMENT.CAPTURE.REFUNDED":29 await processRefundNotification(event.resource);30 break;3132 default:33 console.log("Unhandled event type:", event.event_type);34 }3536 res.status(200).json({ received: true });37}