* Splitting out PaymentBuilderServices for each command to facilitate
 unit testing of each payment builder.
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index a55bcea..31bbc67 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -28,10 +28,9 @@
 import io.mifos.individuallending.internal.repository.CaseParametersEntity;
 import io.mifos.individuallending.internal.repository.CaseParametersRepository;
 import io.mifos.individuallending.internal.repository.CreditWorthinessFactorType;
-import io.mifos.individuallending.internal.service.costcomponent.CostComponentService;
+import io.mifos.individuallending.internal.service.costcomponent.*;
 import io.mifos.individuallending.internal.service.DataContextOfAction;
 import io.mifos.individuallending.internal.service.DataContextService;
-import io.mifos.individuallending.internal.service.costcomponent.PaymentBuilder;
 import io.mifos.portfolio.api.v1.domain.*;
 import io.mifos.portfolio.service.ServiceConstants;
 import io.mifos.products.spi.PatternFactory;
@@ -42,6 +41,7 @@
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
@@ -108,13 +108,24 @@
         AccountType.LIABILITY.name()));
     INDIVIDUAL_LENDING_PATTERN.setAccountAssignmentsRequired(individualLendingRequiredAccounts);
   }
+
+
   public static Pattern individualLendingPattern() {
     return INDIVIDUAL_LENDING_PATTERN;
   }
 
   private final CaseParametersRepository caseParametersRepository;
   private final DataContextService dataContextService;
-  private final CostComponentService costComponentService;
+  private final OpenPaymentBuilderService openPaymentBuilderService;
+  private final ApprovePaymentBuilderService approvePaymentBuilderService;
+  private final DenyPaymentBuilderService denyPaymentBuilderService;
+  private final DisbursePaymentBuilderService disbursePaymentBuilderService;
+  private final ApplyInterestPaymentBuilderService applyInterestPaymentBuilderService;
+  private final AcceptPaymentBuilderService acceptPaymentBuilderService;
+  private final ClosePaymentBuilderService closePaymentBuilderService;
+  private final MarkLatePaymentBuilderService markLatePaymentBuilderService;
+  private final WriteOffPaymentBuilderService writeOffPaymentBuilderService;
+  private final RecoverPaymentBuilderService recoverPaymentBuilderService;
   private final CustomerManager customerManager;
   private final IndividualLendingCommandDispatcher individualLendingCommandDispatcher;
   private final Gson gson;
@@ -123,14 +134,33 @@
   IndividualLendingPatternFactory(
       final CaseParametersRepository caseParametersRepository,
       final DataContextService dataContextService,
-      final CostComponentService costComponentService,
+      final OpenPaymentBuilderService openPaymentBuilderService,
+      final ApprovePaymentBuilderService approvePaymentBuilderService,
+      final DenyPaymentBuilderService denyPaymentBuilderService,
+      final DisbursePaymentBuilderService disbursePaymentBuilderService,
+      final ApplyInterestPaymentBuilderService applyInterestPaymentBuilderService,
+      final AcceptPaymentBuilderService acceptPaymentBuilderService,
+      final ClosePaymentBuilderService closePaymentBuilderService,
+      final MarkLatePaymentBuilderService markLatePaymentBuilderService,
+      final WriteOffPaymentBuilderService writeOffPaymentBuilderService,
+      final RecoverPaymentBuilderService recoverPaymentBuilderService,
       final CustomerManager customerManager,
       final IndividualLendingCommandDispatcher individualLendingCommandDispatcher,
       @Qualifier(ServiceConstants.GSON_NAME) final Gson gson)
   {
     this.caseParametersRepository = caseParametersRepository;
     this.dataContextService = dataContextService;
-    this.costComponentService = costComponentService;
+    this.openPaymentBuilderService = openPaymentBuilderService;
+    this.approvePaymentBuilderService = approvePaymentBuilderService;
+    this.denyPaymentBuilderService = denyPaymentBuilderService;
+    this.disbursePaymentBuilderService = disbursePaymentBuilderService;
+    this.applyInterestPaymentBuilderService = applyInterestPaymentBuilderService;
+    this.acceptPaymentBuilderService = acceptPaymentBuilderService;
+    this.closePaymentBuilderService = closePaymentBuilderService;
+    this.markLatePaymentBuilderService = markLatePaymentBuilderService;
+    this.writeOffPaymentBuilderService = writeOffPaymentBuilderService;
+    this.recoverPaymentBuilderService = recoverPaymentBuilderService;
+
     this.customerManager = customerManager;
     this.individualLendingCommandDispatcher = individualLendingCommandDispatcher;
     this.gson = gson;
@@ -386,11 +416,60 @@
     final Case.State caseState = Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState());
     checkActionCanBeExecuted(caseState, action);
 
-    final PaymentBuilder paymentBuilder = costComponentService.getCostComponentsForAction(
+    return getPaymentForAction(
         action,
         dataContextOfAction,
+        forAccountDesignators,
         forPaymentSize,
         forDateTime.toLocalDate());
+  }
+
+  private Payment getPaymentForAction(
+      final Action action,
+      final DataContextOfAction dataContextOfAction,
+      final Set<String> forAccountDesignators,
+      final BigDecimal forPaymentSize,
+      final LocalDate forDate) {
+    final PaymentBuilderService paymentBuilderService;
+    switch (action) {
+      case OPEN:
+        paymentBuilderService = openPaymentBuilderService;
+        break;
+      case APPROVE:
+        paymentBuilderService = approvePaymentBuilderService;
+        break;
+      case DENY:
+        paymentBuilderService = denyPaymentBuilderService;
+        break;
+      case DISBURSE:
+        paymentBuilderService = disbursePaymentBuilderService;
+        break;
+      case APPLY_INTEREST:
+        paymentBuilderService = applyInterestPaymentBuilderService;
+        break;
+      case ACCEPT_PAYMENT:
+        paymentBuilderService = acceptPaymentBuilderService;
+        break;
+      case CLOSE:
+        paymentBuilderService = closePaymentBuilderService;
+        break;
+      case MARK_LATE:
+        paymentBuilderService = markLatePaymentBuilderService;
+        break;
+      case WRITE_OFF:
+        paymentBuilderService = writeOffPaymentBuilderService;
+        break;
+      case RECOVER:
+        paymentBuilderService = recoverPaymentBuilderService;
+        break;
+      default:
+        throw ServiceException.internalError("Invalid action: ''{0}''.", action.name());
+    }
+
+    final PaymentBuilder paymentBuilder = paymentBuilderService.getPaymentBuilder(
+        dataContextOfAction,
+        forPaymentSize,
+        forDate);
 
     return paymentBuilder.buildPayment(action, forAccountDesignators);
   }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java b/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
index f3a3d9f..f980983 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
@@ -30,9 +30,8 @@
 import io.mifos.individuallending.internal.command.*;
 import io.mifos.individuallending.internal.repository.CaseParametersRepository;
 import io.mifos.individuallending.internal.service.*;
-import io.mifos.individuallending.internal.service.costcomponent.CostComponentService;
+import io.mifos.individuallending.internal.service.costcomponent.*;
 import io.mifos.individuallending.internal.service.DataContextOfAction;
-import io.mifos.individuallending.internal.service.costcomponent.PaymentBuilder;
 import io.mifos.individuallending.internal.service.schedule.ScheduledActionHelpers;
 import io.mifos.portfolio.api.v1.domain.AccountAssignment;
 import io.mifos.portfolio.api.v1.domain.Case;
@@ -67,7 +66,16 @@
 public class IndividualLoanCommandHandler {
   private final CaseRepository caseRepository;
   private final DataContextService dataContextService;
-  private final CostComponentService costComponentService;
+  private final OpenPaymentBuilderService openPaymentBuilderService;
+  private final ApprovePaymentBuilderService approvePaymentBuilderService;
+  private final DenyPaymentBuilderService denyPaymentBuilderService;
+  private final DisbursePaymentBuilderService disbursePaymentBuilderService;
+  private final ApplyInterestPaymentBuilderService applyInterestPaymentBuilderService;
+  private final AcceptPaymentBuilderService acceptPaymentBuilderService;
+  private final ClosePaymentBuilderService closePaymentBuilderService;
+  private final MarkLatePaymentBuilderService markLatePaymentBuilderService;
+  private final WriteOffPaymentBuilderService writeOffPaymentBuilderService;
+  private final RecoverPaymentBuilderService recoverPaymentBuilderService;
   private final AccountingAdapter accountingAdapter;
   private final TaskInstanceRepository taskInstanceRepository;
   private final CaseParametersRepository caseParametersRepository;
@@ -76,13 +84,31 @@
   public IndividualLoanCommandHandler(
       final CaseRepository caseRepository,
       final DataContextService dataContextService,
-      final CostComponentService costComponentService,
+      final OpenPaymentBuilderService openPaymentBuilderService,
+      final ApprovePaymentBuilderService approvePaymentBuilderService,
+      final DenyPaymentBuilderService denyPaymentBuilderService,
+      final DisbursePaymentBuilderService disbursePaymentBuilderService,
+      final ApplyInterestPaymentBuilderService applyInterestPaymentBuilderService,
+      final AcceptPaymentBuilderService acceptPaymentBuilderService,
+      final ClosePaymentBuilderService closePaymentBuilderService,
+      final MarkLatePaymentBuilderService markLatePaymentBuilderService,
+      final WriteOffPaymentBuilderService writeOffPaymentBuilderService,
+      final RecoverPaymentBuilderService recoverPaymentBuilderService,
       final AccountingAdapter accountingAdapter,
       final TaskInstanceRepository taskInstanceRepository,
       final CaseParametersRepository caseParametersRepository) {
     this.caseRepository = caseRepository;
     this.dataContextService = dataContextService;
-    this.costComponentService = costComponentService;
+    this.openPaymentBuilderService = openPaymentBuilderService;
+    this.approvePaymentBuilderService = approvePaymentBuilderService;
+    this.denyPaymentBuilderService = denyPaymentBuilderService;
+    this.disbursePaymentBuilderService = disbursePaymentBuilderService;
+    this.applyInterestPaymentBuilderService = applyInterestPaymentBuilderService;
+    this.acceptPaymentBuilderService = acceptPaymentBuilderService;
+    this.closePaymentBuilderService = closePaymentBuilderService;
+    this.markLatePaymentBuilderService = markLatePaymentBuilderService;
+    this.writeOffPaymentBuilderService = writeOffPaymentBuilderService;
+    this.recoverPaymentBuilderService = recoverPaymentBuilderService;
     this.accountingAdapter = accountingAdapter;
     this.taskInstanceRepository = taskInstanceRepository;
     this.caseParametersRepository = caseParametersRepository;
@@ -103,7 +129,7 @@
     checkIfTasksAreOutstanding(dataContextOfAction, Action.OPEN);
 
     final PaymentBuilder paymentBuilder
-        = costComponentService.getCostComponentsForOpen(dataContextOfAction);
+        = openPaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today());
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
             = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -140,7 +166,7 @@
     checkIfTasksAreOutstanding(dataContextOfAction, Action.DENY);
 
     final PaymentBuilder paymentBuilder
-        = costComponentService.getCostComponentsForDeny(dataContextOfAction);
+        = denyPaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today());
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -222,7 +248,7 @@
     caseRepository.save(dataContextOfAction.getCustomerCaseEntity());
 
     final PaymentBuilder paymentBuilder =
-        costComponentService.getCostComponentsForApprove(dataContextOfAction);
+        approvePaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today());
 
     final List<ChargeInstance> charges = paymentBuilder.buildCharges(Action.APPROVE, designatorToAccountIdentifierMapper);
 
@@ -256,7 +282,7 @@
 
     final BigDecimal disbursalAmount = Optional.ofNullable(command.getCommand().getPaymentSize()).orElse(BigDecimal.ZERO);
     final PaymentBuilder paymentBuilder =
-        costComponentService.getCostComponentsForDisburse(dataContextOfAction, disbursalAmount);
+        disbursePaymentBuilderService.getPaymentBuilder(dataContextOfAction, disbursalAmount, CostComponentService.today());
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -285,7 +311,7 @@
     final String customerLoanFeesAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_FEES);
     final BigDecimal currentBalance = accountingAdapter.getTotalOfCurrentAccountBalances(customerLoanPrinicipalAccountIdentifier, customerLoanInterestAccountIdentifier, customerLoanFeesAccountIdentifier);
 
-    final BigDecimal newLoanPaymentSize = costComponentService.getLoanPaymentSizeForSingleDisbursement(
+    final BigDecimal newLoanPaymentSize = disbursePaymentBuilderService.getLoanPaymentSizeForSingleDisbursement(
         currentBalance.add(disbursalAmount),
         dataContextOfAction);
 
@@ -312,7 +338,7 @@
           "End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
 
     final PaymentBuilder paymentBuilder =
-        costComponentService.getCostComponentsForApplyInterest(dataContextOfAction);
+        applyInterestPaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today());
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -347,7 +373,7 @@
           "End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
 
     final PaymentBuilder paymentBuilder =
-        costComponentService.getCostComponentsForAcceptPayment(
+        acceptPaymentBuilderService.getPaymentBuilder(
             dataContextOfAction,
             command.getCommand().getPaymentSize(),
             DateConverter.fromIsoString(command.getCommand().getCreatedOn()).toLocalDate());
@@ -387,7 +413,7 @@
           "End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
 
     final PaymentBuilder paymentBuilder =
-        costComponentService.getCostComponentsForMarkLate(dataContextOfAction, DateConverter.fromIsoString(command.getForTime()));
+        markLatePaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, DateConverter.fromIsoString(command.getForTime()).toLocalDate());
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -439,7 +465,7 @@
     checkIfTasksAreOutstanding(dataContextOfAction, Action.CLOSE);
 
     final PaymentBuilder paymentBuilder =
-        costComponentService.getCostComponentsForClose(dataContextOfAction);
+        closePaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today());
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderService.java
new file mode 100644
index 0000000..22bcb37
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderService.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledActionHelpers;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class AcceptPaymentBuilderService implements PaymentBuilderService {
+  private final CostComponentService costComponentService;
+  private final ScheduledChargesService scheduledChargesService;
+  private final AccountingAdapter accountingAdapter;
+
+  @Autowired
+  public AcceptPaymentBuilderService(
+      final CostComponentService costComponentService,
+      final ScheduledChargesService scheduledChargesService,
+      final AccountingAdapter accountingAdapter) {
+    this.costComponentService = costComponentService;
+    this.scheduledChargesService = scheduledChargesService;
+    this.accountingAdapter = accountingAdapter;
+  }
+
+  public PaymentBuilder getPaymentBuilder(
+      final DataContextOfAction dataContextOfAction,
+      final BigDecimal requestedLoanPaymentSize,
+      final LocalDate forDate)
+  {
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+
+    final LocalDate startOfTerm = costComponentService.getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
+
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+    final ScheduledAction scheduledAction
+        = ScheduledActionHelpers.getNextScheduledPayment(
+        startOfTerm,
+        forDate,
+        dataContextOfAction.getCustomerCaseEntity().getEndOfTerm().toLocalDate(),
+        dataContextOfAction.getCaseParameters()
+    );
+
+    final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
+        productIdentifier,
+        Collections.singletonList(scheduledAction));
+
+    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledChargesForThisAction.stream()
+        .collect(Collectors.partitioningBy(x -> CostComponentService.isAccruedChargeForAction(x.getChargeDefinition(), Action.ACCEPT_PAYMENT)));
+
+    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+        .stream()
+        .map(ScheduledCharge::getChargeDefinition)
+        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+            chargeDefinition -> costComponentService.getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+
+
+    final BigDecimal loanPaymentSize;
+
+    if (requestedLoanPaymentSize != null) {
+      loanPaymentSize = requestedLoanPaymentSize;
+    }
+    else {
+      if (scheduledAction.getActionPeriod() != null && scheduledAction.getActionPeriod().isLastPeriod()) {
+        loanPaymentSize = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP);
+      }
+      else {
+        final BigDecimal paymentSizeBeforeOnTopCharges = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP)
+            .min(dataContextOfAction.getCaseParametersEntity().getPaymentSize());
+
+        @SuppressWarnings("UnnecessaryLocalVariable")
+        final BigDecimal paymentSizeIncludingOnTopCharges = accruedCostComponents.entrySet().stream()
+            .filter(entry -> entry.getKey().getChargeOnTop() != null && entry.getKey().getChargeOnTop())
+            .map(entry -> entry.getValue().getAmount())
+            .reduce(paymentSizeBeforeOnTopCharges, BigDecimal::add);
+
+        loanPaymentSize = paymentSizeIncludingOnTopCharges;
+      }
+    }
+
+
+    return CostComponentService.getCostComponentsForScheduledCharges(
+        accruedCostComponents,
+        chargesSplitIntoScheduledAndAccrued.get(false),
+        caseParameters.getBalanceRangeMaximum(),
+        runningBalances,
+        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
+        BigDecimal.ZERO,
+        loanPaymentSize,
+        dataContextOfAction.getInterest(),
+        minorCurrencyUnitDigits,
+        true);
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderService.java
new file mode 100644
index 0000000..620bc32
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderService.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.schedule.Period;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class ApplyInterestPaymentBuilderService implements PaymentBuilderService {
+  private final CostComponentService costComponentService;
+  private final ScheduledChargesService scheduledChargesService;
+  private final AccountingAdapter accountingAdapter;
+
+  @Autowired
+  public ApplyInterestPaymentBuilderService(
+      final CostComponentService costComponentService,
+      final ScheduledChargesService scheduledChargesService,
+      final AccountingAdapter accountingAdapter) {
+    this.costComponentService = costComponentService;
+    this.scheduledChargesService = scheduledChargesService;
+    this.accountingAdapter = accountingAdapter;
+  }
+
+  public PaymentBuilder getPaymentBuilder(
+      final DataContextOfAction dataContextOfAction,
+      final BigDecimal ignored,
+      final LocalDate forDate)
+  {
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+
+    final LocalDate startOfTerm = costComponentService.getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
+
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+    final ScheduledAction interestAction = new ScheduledAction(Action.APPLY_INTEREST, forDate, new Period(1, forDate));
+
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+        productIdentifier,
+        Collections.singletonList(interestAction));
+
+    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
+        .collect(Collectors.partitioningBy(x -> CostComponentService.isAccruedChargeForAction(x.getChargeDefinition(), Action.APPLY_INTEREST)));
+
+    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+        .stream()
+        .map(ScheduledCharge::getChargeDefinition)
+        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+            chargeDefinition -> costComponentService.getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+
+    return CostComponentService.getCostComponentsForScheduledCharges(
+        accruedCostComponents,
+        chargesSplitIntoScheduledAndAccrued.get(false),
+        caseParameters.getBalanceRangeMaximum(),
+        runningBalances,
+        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        dataContextOfAction.getInterest(),
+        minorCurrencyUnitDigits,
+        true);
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApprovePaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApprovePaymentBuilderService.java
new file mode 100644
index 0000000..3f7566d
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApprovePaymentBuilderService.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class ApprovePaymentBuilderService implements PaymentBuilderService {
+  private final ScheduledChargesService scheduledChargesService;
+
+  @Autowired
+  public ApprovePaymentBuilderService(
+      final ScheduledChargesService scheduledChargesService) {
+    this.scheduledChargesService = scheduledChargesService;
+  }
+
+  public PaymentBuilder getPaymentBuilder(
+      final DataContextOfAction dataContextOfAction,
+      final BigDecimal ignored,
+      final LocalDate forDate)
+  {
+    //Charge the approval fee if applicable.
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+    final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.APPROVE, forDate));
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+        productIdentifier, scheduledActions);
+
+    return CostComponentService.getCostComponentsForScheduledCharges(
+        Collections.emptyMap(),
+        scheduledCharges,
+        caseParameters.getBalanceRangeMaximum(),
+        new SimulatedRunningBalances(),
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        dataContextOfAction.getInterest(),
+        minorCurrencyUnitDigits,
+        true);
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ClosePaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ClosePaymentBuilderService.java
new file mode 100644
index 0000000..cfa00e8
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ClosePaymentBuilderService.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.core.lang.ServiceException;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.schedule.Period;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class ClosePaymentBuilderService implements PaymentBuilderService {
+  private final CostComponentService costComponentService;
+  private final ScheduledChargesService scheduledChargesService;
+  private final AccountingAdapter accountingAdapter;
+
+  @Autowired
+  public ClosePaymentBuilderService(
+      final CostComponentService costComponentService,
+      final ScheduledChargesService scheduledChargesService,
+      final AccountingAdapter accountingAdapter) {
+    this.costComponentService = costComponentService;
+    this.scheduledChargesService = scheduledChargesService;
+    this.accountingAdapter = accountingAdapter;
+  }
+
+  public PaymentBuilder getPaymentBuilder(
+      final DataContextOfAction dataContextOfAction,
+      final BigDecimal ignored,
+      final LocalDate forDate)
+  {
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+
+    if (runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP).compareTo(BigDecimal.ZERO) != 0)
+      throw ServiceException.conflict("Cannot close loan until the balance is zero.");
+
+    final LocalDate startOfTerm = costComponentService.getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
+
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+    final ScheduledAction closeAction = new ScheduledAction(Action.CLOSE, forDate, new Period(1, forDate));
+
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+        productIdentifier,
+        Collections.singletonList(closeAction));
+
+    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
+        .collect(Collectors.partitioningBy(x -> CostComponentService.isAccruedChargeForAction(x.getChargeDefinition(), Action.CLOSE)));
+
+    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+        .stream()
+        .map(ScheduledCharge::getChargeDefinition)
+        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+            chargeDefinition -> costComponentService.getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+
+    return CostComponentService.getCostComponentsForScheduledCharges(
+        accruedCostComponents,
+        chargesSplitIntoScheduledAndAccrued.get(false),
+        caseParameters.getBalanceRangeMaximum(),
+        runningBalances,
+        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        dataContextOfAction.getInterest(),
+        minorCurrencyUnitDigits,
+        true);
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/CostComponentService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/CostComponentService.java
index 8aa2afb..3852772 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/CostComponentService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/CostComponentService.java
@@ -19,8 +19,10 @@
 import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
-import io.mifos.individuallending.internal.repository.CaseParametersEntity;
-import io.mifos.individuallending.internal.service.*;
+import io.mifos.individuallending.internal.service.AnnuityPayment;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.RateCollectors;
 import io.mifos.individuallending.internal.service.schedule.*;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.CostComponent;
@@ -30,8 +32,6 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
 import javax.money.MonetaryAmount;
 import java.math.BigDecimal;
 import java.time.Clock;
@@ -49,380 +49,13 @@
   private static final int EXTRA_PRECISION = 4;
   private static final int RUNNING_CALCULATION_PRECISION = 8;
 
-  private final ScheduledChargesService scheduledChargesService;
   private final AccountingAdapter accountingAdapter;
 
   @Autowired
-  public CostComponentService(
-      final ScheduledChargesService scheduledChargesService,
-      final AccountingAdapter accountingAdapter) {
-    this.scheduledChargesService = scheduledChargesService;
+  public CostComponentService(final AccountingAdapter accountingAdapter) {
     this.accountingAdapter = accountingAdapter;
   }
 
-  public PaymentBuilder getCostComponentsForAction(
-      final Action action,
-      final DataContextOfAction dataContextOfAction,
-      final BigDecimal forPaymentSize,
-      final LocalDate forDate) {
-    switch (action) {
-      case OPEN:
-        return getCostComponentsForOpen(dataContextOfAction);
-      case APPROVE:
-        return getCostComponentsForApprove(dataContextOfAction);
-      case DENY:
-        return getCostComponentsForDeny(dataContextOfAction);
-      case DISBURSE:
-        return getCostComponentsForDisburse(dataContextOfAction, forPaymentSize);
-      case APPLY_INTEREST:
-        return getCostComponentsForApplyInterest(dataContextOfAction);
-      case ACCEPT_PAYMENT:
-        return getCostComponentsForAcceptPayment(dataContextOfAction, forPaymentSize, forDate);
-      case CLOSE:
-        return getCostComponentsForClose(dataContextOfAction);
-      case MARK_LATE:
-        return getCostComponentsForMarkLate(dataContextOfAction, today().atStartOfDay());
-      case WRITE_OFF:
-        return getCostComponentsForWriteOff(dataContextOfAction);
-      case RECOVER:
-        return getCostComponentsForRecover(dataContextOfAction);
-      default:
-        throw ServiceException.internalError("Invalid action: ''{0}''.", action.name());
-    }
-  }
-
-  public PaymentBuilder getCostComponentsForOpen(final DataContextOfAction dataContextOfAction) {
-    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
-    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
-    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
-    final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.OPEN, today()));
-    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
-        productIdentifier, scheduledActions);
-
-    return getCostComponentsForScheduledCharges(
-        Collections.emptyMap(),
-        scheduledCharges,
-        caseParameters.getBalanceRangeMaximum(),
-        new SimulatedRunningBalances(),
-        BigDecimal.ZERO,
-        BigDecimal.ZERO,
-        BigDecimal.ZERO,
-        dataContextOfAction.getInterest(),
-        minorCurrencyUnitDigits,
-        true);
-  }
-
-  public PaymentBuilder getCostComponentsForDeny(final DataContextOfAction dataContextOfAction) {
-    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
-    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
-    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
-    final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DENY, today()));
-    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
-        productIdentifier, scheduledActions);
-
-    return getCostComponentsForScheduledCharges(
-        Collections.emptyMap(),
-        scheduledCharges,
-        caseParameters.getBalanceRangeMaximum(),
-        new SimulatedRunningBalances(),
-        BigDecimal.ZERO,
-        BigDecimal.ZERO,
-        BigDecimal.ZERO,
-        dataContextOfAction.getInterest(),
-        minorCurrencyUnitDigits,
-        true);
-  }
-
-  public PaymentBuilder getCostComponentsForApprove(final DataContextOfAction dataContextOfAction) {
-    //Charge the approval fee if applicable.
-    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
-    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
-    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
-    final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.APPROVE, today()));
-    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
-        productIdentifier, scheduledActions);
-
-    return getCostComponentsForScheduledCharges(
-        Collections.emptyMap(),
-        scheduledCharges,
-        caseParameters.getBalanceRangeMaximum(),
-        new SimulatedRunningBalances(),
-        BigDecimal.ZERO,
-        BigDecimal.ZERO,
-        BigDecimal.ZERO,
-        dataContextOfAction.getInterest(),
-        minorCurrencyUnitDigits,
-        true);
-  }
-
-  public PaymentBuilder getCostComponentsForDisburse(
-      final @Nonnull DataContextOfAction dataContextOfAction,
-      final @Nullable BigDecimal requestedDisbursalSize) {
-    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
-        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
-    final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
-    final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
-    final BigDecimal currentBalance = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
-
-    if (requestedDisbursalSize != null &&
-        dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum().compareTo(
-        currentBalance.add(requestedDisbursalSize)) < 0)
-      throw ServiceException.conflict("Cannot disburse over the maximum balance.");
-
-    final Optional<LocalDateTime> optionalStartOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
-        customerLoanPrincipalAccountIdentifier,
-        dataContextOfAction.getMessageForCharge(Action.DISBURSE));
-    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
-    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
-    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
-    final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DISBURSE, today()));
-
-    final BigDecimal disbursalSize;
-    if (requestedDisbursalSize == null)
-      disbursalSize = dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum();
-    else
-      disbursalSize = requestedDisbursalSize;
-
-    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
-        productIdentifier, scheduledActions);
-
-
-    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
-        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.DISBURSE)));
-
-    final Map<ChargeDefinition, CostComponent> accruedCostComponents =
-        optionalStartOfTerm.map(startOfTerm ->
-        chargesSplitIntoScheduledAndAccrued.get(true)
-        .stream()
-        .map(ScheduledCharge::getChargeDefinition)
-        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
-            chargeDefinition -> getAccruedCostComponentToApply(
-                dataContextOfAction,
-                designatorToAccountIdentifierMapper,
-                startOfTerm.toLocalDate(),
-                chargeDefinition)))).orElse(Collections.emptyMap());
-
-    return getCostComponentsForScheduledCharges(
-        accruedCostComponents,
-        chargesSplitIntoScheduledAndAccrued.get(false),
-        caseParameters.getBalanceRangeMaximum(),
-        runningBalances,
-        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
-        disbursalSize,
-        BigDecimal.ZERO,
-        dataContextOfAction.getInterest(),
-        minorCurrencyUnitDigits,
-        true);
-  }
-
-  public PaymentBuilder getCostComponentsForApplyInterest(
-      final DataContextOfAction dataContextOfAction)
-  {
-    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
-        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
-    final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
-
-    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
-
-    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
-    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
-    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
-    final LocalDate today = today();
-    final ScheduledAction interestAction = new ScheduledAction(Action.APPLY_INTEREST, today, new Period(1, today));
-
-    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
-        productIdentifier,
-        Collections.singletonList(interestAction));
-
-    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
-        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.APPLY_INTEREST)));
-
-    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
-        .stream()
-        .map(ScheduledCharge::getChargeDefinition)
-        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
-            chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
-
-    return getCostComponentsForScheduledCharges(
-        accruedCostComponents,
-        chargesSplitIntoScheduledAndAccrued.get(false),
-        caseParameters.getBalanceRangeMaximum(),
-        runningBalances,
-        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
-        BigDecimal.ZERO,
-        BigDecimal.ZERO,
-        dataContextOfAction.getInterest(),
-        minorCurrencyUnitDigits,
-        true);
-  }
-
-  public PaymentBuilder getCostComponentsForAcceptPayment(
-      final DataContextOfAction dataContextOfAction,
-      final @Nullable BigDecimal requestedLoanPaymentSize,
-      final LocalDate forDate)
-  {
-    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
-        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
-    final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
-
-    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
-
-    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
-    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
-    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
-    final ScheduledAction scheduledAction
-        = ScheduledActionHelpers.getNextScheduledPayment(
-        startOfTerm,
-        forDate,
-        dataContextOfAction.getCustomerCaseEntity().getEndOfTerm().toLocalDate(),
-        dataContextOfAction.getCaseParameters()
-    );
-
-    final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
-        productIdentifier,
-        Collections.singletonList(scheduledAction));
-
-    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledChargesForThisAction.stream()
-        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.ACCEPT_PAYMENT)));
-
-    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
-        .stream()
-        .map(ScheduledCharge::getChargeDefinition)
-        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
-            chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
-
-
-    final BigDecimal loanPaymentSize;
-
-    if (requestedLoanPaymentSize != null) {
-      loanPaymentSize = requestedLoanPaymentSize;
-    }
-    else {
-      if (scheduledAction.getActionPeriod() != null && scheduledAction.getActionPeriod().isLastPeriod()) {
-        loanPaymentSize = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP);
-      }
-      else {
-        final BigDecimal paymentSizeBeforeOnTopCharges = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP)
-            .min(dataContextOfAction.getCaseParametersEntity().getPaymentSize());
-
-        @SuppressWarnings("UnnecessaryLocalVariable")
-        final BigDecimal paymentSizeIncludingOnTopCharges = accruedCostComponents.entrySet().stream()
-            .filter(entry -> entry.getKey().getChargeOnTop() != null && entry.getKey().getChargeOnTop())
-            .map(entry -> entry.getValue().getAmount())
-            .reduce(paymentSizeBeforeOnTopCharges, BigDecimal::add);
-
-        loanPaymentSize = paymentSizeIncludingOnTopCharges;
-      }
-    }
-
-
-    return getCostComponentsForScheduledCharges(
-        accruedCostComponents,
-        chargesSplitIntoScheduledAndAccrued.get(false),
-        caseParameters.getBalanceRangeMaximum(),
-        runningBalances,
-        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
-        BigDecimal.ZERO,
-        loanPaymentSize,
-        dataContextOfAction.getInterest(),
-        minorCurrencyUnitDigits,
-        true);
-  }
-
-  public PaymentBuilder getCostComponentsForClose(final DataContextOfAction dataContextOfAction) {
-    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
-        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
-    final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
-
-    if (runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP).compareTo(BigDecimal.ZERO) != 0)
-      throw ServiceException.conflict("Cannot close loan until the balance is zero.");
-
-    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
-
-    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
-    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
-    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
-    final LocalDate today = today();
-    final ScheduledAction closeAction = new ScheduledAction(Action.CLOSE, today, new Period(1, today));
-
-    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
-        productIdentifier,
-        Collections.singletonList(closeAction));
-
-    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
-        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.CLOSE)));
-
-    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
-        .stream()
-        .map(ScheduledCharge::getChargeDefinition)
-        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
-            chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
-
-    return getCostComponentsForScheduledCharges(
-        accruedCostComponents,
-        chargesSplitIntoScheduledAndAccrued.get(false),
-        caseParameters.getBalanceRangeMaximum(),
-        runningBalances,
-        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
-        BigDecimal.ZERO,
-        BigDecimal.ZERO,
-        dataContextOfAction.getInterest(),
-        minorCurrencyUnitDigits,
-        true);
-  }
-
-  public PaymentBuilder getCostComponentsForMarkLate(
-      final DataContextOfAction dataContextOfAction,
-      final LocalDateTime forTime)
-  {
-    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
-        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
-    final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
-
-    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
-
-    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
-    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
-    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
-    final ScheduledAction scheduledAction = new ScheduledAction(Action.MARK_LATE, forTime.toLocalDate());
-
-    final BigDecimal loanPaymentSize = dataContextOfAction.getCaseParametersEntity().getPaymentSize();
-
-    final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
-        productIdentifier,
-        Collections.singletonList(scheduledAction));
-
-    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledChargesForThisAction.stream()
-        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.MARK_LATE)));
-
-    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
-        .stream()
-        .map(ScheduledCharge::getChargeDefinition)
-        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
-            chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
-
-
-    return getCostComponentsForScheduledCharges(
-        accruedCostComponents,
-        chargesSplitIntoScheduledAndAccrued.get(false),
-        caseParameters.getBalanceRangeMaximum(),
-        runningBalances,
-        loanPaymentSize,
-        BigDecimal.ZERO,
-        BigDecimal.ZERO,
-        dataContextOfAction.getInterest(),
-        minorCurrencyUnitDigits,
-        true);
-  }
-
-  private PaymentBuilder getCostComponentsForWriteOff(final DataContextOfAction dataContextOfAction) {
-    return null;
-  }
-
-  private PaymentBuilder getCostComponentsForRecover(final DataContextOfAction dataContextOfAction) {
-    return null;
-  }
-
   public static PaymentBuilder getCostComponentsForScheduledCharges(
       final Map<ChargeDefinition, CostComponent> accruedCostComponents,
       final Collection<ScheduledCharge> scheduledCharges,
@@ -556,23 +189,6 @@
     }
   }
 
-  public BigDecimal getLoanPaymentSizeForSingleDisbursement(
-      final BigDecimal disbursementSize,
-      final DataContextOfAction dataContextOfAction) {
-    final List<ScheduledAction> hypotheticalScheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(
-        today(),
-        dataContextOfAction.getCaseParameters());
-    final List<ScheduledCharge> hypotheticalScheduledCharges = scheduledChargesService.getScheduledCharges(
-        dataContextOfAction.getProductEntity().getIdentifier(),
-        hypotheticalScheduledActions);
-    return getLoanPaymentSize(
-        disbursementSize,
-        disbursementSize,
-        dataContextOfAction.getInterest(),
-        dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits(),
-        hypotheticalScheduledCharges);
-  }
-
   public static BigDecimal getLoanPaymentSize(
       final BigDecimal maximumBalanceSize,
       final BigDecimal disbursementSize,
@@ -616,7 +232,7 @@
     return BigDecimal.valueOf(presentValue.getNumber().doubleValueExact()).setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
   }
 
-  private static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
+  static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
     return chargeDefinition.getAccrueAction() != null &&
         chargeDefinition.getChargeAction().equals(action.name());
   }
@@ -626,10 +242,10 @@
         chargeDefinition.getAccrueAction().equals(action.name());
   }
 
-  private CostComponent getAccruedCostComponentToApply(final DataContextOfAction dataContextOfAction,
-                                                       final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper,
-                                                       final LocalDate startOfTerm,
-                                                       final ChargeDefinition chargeDefinition) {
+  CostComponent getAccruedCostComponentToApply(final DataContextOfAction dataContextOfAction,
+                                               final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper,
+                                               final LocalDate startOfTerm,
+                                               final ChargeDefinition chargeDefinition) {
     final CostComponent ret = new CostComponent();
 
     final String accrualAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getAccrualAccountDesignator());
@@ -648,8 +264,8 @@
     return ret;
   }
 
-  private LocalDate getStartOfTermOrThrow(final DataContextOfAction dataContextOfAction,
-                                          final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
+  LocalDate getStartOfTermOrThrow(final DataContextOfAction dataContextOfAction,
+                                  final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
 
     final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
 
@@ -663,7 +279,7 @@
             dataContextOfAction.getCompoundIdentifer()));
   }
 
-  private static LocalDate today() {
+  public static LocalDate today() {
     return LocalDate.now(Clock.systemUTC());
   }
 
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DenyPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DenyPaymentBuilderService.java
new file mode 100644
index 0000000..1912617
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DenyPaymentBuilderService.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class DenyPaymentBuilderService implements PaymentBuilderService {
+  private final ScheduledChargesService scheduledChargesService;
+
+  @Autowired
+  public DenyPaymentBuilderService(
+      final ScheduledChargesService scheduledChargesService) {
+    this.scheduledChargesService = scheduledChargesService;
+  }
+
+  public PaymentBuilder getPaymentBuilder(
+      final DataContextOfAction dataContextOfAction,
+      final BigDecimal ignored,
+      final LocalDate forDate)
+  {
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+    final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DENY, forDate));
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+        productIdentifier, scheduledActions);
+
+    return CostComponentService.getCostComponentsForScheduledCharges(
+        Collections.emptyMap(),
+        scheduledCharges,
+        caseParameters.getBalanceRangeMaximum(),
+        new SimulatedRunningBalances(),
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        dataContextOfAction.getInterest(),
+        minorCurrencyUnitDigits,
+        true);
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DisbursePaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DisbursePaymentBuilderService.java
new file mode 100644
index 0000000..044d79c
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DisbursePaymentBuilderService.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.core.lang.ServiceException;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledActionHelpers;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class DisbursePaymentBuilderService implements PaymentBuilderService {
+  private final CostComponentService costComponentService;
+  private final ScheduledChargesService scheduledChargesService;
+  private final AccountingAdapter accountingAdapter;
+
+  @Autowired
+  public DisbursePaymentBuilderService(
+      final CostComponentService costComponentService,
+      final ScheduledChargesService scheduledChargesService,
+      final AccountingAdapter accountingAdapter) {
+    this.costComponentService = costComponentService;
+    this.scheduledChargesService = scheduledChargesService;
+    this.accountingAdapter = accountingAdapter;
+  }
+
+  public PaymentBuilder getPaymentBuilder(
+      final @Nonnull DataContextOfAction dataContextOfAction,
+      final @Nullable BigDecimal requestedDisbursalSize,
+      final LocalDate forDate)
+  {
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+    final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+    final BigDecimal currentBalance = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+
+    if (requestedDisbursalSize != null &&
+        dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum().compareTo(
+            currentBalance.add(requestedDisbursalSize)) < 0)
+      throw ServiceException.conflict("Cannot disburse over the maximum balance.");
+
+    final Optional<LocalDateTime> optionalStartOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
+        customerLoanPrincipalAccountIdentifier,
+        dataContextOfAction.getMessageForCharge(Action.DISBURSE));
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+    final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DISBURSE, forDate));
+
+    final BigDecimal disbursalSize;
+    if (requestedDisbursalSize == null)
+      disbursalSize = dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum();
+    else
+      disbursalSize = requestedDisbursalSize;
+
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+        productIdentifier, scheduledActions);
+
+
+    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
+        .collect(Collectors.partitioningBy(x -> CostComponentService.isAccruedChargeForAction(x.getChargeDefinition(), Action.DISBURSE)));
+
+    final Map<ChargeDefinition, CostComponent> accruedCostComponents =
+        optionalStartOfTerm.map(startOfTerm ->
+            chargesSplitIntoScheduledAndAccrued.get(true)
+                .stream()
+                .map(ScheduledCharge::getChargeDefinition)
+                .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+                    chargeDefinition -> costComponentService.getAccruedCostComponentToApply(
+                        dataContextOfAction,
+                        designatorToAccountIdentifierMapper,
+                        startOfTerm.toLocalDate(),
+                        chargeDefinition)))).orElse(Collections.emptyMap());
+
+    return CostComponentService.getCostComponentsForScheduledCharges(
+        accruedCostComponents,
+        chargesSplitIntoScheduledAndAccrued.get(false),
+        caseParameters.getBalanceRangeMaximum(),
+        runningBalances,
+        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
+        disbursalSize,
+        BigDecimal.ZERO,
+        dataContextOfAction.getInterest(),
+        minorCurrencyUnitDigits,
+        true);
+  }
+
+  public BigDecimal getLoanPaymentSizeForSingleDisbursement(
+      final BigDecimal disbursementSize,
+      final DataContextOfAction dataContextOfAction) {
+    final List<ScheduledAction> hypotheticalScheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(
+        CostComponentService.today(),
+        dataContextOfAction.getCaseParameters());
+    final List<ScheduledCharge> hypotheticalScheduledCharges = scheduledChargesService.getScheduledCharges(
+        dataContextOfAction.getProductEntity().getIdentifier(),
+        hypotheticalScheduledActions);
+    return CostComponentService.getLoanPaymentSize(
+        disbursementSize,
+        disbursementSize,
+        dataContextOfAction.getInterest(),
+        dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits(),
+        hypotheticalScheduledCharges);
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkLatePaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkLatePaymentBuilderService.java
new file mode 100644
index 0000000..a1667b5
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkLatePaymentBuilderService.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class MarkLatePaymentBuilderService implements PaymentBuilderService {
+  private final CostComponentService costComponentService;
+  private final ScheduledChargesService scheduledChargesService;
+  private final AccountingAdapter accountingAdapter;
+
+  @Autowired
+  public MarkLatePaymentBuilderService(
+      final CostComponentService costComponentService,
+      final ScheduledChargesService scheduledChargesService,
+      final AccountingAdapter accountingAdapter) {
+    this.costComponentService = costComponentService;
+    this.scheduledChargesService = scheduledChargesService;
+    this.accountingAdapter = accountingAdapter;
+  }
+
+  public PaymentBuilder getPaymentBuilder(
+      final @Nonnull DataContextOfAction dataContextOfAction,
+      final @Nullable BigDecimal ignored,
+      final LocalDate forDate)
+  {
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+
+    final LocalDate startOfTerm = costComponentService.getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
+
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+    final ScheduledAction scheduledAction = new ScheduledAction(Action.MARK_LATE, forDate);
+
+    final BigDecimal loanPaymentSize = dataContextOfAction.getCaseParametersEntity().getPaymentSize();
+
+    final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
+        productIdentifier,
+        Collections.singletonList(scheduledAction));
+
+    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledChargesForThisAction.stream()
+        .collect(Collectors.partitioningBy(x -> CostComponentService.isAccruedChargeForAction(x.getChargeDefinition(), Action.MARK_LATE)));
+
+    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+        .stream()
+        .map(ScheduledCharge::getChargeDefinition)
+        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+            chargeDefinition -> costComponentService.getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+
+
+    return CostComponentService.getCostComponentsForScheduledCharges(
+        accruedCostComponents,
+        chargesSplitIntoScheduledAndAccrued.get(false),
+        caseParameters.getBalanceRangeMaximum(),
+        runningBalances,
+        loanPaymentSize,
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        dataContextOfAction.getInterest(),
+        minorCurrencyUnitDigits,
+        true);
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/OpenPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/OpenPaymentBuilderService.java
new file mode 100644
index 0000000..a1e05c6
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/OpenPaymentBuilderService.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class OpenPaymentBuilderService implements PaymentBuilderService {
+  private final ScheduledChargesService scheduledChargesService;
+
+  @Autowired
+  public OpenPaymentBuilderService(
+      final ScheduledChargesService scheduledChargesService) {
+    this.scheduledChargesService = scheduledChargesService;
+  }
+
+  public PaymentBuilder getPaymentBuilder(
+      final DataContextOfAction dataContextOfAction,
+      final BigDecimal ignored,
+      final LocalDate forDate)
+  {
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+    final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.OPEN, forDate));
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+        productIdentifier, scheduledActions);
+
+    return CostComponentService.getCostComponentsForScheduledCharges(
+        Collections.emptyMap(),
+        scheduledCharges,
+        caseParameters.getBalanceRangeMaximum(),
+        new SimulatedRunningBalances(),
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        dataContextOfAction.getInterest(),
+        minorCurrencyUnitDigits,
+        true);
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderService.java
new file mode 100644
index 0000000..79914a2
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderService.java
@@ -0,0 +1,15 @@
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+public interface PaymentBuilderService {
+  PaymentBuilder getPaymentBuilder(
+      final @Nonnull DataContextOfAction dataContextOfAction,
+      final @Nullable BigDecimal requestedDisbursalSize,
+      final LocalDate forDate);
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RecoverPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RecoverPaymentBuilderService.java
new file mode 100644
index 0000000..e3b01a6
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RecoverPaymentBuilderService.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class RecoverPaymentBuilderService implements PaymentBuilderService {
+  @Override
+  public PaymentBuilder getPaymentBuilder(@Nonnull DataContextOfAction dataContextOfAction, @Nullable BigDecimal requestedDisbursalSize, LocalDate forDate) {
+    return null;
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderService.java
new file mode 100644
index 0000000..8d392b9
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderService.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class WriteOffPaymentBuilderService implements PaymentBuilderService {
+  @Override
+  public PaymentBuilder getPaymentBuilder(@Nonnull DataContextOfAction dataContextOfAction, @Nullable BigDecimal requestedDisbursalSize, LocalDate forDate) {
+    return null;
+  }
+}