Web SDK Integration

STEP 1 : Retrieve VIS Plans

To integrate the provided API from the NI SDK into your payment page to retrieve available plans, you can follow these steps:

Include NI SDK in your project: Make sure you have included the necessary files or dependencies for the NI SDK in your project. This typically involves importing the SDK into your project's codebase.

Call the API: When your payment page loads or when needed, make a call to the API provided by the NI SDK to retrieve available plans. This call should include the necessary payload required by the API.

Process the Response: Once you receive the response from the API call, process it accordingly. This may involve parsing the response data.

Display the Plans: Display the retrieved plans on your payment page according to VISA standards.

window.NI.handleGetVISPlans({
    apiKey,
    outletRef: testOutletRef,
    sessionId,
    id: 'installment-options',
    body: {
      action: orderAction,
      amount: {
        currencyCode: merchantCurrency,
        value: orderAmount
      }
    }
  })

Sample Response

{
    "matchedPlans": [
        {
            "costInfo": {
                "currency": "AED",
                "feeInfo": [
                    {
                        "ratePercentage": 250,
                        "type": "CONSUMER",
                        "flatFee": 5000
                    },
                    {
                        "ratePercentage": 0,
                        "type": "MERCHANT_FUNDING",
                        "flatFee": 0
                    },
                    {
                        "ratePercentage": 10,
                        "type": "MERCHANT_SERVICE",
                        "flatFee": 0
                    }
                ],
                "firstInstallment": {
                    "amount": 74000,
                    "installmentFee": 3517,
                    "totalAmount": 77517,
                    "upfrontFee": 0
                },
                "lastInstallment": {
                    "amount": 74000,
                    "installmentFee": 3516,
                    "totalAmount": 77516,
                    "upfrontFee": 0
                },
                "totalFees": 10550,
                "totalPlanCost": 232550,
                "totalRecurringFees": 10550,
                "totalUpfrontFees": 0,
                "annualPercentageRate": 0
            },
            "installmentFrequency": "MONTHLY",
            "name": "VISTest3MonthAED",
            "numberOfInstallments": 3,
            "termsAndConditions": [
                {
                    "text": "This is a sample text to describe the terms and conditions that govern the Visa Installment services.",
                    "version": 2,
                    "languageCode": "eng",
                    "url": "https://www.visa.com"
                },
                {
                    "text": "هذا نص نموذجي لوصف الشروط والأحكام التي تحكم خدمات أقساط التأشيرة.",
                    "version": 2,
                    "languageCode": "ara",
                    "url": "https://www.visa.com"
                }
            ],
            "fundedBy": [
                "CONSUMER"
            ],
            "type": "ISSUER_DEFAULT",
            "vPlanID": "a57d2cfc-fed2-395d-5784-166cb037c301",
            "vPlanIDRef": "00000000CA"
        },
        {
            "costInfo": {
                "currency": "AED",
                "feeInfo": [
                    {
                        "ratePercentage": 0,
                        "type": "CONSUMER",
                        "flatFee": 1000
                    },
                    {
                        "ratePercentage": 0,
                        "type": "MERCHANT_FUNDING",
                        "flatFee": 0
                    },
                    {
                        "ratePercentage": 10,
                        "type": "MERCHANT_SERVICE",
                        "flatFee": 0
                    }
                ],
                "firstInstallment": {
                    "amount": 37000,
                    "installmentFee": 167,
                    "totalAmount": 37167,
                    "upfrontFee": 0
                },
                "lastInstallment": {
                    "amount": 37000,
                    "installmentFee": 165,
                    "totalAmount": 37165,
                    "upfrontFee": 0
                },
                "totalFees": 1000,
                "totalPlanCost": 223000,
                "totalRecurringFees": 1000,
                "totalUpfrontFees": 0,
                "annualPercentageRate": 0
            },
            "installmentFrequency": "MONTHLY",
            "name": "VISTest6MonthAED",
            "numberOfInstallments": 6,
            "termsAndConditions": [
                {
                    "text": "This is a sample text to describe the terms and conditions that govern the Visa Installment services.",
                    "version": 2,
                    "languageCode": "eng",
                    "url": "https://www.visa.com"
                },
                {
                    "text": "هذا نص نموذجي لوصف الشروط والأحكام التي تحكم خدمات أقساط التأشيرة.",
                    "version": 2,
                    "languageCode": "ara",
                    "url": "https://www.visa.com"
                }
            ],
            "fundedBy": [
                "CONSUMER"
            ],
            "type": "ISSUER_DEFAULT",
            "vPlanID": "d1c7254a-0ee9-ed76-97e6-10ba2b310101",
            "vPlanIDRef": "00000000CB"
        },
        {
            "costInfo": {
                "currency": "AED",
                "feeInfo": [
                    {
                        "ratePercentage": 500,
                        "type": "CONSUMER",
                        "flatFee": 0
                    },
                    {
                        "ratePercentage": 0,
                        "type": "MERCHANT_FUNDING",
                        "flatFee": 0
                    },
                    {
                        "ratePercentage": 10,
                        "type": "MERCHANT_SERVICE",
                        "flatFee": 0
                    }
                ],
                "firstInstallment": {
                    "amount": 18500,
                    "installmentFee": 925,
                    "totalAmount": 19425,
                    "upfrontFee": 0
                },
                "lastInstallment": {
                    "amount": 18500,
                    "installmentFee": 925,
                    "totalAmount": 19425,
                    "upfrontFee": 0
                },
                "totalFees": 11100,
                "totalPlanCost": 233100,
                "totalRecurringFees": 11100,
                "totalUpfrontFees": 0,
                "annualPercentageRate": 0
            },
            "installmentFrequency": "MONTHLY",
            "name": "VISTest12MonthAED",
            "numberOfInstallments": 12,
            "termsAndConditions": [
                {
                    "text": "This is a sample text to describe the terms and conditions that govern the Visa Installment services.",
                    "version": 2,
                    "languageCode": "eng",
                    "url": "https://www.visa.com"
                },
                {
                    "text": "هذا نص نموذجي لوصف الشروط والأحكام التي تحكم خدمات أقساط التأشيرة.",
                    "version": 2,
                    "languageCode": "ara",
                    "url": "https://www.visa.com"
                }
            ],
            "fundedBy": [
                "CONSUMER"
            ],
            "type": "ISSUER_DEFAULT",
            "vPlanID": "d4e913ce-9206-14ee-e564-17e129e9dc01",
            "vPlanIDRef": "00000000CC"
        }
    ],
    "transactionAmount": 222000,
    "transactionCurrency": "AED",
    "merchantInfo": {
        "category": "5399",
        "partnerMerchantReferenceID": "5708129739"
    },
    "paymentAccountReference": "V0010013022320223205435954671"
}


STEP 2 : Create a div to append the plans

Create a div to append the retrieved plans from the above api in the payment screen ( id is mandatory,
same id should be sent in payload for the above api )

<div id="installment-options" data-show="INSTALLMENT_PLAN"></div>

Example : The below code snippet demonstrate how to fetch the VIS plans and how we append all the plans that are sent in the response

const container = document.getElementById('installment-options');
  const {
    response: { matchedPlans }
  } = await window.NI.handleGetVISPlans({
    apiKey,
    outletRef: testOutletRef,
    sessionId,
    id: 'installment-options',
    body: {
      action: orderAction,
      amount: {
        currencyCode: merchantCurrency,
        value: orderAmount
      }
    }
  });

  if (!matchedPlans.length) {
    return;
  }
  // Create a flex container element
  container.style.display = 'inline';
  const flexContainer = document.createElement('div');
  flexContainer.classList.add('flex-container');
  disableMakePaymentButton();

  matchedPlans.unshift({
    numberOfInstallments: 'FULL'
  });

  // Append flex items with dynamic values to the flex container
  matchedPlans.forEach((visOption, index) => {
    const flexItem = document.createElement('div');
    flexItem.classList.add('flex-item');

    const frequencyDiv = document.createElement('div');
    frequencyDiv.innerHTML = `<strong id=${`pay_in`}">PAY IN ${
      visOption.numberOfInstallments
    }</strong>
    <p>${testOrder.amount.value}</p>`;
    flexItem.appendChild(frequencyDiv);

    const installmentFrequencyMapping = {
      MONTHLY: 'Monthly',
      WEEKLY: 'Weekly',
      'BI-MONTHLY': 'Bi-monthly',
      'BI-WEEKLY': 'Bi-weekly'
    };

    let installmentDiv;
    let monthlyRateDiv;
    let processingFeeDiv;
    let termsAndConditions;
    let checkbox;
    let readMoreLink;
    let readLessLink;

    if (index !== 0) {
      installmentDiv = document.createElement('div');
      installmentDiv.textContent = `${
        installmentFrequencyMapping[visOption.installmentFrequency]
      } Installment: ${minorToMajorUnits(
        visOption.costInfo.lastInstallment.totalAmount
      )} ${visOption.costInfo.currency}`;
      flexItem.appendChild(installmentDiv);

      monthlyRateDiv = document.createElement('div');
      monthlyRateDiv.textContent = `${
        installmentFrequencyMapping[visOption.installmentFrequency]
      } Rate: ${(visOption.costInfo.annualPercentageRate / 100).toFixed(2)} %`;
      flexItem.appendChild(monthlyRateDiv);

      processingFeeDiv = document.createElement('div');
      processingFeeDiv.textContent = `Processing Fees: ${minorToMajorUnits(
        visOption.costInfo.totalUpfrontFees,
        visOption.costInfo.currency
      )} ${visOption.costInfo.currency}`;
      flexItem.appendChild(processingFeeDiv);

      // Create terms and conditions element
      termsAndConditions = document.createElement('div');
      termsAndConditions.classList.add('terms-and-conditions');

      const engTnC = visOption.termsAndConditions.filter(
        tncOption => tncOption.languageCode === 'eng'
      );

      // Create checkbox
      checkbox = document.createElement('input');
      checkbox.type = 'checkbox';
      checkbox.style.width = '18px';
      checkbox.style.height = '18px';
      checkbox.style.marginRight = '4px';
      checkbox.id = `checkbox-${engTnC[0].languageCode}`; // Unique ID for each checkbox
      checkbox.disabled = true; // Disable the checkbox initially
      termsAndConditions.appendChild(checkbox);

      // Create label for checkbox
      const label = document.createElement('label');
      label.setAttribute('for', checkbox.id);
      label.textContent = ` Terms and Conditions: `;
      label.style.verticalAlign = 'bottom';
      label.style.lineHeight = '18px';
      termsAndConditions.appendChild(label);

      // Create Read More link
      readMoreLink = document.createElement('a');
      readMoreLink.textContent = 'Read More';
      readMoreLink.style.verticalAlign = 'bottom';
      readMoreLink.style.lineHeight = '18px';
      readMoreLink.classList.add('read-more-link');
      termsAndConditions.appendChild(readMoreLink);

      // Create div for terms and conditions text
      const tncText = document.createElement('div');
      tncText.innerHTML = formatText(engTnC[0].text);
      tncText.style.fontSize = '12px';
      termsAndConditions.appendChild(tncText);
      tncText.style.display = 'none'; // Hide terms and conditions text by default

      // Add event listener to toggle visibility of terms and conditions text
      readMoreLink.addEventListener('click', event => {
        event.preventDefault();
        event.stopPropagation();
        tncText.style.display =
          tncText.style.display === 'none' ? 'inline' : 'none'; // Toggle visibility
        tncText.style.verticalAlign = 'bottom';
        readMoreLink.style.display = 'none'; // Hide "Read More" link
        checkbox.disabled = false; // Enable the checkbox
        readLessLink = document.createElement('a');
        readLessLink.textContent = 'Read Less';
        readLessLink.style.marginLeft = '4px';
        readLessLink.classList.add('read-less-link');
        termsAndConditions.appendChild(readLessLink);

        // Add event listener to toggle visibility of terms and conditions text when "Read Less" link is clicked
        readLessLink.addEventListener('click', e => {
          e.preventDefault();
          e.stopPropagation();
          tncText.style.display = 'none'; // Hide terms and conditions text
          readLessLink.style.display = 'none'; // Hide "Read Less" link
          readMoreLink.style.display = 'inline'; // Show "Read More" link
          if (!checkbox.checked) {
            checkbox.disabled = true; // Disable the checkbox
          } else {
            selectedVisPlan = null;
          }
        });
      });

      flexItem.appendChild(termsAndConditions);
      // Add event listener to the installmentCheckbox to stop event propagation
      checkbox.addEventListener('click', event => {
        if (event.target.checked) {
          selectedVisPlan = visOption;
          enableMakePaymentButton();
        } else {
          disableMakePaymentButton();
          selectedVisPlan = null;
        }
        if (readMoreLink.style.display === 'inline') {
          checkbox.disabled = true;
        }
        event.stopPropagation(); // Stop event propagation to prevent triggering the flex item click event
      });
    }

    // Add event listener to toggle expansion and selection
    flexItem.addEventListener('click', event => {
      // Check if the click target is the checkbox
      if (readLessLink) {
        readLessLink.click();
      }
      let installmentCheckbox;
      if (!event.target.closest('input[type="checkbox"]')) {
        // Toggle expanded class
        flexItem.classList.toggle('expanded');
        if (
          visOption.numberOfInstallments === 'FULL' &&
          flexItem.classList.contains('expanded')
        ) {
          enableMakePaymentButton();
          selectedVisPlan = visOption;
        } else {
          disableMakePaymentButton();
          selectedVisPlan = null;
        }
        flexItem.getElementsByTagName('strong')[0].style.color = 'blue';
        // Deselect previously selected items
        if (selectedFlexItem && selectedFlexItem !== flexItem) {
          selectedFlexItem.classList.remove('selected');
          selectedFlexItem.classList.remove('expanded');
          selectedFlexItem.getElementsByTagName('strong')[0].style.color =
            'black';
          installmentCheckbox = selectedFlexItem.querySelector(
            'input[type="checkbox"]'
          );
          if (installmentCheckbox) {
            installmentCheckbox.checked = false; // Uncheck installmentCheckbox when deselecting item
          }
        }
        // Toggle selected class
        flexItem.classList.toggle('selected');
        if (!flexItem.classList.contains('selected')) {
          checkbox.checked = false;
          selectedVisPlan = null;
          flexItem.getElementsByTagName('strong')[0].style.color = 'black';
        }
        selectedFlexItem = flexItem; // Update selectedFlexItem
      }
    });

    flexContainer.appendChild(flexItem);
  });

  // Append the flex container to the main container in the HTML document
  container.appendChild(flexContainer);


// CSS 

.flex-container {
  display: flex;
  flex-direction: column; /* Set flex container in column layout */
  margin: 10px 0;
  font-size: 14px;
}

.flex-item {
  flex: 1 0 calc(25% - 10px); /* Each item takes 25% width with margins */
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  padding: 10px;
  margin: 5px;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
  cursor: pointer;
  transition: border-color 0.3s ease-in-out, box-shadow 0.3s ease-in-out;
  background-color: #fff;
  border-radius: 8px;
}

@media screen and (max-width: 600px) {
  /* Adjust number of items per row for smaller screens */
  .flex-item {
    flex-basis: calc(50% - 10px); /* Two items per row */
  }
}

@media screen and (max-width: 400px) {
  /* Adjust number of items per row for even smaller screens */
  .flex-item {
    flex-basis: 100%; /* One item per row */
  }
}
.flex-item.selected {
  border: 2px solid #253cfc;
}
.dynamic-value {
  margin-bottom: 5px;
  width: calc(25% - 5px);
  text-align: center;
}

.dynamic-value:not(:last-child) {
  border-right: 1px solid #ccc; /* Add right border for each dynamic-value except the last one */
}

.dynamic-value:nth-last-child(2):not(:last-child) {
  border-right: none; /* Remove right border for the last but one dynamic-value */
}

.terms-and-conditions {
  display: none;
  padding: 10px;
  border: 1px solid #ccc;
  margin-top: 10px;
}
.flex-item.expanded .terms-and-conditions {
  display: block;
}


STEP 3 : To send payload after selecting the plan

If PAY IN FULL is selected the payload should looks like below

vis section should be sent inside the payment object which is part of order

"vis": {
                "planSelectionIndicator": false,
                "vPlanId": null,
                "acceptedTAndCVersion": null
            }
{
    "order": {
        "action": "PURCHASE",
        "amount": {
            "currencyCode": "AED",
            "value": 222000
        },
        "language": "en",
        "payment": {
            "currency": "AED",
            "vis": {
                "planSelectionIndicator": false,
                "vPlanId": null,
                "acceptedTAndCVersion": null
            }
        }
    },
    "outletRef": "97333dc8-c3af-4098-8538-5eab87db99b8",
    "sessionId": "4d5512e3-b3b3-42ff-ba1e-52e0638f309f"
}

If any other plan is selected the payload looks like below

"vis": {  
                "planSelectionIndicator": true,  
                "vPlanId": "d4e913ce-9206-14ee-e564-17e129e9dc01",  
                "acceptedTAndCVersion": 2  
            }

📘

Important:

planSelectionIndicator : should be true if PAY IN FULL is not selected

acceptedTAndCVersion : It is the version of terms and conditions that is selected in the respective plan

{
    "order": {
        "action": "PURCHASE",
        "amount": {
            "currencyCode": "AED",
            "value": 222000
        },
        "language": "en",
        "payment": {
            "currency": "AED",
            "vis": {
                "planSelectionIndicator": true,
                "vPlanId": "d4e913ce-9206-14ee-e564-17e129e9dc01",
                "acceptedTAndCVersion": 2
            }
        }
    },
    "outletRef": "97333dc8-c3af-4098-8538-5eab87db99b8",
    "sessionId": "33c0a2ad-a49f-4ef0-b07f-603d0cf6c898"
}