For any question, we are one click away

Contact us

Web SDK Payment

About

This is a JavaScript library to display a payment form on the page of the merchant who does not have PCI DSS.

Scenario of working:

  1. The merchant registers an order via the REST API in the payment gateway
  2. The received mdOrder is passed by the merchant to the page where this js-library is used

The library implements input fields that are located via an iframe on the payment gateway side

Pros:

Cons:

The library helps in collecting card data, validating and verifying it, making a payment, and automatically redirecting the buyer to the returnUrl specified in the settings (finish page).

How to use

Connect the script

Test environment

<script src="https://uat.dskbank.bg/payment/modules/multiframe/main.js"></script>

Production environment

<script src="https://epg.dskbank.bg/payment/modules/multiframe/main.js"></script>

Preparation

First, you must create an HTML form for accepting payment. The form must contain the following blocks: #pan, #expiry, #cvc, and the button #pay. It is not necessary to use exactly these field names. You will be able to customize the names you need during initialization.

Here's an example of an HTML form that doesn't enable stored credentials:

<div class="card-body">
    <div class="col-12">
        <label for="pan" class="form-label">Card number</label>
        <!-- Container for card number field -->
        <div id="pan" class="form-control"></div>
    </div>
    <div class="col-6 col-expiry">
        <label for="expiry" class="form-label">Expiry</label>
        <!-- Container for expiry card field -->
        <div id="expiry" class="form-control"></div>
    </div>
    <div class="col-6 col-cvc">
        <label for="cvc" class="form-label">CVC / CVV</label>
        <!-- Container for CVC/CVV field -->
        <div id="cvc" class="form-control"></div>
    </div>
</div>
<!-- Pay button -->
<button class="btn btn-primary btn-lg" type="submit" id="pay">
    <!-- Payment loader -->
    <span class="spinner-border spinner-border-sm visually-hidden" id="pay-spinner"></span>
    <span>Pay</span>
</button>
<!-- Container for errors -->
<div class="error my-2 text-center text-danger visually-hidden" id="error"></div>

If you use stored credentials, you need to enable an additional HTML block: #select-binding.

Here's an example of an HTML form with stored credential fields:

<div class="card-body">
    <div class="col-12" id="select-binding-container" style="display: none">
        <!-- Select for bindings -->
        <select class="form-select" id="select-binding" aria-label="Default select example">
            <option selected value="new_card">Pay with a new card</option>
        </select>
    </div>
    <div class="col-12">
        <label for="pan" class="form-label">Card number</label>
        <!-- Container for card number field -->
        <div id="pan" class="form-control"></div>
    </div>
    <div class="col-6 col-expiry">
        <label for="expiry" class="form-label">Expiry</label>
        <!-- Container for expiry card field -->
        <div id="expiry" class="form-control"></div>
    </div>
    <div class="col-6 col-cvc">
        <label for="cvc" class="form-label">CVC / CVV</label>
        <!-- Container for cvc/cvv field -->
        <div id="cvc" class="form-control"></div>
    </div>
    <label class="col-12" id="save-card-container">
        <!-- Save card checkbox -->
        <input class="form-check-input" type="checkbox" value="" id="save-card" />
        Save card
    </label>
</div>
<!-- Pay button -->
<button class="btn btn-primary btn-lg" type="submit" id="pay">
    <!-- Payment loader -->
    <span class="spinner-border spinner-border-sm visually-hidden" id="pay-spinner"></span>
    <span>Pay</span>
</button>
<!-- Container for errors -->
<div class="error my-2 text-center text-danger visually-hidden" id="error"></div>

You can add any additional fields to the form, such as the cardholder name, email, phone, etc. However, don't forget to pass them into the doPayment() method later.

Web SDK initialization

Description payment form

You need to execute the constructor function new window.PaymentForm()`.

window.PaymentForm() can take the following properties:

PaymentForm initialization properties

mdOrder string required
Order ID

Description of fields on the form

apiContext string optional
Context (a part of the payment gateway URL after the domain) for API queries.
By default, the apiContext is automatically taken from the link used to connect the script modules/multiframe/main.js.

language string optional
Language used for localization of errors and placeholder names.
Default value is en.

autofocus boolean optional
Automatically shift focus as fields are filled out.
Default value is true

showPanIcon boolean optional
Show payment system icon.
Default value is true

panIconStyle CSSStyleDeclaration optional
Custom styles for payment system icon

Custom styles for payment system icon

containerClassName string optional
Optional class name for container.
Default value is field-container

onFormValidate (result: boolean) => void optional
Callback for handling form validation change.
For example:
onFormValidate: (isValid) => {
    alert(isValid ? 'Congratulations!' : 'Oops! We regret.'); 
}

Web SDK initialization example

const webSdkPaymentForm = new window.PaymentForm({
      // Order number (order registration happens before initialization of the form)
      mdOrder: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
      // Name of the class that will be set for containers with frames
      containerClassName: 'field-container',
      onFormValidate: (isValid) => {
        // Handling form validation
      },
      // Context for API queries
      apiContext: '/payment',
      // Language - is used for localization of errors and placeholder names.
      // The language must be supported in Merchant settings
      language: 'en',
      // Automatically shift focus as fields are filled out
      autoFocus: true,
      // Show payment system icon
      showPanIcon: true,
      // Custom styles for payment system icon
      panIconStyle: {
        height: '16px',
        top: 'calc(50% - 8px)',
        right: '8px',
      },
      fields: {
        pan: {
          container: document.querySelector('#pan'),
          onFocus: (containerElement) => {
            // Action when field gets focus
            // (containerElement contains link to field container element)
          },
          onBlur: (containerElement) => {
            // Action when field gets focus off it
            // (containerElement contains link to field container element)
          },
          onValidate: (isValid, containerElement) => {
            // Action when field is valid
            // (isValid is true if field is valid, otherwise is false)
            // (containerElement contains link to field container element)
          },
        },
        expiry: {
          container: document.querySelector('#expiry'),
          // ...
        },
        cvc: {
          container: document.querySelector('#cvc'),
          // ...
        },
      },
      // Style for input fields
      styles: {
        // Base state
        base: {
          color: 'black',
        },
        // Focused state
        focus: {
          color: 'blue',
        },
        // Disabled state
        disabled: {
          color: 'gray',
        },
        // Has valid value
        valid: {
          color: 'green',
        },
        // Has invalid value
        invalid: {
          color: 'red',
        },
        // Style for placeholder
        placeholder: {
          // Base style
          base: {
            color: 'gray',
          },
          // Style when focused
          focus: {
            color: 'transparent',
          },
        },
      },
    });

Destroy method

The destroy() method in a Web SDK is used to remove all the resources and event listeners associated with a particular instance of the SDK. When you call the destroy() method, it will clean up any event listeners and iframes that were created by the SDK during its lifecycle. This is useful when you no longer need the SDK instance.

The destroy() method typically performs the following tasks:

  1. Removes all event listeners that were added to the SDK instance.
  2. Clears any iframes that were created.

Destroy method example

document.querySelector("#destroy").addEventListener("click", function () {
     webSdkPaymentForm.destroy();
 });

Stylization

You can define styles for the iframe container using class names:

className is set on initialization in the containerClassName parameter.

Checkout without stored credentials

Step 1. Execute initialization method

After defining Web SDK parameters, you must call init(). This function returns a callback where you can, for example, hide the loader or do something else. init() returns a promise.

For example:

webSdkFormWithoutBindings
    .init()
    .then(() => {
        // Script initialized successfully
        document.querySelector('.payment-form-loader').classList.remove('payment-form-loader--active');
    })
    .then((error) => {
      // Errors occurred during the initialization of the script. Further execution is not possible.
    }); ;

Step 2. Pay button click handling. Execute payment method.

You can call the payment in any way that is convenient for you. To do this, call the function doPayment(). You do not need to send card data. The Web SDK will handle it. doPayment() returns a promise.

The method takes the following parameters:

phone String optional
Customer's phone number

email String optional
Customer's email

cardholderName String optional
Name of the cardholder.

jsonParams Object optional
Additional fields. For example, you can send additional information about the order or any other information that is useful to you. For example, jsonParams: { "t-shirt-color": "black", "size": "M" }

Updates to Visa Secure Data Field Mandate

Please be advised about the following information received from IPS VISA: Twelve additional data fields are required for EMV 3DS authentication requests. Merchants must provide complete and accurate transaction data in their authentication requests. They must also ensure that the 3DS Method URL collects device data to support successful authentication, if the 3DS Method URL is provided by the issuer.

Therefore, it will be the merchant's responsibility to collect the additional fields for VISA. Please find the VBN from Visa attached.

Additional fields are passed as properties of an object in the doPayment() argument.

Call example

webSdkFormWithoutBindings.doPayment({
    // Additional params
    email: 'foo@bar.com',
    phone: '4420123456789',
    cardholderName: 'JOHN DOE',
    jsonParams: { foo: 'bar' },
})
    .then((result) => {
        console.log('result', result);
    })
    .catch((e) => {
        // Error processing. For example, we show a block with an error
        errorEl.innerHTML = e.message;
        errorEl.classList.remove('visually-hidden');
    })
    .finally(() => {
        // Execute in any case. For example, make the "Pay" button active again.
        payButton.disabled = false;
        spinnerEl.classList.add('visually-hidden');
    });

Demo without stored credentials

Web SDK Payment requires an mdOrder (Order ID registered in the payment gateway).
You can register an order through the API.

This input is only for demo purposes - use Order ID value = xxxxx-xxxxx-xxxxx-xxxxx

The entire demo code

<div class="container_demo">
    <div class="about">
        <form name="formRunTest">
            <label for="mdOrder"> Order ID (mdOrder) <br>
              <span class="label__desc">(Should come from the backend. This input only for demo)</span>
            </label>
            <div class="run-test">
                <input id="mdOrder" type="text" placeholder="Paste the mdOrder registered for sandbox"/>
                <button class="btn-mini" id="load" type="submit">Load</button>
            </div>
        </form>
    </div>
    <div class="payment-form">
        <div class="payment-form-loader payment-form-loader--active">
            Web SDK Payment requires mdOrder (pre-registered order in the gateway).<br>
            You can register an order through Merchant Portal or through API. <br><br>
            Or try use <code>xxxxx-xxxxx-xxxxx-xxxxx</code> if you to want check only payment form.
        </div>
        <div class="card-body">
            <div class="col-12">
                <label for="pan" class="form-label">Card number</label>
                <div id="pan" class="form-control"></div>
            </div>
            <div class="col-6 col-expiry">
                <label for="expiry" class="form-label">Expiry</label>
                <div id="expiry" class="form-control"></div>
            </div>
            <div class="col-6 col-cvc">
                <label for="cvc" class="form-label">CVC / CVV</label>
                <div id="cvc" class="form-control"></div>
            </div>
        </div>
        <!-- Additional fields for Visa Mandatory -->
        <div class="col-12 additional-field" style="display:none;">
          <label for="" class="form-label">Cardholder</label>
          <div id="cardholder" class="additional-field-container">
              <input type="text" class="additional-field-input" placeholder="Surname"value="JOHN DOE">
          </div>
        </div>
        <div class="col-12 additional-field"  style="display:none;">
            <label for="" class="form-label">Mobile phone</label>
            <div id="mobile" class="additional-field-container">
                <input type="tel" class="additional-field-input" placeholder="+4915112345" value="+4915112345678">
            </div>
        </div>
        <div class="col-12 additional-field" style="display:none;">
            <label for="" class="form-label">Email address</label>
            <div id="email" class="additional-field-container">
                <input type="text" class="additional-field-input" placeholder="address@mail" value="address@mail.com">
            </div>
        </div>
        <button class="btn btn-primary btn-lg" type="submit" id="pay">
            <span
            class="spinner-border spinner-border-sm me-2 visually-hidden"
            role="status"
            aria-hidden="true"
            id="pay-spinner">
            </span>
            <span>Pay</span>
        </button>
        <!-- This buttons are needed only for demonstrate how destroy method works -->
        <button class="btn btn-secondary" type="button" id="destroyFormWithoutCredentials">
            <span>Destroy</span>
        </button>
        <button class="btn btn-secondary" type="button" id="reinitFormWithoutCredentials" style="display: none;">
            <span>Reinit</span>
        </button>
        <div class="error my-2 text-center text-danger visually-hidden" id="error"></div>
    </div>
</div>

<script>
document.addEventListener("DOMContentLoaded", () => {
  // Function to initialize payment form to reuse it after destroy
  function initPaymentForm() {
    const mrOrderInput = document.getElementById("mdOrder");
    mrOrderInput.classList.remove("invalid");
    if (!/(\w+-){3,10}\w+/g.test(mrOrderInput.value)) {
      mrOrderInput.classList.add("invalid");
      return;
    }

    // Initialization of Web SDK. A required mdOrder (order ID) is needed.
    initPayment(mrOrderInput.value);
  }

  document.formRunTest.addEventListener("submit", function (e) {
    e.preventDefault();
    // Initialize payment form
    initPaymentForm();
  });

  let webSdkFormWithoutBindings;
  // Array of additional field objects with field id, validation template and valid input characters
  const mandatoryFieldsWithoutBinding = [
    {
        id: '#cardholder',
        template: /^[a-zA-Z '`.\-]{4,24}$/,
        replace: /[^a-zA-Z ' \-`.]/g,
    },
    {
        id: '#mobile',
        template: /^\+?[1-9][0-9]{7,14}$/,
        replace: /[^0-9\+]/g,
    },
    {
        id: '#email',
        template: /^[a-zA-Z0-9._-]{1,64}@([a-zA-Z0-9.-]{2,255})\.[a-zA-Z]{2,255}$/,
        replace: /[^a-zA-Z0-9@._-]/g,
    }
  ]

  function initPayment(mdOrder) {
    webSdkFormWithoutBindings = new window.PaymentForm({
      mdOrder: mdOrder,
      onFormValidate: () => {},
      language: "en",
      containerClassName: "field-container",
      autoFocus: true,
      showPanIcon: true,
      panIconStyle: {
        height: "16px",
        top: "calc(50% - 8px)",
        right: "8px",
      },
      fields: {
        pan: {
          container: document.querySelector("#pan"),
        },
        expiry: {
          container: document.querySelector("#expiry"),
        },
        cvc: {
          container: document.querySelector("#cvc"),
        },
      },
      styles: {
        base: {
          padding: "0px 16px",
          color: "black",
          fontSize: "18px",
        },
        invalid: {
          color: "red",
        },
        placeholder: {
          base: {
            color: "gray",
          },
          focus: {
            color: "transparent",
          },
        },
      },
    });

    // Action after initialization
    webSdkFormWithoutBindings.init().then(() => {
      document.querySelector(".payment-form-loader").classList.remove("payment-form-loader--active");
    })
    .finally(() => {
      // Validation and characters replacement on fields input
      mandatoryFieldsWithBinding.forEach(item => {
        const field = document.querySelector(item.id)
        field.closest(".additional-field").style.display = '';
        field.addEventListener('input', () => {
            let inputValue = field.querySelector('input')
            inputValue.value = item.replace ? inputValue.value.replace(item.replace,'') : inputValue.value;
            if (item.id.includes("#cardholder")) {
                inputValue.value = inputValue.value.toUpperCase()
            }
            if (item.template) {
                // The CSS class ".additional-field-invalid" is used to display invalid fields
                if (item.template.test(inputValue.value)) {
                    field.classList.remove("additional-field-invalid")
                } else {
                    field.classList.add("additional-field-invalid")
                }
            }
        })
      })
    })
  }

  // Pay handler
  document.querySelector("#pay").addEventListener("click", () => {
    const payButton = document.querySelector("#pay");

    // Make the "Pay" button inactive to avoid double payments
    payButton.disabled = true;

    // Show the loader to the user
    const spinnerEl = document.querySelector("#pay-spinner");
    spinnerEl.classList.remove("visually-hidden");

    // Hide the error container
    const errorEl = document.querySelector("#error");
    errorEl.classList.add("visually-hidden");
    // Checking of additional fields validation before Payment
    if (document.querySelectorAll('.additional-field-invalid').length) {
      errorEl.innerHTML = "Form is not valid";
      errorEl.classList.remove('visually-hidden');
      spinnerEl.classList.add('visually-hidden');
      return
    }

    // Start payment
    webSdkFormWithoutBindings
      .doPayment({
        // Additional parameters
        email: document.querySelector('#email input').value,
        phone: document.querySelector('#mobile input').value,
        cardholderName: document.querySelector('#cardholder input').value,
        jsonParams: { size: "L" },
      })
      .then((result) => {
        console.log("result", result);
      })
      .catch((e) => {
        // Execute on error
        errorEl.innerHTML = e.message;
        errorEl.classList.remove("visually-hidden");
      })
      .finally(() => {
        // Execute in any case. For example, make the "Pay" button active again.
        payButton.disabled = false;
        spinnerEl.classList.add("visually-hidden");
      });
  });

  // Destroy handler
  document.querySelector("#destroyFormWithoutCredentials").addEventListener("click", function () {
    // Remove destroy button and display reinit button for demo
    this.style.display = "none";
    document.querySelector("#reinitFormWithoutCredentials").style.display = "";
    // Destroy web sdk form
    webSdkFormWithoutBindings.destroy();
  });

  // Init form handler
  document.querySelector("#reinitFormWithoutCredentials").addEventListener("click", function () {
    // Remove reinit button and display destroy button for demo
    this.style.display = "none";
    document.querySelector("#destroyFormWithoutCredentials").style.display = "";
    // Payment form initialization
    initPaymentForm();
  });
});
</script>

Checkout with stored credentials

Step 1. Execute initialization method

After defining Web SDK parameters, you must call init(). This function returns a callback where you can, for example, hide the loader or do something else. init() returns a promise.

For example:

// Initialization
webSdkFormWithBindings
  .init()
  .then(({ orderSession }) => {
    // The `orderSession` object contains all the information about the order, including information about the bindings.
    console.info("orderSession", orderSession);

    // Show select binding element
    document.querySelector("#select-binding-container").style.display = orderSession.bindings.length ? "" : "none";

    // Fill the select with stored credentials
    orderSession.bindings.forEach((binding) => {
      document
        .querySelector("#select-binding")
        .options.add(new Option(binding.pan, binding.id));
    });

    // Handle select stored credential or a new card
    document
      .querySelector("#select-binding")
      .addEventListener("change", function () {
        const bindingId = this.value;
        if (bindingId !== "new_card") {
          webSdkFormWithBindings.selectBinding(bindingId);

          // Hide the 'Save card' checkbox
          document.querySelector("#save-card-container").style.display = "none";
        } else {
          // Selecting stored credentials with null means switching to a new card
          webSdkFormWithBindings.selectBinding(null);

          // Show the 'Save card' checkbox
          document.querySelector("#save-card-container").style.display = "";
        }
      });

    // When the form is ready, we can hide the loader
    document.querySelector("#pay-form-loader").classList.add("visually-hidden");
  })
  .catch((error) => {
    // Errors occurred during the initialization of the script. Further execution is not possible.
  });

Step 2. Pay button click handling. Execute payment method.

You can call payment in any way that is convenient for you. To do this, call the function doPayment(). You do not need to send card data. WebSDK will do it. doPayment() returns a promise.

The method takes the following parameters:

phone String optional
Customer's phone number

saveCard boolean optional
Saving card option. For example, document.querySelector('#save-card').checked

email String optional
Customer's email

cardholderName String optional
Name of the card holder.

jsonParams Object optional
Additional fields. For example, you can send additional information about the order or any other information that is useful to you. For example, jsonParams: { "t-shirt-color": "black", "size": "M" }

Apply stored credentials

To pay with a stored credential, you need to pass the selected bindingId to the form before calling doPayment:
webSdkFormWithBindings.selectBinding('bindingId here');

If you changed your mind and want to pay with a new card don't forget to remove bindingId from the form:
webSdkFormWithBindings.selectBinding(null);

Call example:

webSdkFormWithBindings.doPayment({
    // Additional parameters
    email: 'foo@bar.com',
    phone: '4420123456789',
    saveCard: document.querySelector('#save-card').checked,
    cardholderName: 'JOHN DOE',
    jsonParams: { foo: 'bar' },
})
    .then((result) => {
        console.log('result', result);
    })
    .catch((e) => {
        // Error processing. For example, we show a block with an error
        errorEl.innerHTML = e.message;
        errorEl.classList.remove('visually-hidden');
    })
    .finally(() => {
        // Execute in any case. For example, make the "Pay" button active again.
        payButton.disabled = false;
        spinnerEl.classList.add('visually-hidden');
    });

Demo with stored credentials

Web SDK Payment requires an mdOrder (Order ID registered in the payment gateway).
You can register an order through the API.

This input is only for demo purposes - use Order ID value = xxxxx-xxxxx-xxxxx-xxxxx

The entire demo code

<div class="container_demo">
    <div class="about">
        <form name="formRunTest">
            <label for="mdOrder"> Order ID (mdOrder) <br>
              <span class="label__desc">(This input is only for demo purposes. The actual order ID should come from the backend.)</span>
            </label>
            <div class="run-test">
                <input id="mdOrder" type="text" placeholder="Paste the mdOrder registered for sandbox"/>
                <button class="btn-mini" id="load" type="submit">Load</button>
            </div>
        </form>
    </div>
    <div class="payment-form">
        <div class="payment-form-loader payment-form-loader--active">
            Web SDK Payment requires mdOrder (pre-registered order in the gateway).<br>
            You can register an order through Merchant Portal or through API. <br><br>
            Or try use <code>xxxxx-xxxxx-xxxxx-xxxxx</code> if you to want check only payment form.
        </div>
        <div id="pay-form-loader" class="spinner-container visually-hidden">
            <div class="spinner-border" role="status"></div>
        </div>
        <div class="card-body">
            <div class="col-12" id="select-binding-container" style="display: none">
              <select class="form-select" id="select-binding" aria-label="Default select example">
                <option selected value="new_card">Pay with a new card</option>
              </select>
            </div>
            <div class="col-12">
                <label for="pan" class="form-label">Card number</label>
                <div id="pan" class="form-control"></div>
            </div>
            <div class="col-6 col-expiry">
                <label for="expiry" class="form-label">Expiry</label>
                <div id="expiry" class="form-control"></div>
            </div>
            <div class="col-6 col-cvc">
                <label for="cvc" class="form-label">CVC / CVV</label>
                <div id="cvc" class="form-control"></div>
            </div>
            <label class="col-12" id="save-card-container">
              <input class="form-check-input" type="checkbox" value="" id="save-card" />
              Save card
            </label>
            <!-- Additional fields for Visa Mandatory -->
            <div class="col-12 additional-field" style="display:none;">
              <label for="" class="form-label">Cardholder</label>
              <div id="cardholder" class="additional-field-container">
                  <input type="text" class="additional-field-input" placeholder="NAME SURNAME"value="JOHN DOE">
              </div>
            </div>
            <div class="col-12 additional-field"  style="display:none;">
                <label for="" class="form-label">Mobile phone</label>
                <div id="mobile" class="additional-field-container">
                    <input type="tel" class="additional-field-input" placeholder="+4915112345" value="+4915112345678">
                </div>
            </div>
            <div class="col-12 additional-field" style="display:none;">
                <label for="" class="form-label">Email address</label>
                <div id="email" class="additional-field-container">
                    <input type="text" class="additional-field-input" placeholder="address@mail" value="address@mail.com">
                </div>
            </div>
        </div>
        <button class="btn btn-primary btn-lg" type="submit" id="pay">
            <span
            class="spinner-border spinner-border-sm me-2 visually-hidden"
            role="status"
            aria-hidden="true"
            id="pay-spinner">
            </span>
            <span>Pay</span>
        </button>
        <!-- This buttons are needed only for demonstrate how destroy method works -->
        <button class="btn btn-secondary" type="button" id="destroyFormWithoutCredentials">
            <span>Destroy</span>
        </button>
        <button class="btn btn-secondary" type="button" id="reinitFormWithoutCredentials" style="display: none;">
            <span>Reinit</span>
        </button>
        <div class="error my-2 visually-hidden" id="error"></div>
    </div>
</div>

<script>
document.addEventListener("DOMContentLoaded", () => {
  // Function to initialize payment form to reuse it after destroy
  function initPaymentForm() {
    const mrOrderInput = document.getElementById("mdOrder");
    mrOrderInput.classList.remove("invalid");
    if (!/(\w+-){3,10}\w+/g.test(mrOrderInput.value)) {
      mrOrderInput.classList.add("invalid");
      return;
    }
    // Removing example placeholder
    document.querySelector(".payment-form-loader").classList.remove("payment-form-loader--active");
    // Adding payment form loader
    document.querySelector("#pay-form-loader").classList.remove("visually-hidden");

    // Initialization of Web SDK. A required mdOrder (order ID) is needed.
    initPayment(mrOrderInput.value);
  }

  // Handler initialization for expamle input
  function handleSubmit(e) {
    e.preventDefault();
    // Initialize payment form
    initPaymentForm();
  }

  // Register event for example input
  document.formRunTest.addEventListener("submit", handleSubmit);

  let webSdkFormWithBindings;
  // Array of additional field objects with field id, validation template and valid input characters
  const mandatoryFieldsWithoutBinding = [
    {
        id: '#cardholder',
        template: /^[a-zA-Z '`.\-]{4,24}$/,
        replace: /[^a-zA-Z ' \-`.]/g,
    },
    {
        id: '#mobile',
        template: /^\+?[1-9][0-9]{7,14}$/,
        replace: /[^0-9\+]/g,
    },
    {
        id: '#email',
        template: /^[a-zA-Z0-9._-]{1,64}@([a-zA-Z0-9.-]{2,255})\.[a-zA-Z]{2,255}$/,
        replace: /[^a-zA-Z0-9@._-]/g,
    }
  ]

  function initPayment(mdOrder) {
    webSdkFormWithBindings = new window.PaymentForm({
      // Order number (order registration happens before initialization of the form)
      mdOrder: mdOrder,
      // Handling form validation
      onFormValidate: (isValid) => {
        // For example, you can disable "pay" and "Get token" buttons if from is not valid, like this:
        // const payButton = document.querySelector('#pay');
        // payButton.disabled = !isValid;
      },
      //  Context for API queries
      apiContext: "/payment",
      // Language - is used for localization of errors and names for placeholders.
      // The language should be supported in the merchant's settings
      language: "en",
      // Class name for container elements containing iFrames
      containerClassName: "field-container",
      // Automatic switching of focus when fields are filled
      autoFocus: true,
      // Show payment system icon
      showPanIcon: true,
      // Additional styles for the payment system icon
      panIconStyle: {
        height: "16px",
        top: "calc(50% - 8px)",
        right: "8px",
      },
      // Field settings
      fields: {
        // Container element in which the iFrame with the field will be placed
        pan: {
          container: document.querySelector("#pan"),
        },
        // Card expiration date
        expiry: {
          container: document.querySelector("#expiry"),
        },
        // CVC/CVV-code
        cvc: {
          container: document.querySelector("#cvc"),
        },
      },
      // Additional styles to customize the appearance of input fields within iFrames
      styles: {
        base: {
          padding: "0px 16px",
          color: "black",
          fontSize: "18px",
        },
        disabled: {
          backgroundColor: "#e9ecef",
        },
        invalid: {
          color: "red",
        },
        placeholder: {
          base: {
            color: "gray",
          },
          focus: {
            color: "transparent",
          },
        },
      },
    });

    // Action after initialization
    webSdkFormWithBindings
      .init()
      .then(({ orderSession }) => {
        // The `orderSession` object contains all the information about the order, including information about the stored credentials.
        console.info("orderSession", orderSession);

        // Show the select stored credential element
        document.querySelector("#select-binding-container").style.display = orderSession.bindings.length ? "" : "none";

        // Fill the select with stored credentials
        orderSession.bindings.forEach((binding) => {
          document.querySelector("#select-binding").options.add(new Option(binding.pan, binding.id));
        });

        // Handle select stored credential or a new card
        document.querySelector("#select-binding").addEventListener("change", function () {
          const bindingId = this.value;
          if (bindingId !== "new_card") {
            // Set binding id
            webSdkFormWithBindings.selectBinding(bindingId);

            // Hide the 'Save card' checkbox
            document.querySelector("#save-card-container").style.display = "none";
          } else {
            // Selecting stored credentials with null means switching to a new card
            webSdkFormWithBindings.selectBinding(null);

            // Show the 'Save card' checkbox
            document.querySelector("#save-card-container").style.display = "";
          }
        });

        // When the form is ready, we can hide the loader
        document.querySelector("#pay-form-loader").classList.add("visually-hidden");

        // Remove the event for the example input
        document.formRunTest.removeEventListener("submit", handleSubmit);
      })
      .catch((error) => {
        // Execute on error
        const errorEl = document.querySelector("#error");
        errorEl.innerHTML = e.message;
        errorEl.classList.remove("visually-hidden");
      })
      .finally(() => {
        // Validation and characters replacement on fields input
        mandatoryFieldsWithBinding.forEach(item => {
          const field = document.querySelector(item.id)
          field.closest(".additional-field").style.display = '';
          field.addEventListener('input', () => {
              let inputValue = field.querySelector('input')
              inputValue.value = item.replace ? inputValue.value.replace(item.replace,'') : inputValue.value;
              if (item.id.includes("#cardholder")) {
                  inputValue.value = inputValue.value.toUpperCase()
              }
              if (item.template) {
                  // The CSS class ".additional-field-invalid" is used to display invalid fields
                  if (item.template.test(inputValue.value)) {
                      field.classList.remove("additional-field-invalid")
                  } else {
                      field.classList.add("additional-field-invalid")
                  }
              }
          })
        })
      });
  }

  // Payment handler
  document.querySelector("#pay").addEventListener("click", () => {
    // Make the "Pay" button inactive to avoid double payments
    const payButton = document.querySelector("#pay");
    payButton.disabled = true;

    // Show the loader, for the user
    const spinnerEl = document.querySelector("#pay-spinner");
    spinnerEl.classList.remove("visually-hidden");

    // Hide the error container
    const errorEl = document.querySelector("#error");
    errorEl.classList.add("visually-hidden");
    // Checking of additional fields validation before Payment
    if (document.querySelectorAll('.additional-field-invalid').length) {
      errorEl.innerHTML = "Form is not valid";
      errorEl.classList.remove('visually-hidden');
      spinnerEl.classList.add('visually-hidden');
      return
    }
    // Start payment
    webSdkFormWithBindings
      .doPayment({
        // Additional parameters
        email: document.querySelector('#email input').value,
        phone: document.querySelector('#mobile input').value,
        cardholderName: document.querySelector('#cardholder input').value,
        saveCard: document.querySelector("#save-card").checked,
        jsonParams: { foo: "bar" },
      })
      .then((result) => {
        // Here you can do something with payment result
        console.log("result", result);
      })
      .catch((e) => {
        // Execute on error
        errorEl.innerHTML = e.message;
        errorEl.classList.remove("visually-hidden");
      })
      .finally(() => {
        // Execute in any case. For example, make the "Pay" button active again.
        payButton.disabled = false;
        spinnerEl.classList.add("visually-hidden");
      });
  });

  // Destroy handler
  document.querySelector("#destroyFormWithCredentials").addEventListener("click", function () {
    // Remove destroy button and display reinit button for demo
    this.style.display = "none";
    document.querySelector("#reinitFormWithCredentials").style.display = "";
    // Execute destroy method on web sdk form
    webSdkFormWithBindings.destroy();
  });

  // Init payment form handler
  document.querySelector("#reinitFormWithCredentials").addEventListener("click", function () {
    // Remove reinit button and display destroy button for demo
    this.style.display = "none";
    document.querySelector("#destroyFormWithCredentials").style.display = "";
    // Payment form initialization
    initPaymentForm();
  });
}); 
</script>

Web SDK in React SPA

In single page application on React it is necessary to initialize the Web SDK by executing the webSdkPaymentForm.init() method on each initial render of the page with the Web SDK form.

On the event of resetting the page with Web SDK form (i.e. when switching to another page), it is necessary to execute the webSdkPaymentForm.destroy() method. This is important, because only one webSDK form handler (multiframe-commutator) should remain on the page.

When returning to the page with the Web SDK form it is necessary to perform initialization again using webSdkPaymentForm.init() method.

Note that PCI DSS compliance is required for using this library since it handles card data. Read more about PCI DSS here.

Example of React component

import { useEffect, useRef } from "react";

function addScript(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");

    script.setAttribute("src", src);
    script.addEventListener("load", resolve);
    script.addEventListener("error", reject);

    document.body.appendChild(script);
  });
}

function App() {
  const panRef = useRef(null);
  const expiryRef = useRef(null);
  const cvcRef = useRef(null);
  const selectBindingRef = useRef(null);
  const saveCardContainerRef = useRef(null);
  const payButtonRef = useRef(null);
  let webSdkPaymentForm = null;

  useEffect(() => {
    const initPaymentForm = async () => {
      await addScript(
        "https://uat.dskbank.bg/payment/modules/multiframe/main.js",
      );

      webSdkPaymentForm = new window.PaymentForm({
        mdOrder: mdOrder,
        containerClassName: "field-container",
        onFormValidate: (isValid) => {
          // Handle form validation
        },
        apiContext: "/payment",
        language: "en",
        autoFocus: true,
        showPanIcon: true,
        panIconStyle: {
          height: "16px",
          top: "calc(50% - 8px)",
          right: "8px",
        },
        fields: {
          pan: {
            container: panRef.current,
            onFocus: (containerElement) => {
              // Handle focus
            },
            onBlur: (containerElement) => {
              // Handle blur
            },
            onValidate: (isValid, containerElement) => {
              // Handle validation
            },
          },
          expiry: {
            container: expiryRef.current,
            // Handle expiry setup
          },
          cvc: {
            container: cvcRef.current,
            // Handle cvc setup
          },
        },
        styles: {
          base: {
            color: "black",
            fontSize: "18px",
          },
          focus: {
            color: "blue",
          },
          disabled: {
            color: "gray",
          },
          valid: {
            color: "green",
          },
          invalid: {
            color: "red",
          },
          placeholder: {
            base: {
              color: "gray",
            },
            focus: {
              color: "transparent",
            },
          },
        },
      });

      webSdkPaymentForm
        .init()
        .then(({ orderSession }) => {
          console.info("orderSession", orderSession);

          if (orderSession.bindings.length) {
            selectBindingRef.current.style.display = "";
          } else {
            selectBindingRef.current.style.display = "none";
          }

          if (orderSession.bindingEnabled) {
            saveCardContainerRef.current.style.display = "";
          } else {
            saveCardContainerRef.current.style.display = "none";
          }

          orderSession.bindings.forEach((binding) => {
            const option = new Option(binding.pan, binding.id);
            selectBindingRef.current.options.add(option);
          });
        })
        .catch(() => {
          // Handle initialization error
        });
    };

    initPaymentForm();

    return () => {
      if (webSdkPaymentForm) {
        webSdkPaymentForm.destroy();
      }
    };
  }, []);

  const handlePayment = () => {
    payButtonRef.current.disabled = true;

    webSdkPaymentForm
      .doPayment({})
      .then((result) => {
        // Handle successful payment
      })
      .catch((e) => {
        alert("Error");
      })
      .finally(() => {
        payButtonRef.current.disabled = false;
      });
  };

  const handleSelectBinding = () => {
    const bindingId = selectBindingRef.current.value;
    if (bindingId !== "new_card") {
      webSdkPaymentForm.selectBinding(bindingId);
      saveCardContainerRef.current.style.display = "none";
    } else {
      webSdkPaymentForm.selectBinding(null);
      saveCardContainerRef.current.style.display = "";
    }
  };

  return (
    <div className="container">
      <div className="websdk-form">
        <div className="card-body">
          <div
            className="col-12"
            id="select-binding-container"
            onChange={handleSelectBinding}
          >
            <select
              className="form-select"
              id="select-binding"
              ref={selectBindingRef}
              aria-label="Default select example"
            >
              <option value="new_card">Pay by new card</option>
            </select>
          </div>
          <div className="col-12 input-form">
            <label htmlFor="pan" className="form-label">
              Card number
            </label>
            <div id="pan" className="form-control" ref={panRef}></div>
          </div>
          <div className="col-6 col-expiry input-form">
            <label htmlFor="expiry" className="form-label">
              Expiry
            </label>
            <div id="expiry" className="form-control" ref={expiryRef}></div>
          </div>
          <div className="col-6 col-cvc">
            <label htmlFor="cvc" className="form-label">
              CVC / CVV
            </label>
            <div id="cvc" className="form-control" ref={cvcRef}></div>
          </div>
          <label className="col-12" id="save-card-container">
            <input
              className="form-check-input me-1"
              ref={saveCardContainerRef}
              type="checkbox"
              value=""
              id="save-card"
            />
            Save card
          </label>
        </div>
        <div className="pay-control">
          <button
            className="btn btn-primary btn-lg"
            type="submit"
            id="pay"
            ref={payButtonRef}
            onClick={handlePayment}
          >
            Pay
          </button>
        </div>
        <div
          className="error my-2 text-center text-danger visually-hidden"
          id="error"
        ></div>
      </div>
    </div>
  );
}

export default App;
Categories:
eCommerce SDK
Categories
Search results