Payment Schedule Plugin Detailed Example
This example is a sample implementation of the payment schedule plugin. It implements multiple payment schedules, some of which are not possible in the base Socotra implementation outside of the plugin.
Note
This example is useful for new business transactions. An extended example for other policy transactions will be released shortly.
Configuration
The payment schedule name is passed to the plugin. The possible values for the schedule name are as defined in configuration for the product, in policy.json
. For this example, update your policy.json file to have the following definition for paymentSchedules
:
{
"paymentSchedules": [
{
"type": "custom",
"name": "2pay",
"displayName": "2 Payments"
},
{
"type": "custom",
"name": "5pay",
"displayName": "5 Payments"
},
{
"type": "custom",
"name": "12pay",
"displayName": "12 Payments"
},
{
"type": "total",
"name": "upfront",
"displayName": "Up Front"
},
{
"type": "monthly",
"name": "monthly",
"displayName": "Monthly"
},
{
"type": "quarterly",
"name": "quarterly",
"displayName": "Quarterly"
},
{
"type": "every_week",
"name": "every_week",
"displayName": "Every Week"
},
{
"type": "every_two_weeks",
"name": "every_two_weeks",
"displayName": "Every Two Weeks"
}
]
}
Plugin Implementation
The main plugin is defined in installments.js
. This sample relies on some additional files. These are:
moment-timezone-with-data.js
andmoment-with-locales.js
. From the moment.js project, which is a popular date handling library.dates.js
. A wrapper for the moment.js functionality which adds additional functionality. The main plugin file does not interact with moment.js directly.arrays.js
. A polyfill to add methods to arrays, for grouping, summing, aggregating, etc.
A zip file containing all these files is here
.
// installments.js
let arrays = require('./arrays.js');
let dates = require('./dates.js');
function createInstallments(data)
{
arrays.polyfill();
let installments;
switch (data.paymentScheduleName)
{
case 'upfront':
installments = getUpfront(data);
break;
case 'monthly':
installments = getInstallments(data, 'month');
break;
case 'quarterly':
installments = getInstallments(data, 'quarter');
break;
case 'every_week':
installments = getInstallments(data, 'week');
break;
case 'every_two_weeks':
installments = getInstallments(data, '2week');
break;
case '2pay':
installments = getInstallments(data, 'month', 2);
break;
case '5pay':
installments = getInstallments(data, 'month', 5);
break;
case '12pay':
installment = getInstallments(data, 'month', 12);
break;
default:
throw `${data.paymentScheduleName} not implemented!`;
}
return { installments: installments };
}
function getUpfront(data)
{
let invoiceItems = data.charges.map(ch => ({
amount: parseFloat(ch.amount),
chargeId: ch.chargeId
}));
return [{
dueTimestamp: data.coverageStartTimestamp,
issueTimestamp: data.coverageStartTimestamp,
startTimestamp: data.coverageStartTimestamp,
endTimestamp: data.coverageEndTimestamp,
invoiceItems: invoiceItems,
writeOff: false
}];
}
function getInstallments(data, increment, maxInstallments = 1000)
{
let nowTimestamp = new Date().getTime();
let startTimestamp = parseInt(data.policy.originalContractStartTimestamp);
let endTimestamp = parseInt(data.policy.effectiveContractEndTimestamp);
let startMoment = dates.fromTimestamp(startTimestamp, data.tenantTimeZone);
let endMoment = dates.fromTimestamp(endTimestamp, data.tenantTimeZone);
let installmentTimestamps = dates.span(startMoment, endMoment, increment)
.map(m => dates.getTimestamp(m));
if (installmentTimestamps.length == 0)
installmentTimestamps = [nowTimestamp];
else if (installmentTimestamps.length > maxInstallments)
installmentTimestamps = installmentTimestamps.slice(0, maxInstallments);
let installments = [];
for (let i = 0; i < installmentTimestamps.length; i++)
{
let it = installmentTimestamps[i];
installments.push({
invoiceItems: [],
dueTimestamp: it,
startTimestamp: it,
issueTimestamp: it,
endTimestamp: i < installmentTimestamps.length - 1 ? installmentTimestamps[i + 1] : endTimestamp,
writeOff: false
});
}
for (charge of data.charges)
{
let newItems = [];
for (let i = 0; i < installments.length; i++)
{
if (charge.coverageStartTimestamp <= installments[i].dueTimestamp &&
charge.coverageEndTimestamp > installments[i].dueTimestamp)
{
let newItem = { chargeId: charge.chargeId };
newItems.push(newItem);
installments[i].invoiceItems.push(newItem);
}
}
if (newItems.length == 0)
{
// No installments fell within the charge coverage time, so find one
let item = { chargeId: charge.chargeId, amount: parseFloat(charge.amount) };
let inst;
for (let i = 0; i < installments.length; i++)
if (installments[i].dueTimestamp <= charge.coverageStartTimestamp)
{
inst = installments[i];
break;
}
if (inst === undefined)
inst = installments[0];
inst.invoiceItems.push(item);
}
else
{
let amount = parseFloat(charge.amount);
let fraction = round2(amount / newItems.length);
for (let i = 0; i < newItems.length; i++)
{
if (i < newItems.length - 1)
newItems[i].amount = fraction;
else
newItems[i].amount = round2(amount - newItems.slice(0, newItems.length - 1).sum(ni => ni.amount));
}
}
}
return installments.filter(inst => inst.invoiceItems.length > 0);
}
function round2(amount)
{
return Math.round(amount * 100.0) / 100.0;
}
exports.createInstallments = createInstallments;