Installments
Overview
Installments for a policy are created using the timeline in the current Installment Lattice. These are based on the settings for billing the policy, such as the day of the month for invoices, the cadence, etc. But, these settings may not express all the considerations that are important for invoice timing. For example:
In account-level billing, the lattices may not be aligned, which will cause separate invoices to be created, rather than combining the policies’ charges on a single stream of invoices.
The first installment on issuance or after a policy change will typically be scheduled immediately rather than being delayed until the next invoice period.
The timing of an installment can not vary based on its amount or the kind of charges it contains.
The Installments Plugin addresses these concerns by supporting overriding of the timing for an installment. These times are now overridable:
generateTime
dueTime
autopayTime
Plugin Execution
After the installment lattice is used to create installments, but before the installments have been finalized, the installments plugin will be executed and passed the following data:
{
context: {
accountLocator: locator,
quoteLocator: locator,
policyLocator: locator,
transactionLocator: locator
},
installments: [ InstallmentResponse ],
installmentLattice: InstallmentLattice
}
You can then use this information, along with the Plugin Data Fetcher, to make adjustments to the scheduled times for each installment.
The plugin should return a response that contains an installmentUpdates
property of type Map<String, InstallmentUpdate>
, where each entry is keyed to an installment locator, and the update contains any or all of a new generateTime
, autopayTime
, and/or dueTime
. Each of the times is optional; any times left null
in the response will indicate that the original time for the installment will remain unchanged, unless it becomes incompatible with other changes (See Adjustment Rules, below.)
For example, the response might look like:
{
"installmentUpdates": {
"<installmentLocator1>": {
"generateTime": "2025-08-01T00:00:00Z",
"dueTime": "2025-08-15T00:00:00Z"
},
"<installmentLocator2>": {
"autopayTime": "2025-09-13T00:00:00Z"
}
}
}
This plugin can be implemented for all products in the top-level /plugins/java
folder, or as product-specific plugins within each product’s folder, such as <product-name>/plugins/java
.
Note
Installments that have the same generateTime
and dueTime
will be combined onto the same invoice when the generate time is reached.
Adjustment Rules
The system will auto-adjust installment times returned from the plugin according to these rules:
If the
generateTime
is afterdueTime
, it will be set to equal thedueTime
.If the
autopayTime
is not betweengenerateTime
anddueTime
, it will be set to the later ofgenerateTime
or one day prior todueTime
.
Implementation Example
This example shows how installments can be scheduled to generate not before the beginning of the following month:
public class InstallmentsPluginImpl implements InstallmentsPlugin {
private static final Logger log = LoggerFactory.getLogger(InstallmentsPluginImpl.class);
@Override
public InstallmentsPluginResponse updateInstallments(PersonalAutoRequest request) {
Map<String, InstallmentUpdate> installmentUpdates = movePastInstallmentsToNextMonth(request);
return InstallmentsPluginResponse.builder().installmentUpdates(installmentUpdates).build();
}
private Map<String, InstallmentUpdate> movePastInstallmentsToNextMonth(PersonalAutoRequest request) {
InstallmentsPluginContext context = request.context();
Collection<Installment> installments = request.installments();
InstallmentLattice installmentLattice = request.installmentLattice();
log.info(
"Received InstallmentsPlugin request for context: '{}', installments: '{}' and installmentLattice: '{}'",
context,
installments,
installmentLattice);
Instant now = Instant.now();
DataFetcher dataFetcher = DataFetcher.getInstance();
Policy policy =
dataFetcher.getPolicy(context.policyLocator().orElseThrow());
ZoneId zoneId = ZoneId.of(policy.timezone());
ZonedDateTime zonedDateTime = now.atZone(zoneId);
ZonedDateTime nextMonthDayOne =
zonedDateTime.plusMonths(1).withDayOfMonth(1).toLocalDate().atStartOfDay(zoneId);
Instant newGenerateTime = nextMonthDayOne.toInstant();
Map<String, InstallmentUpdate> installmentUpdates = new HashMap<>();
for (Installment installment : installments) {
if (installment.generateTime().isBefore(newGenerateTime)) {
InstallmentUpdate update =
InstallmentUpdate.builder()
.generateTime(newGenerateTime)
.dueTime(newGenerateTime.plusMillis(Duration.between(installment.generateTime(), installment.dueTime()).toMillis()))
.build();
installmentUpdates.put(installment.locator().toString(), update);
log.info(
"Updated installment {} to new generate time: {} and due time: {}",
installment.locator(),
newGenerateTime,
update.dueTime());
} else {
log.info(
"Installment {} is not updated as its generate time: {} is not before the new generate time: {}",
installment.locator(),
installment.generateTime(),
newGenerateTime);
}
}
log.info(
"Returning installment updates: {}",
installmentUpdates);
return installmentUpdates;
}
}