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"
}