Integrate BLIK Pya Later with JavaScript SDK

Last updated: Apr 16th, 1:34pm

BLIK - jssdk

Merchants can integrate BLIK Pay Later on the client side using the PayPal JavaScript SDK.

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 clientToken or clientId — credentials must never be hardcoded client-side.
  • You have the @paypal/paypal-server-sdk npm package installed on your server.
  • You have a webhook endpoint configured and a verifyWebhookSignature utility in place.
  • You have returnUrl and cancelUrl values 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<script
    2 async
    3 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();
      3
      4 const sdkInstance = await window.paypal.createInstance({
      5 ...auth,
      6 testBuyerCountry: "PL",
      7 components: ["blikpaylater-payments"],
      8 });
      9
      10 const paymentMethods = await sdkInstance.findEligibleMethods({
      11 currencyCode: "PLN",
      12 });
      13
      14 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 status
        5 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 });
        18
        19 const nameField = blikpaylaterCheckout.createPaymentFields({
        20 type: "name",
        21 value: "",
        22 });
        23
        24 document.querySelector("#blikpaylater-full-name").appendChild(nameField);
        25
        26 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");
              3
              4async 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 };
              38
              39 const { result, statusCode } = await ordersController.createOrder({
              40 body: orderRequestBody,
              41 paypalRequestId: randomUUID(),
              42 prefer: "return=minimal",
              43 });
              44
              45 res.status(statusCode).json(result);
              46}

              Webhook handler (Node.js)

                1const crypto = require("crypto");
                2
                3async function handleWebhook(req, res) {
                4 // Verify webhook signature
                5 const isValid = await verifyWebhookSignature(req);
                6
                7 if (!isValid) {
                8 return res.status(401).json({ error: "Invalid signature" });
                9 }
                10
                11 const event = req.body;
                12
                13 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;
                20
                21 case "PAYMENT.CAPTURE.DENIED":
                22 await updateOrderStatus(
                23 event.resource.supplementary_data.related_ids.order_id,
                24 "DENIED"
                25 );
                26 break;
                27
                28 case "PAYMENT.CAPTURE.REFUNDED":
                29 await processRefundNotification(event.resource);
                30 break;
                31
                32 default:
                33 console.log("Unhandled event type:", event.event_type);
                34 }
                35
                36 res.status(200).json({ received: true });
                37}