List<CostComponents> -> Payment object
Corresponding changes to PlannedPayment
Changes to charges and required account assignments.
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/Balance.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/Balance.java
new file mode 100644
index 0000000..a8803d4
--- /dev/null
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/Balance.java
@@ -0,0 +1,65 @@
+/*
+ * 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.api.v1.domain.caseinstance;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+public class Balance {
+  private String accountDesignator;
+  private BigDecimal amount;
+
+  public String getAccountDesignator() {
+    return accountDesignator;
+  }
+
+  public void setAccountDesignator(String accountDesignator) {
+    this.accountDesignator = accountDesignator;
+  }
+
+  public BigDecimal getAmount() {
+    return amount;
+  }
+
+  public void setAmount(BigDecimal amount) {
+    this.amount = amount;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    Balance balance = (Balance) o;
+    return Objects.equals(accountDesignator, balance.accountDesignator) &&
+        Objects.equals(amount, balance.amount);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(accountDesignator, amount);
+  }
+
+  @Override
+  public String toString() {
+    return "Balance{" +
+        "accountDesignator='" + accountDesignator + '\'' +
+        ", amount=" + amount +
+        '}';
+  }
+}
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/PlannedPayment.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/PlannedPayment.java
index 30c4208..c988347 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/PlannedPayment.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/PlannedPayment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Mifos Initiative.
+ * 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.
@@ -15,62 +15,38 @@
  */
 package io.mifos.individuallending.api.v1.domain.caseinstance;
 
-import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.api.v1.domain.Payment;
 
-import javax.annotation.Nullable;
 import java.math.BigDecimal;
-import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
  * @author Myrle Krantz
  */
-@SuppressWarnings({"WeakerAccess", "unused"})
-public final class PlannedPayment {
-  private Double interestRate;
-  private List<CostComponent> costComponents;
-  private BigDecimal remainingPrincipal;
-  private @Nullable String date;
+public class PlannedPayment {
+  private Payment payment;
+  private Map<String, BigDecimal> balances;
 
-  public PlannedPayment() {
+  public PlannedPayment(Payment payment, Map<String, BigDecimal> balances) {
+    this.payment = payment;
+    this.balances = balances;
   }
 
-  public PlannedPayment(Double interestRate, List<CostComponent> costComponents, BigDecimal remainingPrincipal) {
-    this.interestRate = interestRate;
-    this.costComponents = costComponents;
-    this.remainingPrincipal = remainingPrincipal;
+  public Payment getPayment() {
+    return payment;
   }
 
-  public Double getInterestRate() {
-    return interestRate;
+  public void setPayment(Payment payment) {
+    this.payment = payment;
   }
 
-  public void setInterestRate(Double interestRate) {
-    this.interestRate = interestRate;
+  public Map<String, BigDecimal> getBalances() {
+    return balances;
   }
 
-  public List<CostComponent> getCostComponents() {
-    return costComponents;
-  }
-
-  public void setCostComponents(List<CostComponent> costComponents) {
-    this.costComponents = costComponents;
-  }
-
-  public BigDecimal getRemainingPrincipal() {
-    return remainingPrincipal;
-  }
-
-  public void setRemainingPrincipal(BigDecimal remainingPrincipal) {
-    this.remainingPrincipal = remainingPrincipal;
-  }
-
-  public String getDate() {
-    return date;
-  }
-
-  public void setDate(String date) {
-    this.date = date;
+  public void setBalances(Map<String, BigDecimal> balances) {
+    this.balances = balances;
   }
 
   @Override
@@ -78,24 +54,20 @@
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
     PlannedPayment that = (PlannedPayment) o;
-    return Objects.equals(interestRate, that.interestRate) &&
-            Objects.equals(costComponents, that.costComponents) &&
-            Objects.equals(remainingPrincipal, that.remainingPrincipal) &&
-            Objects.equals(date, that.date);
+    return Objects.equals(payment, that.payment) &&
+        Objects.equals(balances, that.balances);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(interestRate, costComponents, remainingPrincipal, date);
+    return Objects.hash(payment, balances);
   }
 
   @Override
   public String toString() {
     return "PlannedPayment{" +
-            "interestRate=" + interestRate +
-            ", costComponents=" + costComponents +
-            ", remainingPrincipal=" + remainingPrincipal +
-            ", date='" + date + '\'' +
-            '}';
+        "payment=" + payment +
+        ", balances=" + balances +
+        '}';
   }
 }
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
index 65bd4e6..912ef35 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
@@ -21,9 +21,7 @@
 @SuppressWarnings("unused")
 public interface AccountDesignators {
   String CUSTOMER_LOAN = "customer-loan";
-  String PENDING_DISBURSAL = "pending-disbursal";
   String LOAN_FUNDS_SOURCE = "loan-funds-source";
-  String LOANS_PAYABLE = "loans-payable";
   String PROCESSING_FEE_INCOME = "processing-fee-income";
   String ORIGINATION_FEE_INCOME = "origination-fee-income";
   String DISBURSEMENT_FEE_INCOME = "disbursement-fee-income";
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
index 9518a1b..f9fbbd9 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
@@ -22,10 +22,6 @@
  */
 @SuppressWarnings("unused")
 public interface ChargeIdentifiers {
-  String LOAN_FUNDS_ALLOCATION_NAME = "Loan funds allocation";
-  String LOAN_FUNDS_ALLOCATION_ID = "loan-funds-allocation";
-  String RETURN_DISBURSEMENT_NAME = "Return disbursement";
-  String RETURN_DISBURSEMENT_ID = "return-disbursement";
   String INTEREST_NAME = "Interest";
   String INTEREST_ID = "interest";
   String ALLOW_FOR_WRITE_OFF_NAME = "Allow for write-off";
@@ -36,16 +32,12 @@
   String DISBURSEMENT_FEE_ID = "disbursement-fee";
   String DISBURSE_PAYMENT_NAME = "Disburse payment";
   String DISBURSE_PAYMENT_ID = "disburse-payment";
-  String TRACK_DISBURSAL_PAYMENT_NAME = "Track disburse payment";
-  String TRACK_DISBURSAL_PAYMENT_ID = "track-disburse-payment";
   String LOAN_ORIGINATION_FEE_NAME = "Loan origination fee";
   String LOAN_ORIGINATION_FEE_ID = "loan-origination-fee";
   String PROCESSING_FEE_NAME = "Processing fee";
   String PROCESSING_FEE_ID = "processing-fee";
   String REPAYMENT_NAME = "Repayment";
   String REPAYMENT_ID = "repayment";
-  String TRACK_RETURN_PRINCIPAL_NAME = "Track return principal";
-  String TRACK_RETURN_PRINCIPAL_ID = "track-return-principal";
 
   static String nameToIdentifier(String name) {
     return name.toLowerCase(Locale.US).replace(" ", "-");
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeProportionalDesignator.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeProportionalDesignator.java
index 6f60a57..31cd960 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeProportionalDesignator.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeProportionalDesignator.java
@@ -25,8 +25,9 @@
   NOT_PROPORTIONAL("{notproportional}", 0),
   MAXIMUM_BALANCE_DESIGNATOR("{maximumbalance}", 1),
   RUNNING_BALANCE_DESIGNATOR("{runningbalance}", 2),
-  PRINCIPAL_ADJUSTMENT_DESIGNATOR("{principaladjustment}", 3),
-  REPAYMENT_DESIGNATOR("{repayment}", 4),
+  REQUESTED_DISBURSEMENT_DESIGNATOR("{requesteddisbursement}", 3),
+  REQUESTED_REPAYMENT_DESIGNATOR("{requestedrepayment}", 4),
+  CONTRACTUAL_REPAYMENT_DESIGNATOR("{contractualrepayment}", 5),
   ;
 
   private final String value;
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
index ac86d39..7acf0c1 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
@@ -17,6 +17,7 @@
 
 import io.mifos.core.api.annotation.ThrowsException;
 import io.mifos.core.api.util.CustomFeignClientsConfiguration;
+import io.mifos.portfolio.api.v1.domain.Payment;
 import io.mifos.portfolio.api.v1.domain.*;
 import io.mifos.portfolio.api.v1.validation.ValidSortColumn;
 import io.mifos.portfolio.api.v1.validation.ValidSortDirection;
@@ -345,7 +346,7 @@
       produces = MediaType.ALL_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE
   )
-  List<CostComponent> getCostComponentsForAction(
+  Payment getCostComponentsForAction(
       @PathVariable("productidentifier") final String productIdentifier,
       @PathVariable("caseidentifier") final String caseIdentifier,
       @PathVariable("actionidentifier") final String actionIdentifier,
@@ -360,7 +361,7 @@
       produces = MediaType.ALL_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE
   )
-  List<CostComponent> getCostComponentsForAction(
+  Payment getCostComponentsForAction(
       @PathVariable("productidentifier") final String productIdentifier,
       @PathVariable("caseidentifier") final String caseIdentifier,
       @PathVariable("actionidentifier") final String actionIdentifier,
@@ -375,7 +376,7 @@
       produces = MediaType.ALL_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE
   )
-  List<CostComponent> getCostComponentsForAction(
+  Payment getCostComponentsForAction(
       @PathVariable("productidentifier") final String productIdentifier,
       @PathVariable("caseidentifier") final String caseIdentifier,
       @PathVariable("actionidentifier") final String actionIdentifier,
@@ -388,7 +389,7 @@
       produces = MediaType.ALL_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE
   )
-  List<CostComponent> getCostComponentsForAction(
+  Payment getCostComponentsForAction(
       @PathVariable("productidentifier") final String productIdentifier,
       @PathVariable("caseidentifier") final String caseIdentifier,
       @PathVariable("actionidentifier") final String actionIdentifier);
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/Payment.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/Payment.java
new file mode 100644
index 0000000..97d5d51
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/Payment.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * 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.portfolio.api.v1.domain;
+
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public final class Payment {
+  private List<CostComponent> costComponents;
+  private Map<String, BigDecimal> balanceAdjustments;
+  private @Nullable String date;
+
+  public Payment() {
+  }
+
+  public Payment(List<CostComponent> costComponents, Map<String, BigDecimal> balanceAdjustments) {
+    this.costComponents = costComponents;
+    this.balanceAdjustments = balanceAdjustments;
+  }
+
+  public List<CostComponent> getCostComponents() {
+    return costComponents;
+  }
+
+  public void setCostComponents(List<CostComponent> costComponents) {
+    this.costComponents = costComponents;
+  }
+
+  public Map<String, BigDecimal> getBalanceAdjustments() {
+    return balanceAdjustments;
+  }
+
+  public void setBalanceAdjustments(Map<String, BigDecimal> balanceAdjustments) {
+    this.balanceAdjustments = balanceAdjustments;
+  }
+
+  public String getDate() {
+    return date;
+  }
+
+  public void setDate(String date) {
+    this.date = date;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    Payment that = (Payment) o;
+    return Objects.equals(costComponents, that.costComponents) &&
+            Objects.equals(balanceAdjustments, that.balanceAdjustments) &&
+            Objects.equals(date, that.date);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(costComponents, balanceAdjustments, date);
+  }
+
+  @Override
+  public String toString() {
+    return "Payment{" +
+        "costComponents=" + costComponents +
+        ", balanceAdjustments=" + balanceAdjustments +
+        ", date='" + date + '\'' +
+        '}';
+  }
+}
diff --git a/api/src/test/java/io/mifos/Fixture.java b/api/src/test/java/io/mifos/Fixture.java
index 5643ecb..a6f1435 100644
--- a/api/src/test/java/io/mifos/Fixture.java
+++ b/api/src/test/java/io/mifos/Fixture.java
@@ -35,15 +35,9 @@
  */
 @SuppressWarnings("WeakerAccess")
 public class Fixture {
-  static final String INCOME_LEDGER_IDENTIFIER = "1000";
-  static final String FEES_AND_CHARGES_LEDGER_IDENTIFIER = "1300";
-  static final String CASH_LEDGER_IDENTIFIER = "7300";
-  static final String PENDING_DISBURSAL_LEDGER_IDENTIFIER = "7320";
-  static final String CUSTOMER_LOAN_LEDGER_IDENTIFIER = "7353";
   static final String LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER = "7310";
   static final String LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER = "1310";
   static final String PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER = "1312";
-  static final String TELLER_ONE_ACCOUNT_IDENTIFIER = "7352";
 
   static int uniquenessSuffix = 0;
 
@@ -62,7 +56,6 @@
     product.setMinorCurrencyUnitDigits(2);
 
     final Set<AccountAssignment> accountAssignments = new HashSet<>();
-    accountAssignments.add(new AccountAssignment(PENDING_DISBURSAL, PENDING_DISBURSAL_LEDGER_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(PROCESSING_FEE_INCOME, PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(ORIGINATION_FEE_INCOME, LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(DISBURSEMENT_FEE_INCOME, "001-004"));
diff --git a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
index 8ef3fb6..68461f4 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
@@ -268,14 +268,14 @@
                                           final Set<String> accountDesignators,
                                           final BigDecimal amount,
                                           final CostComponent... expectedCostComponents) {
-    final List<CostComponent> costComponents = portfolioManager.getCostComponentsForAction(
+    final Payment payment = portfolioManager.getCostComponentsForAction(
         productIdentifier,
         customerCaseIdentifier,
         action.name(),
         accountDesignators,
         amount
     );
-    final Set<CostComponent> setOfCostComponents = new HashSet<>(costComponents);
+    final Set<CostComponent> setOfCostComponents = new HashSet<>(payment.getCostComponents());
     final Set<CostComponent> setOfExpectedCostComponents = Stream.of(expectedCostComponents)
         .filter(x -> x.getAmount().compareTo(BigDecimal.ZERO) != 0)
         .collect(Collectors.toSet());
diff --git a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
index 00e74f7..7df57bf 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -50,7 +50,6 @@
   private static final String FEES_AND_CHARGES_LEDGER_IDENTIFIER = "1300";
   private static final String ASSET_LEDGER_IDENTIFIER = "7000";
   private static final String CASH_LEDGER_IDENTIFIER = "7300";
-  static final String PENDING_DISBURSAL_LEDGER_IDENTIFIER = "7320";
   static final String CUSTOMER_LOAN_LEDGER_IDENTIFIER = "7353";
   private static final String ACCRUED_INCOME_LEDGER_IDENTIFIER = "7800";
 
@@ -61,7 +60,6 @@
   static final String TELLER_ONE_ACCOUNT_IDENTIFIER = "7352";
   static final String LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER = "7810";
   static final String CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER = "1103";
-  static final String LOANS_PAYABLE_ACCOUNT_IDENTIFIER ="8690";
   static final String LATE_FEE_INCOME_ACCOUNT_IDENTIFIER = "1311";
   static final String LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER = "7840";
   static final String ARREARS_ALLOWANCE_ACCOUNT_IDENTIFIER = "3010";
@@ -129,15 +127,6 @@
     return ret;
   }
 
-  private static Ledger pendingDisbursalLedger() {
-    final Ledger ret = new Ledger();
-    ret.setIdentifier(PENDING_DISBURSAL_LEDGER_IDENTIFIER);
-    ret.setParentLedgerIdentifier(CASH_LEDGER_IDENTIFIER);
-    ret.setType(AccountType.ASSET.name());
-    ret.setCreatedOn(DateConverter.toIsoString(universalCreationDate));
-    return ret;
-  }
-
   private static Ledger customerLoanLedger() {
     final Ledger ret = new Ledger();
     ret.setIdentifier(CUSTOMER_LOAN_LEDGER_IDENTIFIER);
@@ -223,14 +212,6 @@
     return ret;
   }
 
-  private static Account loansPayableAccount() {
-    final Account ret = new Account();
-    ret.setIdentifier(LOANS_PAYABLE_ACCOUNT_IDENTIFIER);
-    //ret.setLedger(LOAN_INCOME_LEDGER_IDENTIFIER);
-    ret.setType(AccountType.LIABILITY.name());
-    return ret;
-  }
-
   private static Account lateFeeIncomeAccount() {
     final Account ret = new Account();
     ret.setIdentifier(LATE_FEE_INCOME_ACCOUNT_IDENTIFIER);
@@ -270,23 +251,6 @@
     return ret;
   }
 
-  private static Object pendingDisbursalAccountsPage() {
-    final Account pendingDisbursalAccount1 = new Account();
-    pendingDisbursalAccount1.setIdentifier("pendingDisbursalAccount1");
-
-    final Account pendingDisbursalAccount2 = new Account();
-    pendingDisbursalAccount2.setIdentifier("pendingDisbursalAccount2");
-
-    final Account pendingDisbursalAccount3 = new Account();
-    pendingDisbursalAccount3.setIdentifier("pendingDisbursalAccount3");
-
-    final AccountPage ret = new AccountPage();
-    ret.setTotalElements(3L);
-    ret.setTotalPages(1);
-    ret.setAccounts(Arrays.asList(pendingDisbursalAccount1, pendingDisbursalAccount2, pendingDisbursalAccount3));
-    return ret;
-  }
-
   private static <T> Valid<T> isValid() {
     return new Valid<>();
   }
@@ -457,7 +421,6 @@
     makeAccountResponsive(tellerOneAccount(), universalCreationDate, ledgerManagerMock);
     makeAccountResponsive(loanInterestAccrualAccount(), universalCreationDate, ledgerManagerMock);
     makeAccountResponsive(consumerLoanInterestAccount(), universalCreationDate, ledgerManagerMock);
-    makeAccountResponsive(loansPayableAccount(), universalCreationDate, ledgerManagerMock);
     makeAccountResponsive(lateFeeIncomeAccount(), universalCreationDate, ledgerManagerMock);
     makeAccountResponsive(lateFeeAccrualAccount(), universalCreationDate, ledgerManagerMock);
     makeAccountResponsive(arrearsAllowanceAccount(), universalCreationDate, ledgerManagerMock);
@@ -465,14 +428,11 @@
     Mockito.doReturn(incomeLedger()).when(ledgerManagerMock).findLedger(INCOME_LEDGER_IDENTIFIER);
     Mockito.doReturn(feesAndChargesLedger()).when(ledgerManagerMock).findLedger(FEES_AND_CHARGES_LEDGER_IDENTIFIER);
     Mockito.doReturn(cashLedger()).when(ledgerManagerMock).findLedger(CASH_LEDGER_IDENTIFIER);
-    Mockito.doReturn(pendingDisbursalLedger()).when(ledgerManagerMock).findLedger(PENDING_DISBURSAL_LEDGER_IDENTIFIER);
     Mockito.doReturn(customerLoanLedger()).when(ledgerManagerMock).findLedger(CUSTOMER_LOAN_LEDGER_IDENTIFIER);
     Mockito.doReturn(loanIncomeLedger()).when(ledgerManagerMock).findLedger(LOAN_INCOME_LEDGER_IDENTIFIER);
     Mockito.doReturn(accruedIncomeLedger()).when(ledgerManagerMock).findLedger(ACCRUED_INCOME_LEDGER_IDENTIFIER);
     Mockito.doReturn(customerLoanAccountsPage()).when(ledgerManagerMock).fetchAccountsOfLedger(Mockito.eq(CUSTOMER_LOAN_LEDGER_IDENTIFIER),
             Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
-    Mockito.doReturn(pendingDisbursalAccountsPage()).when(ledgerManagerMock).fetchAccountsOfLedger(Mockito.eq(PENDING_DISBURSAL_LEDGER_IDENTIFIER),
-            Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
 
     Mockito.doAnswer(new FindAccountAnswer()).when(ledgerManagerMock).findAccount(Matchers.anyString());
     Mockito.doAnswer(new CreateAccountAnswer()).when(ledgerManagerMock).createAccount(Matchers.any());
@@ -492,20 +452,6 @@
   }
 
   static void verifyTransfer(final LedgerManager ledgerManager,
-                             final String fromAccountIdentifier,
-                             final String toAccountIdentifier,
-                             final BigDecimal amount,
-                             final String productIdentifier,
-                             final String caseIdentifier,
-                             final Action action) {
-    final JournalEntryMatcher specifiesCorrectJournalEntry = new JournalEntryMatcher(
-        Collections.singleton(new Debtor(fromAccountIdentifier, amount.toPlainString())),
-        Collections.singleton(new Creditor(toAccountIdentifier, amount.toPlainString())),
-        productIdentifier, caseIdentifier, action);
-    Mockito.verify(ledgerManager).createJournalEntry(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectJournalEntry)));
-  }
-
-  static void verifyTransfer(final LedgerManager ledgerManager,
                              final Set<Debtor> debtors,
                              final Set<Creditor> creditors,
                              final String productIdentifier,
diff --git a/component-test/src/main/java/io/mifos/portfolio/Fixture.java b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
index de8fc57..900ac99 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -60,16 +60,11 @@
     product.setMinorCurrencyUnitDigits(MINOR_CURRENCY_UNIT_DIGITS);
 
     final Set<AccountAssignment> accountAssignments = new HashSet<>();
-    final AccountAssignment pendingDisbursalAccountAssignment = new AccountAssignment();
-    pendingDisbursalAccountAssignment.setDesignator(PENDING_DISBURSAL);
-    pendingDisbursalAccountAssignment.setLedgerIdentifier(PENDING_DISBURSAL_LEDGER_IDENTIFIER);
-    accountAssignments.add(pendingDisbursalAccountAssignment);
     accountAssignments.add(new AccountAssignment(PROCESSING_FEE_INCOME, PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(ORIGINATION_FEE_INCOME, LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(DISBURSEMENT_FEE_INCOME, DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(INTEREST_INCOME, CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(INTEREST_ACCRUAL, LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER));
-    accountAssignments.add(new AccountAssignment(LOANS_PAYABLE, LOANS_PAYABLE_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(LATE_FEE_INCOME, LATE_FEE_INCOME_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(LATE_FEE_ACCRUAL, LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(ARREARS_ALLOWANCE, ARREARS_ALLOWANCE_ACCOUNT_IDENTIFIER));
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
index 50724ed..def64ad 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -35,6 +35,7 @@
 import io.mifos.rhythm.spi.v1.client.BeatListener;
 import io.mifos.rhythm.spi.v1.domain.BeatPublish;
 import io.mifos.rhythm.spi.v1.events.BeatPublishEvent;
+import org.assertj.core.util.Sets;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -67,8 +68,6 @@
   private Product product = null;
   private Case customerCase = null;
   private TaskDefinition taskDefinition = null;
-  private CaseParameters caseParameters = null;
-  private String pendingDisbursalAccountIdentifier = null;
   private String customerLoanAccountIdentifier = null;
 
   private BigDecimal expectedCurrentBalance = null;
@@ -181,16 +180,20 @@
       week++;
     }
 
-    final BigDecimal minPayment = repayments.stream().min(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
-    final BigDecimal maxPayment = repayments.stream().max(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
-    final BigDecimal delta = maxPayment.subtract(minPayment).abs();
-    Assert.assertTrue("Payments are " + repayments,
-        delta.divide(maxPayment, BigDecimal.ROUND_HALF_EVEN).compareTo(BigDecimal.valueOf(0.01)) <= 0);
+    checkRepaymentVariance(repayments);
 
 
     step8Close();
   }
 
+  private void checkRepaymentVariance(List<BigDecimal> repayments) {
+    final BigDecimal minPayment = repayments.stream().min(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
+    final BigDecimal maxPayment = repayments.stream().max(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
+    final BigDecimal delta = maxPayment.subtract(minPayment).abs();
+    Assert.assertTrue("Payments vary too much. Payments are " + repayments,
+        delta.divide(maxPayment, BigDecimal.ROUND_HALF_EVEN).compareTo(BigDecimal.valueOf(0.01)) <= 0);
+  }
+
   @Test
   public void workflowWithOneLateRepayment() throws InterruptedException {
     final LocalDateTime today = midnightToday();
@@ -209,7 +212,7 @@
     while (expectedCurrentBalance.compareTo(BigDecimal.ZERO) > 0) {
       logger.info("Simulating week {}. Expected current balance {}.", week, expectedCurrentBalance);
       if (week == weekOfLateRepayment) {
-        final BigDecimal lateFee = BigDecimal.valueOf(14_49, MINOR_CURRENCY_UNIT_DIGITS);
+        final BigDecimal lateFee = BigDecimal.valueOf(17_31, MINOR_CURRENCY_UNIT_DIGITS);
         step6CalculateInterestAndCheckForLatenessForRangeOfDays(
             today,
             (week * 7) + 1,
@@ -231,12 +234,7 @@
 
     repayments.remove(3);
 
-    final BigDecimal minPayment = repayments.stream().min(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
-    final BigDecimal maxPayment = repayments.stream().max(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
-    final BigDecimal delta = maxPayment.subtract(minPayment).abs();
-    Assert.assertTrue("Payments are " + repayments,
-        delta.divide(maxPayment, BigDecimal.ROUND_HALF_EVEN).compareTo(BigDecimal.valueOf(0.01)) <= 0);
-
+    checkRepaymentVariance(repayments);
 
     step8Close();
   }
@@ -244,16 +242,16 @@
   private BigDecimal findNextRepaymentAmount(
       final LocalDateTime referenceDate,
       final int dayNumber) {
-    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
+    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance);
 
-    final List<CostComponent> costComponentsForNextPayment = portfolioManager.getCostComponentsForAction(
+    final Payment nextPayment = portfolioManager.getCostComponentsForAction(
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.ACCEPT_PAYMENT.name(),
         null,
         null,
         DateConverter.toIsoString(referenceDate.plusDays(dayNumber)));
-    return costComponentsForNextPayment.stream().filter(x -> x.getChargeIdentifier().equals(ChargeIdentifiers.REPAYMENT_ID)).findFirst()
+    return nextPayment.getCostComponents().stream().filter(x -> x.getChargeIdentifier().equals(ChargeIdentifiers.REPAYMENT_ID)).findFirst()
         .orElseThrow(() -> new IllegalArgumentException("return missing repayment charge."))
         .getAmount();
   }
@@ -277,7 +275,7 @@
         = portfolioManager.getChargeDefinition(product.getIdentifier(), ChargeIdentifiers.DISBURSEMENT_FEE_ID);
     lowerRangeDisbursementFeeChargeDefinition.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
     lowerRangeDisbursementFeeChargeDefinition.setAmount(DISBURSEMENT_FEE_LOWER_RANGE_AMOUNT);
-    lowerRangeDisbursementFeeChargeDefinition.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
+    lowerRangeDisbursementFeeChargeDefinition.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
     lowerRangeDisbursementFeeChargeDefinition.setForSegmentSet(DISBURSEMENT_RANGES);
     lowerRangeDisbursementFeeChargeDefinition.setFromSegment(DISBURSEMENT_LOWER_RANGE);
     lowerRangeDisbursementFeeChargeDefinition.setToSegment(DISBURSEMENT_LOWER_RANGE);
@@ -297,7 +295,7 @@
     upperRangeDisbursementFeeChargeDefinition.setChargeAction(lowerRangeDisbursementFeeChargeDefinition.getChargeAction());
     upperRangeDisbursementFeeChargeDefinition.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
     upperRangeDisbursementFeeChargeDefinition.setAmount(DISBURSEMENT_FEE_UPPER_RANGE_AMOUNT);
-    upperRangeDisbursementFeeChargeDefinition.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
+    upperRangeDisbursementFeeChargeDefinition.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
     upperRangeDisbursementFeeChargeDefinition.setForSegmentSet(DISBURSEMENT_RANGES);
     upperRangeDisbursementFeeChargeDefinition.setFromSegment(DISBURSEMENT_UPPER_RANGE);
     upperRangeDisbursementFeeChargeDefinition.setToSegment(DISBURSEMENT_UPPER_RANGE);
@@ -314,8 +312,8 @@
 
   private void step2CreateCase() throws InterruptedException {
     logger.info("step2CreateCase");
-    caseParameters = Fixture.createAdjustedCaseParameters(x ->
-      x.setPaymentCycle(new PaymentCycle(ChronoUnit.WEEKS, 1, null, null, null))
+    final CaseParameters caseParameters = Fixture.createAdjustedCaseParameters(x ->
+        x.setPaymentCycle(new PaymentCycle(ChronoUnit.WEEKS, 1, null, null, null))
     );
     final String caseParametersAsString = new Gson().toJson(caseParameters);
     customerCase = createAdjustedCase(product.getIdentifier(), x -> x.setParameters(caseParametersAsString));
@@ -330,9 +328,8 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.OPEN,
-        Collections.singleton(AccountDesignators.ENTRY),
         null,
-        new CostComponent(ChargeIdentifiers.PROCESSING_FEE_ID, PROCESSING_FEE_AMOUNT));
+        null);
     checkStateTransfer(
         product.getIdentifier(),
         customerCase.getIdentifier(),
@@ -341,11 +338,6 @@
         IndividualLoanEventConstants.OPEN_INDIVIDUALLOAN_CASE,
         Case.State.PENDING);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, Action.DENY);
-
-    AccountingFixture.verifyTransfer(ledgerManager,
-        AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, AccountingFixture.PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER,
-        PROCESSING_FEE_AMOUNT, product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN
-    );
   }
 
 
@@ -356,7 +348,7 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.DENY,
-        Collections.singleton(AccountDesignators.ENTRY),
+        null,
         null);
     checkStateTransfer(
         product.getIdentifier(),
@@ -379,9 +371,8 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.APPROVE,
-        Collections.singleton(AccountDesignators.ENTRY),
         null,
-        new CostComponent(ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID, LOAN_ORIGINATION_FEE_AMOUNT));
+        null);
     checkStateTransfer(
         product.getIdentifier(),
         customerCase.getIdentifier(),
@@ -391,20 +382,9 @@
         Case.State.APPROVED);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, Action.CLOSE);
 
-    pendingDisbursalAccountIdentifier =
-        AccountingFixture.verifyAccountCreation(ledgerManager, AccountingFixture.PENDING_DISBURSAL_LEDGER_IDENTIFIER, AccountType.ASSET);
     customerLoanAccountIdentifier =
         AccountingFixture.verifyAccountCreation(ledgerManager, AccountingFixture.CUSTOMER_LOAN_LEDGER_IDENTIFIER, AccountType.ASSET);
 
-    final Set<Debtor> debtors = new HashSet<>();
-    debtors.add(new Debtor(AccountingFixture.LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER, caseParameters.getMaximumBalance().toPlainString()));
-    debtors.add(new Debtor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, LOAN_ORIGINATION_FEE_AMOUNT.toPlainString()));
-
-    final Set<Creditor> creditors = new HashSet<>();
-    creditors.add(new Creditor(pendingDisbursalAccountIdentifier, caseParameters.getMaximumBalance().toPlainString()));
-    creditors.add(new Creditor(AccountingFixture.LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER, LOAN_ORIGINATION_FEE_AMOUNT.toPlainString()));
-    AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors, product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE);
-
     expectedCurrentBalance = BigDecimal.ZERO;
   }
 
@@ -418,8 +398,10 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.DISBURSE,
-        Collections.singleton(AccountDesignators.ENTRY),
+        Sets.newLinkedHashSet(AccountDesignators.ENTRY, AccountDesignators.CUSTOMER_LOAN),
         amount, new CostComponent(whichDisbursementFee, disbursementFeeAmount),
+        new CostComponent(ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID, LOAN_ORIGINATION_FEE_AMOUNT),
+        new CostComponent(ChargeIdentifiers.PROCESSING_FEE_ID, PROCESSING_FEE_AMOUNT),
         new CostComponent(ChargeIdentifiers.DISBURSE_PAYMENT_ID, amount));
     checkStateTransfer(
         product.getIdentifier(),
@@ -434,16 +416,17 @@
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPLY_INTEREST,
         Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
 
-
     final Set<Debtor> debtors = new HashSet<>();
-    debtors.add(new Debtor(pendingDisbursalAccountIdentifier, amount.toPlainString()));
-    debtors.add(new Debtor(AccountingFixture.LOANS_PAYABLE_ACCOUNT_IDENTIFIER, amount.toPlainString()));
-    debtors.add(new Debtor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, disbursementFeeAmount.toPlainString()));
+    debtors.add(new Debtor(customerLoanAccountIdentifier, amount.toPlainString()));
+    debtors.add(new Debtor(customerLoanAccountIdentifier, PROCESSING_FEE_AMOUNT.toPlainString()));
+    debtors.add(new Debtor(customerLoanAccountIdentifier, disbursementFeeAmount.toPlainString()));
+    debtors.add(new Debtor(customerLoanAccountIdentifier, LOAN_ORIGINATION_FEE_AMOUNT.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
-    creditors.add(new Creditor(customerLoanAccountIdentifier, amount.toPlainString()));
-    creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, amount.toPlainString()));
+    creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, amount.toString()));
+    creditors.add(new Creditor(AccountingFixture.PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER, PROCESSING_FEE_AMOUNT.toPlainString()));
     creditors.add(new Creditor(AccountingFixture.DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER, disbursementFeeAmount.toPlainString()));
+    creditors.add(new Creditor(AccountingFixture.LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER, LOAN_ORIGINATION_FEE_AMOUNT.toPlainString()));
     AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors, product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE);
 
     expectedCurrentBalance = expectedCurrentBalance.add(amount);
@@ -499,7 +482,7 @@
     final String beatIdentifier = "alignment0";
     final String midnightTimeStamp = DateConverter.toIsoString(forTime);
 
-    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
+    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance);
 
     final BigDecimal calculatedInterest = expectedCurrentBalance.multiply(Fixture.INTEREST_RATE.divide(Fixture.ACCRUAL_PERIODS, 8, BigDecimal.ROUND_HALF_EVEN))
         .setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN);
@@ -509,7 +492,7 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.APPLY_INTEREST,
-        Collections.singleton(AccountDesignators.CUSTOMER_LOAN),
+        null,
         null,
         new CostComponent(ChargeIdentifiers.INTEREST_ID, calculatedInterest));
 
@@ -518,7 +501,7 @@
           product.getIdentifier(),
           customerCase.getIdentifier(),
           Action.MARK_LATE,
-          Collections.singleton(AccountDesignators.CUSTOMER_LOAN),
+          null,
           null,
           new CostComponent(ChargeIdentifiers.LATE_FEE_ID, calculatedLateFee));
     }
@@ -561,9 +544,7 @@
       final BigDecimal lateFee) throws InterruptedException {
     logger.info("step7PaybackPartialAmount '{}'", amount);
 
-    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
-
-    final BigDecimal principal = amount.subtract(interestAccrued).subtract(lateFee);
+    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance);
 
     checkCostComponentForActionCorrect(
         product.getIdentifier(),
@@ -572,7 +553,6 @@
         new HashSet<>(Arrays.asList(AccountDesignators.ENTRY, AccountDesignators.CUSTOMER_LOAN, AccountDesignators.LOAN_FUNDS_SOURCE)),
         amount,
         new CostComponent(ChargeIdentifiers.REPAYMENT_ID, amount),
-        new CostComponent(ChargeIdentifiers.TRACK_RETURN_PRINCIPAL_ID, principal),
         new CostComponent(ChargeIdentifiers.INTEREST_ID, interestAccrued),
         new CostComponent(ChargeIdentifiers.LATE_FEE_ID, lateFee));
     checkStateTransfer(
@@ -589,16 +569,14 @@
         Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
 
     final Set<Debtor> debtors = new HashSet<>();
-    debtors.add(new Debtor(customerLoanAccountIdentifier, amount.toPlainString()));
-    debtors.add(new Debtor(AccountingFixture.LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER, principal.toPlainString()));
+    debtors.add(new Debtor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, amount.toPlainString()));
     if (interestAccrued.compareTo(BigDecimal.ZERO) != 0)
       debtors.add(new Debtor(AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
     if (lateFee.compareTo(BigDecimal.ZERO) != 0)
       debtors.add(new Debtor(AccountingFixture.LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER, lateFee.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
-    creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, amount.toPlainString()));
-    creditors.add(new Creditor(AccountingFixture.LOANS_PAYABLE_ACCOUNT_IDENTIFIER, principal.toPlainString()));
+    creditors.add(new Creditor(customerLoanAccountIdentifier, amount.toPlainString()));
     if (interestAccrued.compareTo(BigDecimal.ZERO) != 0)
       creditors.add(new Creditor(AccountingFixture.CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
     if (lateFee.compareTo(BigDecimal.ZERO) != 0)
@@ -613,13 +591,13 @@
   private void step8Close() throws InterruptedException {
     logger.info("step8Close");
 
-    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
+    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance);
 
     checkCostComponentForActionCorrect(
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.CLOSE,
-        Collections.singleton(AccountDesignators.ENTRY),
+        null,
         null);
     checkStateTransfer(
         product.getIdentifier(),
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
index 685c183..ec4540d 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
@@ -57,11 +57,7 @@
 
     final Set<String> expectedReadOnlyChargeDefinitionIdentifiers = Stream.of(
         ChargeIdentifiers.ALLOW_FOR_WRITE_OFF_ID,
-        ChargeIdentifiers.LOAN_FUNDS_ALLOCATION_ID,
-        ChargeIdentifiers.RETURN_DISBURSEMENT_ID,
         ChargeIdentifiers.DISBURSE_PAYMENT_ID,
-        ChargeIdentifiers.TRACK_DISBURSAL_PAYMENT_ID,
-        ChargeIdentifiers.TRACK_RETURN_PRINCIPAL_ID,
         ChargeIdentifiers.INTEREST_ID,
         ChargeIdentifiers.REPAYMENT_ID)
         .collect(Collectors.toSet());
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestCommands.java b/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
index e3520ad..541f490 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
@@ -15,18 +15,15 @@
  */
 package io.mifos.portfolio;
 
-import io.mifos.accounting.api.v1.domain.AccountEntry;
-import io.mifos.core.lang.DateConverter;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.api.v1.domain.Product;
 import org.junit.Test;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
 
+import java.math.BigDecimal;
+import java.time.Clock;
 import java.time.LocalDateTime;
 import java.util.Collections;
-import java.util.stream.Stream;
 
 import static io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants.*;
 
@@ -34,81 +31,9 @@
  * @author Myrle Krantz
  */
 public class TestCommands extends AbstractPortfolioTest {
-  @Test
-  public void testHappyWorkflow() throws InterruptedException {
-    final Product product = createAndEnableProduct();
-    final Case customerCase = createCase(product.getIdentifier());
-
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN);
-
-
-    checkStateTransfer(
-        product.getIdentifier(),
-        customerCase.getIdentifier(),
-        Action.OPEN,
-        Collections.singletonList(assignEntryToTeller()),
-        OPEN_INDIVIDUALLOAN_CASE,
-        Case.State.PENDING);
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, Action.DENY);
-
-
-    checkStateTransfer(product.getIdentifier(),
-        customerCase.getIdentifier(),
-        Action.APPROVE,
-        Collections.singletonList(assignEntryToTeller()),
-        APPROVE_INDIVIDUALLOAN_CASE,
-        Case.State.APPROVED);
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, Action.CLOSE);
-
-
-    checkStateTransfer(
-        product.getIdentifier(),
-        customerCase.getIdentifier(),
-        Action.DISBURSE,
-        Collections.singletonList(assignEntryToTeller()),
-        DISBURSE_INDIVIDUALLOAN_CASE,
-        Case.State.ACTIVE);
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(),
-            Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
-
-    final AccountEntry firstEntry = new AccountEntry();
-    firstEntry.setAmount(2000.0);
-    firstEntry.setTransactionDate(DateConverter.toIsoString(LocalDateTime.now()));
-    Mockito.doAnswer((x) -> Stream.of(firstEntry))
-        .when(ledgerManager)
-        .fetchAccountEntriesStream(Matchers.anyString(), Matchers.anyString(), Matchers.anyString(), Matchers.eq("ASC"));
-
-
-    checkStateTransfer(
-        product.getIdentifier(),
-        customerCase.getIdentifier(),
-        Action.ACCEPT_PAYMENT,
-        Collections.singletonList(assignEntryToTeller()),
-        ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE,
-        Case.State.ACTIVE);
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(),
-            Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
-
-
-    checkStateTransfer(
-        product.getIdentifier(),
-        customerCase.getIdentifier(),
-        Action.ACCEPT_PAYMENT,
-        Collections.singletonList(assignEntryToTeller()),
-        ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE,
-        Case.State.ACTIVE);
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(),
-            Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
-
-    checkStateTransfer(
-        product.getIdentifier(),
-        customerCase.getIdentifier(),
-        Action.CLOSE,
-        Collections.singletonList(assignEntryToTeller()),
-        CLOSE_INDIVIDUALLOAN_CASE,
-        Case.State.CLOSED);
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier());
-  }
+  // Happy case test deleted because the case is covered in more detail in
+  // TestAccountingInteractionInLoanWorkflow.
+  //public void testHappyWorkflow() throws InterruptedException
 
   @Test
   public void testBadCustomerWorkflow() throws InterruptedException {
@@ -142,8 +67,11 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.DISBURSE,
+        LocalDateTime.now(Clock.systemUTC()),
         Collections.singletonList(assignEntryToTeller()),
+        BigDecimal.valueOf(2000L),
         DISBURSE_INDIVIDUALLOAN_CASE,
+        midnightToday(),
         Case.State.ACTIVE);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(),
             Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestIndividualLoans.java b/component-test/src/main/java/io/mifos/portfolio/TestIndividualLoans.java
index 0ef6c65..1f8c03e 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestIndividualLoans.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestIndividualLoans.java
@@ -16,6 +16,7 @@
 package io.mifos.portfolio;
 
 import com.google.gson.Gson;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.api.v1.domain.CasePage;
 import io.mifos.portfolio.api.v1.domain.Product;
@@ -88,8 +89,8 @@
 
     Assert.assertNotNull(paymentScheduleFirstPage);
     paymentScheduleFirstPage.getElements().forEach(x -> {
-      x.getCostComponents().forEach(y -> Assert.assertEquals(product.getMinorCurrencyUnitDigits(), y.getAmount().scale()));
-      Assert.assertEquals(product.getMinorCurrencyUnitDigits(), x.getRemainingPrincipal().scale());
+      x.getPayment().getCostComponents().forEach(y -> Assert.assertEquals(product.getMinorCurrencyUnitDigits(), y.getAmount().scale()));
+      Assert.assertEquals(product.getMinorCurrencyUnitDigits(), x.getBalances().get(AccountDesignators.CUSTOMER_LOAN).scale());
     });
   }
 
diff --git a/service/build.gradle b/service/build.gradle
index 8194a22..4274445 100644
--- a/service/build.gradle
+++ b/service/build.gradle
@@ -47,7 +47,8 @@
             [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator],
             [group: 'org.javamoney.lib', name: 'javamoney-calc', version: versions.javamoneylib],
             [group: 'javax.money', name: 'money-api', version: '1.0.1'],
-            [group: 'org.javamoney', name: 'moneta', version: '1.0.1']
+            [group: 'org.javamoney', name: 'moneta', version: '1.0.1'],
+            [group: 'net.jodah', name: 'expiringmap', version: versions.expiringmap],
     )
 }
 
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index 31ea45d..bd76bb4 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -15,7 +15,6 @@
  */
 package io.mifos.individuallending;
 
-import com.google.common.collect.Sets;
 import com.google.gson.Gson;
 import io.mifos.core.lang.ServiceException;
 import io.mifos.customer.api.v1.client.CustomerManager;
@@ -30,10 +29,8 @@
 import io.mifos.individuallending.internal.service.CostComponentService;
 import io.mifos.individuallending.internal.service.DataContextOfAction;
 import io.mifos.individuallending.internal.service.DataContextService;
-import io.mifos.portfolio.api.v1.domain.Case;
-import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
-import io.mifos.portfolio.api.v1.domain.CostComponent;
-import io.mifos.portfolio.api.v1.domain.Pattern;
+import io.mifos.individuallending.internal.service.PaymentBuilder;
+import io.mifos.portfolio.api.v1.domain.*;
 import io.mifos.portfolio.service.ServiceConstants;
 import io.mifos.products.spi.PatternFactory;
 import io.mifos.products.spi.ProductCommandDispatcher;
@@ -47,7 +44,6 @@
 import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import static io.mifos.individuallending.api.v1.domain.product.AccountDesignators.*;
 import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
@@ -88,8 +84,8 @@
 
     final Set<String> individualLendingRequiredAccounts = new HashSet<>();
     individualLendingRequiredAccounts.add(CUSTOMER_LOAN);
-    individualLendingRequiredAccounts.add(PENDING_DISBURSAL);
-    individualLendingRequiredAccounts.add(LOAN_FUNDS_SOURCE);
+    //TODO: fix in migration individualLendingRequiredAccounts.add(PENDING_DISBURSAL);
+    //was String PENDING_DISBURSAL = "pending-disbursal";
     individualLendingRequiredAccounts.add(LOAN_FUNDS_SOURCE);
     individualLendingRequiredAccounts.add(PROCESSING_FEE_INCOME);
     individualLendingRequiredAccounts.add(ORIGINATION_FEE_INCOME);
@@ -111,35 +107,37 @@
   public static List<ChargeDefinition> defaultIndividualLoanCharges() {
     final List<ChargeDefinition> ret = new ArrayList<>();
     final ChargeDefinition processingFee = charge(
-            PROCESSING_FEE_NAME,
-            Action.OPEN,
-            BigDecimal.ONE,
-            ENTRY,
-            PROCESSING_FEE_INCOME);
+        PROCESSING_FEE_NAME,
+        Action.DISBURSE, //TODO: fix existing charges in migration
+        BigDecimal.ONE,
+        CUSTOMER_LOAN, //TODO: fix existing charges in migration
+        PROCESSING_FEE_INCOME);
     processingFee.setReadOnly(false);
 
     final ChargeDefinition loanOriginationFee = charge(
-            LOAN_ORIGINATION_FEE_NAME,
-            Action.APPROVE,
-            BigDecimal.ONE,
-            ENTRY,
-            ORIGINATION_FEE_INCOME);
+        LOAN_ORIGINATION_FEE_NAME,
+        Action.DISBURSE, //TODO: fix existing charges in migration
+        BigDecimal.ONE,
+        CUSTOMER_LOAN, //TODO: fix existing charges in migration
+        ORIGINATION_FEE_INCOME);
     loanOriginationFee.setReadOnly(false);
 
-    final ChargeDefinition loanFundsAllocation = charge(
+    /*final ChargeDefinition loanFundsAllocation = charge(
             LOAN_FUNDS_ALLOCATION_ID,
             Action.APPROVE,
             BigDecimal.valueOf(100),
             LOAN_FUNDS_SOURCE,
             PENDING_DISBURSAL);
-    loanFundsAllocation.setReadOnly(true);
+    loanFundsAllocation.setReadOnly(true);*/
+    //TODO: handle removing this extraneous charge in migration.
 
     final ChargeDefinition disbursementFee = charge(
-            DISBURSEMENT_FEE_NAME,
-            Action.DISBURSE,
-            BigDecimal.valueOf(0.1),
-            ENTRY,
-            DISBURSEMENT_FEE_INCOME);
+        DISBURSEMENT_FEE_NAME,
+        Action.DISBURSE,
+        BigDecimal.valueOf(0.1),
+        CUSTOMER_LOAN, //TODO: fix existing charges in migration
+        DISBURSEMENT_FEE_INCOME);
+    disbursementFee.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());  //TODO: fix existing charges in migration
     disbursementFee.setReadOnly(false);
 
     final ChargeDefinition disbursePayment = new ChargeDefinition();
@@ -147,13 +145,14 @@
     disbursePayment.setIdentifier(DISBURSE_PAYMENT_ID);
     disbursePayment.setName(DISBURSE_PAYMENT_NAME);
     disbursePayment.setDescription(DISBURSE_PAYMENT_NAME);
-    disbursePayment.setFromAccountDesignator(LOANS_PAYABLE);
+    disbursePayment.setFromAccountDesignator(CUSTOMER_LOAN);  //TODO: fix existing charges in migration
     disbursePayment.setToAccountDesignator(ENTRY);
-    disbursePayment.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
+    disbursePayment.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
     disbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
     disbursePayment.setAmount(BigDecimal.valueOf(100));
     disbursePayment.setReadOnly(true);
 
+    /*
     final ChargeDefinition trackPrincipalDisbursePayment = new ChargeDefinition();
     trackPrincipalDisbursePayment.setChargeAction(Action.DISBURSE.name());
     trackPrincipalDisbursePayment.setIdentifier(TRACK_DISBURSAL_PAYMENT_ID);
@@ -161,10 +160,11 @@
     trackPrincipalDisbursePayment.setDescription(TRACK_DISBURSAL_PAYMENT_NAME);
     trackPrincipalDisbursePayment.setFromAccountDesignator(PENDING_DISBURSAL);
     trackPrincipalDisbursePayment.setToAccountDesignator(CUSTOMER_LOAN);
-    trackPrincipalDisbursePayment.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
+    trackPrincipalDisbursePayment.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
     trackPrincipalDisbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
     trackPrincipalDisbursePayment.setAmount(BigDecimal.valueOf(100));
-    trackPrincipalDisbursePayment.setReadOnly(true);
+    trackPrincipalDisbursePayment.setReadOnly(true);*/
+    //TODO: handle removing this extraneous charge in migration.
 
     final ChargeDefinition lateFee = charge(
             LATE_FEE_NAME,
@@ -174,17 +174,17 @@
             LATE_FEE_INCOME);
     lateFee.setAccrueAction(Action.MARK_LATE.name());
     lateFee.setAccrualAccountDesignator(LATE_FEE_ACCRUAL);
-    lateFee.setProportionalTo(ChargeProportionalDesignator.REPAYMENT_DESIGNATOR.getValue());
+    lateFee.setProportionalTo(ChargeProportionalDesignator.CONTRACTUAL_REPAYMENT_DESIGNATOR.getValue());
     lateFee.setChargeOnTop(true);
     lateFee.setReadOnly(false);
 
     //TODO: Make multiple write off allowance charges.
     final ChargeDefinition writeOffAllowanceCharge = charge(
-            ALLOW_FOR_WRITE_OFF_NAME,
-            Action.MARK_LATE,
-            BigDecimal.valueOf(30),
-            PENDING_DISBURSAL,
-            ARREARS_ALLOWANCE);
+        ALLOW_FOR_WRITE_OFF_NAME,
+        Action.MARK_LATE,
+        BigDecimal.valueOf(30),
+        LOAN_FUNDS_SOURCE, //TODO: this and previous value ("pending-disbursal") are not correct and will require migration.
+        ARREARS_ALLOWANCE);
     writeOffAllowanceCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
     writeOffAllowanceCharge.setReadOnly(true);
 
@@ -206,46 +206,48 @@
     customerRepaymentCharge.setIdentifier(REPAYMENT_ID);
     customerRepaymentCharge.setName(REPAYMENT_NAME);
     customerRepaymentCharge.setDescription(REPAYMENT_NAME);
-    customerRepaymentCharge.setFromAccountDesignator(CUSTOMER_LOAN);
-    customerRepaymentCharge.setToAccountDesignator(ENTRY);
-    customerRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.REPAYMENT_DESIGNATOR.getValue());
+    customerRepaymentCharge.setFromAccountDesignator(ENTRY);  //TODO: fix existing charges in migration
+    customerRepaymentCharge.setToAccountDesignator(CUSTOMER_LOAN);  //TODO: fix existing charges in migration
+    customerRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.REQUESTED_REPAYMENT_DESIGNATOR.getValue());
     customerRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
     customerRepaymentCharge.setAmount(BigDecimal.valueOf(100));
     customerRepaymentCharge.setReadOnly(true);
 
-    final ChargeDefinition trackReturnPrincipalCharge = new ChargeDefinition();
+    /*final ChargeDefinition trackReturnPrincipalCharge = new ChargeDefinition();
     trackReturnPrincipalCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
     trackReturnPrincipalCharge.setIdentifier(TRACK_RETURN_PRINCIPAL_ID);
     trackReturnPrincipalCharge.setName(TRACK_RETURN_PRINCIPAL_NAME);
     trackReturnPrincipalCharge.setDescription(TRACK_RETURN_PRINCIPAL_NAME);
     trackReturnPrincipalCharge.setFromAccountDesignator(LOAN_FUNDS_SOURCE);
     trackReturnPrincipalCharge.setToAccountDesignator(LOANS_PAYABLE);
-    trackReturnPrincipalCharge.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
+    trackReturnPrincipalCharge.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
     trackReturnPrincipalCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
     trackReturnPrincipalCharge.setAmount(BigDecimal.valueOf(100));
-    trackReturnPrincipalCharge.setReadOnly(true);
+    trackReturnPrincipalCharge.setReadOnly(true);*/
+    //TODO: handle removing this extraneous charge in migration.
 
-    final ChargeDefinition disbursementReturnCharge = charge(
+    /*final ChargeDefinition disbursementReturnCharge = charge(
         RETURN_DISBURSEMENT_NAME,
         Action.CLOSE,
         BigDecimal.valueOf(100),
         PENDING_DISBURSAL,
         LOAN_FUNDS_SOURCE);
     disbursementReturnCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
-    disbursementReturnCharge.setReadOnly(true);
+    disbursementReturnCharge.setReadOnly(true);*/
+    //TODO: handle removing this extraneous charge in migration.
 
     ret.add(processingFee);
     ret.add(loanOriginationFee);
-    ret.add(loanFundsAllocation);
+    //TODO: ret.add(loanFundsAllocation);
     ret.add(disbursementFee);
     ret.add(disbursePayment);
-    ret.add(trackPrincipalDisbursePayment);
+    //TODO: ret.add(trackPrincipalDisbursePayment);
     ret.add(lateFee);
     ret.add(writeOffAllowanceCharge);
     ret.add(interestCharge);
     ret.add(customerRepaymentCharge);
-    ret.add(trackReturnPrincipalCharge);
-    ret.add(disbursementReturnCharge);
+    //TODO: ret.add(trackReturnPrincipalCharge);
+    //TODO: ret.add(disbursementReturnCharge);
 
     return ret;
   }
@@ -344,7 +346,7 @@
   }
 
   @Override
-  public List<CostComponent> getCostComponentsForAction(
+  public Payment getCostComponentsForAction(
       final String productIdentifier,
       final String caseIdentifier,
       final String actionIdentifier,
@@ -356,35 +358,13 @@
     final Case.State caseState = Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState());
     checkActionCanBeExecuted(caseState, action);
 
-    Stream<Map.Entry<ChargeDefinition, CostComponent>> costComponentStream = costComponentService.getCostComponentsForAction(
+    final PaymentBuilder paymentBuilder = costComponentService.getCostComponentsForAction(
         action,
         dataContextOfAction,
         forPaymentSize,
-        forDateTime.toLocalDate())
-        .stream();
+        forDateTime.toLocalDate());
 
-    if (!forAccountDesignators.isEmpty()) {
-      costComponentStream = costComponentStream
-          .filter(costComponentEntry -> chargeReferencesAccountDesignators(costComponentEntry.getKey(), action, forAccountDesignators));
-    }
-
-    return costComponentStream
-        .map(costComponentEntry -> new CostComponent(costComponentEntry.getKey().getIdentifier(), costComponentEntry.getValue().getAmount()))
-        .collect(Collectors.toList());
-  }
-
-  private boolean chargeReferencesAccountDesignators(
-      final ChargeDefinition chargeDefinition,
-      final Action action,
-      final Set<String> forAccountDesignators) {
-    final Set<String> accountsToCompare = Sets.newHashSet(
-        chargeDefinition.getFromAccountDesignator(),
-        chargeDefinition.getToAccountDesignator()
-    );
-    if (chargeDefinition.getAccrualAccountDesignator() != null)
-      accountsToCompare.add(chargeDefinition.getAccrualAccountDesignator());
-
-    return !Sets.intersection(accountsToCompare, forAccountDesignators).isEmpty();
+    return paymentBuilder.buildPayment(action, forAccountDesignators);
   }
 
   public static void checkActionCanBeExecuted(final Case.State state, final Action action) {
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 d6e6aed..fb51ef3 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
@@ -32,7 +32,6 @@
 import io.mifos.individuallending.internal.service.*;
 import io.mifos.portfolio.api.v1.domain.AccountAssignment;
 import io.mifos.portfolio.api.v1.domain.Case;
-import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.CostComponent;
 import io.mifos.portfolio.api.v1.events.EventConstants;
 import io.mifos.portfolio.service.internal.mapper.CaseMapper;
@@ -99,20 +98,13 @@
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.OPEN);
 
-    final CostComponentsForRepaymentPeriod costComponents
+    final PaymentBuilder paymentBuilder
         = costComponentService.getCostComponentsForOpen(dataContextOfAction);
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
             = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
 
-    final List<ChargeInstance> charges = costComponents.stream()
-        .map(entry -> mapCostComponentEntryToChargeInstance(
-            Action.OPEN,
-            entry,
-            designatorToAccountIdentifierMapper))
-        .filter(Optional::isPresent)
-        .map(Optional::get)
-        .collect(Collectors.toList());
+    final List<ChargeInstance> charges = paymentBuilder.buildCharges(Action.OPEN, designatorToAccountIdentifierMapper);
 
     final LocalDateTime today = today();
 
@@ -143,20 +135,13 @@
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.DENY);
 
-    final CostComponentsForRepaymentPeriod costComponents
+    final PaymentBuilder paymentBuilder
         = costComponentService.getCostComponentsForDeny(dataContextOfAction);
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
 
-    final List<ChargeInstance> charges = costComponents.stream()
-        .map(entry -> mapCostComponentEntryToChargeInstance(
-            Action.DENY,
-            entry,
-            designatorToAccountIdentifierMapper))
-        .filter(Optional::isPresent)
-        .map(Optional::get)
-        .collect(Collectors.toList());
+    final List<ChargeInstance> charges = paymentBuilder.buildCharges(Action.DENY, designatorToAccountIdentifierMapper);
 
     final LocalDateTime today = today();
 
@@ -195,17 +180,10 @@
             );
     caseRepository.save(dataContextOfAction.getCustomerCaseEntity());
 
-    final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+    final PaymentBuilder paymentBuilder =
         costComponentService.getCostComponentsForApprove(dataContextOfAction);
 
-    final List<ChargeInstance> charges = costComponentsForRepaymentPeriod.stream()
-        .map(entry -> mapCostComponentEntryToChargeInstance(
-            Action.APPROVE,
-            entry,
-            designatorToAccountIdentifierMapper))
-        .filter(Optional::isPresent)
-        .map(Optional::get)
-        .collect(Collectors.toList());
+    final List<ChargeInstance> charges = paymentBuilder.buildCharges(Action.APPROVE, designatorToAccountIdentifierMapper);
 
     final LocalDateTime today = today();
 
@@ -236,21 +214,13 @@
     checkIfTasksAreOutstanding(dataContextOfAction, Action.DISBURSE);
 
     final BigDecimal disbursalAmount = Optional.ofNullable(command.getCommand().getPaymentSize()).orElse(BigDecimal.ZERO);
-    final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+    final PaymentBuilder paymentBuilder =
         costComponentService.getCostComponentsForDisburse(dataContextOfAction, disbursalAmount);
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
 
-    final List<ChargeInstance> charges =
-        costComponentsForRepaymentPeriod.stream()
-            .map(entry -> mapCostComponentEntryToChargeInstance(
-                Action.DISBURSE,
-                entry,
-                designatorToAccountIdentifierMapper))
-            .filter(Optional::isPresent)
-            .map(Optional::get)
-        .collect(Collectors.toList());
+    final List<ChargeInstance> charges = paymentBuilder.buildCharges(Action.DISBURSE, designatorToAccountIdentifierMapper);
 
     final LocalDateTime today = today();
 
@@ -272,7 +242,7 @@
     final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
     final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
 
-    final BigDecimal newLoanPaymentSize = costComponentService.getLoanPaymentSize(
+    final BigDecimal newLoanPaymentSize = costComponentService.getLoanPaymentSizeForSingleDisbursement(
         currentBalance.add(disbursalAmount),
         dataContextOfAction);
 
@@ -298,20 +268,13 @@
       throw ServiceException.internalError(
           "End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
 
-    final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+    final PaymentBuilder paymentBuilder =
         costComponentService.getCostComponentsForApplyInterest(dataContextOfAction);
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
 
-    final List<ChargeInstance> charges = costComponentsForRepaymentPeriod.stream()
-        .map(entry -> mapCostComponentEntryToChargeInstance(
-            Action.APPLY_INTEREST,
-            entry,
-            designatorToAccountIdentifierMapper))
-        .filter(Optional::isPresent)
-        .map(Optional::get)
-        .collect(Collectors.toList());
+    final List<ChargeInstance> charges = paymentBuilder.buildCharges(Action.APPLY_INTEREST, designatorToAccountIdentifierMapper);
 
     accountingAdapter.bookCharges(charges,
         "Applied interest on " + command.getForTime(),
@@ -340,7 +303,7 @@
       throw ServiceException.internalError(
           "End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
 
-    final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+    final PaymentBuilder paymentBuilder =
         costComponentService.getCostComponentsForAcceptPayment(
             dataContextOfAction,
             command.getCommand().getPaymentSize(),
@@ -349,14 +312,7 @@
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
 
-    final List<ChargeInstance> charges = costComponentsForRepaymentPeriod.stream()
-        .map(entry -> mapCostComponentEntryToChargeInstance(
-            Action.ACCEPT_PAYMENT,
-            entry,
-            designatorToAccountIdentifierMapper))
-        .filter(Optional::isPresent)
-        .map(Optional::get)
-        .collect(Collectors.toList());
+    final List<ChargeInstance> charges = paymentBuilder.buildCharges(Action.ACCEPT_PAYMENT, designatorToAccountIdentifierMapper);
 
     final LocalDateTime today = today();
 
@@ -387,20 +343,13 @@
       throw ServiceException.internalError(
           "End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
 
-    final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+    final PaymentBuilder paymentBuilder =
         costComponentService.getCostComponentsForMarkLate(dataContextOfAction, DateConverter.fromIsoString(command.getForTime()));
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
 
-    final List<ChargeInstance> charges = costComponentsForRepaymentPeriod.stream()
-        .map(entry -> mapCostComponentEntryToChargeInstance(
-            Action.MARK_LATE,
-            entry,
-            designatorToAccountIdentifierMapper))
-        .filter(Optional::isPresent)
-        .map(Optional::get)
-        .collect(Collectors.toList());
+    final List<ChargeInstance> charges = paymentBuilder.buildCharges(Action.MARK_LATE, designatorToAccountIdentifierMapper);
 
     final LocalDateTime today = today();
 
@@ -446,29 +395,21 @@
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.CLOSE);
 
-    final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+    final PaymentBuilder paymentBuilder =
         costComponentService.getCostComponentsForClose(dataContextOfAction);
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
 
-    final List<ChargeInstance> charges =
-        costComponentsForRepaymentPeriod.stream()
-            .map(entry -> mapCostComponentEntryToChargeInstance(
-                Action.DISBURSE,
-                entry,
-                designatorToAccountIdentifierMapper))
-            .filter(Optional::isPresent)
-            .map(Optional::get)
-            .collect(Collectors.toList());
+    final List<ChargeInstance> charges = paymentBuilder.buildCharges(Action.CLOSE, designatorToAccountIdentifierMapper);
 
     final LocalDateTime today = today();
 
     accountingAdapter.bookCharges(charges,
         command.getCommand().getNote(),
         command.getCommand().getCreatedOn(),
-        dataContextOfAction.getMessageForCharge(Action.DISBURSE),
-        Action.DISBURSE.getTransactionType());
+        dataContextOfAction.getMessageForCharge(Action.CLOSE),
+        Action.CLOSE.getTransactionType());
 
     final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
     customerCase.setCurrentState(Case.State.CLOSED.name());
@@ -498,36 +439,6 @@
     return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
   }
 
-  private static Optional<ChargeInstance> mapCostComponentEntryToChargeInstance(
-      final Action action,
-      final Map.Entry<ChargeDefinition, CostComponent> costComponentEntry,
-      final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
-    final ChargeDefinition chargeDefinition = costComponentEntry.getKey();
-    final BigDecimal chargeAmount = costComponentEntry.getValue().getAmount();
-
-    if (CostComponentService.chargeIsAccrued(chargeDefinition)) {
-      if (Action.valueOf(chargeDefinition.getAccrueAction()) == action)
-        return Optional.of(new ChargeInstance(
-            designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getFromAccountDesignator()),
-            designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getAccrualAccountDesignator()),
-            chargeAmount));
-      else if (Action.valueOf(chargeDefinition.getChargeAction()) == action)
-        return Optional.of(new ChargeInstance(
-            designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getAccrualAccountDesignator()),
-            designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getToAccountDesignator()),
-            chargeAmount));
-      else
-        return Optional.empty();
-    }
-    else if (Action.valueOf(chargeDefinition.getChargeAction()) == action)
-      return Optional.of(new ChargeInstance(
-          designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getFromAccountDesignator()),
-          designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getToAccountDesignator()),
-          chargeAmount));
-    else
-      return Optional.empty();
-  }
-
   private Map<String, BigDecimal> getRequestedChargeAmounts(final @Nullable List<CostComponent> costComponents) {
     if (costComponents == null)
       return Collections.emptyMap();
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
index 120378b..cbca5ee 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
@@ -58,7 +58,7 @@
     this.accountingAdapter = accountingAdapter;
   }
 
-  public CostComponentsForRepaymentPeriod getCostComponentsForAction(
+  public PaymentBuilder getCostComponentsForAction(
       final Action action,
       final DataContextOfAction dataContextOfAction,
       final BigDecimal forPaymentSize,
@@ -89,7 +89,7 @@
     }
   }
 
-  public CostComponentsForRepaymentPeriod getCostComponentsForOpen(final DataContextOfAction dataContextOfAction) {
+  public PaymentBuilder getCostComponentsForOpen(final DataContextOfAction dataContextOfAction) {
     final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
     final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
     final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
@@ -101,6 +101,8 @@
         Collections.emptyMap(),
         scheduledCharges,
         caseParameters.getBalanceRangeMaximum(),
+        new SimulatedRunningBalances(),
+        BigDecimal.ZERO,
         BigDecimal.ZERO,
         BigDecimal.ZERO,
         dataContextOfAction.getInterest(),
@@ -108,7 +110,7 @@
         true);
   }
 
-  public CostComponentsForRepaymentPeriod getCostComponentsForDeny(final DataContextOfAction dataContextOfAction) {
+  public PaymentBuilder getCostComponentsForDeny(final DataContextOfAction dataContextOfAction) {
     final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
     final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
     final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
@@ -120,6 +122,8 @@
         Collections.emptyMap(),
         scheduledCharges,
         caseParameters.getBalanceRangeMaximum(),
+        new SimulatedRunningBalances(),
+        BigDecimal.ZERO,
         BigDecimal.ZERO,
         BigDecimal.ZERO,
         dataContextOfAction.getInterest(),
@@ -127,7 +131,7 @@
         true);
   }
 
-  public CostComponentsForRepaymentPeriod getCostComponentsForApprove(final DataContextOfAction dataContextOfAction) {
+  public PaymentBuilder getCostComponentsForApprove(final DataContextOfAction dataContextOfAction) {
     //Charge the approval fee if applicable.
     final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
     final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
@@ -140,6 +144,8 @@
         Collections.emptyMap(),
         scheduledCharges,
         caseParameters.getBalanceRangeMaximum(),
+        new SimulatedRunningBalances(),
+        BigDecimal.ZERO,
         BigDecimal.ZERO,
         BigDecimal.ZERO,
         dataContextOfAction.getInterest(),
@@ -147,13 +153,14 @@
         true);
   }
 
-  public CostComponentsForRepaymentPeriod getCostComponentsForDisburse(
+  public PaymentBuilder getCostComponentsForDisburse(
       final @Nonnull DataContextOfAction dataContextOfAction,
       final @Nullable BigDecimal requestedDisbursalSize) {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
     final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
-    final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
+    final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+    final BigDecimal currentBalance = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN);
 
     if (requestedDisbursalSize != null &&
         dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum().compareTo(
@@ -170,9 +177,9 @@
 
     final BigDecimal disbursalSize;
     if (requestedDisbursalSize == null)
-      disbursalSize = dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum().negate();
+      disbursalSize = dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum();
     else
-      disbursalSize = requestedDisbursalSize.negate();
+      disbursalSize = requestedDisbursalSize;
 
     final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
         productIdentifier, scheduledActions);
@@ -197,20 +204,22 @@
         accruedCostComponents,
         chargesSplitIntoScheduledAndAccrued.get(false),
         caseParameters.getBalanceRangeMaximum(),
-        currentBalance,
+        runningBalances,
+        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
         disbursalSize,
+        BigDecimal.ZERO,
         dataContextOfAction.getInterest(),
         minorCurrencyUnitDigits,
         true);
   }
 
-  public CostComponentsForRepaymentPeriod getCostComponentsForApplyInterest(
+  public PaymentBuilder getCostComponentsForApplyInterest(
       final DataContextOfAction dataContextOfAction)
   {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
     final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
-    final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
+    final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
 
     final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
 
@@ -237,14 +246,16 @@
         accruedCostComponents,
         chargesSplitIntoScheduledAndAccrued.get(false),
         caseParameters.getBalanceRangeMaximum(),
-        currentBalance,
+        runningBalances,
+        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
+        BigDecimal.ZERO,
         BigDecimal.ZERO,
         dataContextOfAction.getInterest(),
         minorCurrencyUnitDigits,
         true);
   }
 
-  public CostComponentsForRepaymentPeriod getCostComponentsForAcceptPayment(
+  public PaymentBuilder getCostComponentsForAcceptPayment(
       final DataContextOfAction dataContextOfAction,
       final @Nullable BigDecimal requestedLoanPaymentSize,
       final LocalDate forDate)
@@ -252,7 +263,7 @@
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
     final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
-    final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
+    final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
 
     final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
 
@@ -288,10 +299,11 @@
     }
     else {
       if (scheduledAction.actionPeriod != null && scheduledAction.actionPeriod.isLastPeriod()) {
-        loanPaymentSize = currentBalance;
+        loanPaymentSize = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN);
       }
       else {
-        final BigDecimal paymentSizeBeforeOnTopCharges = currentBalance.min(dataContextOfAction.getCaseParametersEntity().getPaymentSize());
+        final BigDecimal paymentSizeBeforeOnTopCharges = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN)
+            .min(dataContextOfAction.getCaseParametersEntity().getPaymentSize());
 
         @SuppressWarnings("UnnecessaryLocalVariable")
         final BigDecimal paymentSizeIncludingOnTopCharges = accruedCostComponents.entrySet().stream()
@@ -308,21 +320,25 @@
         accruedCostComponents,
         chargesSplitIntoScheduledAndAccrued.get(false),
         caseParameters.getBalanceRangeMaximum(),
-        currentBalance,
+        runningBalances,
+        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
+        BigDecimal.ZERO,
         loanPaymentSize,
         dataContextOfAction.getInterest(),
         minorCurrencyUnitDigits,
         true);
   }
 
-  public CostComponentsForRepaymentPeriod getCostComponentsForClose(final DataContextOfAction dataContextOfAction) {
+  public PaymentBuilder getCostComponentsForClose(final DataContextOfAction dataContextOfAction) {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
     final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
-    final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
-    if (currentBalance.compareTo(BigDecimal.ZERO) != 0)
+    final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+
+    if (runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN).compareTo(BigDecimal.ZERO) != 0)
       throw ServiceException.conflict("Cannot close loan until the balance is zero.");
 
+
     final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
 
     final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
@@ -348,19 +364,23 @@
         accruedCostComponents,
         chargesSplitIntoScheduledAndAccrued.get(false),
         caseParameters.getBalanceRangeMaximum(),
-        currentBalance,
+        runningBalances,
+        dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
+        BigDecimal.ZERO,
         BigDecimal.ZERO,
         dataContextOfAction.getInterest(),
         minorCurrencyUnitDigits,
         true);
   }
 
-  public CostComponentsForRepaymentPeriod getCostComponentsForMarkLate(final DataContextOfAction dataContextOfAction,
-                                                                       final LocalDateTime forTime) {
+  public PaymentBuilder getCostComponentsForMarkLate(
+      final DataContextOfAction dataContextOfAction,
+      final LocalDateTime forTime)
+  {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
     final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
-    final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
+    final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
 
     final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
 
@@ -389,46 +409,46 @@
         accruedCostComponents,
         chargesSplitIntoScheduledAndAccrued.get(false),
         caseParameters.getBalanceRangeMaximum(),
-        currentBalance,
+        runningBalances,
         loanPaymentSize,
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
         dataContextOfAction.getInterest(),
         minorCurrencyUnitDigits,
         true);
   }
 
-  private CostComponentsForRepaymentPeriod getCostComponentsForWriteOff(final DataContextOfAction dataContextOfAction) {
+  private PaymentBuilder getCostComponentsForWriteOff(final DataContextOfAction dataContextOfAction) {
     return null;
   }
 
-  private CostComponentsForRepaymentPeriod getCostComponentsForRecover(final DataContextOfAction dataContextOfAction) {
+  private PaymentBuilder getCostComponentsForRecover(final DataContextOfAction dataContextOfAction) {
     return null;
   }
 
-  static CostComponentsForRepaymentPeriod getCostComponentsForScheduledCharges(
+  static PaymentBuilder getCostComponentsForScheduledCharges(
       final Map<ChargeDefinition, CostComponent> accruedCostComponents,
       final Collection<ScheduledCharge> scheduledCharges,
       final BigDecimal maximumBalance,
-      final BigDecimal runningBalance,
-      final BigDecimal entryAccountAdjustment, //disbursement or payment size.
+      final RunningBalances preChargeBalances,
+      final BigDecimal contractualRepayment,
+      final BigDecimal requestedDisbursement,
+      final BigDecimal requestedRepayment,
       final BigDecimal interest,
       final int minorCurrencyUnitDigits,
       final boolean accrualAccounting) {
-    final Map<String, BigDecimal> balanceAdjustments = new HashMap<>();
-    balanceAdjustments.put(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO);
-
-    final Map<ChargeDefinition, CostComponent> costComponentMap = new HashMap<>();
+    final PaymentBuilder paymentBuilder = new PaymentBuilder(preChargeBalances, accrualAccounting);
+    //TODO: once you've fixed getAmountProportionalTo, use the passed in variable.
 
     for (Map.Entry<ChargeDefinition, CostComponent> entry : accruedCostComponents.entrySet()) {
       final ChargeDefinition chargeDefinition = entry.getKey();
       final BigDecimal chargeAmount = entry.getValue().getAmount();
-      costComponentMap.put(
-          chargeDefinition,
-          entry.getValue());
 
       //TODO: This should adjust differently depending on accrual accounting.
       // It can't be fixed until getAmountProportionalTo is fixed.
-      adjustBalance(chargeDefinition.getFromAccountDesignator(), chargeAmount.negate(), balanceAdjustments);
-      adjustBalance(chargeDefinition.getToAccountDesignator(), chargeAmount, balanceAdjustments);
+      paymentBuilder.addToBalance(chargeDefinition.getFromAccountDesignator(), chargeAmount.negate());
+      paymentBuilder.addToBalance(chargeDefinition.getToAccountDesignator(), chargeAmount);
+      paymentBuilder.addToCostComponent(chargeDefinition, chargeAmount);
     }
 
 
@@ -437,84 +457,80 @@
         final BigDecimal amountProportionalTo = getAmountProportionalTo(
             scheduledCharge,
             maximumBalance,
-            runningBalance,
-            entryAccountAdjustment,
-            balanceAdjustments);
+            preChargeBalances,
+            contractualRepayment,
+            requestedDisbursement,
+            requestedRepayment,
+            paymentBuilder);
         //TODO: getAmountProportionalTo is programmed under the assumption of non-accrual accounting.
         if (scheduledCharge.getChargeRange().map(x ->
             !x.amountIsWithinRange(amountProportionalTo)).orElse(false))
           continue;
 
-        final CostComponent costComponent = costComponentMap
-            .computeIfAbsent(scheduledCharge.getChargeDefinition(), CostComponentService::constructEmptyCostComponent);
-
         final BigDecimal chargeAmount = howToApplyScheduledChargeToAmount(scheduledCharge, interest)
             .apply(amountProportionalTo)
             .setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
-        adjustBalances(
+        paymentBuilder.adjustBalances(
             scheduledCharge.getScheduledAction().action,
             scheduledCharge.getChargeDefinition(),
-            chargeAmount,
-            balanceAdjustments,
-            false); //TODO: once you've fixed getAmountProportionalTo, use the passed in variable.
-        costComponent.setAmount(costComponent.getAmount().add(chargeAmount));
+            chargeAmount);
       }
     }
 
-    return new CostComponentsForRepaymentPeriod(
-        costComponentMap,
-        balanceAdjustments.getOrDefault(AccountDesignators.LOANS_PAYABLE, BigDecimal.ZERO).negate());
+    return paymentBuilder;
   }
 
   private static BigDecimal getAmountProportionalTo(
       final ScheduledCharge scheduledCharge,
       final BigDecimal maximumBalance,
-      final BigDecimal runningBalance,
-      final BigDecimal loanPaymentSize,
-      final Map<String, BigDecimal> balanceAdjustments) {
+      final RunningBalances runningBalances,
+      final BigDecimal contractualRepayment,
+      final BigDecimal requestedDisbursement,
+      final BigDecimal requestedRepayment,
+      final PaymentBuilder paymentBuilder) {
     final Optional<ChargeProportionalDesignator> optionalChargeProportionalTo
         = ChargeProportionalDesignator.fromString(scheduledCharge.getChargeDefinition().getProportionalTo());
     return optionalChargeProportionalTo.map(chargeProportionalTo ->
-        getAmountProportionalTo(chargeProportionalTo, maximumBalance, runningBalance, loanPaymentSize, balanceAdjustments))
+        getAmountProportionalTo(
+            chargeProportionalTo,
+            maximumBalance,
+            runningBalances,
+            contractualRepayment,
+            requestedDisbursement,
+            requestedRepayment,
+            paymentBuilder))
         .orElse(BigDecimal.ZERO);
   }
 
   static BigDecimal getAmountProportionalTo(
       final ChargeProportionalDesignator chargeProportionalTo,
       final BigDecimal maximumBalance,
-      final BigDecimal runningBalance,
-      final BigDecimal loanPaymentSize,
-      final Map<String, BigDecimal> balanceAdjustments) {
+      final RunningBalances runningBalances,
+      final BigDecimal contractualRepayment,
+      final BigDecimal requestedDisbursement,
+      final BigDecimal requestedRepayment,
+      final PaymentBuilder paymentBuilder) {
     switch (chargeProportionalTo) {
       case NOT_PROPORTIONAL:
-        return BigDecimal.ZERO;
+        return BigDecimal.ONE;
       case MAXIMUM_BALANCE_DESIGNATOR:
         return maximumBalance;
-      case RUNNING_BALANCE_DESIGNATOR:
-        return runningBalance.subtract(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO));
-      case REPAYMENT_DESIGNATOR:
-        return loanPaymentSize;
-      case PRINCIPAL_ADJUSTMENT_DESIGNATOR: {
-        if (loanPaymentSize.compareTo(BigDecimal.ZERO) <= 0)
-          return loanPaymentSize.abs();
-        final BigDecimal newRunningBalance
-            = runningBalance.subtract(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO));
-        final BigDecimal newLoanPaymentSize = loanPaymentSize.min(newRunningBalance);
-        return newLoanPaymentSize.add(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO)).abs();
+      case RUNNING_BALANCE_DESIGNATOR: {
+        final BigDecimal customerLoanRunningBalance = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN);
+        return customerLoanRunningBalance.subtract(paymentBuilder.getBalanceAdjustment(AccountDesignators.CUSTOMER_LOAN));
       }
+      case CONTRACTUAL_REPAYMENT_DESIGNATOR:
+        return contractualRepayment;
+      case REQUESTED_DISBURSEMENT_DESIGNATOR:
+        return requestedDisbursement;
+      case REQUESTED_REPAYMENT_DESIGNATOR:
+        return requestedRepayment;
       default:
         return BigDecimal.ZERO;
     }
 //TODO: correctly implement charges which are proportional to other charges.
   }
 
-  private static CostComponent constructEmptyCostComponent(final ChargeDefinition chargeDefinition) {
-    final CostComponent ret = new CostComponent();
-    ret.setChargeIdentifier(chargeDefinition.getIdentifier());
-    ret.setAmount(BigDecimal.ZERO);
-    return ret;
-  }
-
   private static Function<BigDecimal, BigDecimal> howToApplyScheduledChargeToAmount(
       final ScheduledCharge scheduledCharge, final BigDecimal interest)
   {
@@ -537,8 +553,8 @@
     }
   }
 
-  public BigDecimal getLoanPaymentSize(
-      final BigDecimal assumedBalance,
+  public BigDecimal getLoanPaymentSizeForSingleDisbursement(
+      final BigDecimal disbursementSize,
       final DataContextOfAction dataContextOfAction) {
     final List<ScheduledAction> hypotheticalScheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(
         today(),
@@ -547,73 +563,54 @@
         dataContextOfAction.getProductEntity().getIdentifier(),
         hypotheticalScheduledActions);
     return getLoanPaymentSize(
-        assumedBalance,
+        disbursementSize,
+        disbursementSize,
         dataContextOfAction.getInterest(),
         dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits(),
         hypotheticalScheduledCharges);
   }
 
-  static BigDecimal getLoanPaymentSize(final BigDecimal startingBalance,
-                                       final BigDecimal interest,
-                                       final int minorCurrencyUnitDigits,
-                                       final List<ScheduledCharge> scheduledCharges) {
-    final int precision = startingBalance.precision() + minorCurrencyUnitDigits + EXTRA_PRECISION;
+  static BigDecimal getLoanPaymentSize(
+      final BigDecimal maximumBalanceSize,
+      final BigDecimal disbursementSize,
+      final BigDecimal interest,
+      final int minorCurrencyUnitDigits,
+      final List<ScheduledCharge> scheduledCharges) {
+    final int precision = disbursementSize.precision() - 4 + minorCurrencyUnitDigits + EXTRA_PRECISION;
     final Map<Period, BigDecimal> accrualRatesByPeriod
-        = PeriodChargeCalculator.getPeriodAccrualInterestRate(interest, scheduledCharges, precision);
+        = PeriodChargeCalculator.getPeriodAccrualInterestRate(interest, scheduledCharges, disbursementSize.precision());
 
     final int periodCount = accrualRatesByPeriod.size();
     if (periodCount == 0)
-      return startingBalance;
+      return disbursementSize;
 
     final BigDecimal geometricMeanAccrualRate = accrualRatesByPeriod.values().stream()
         .collect(RateCollectors.geometricMean(precision));
 
+    final List<ScheduledCharge> disbursementFees = scheduledCharges.stream()
+        .filter(x -> x.getScheduledAction().action.equals(Action.DISBURSE))
+        .collect(Collectors.toList());
+    final PaymentBuilder paymentBuilder = getCostComponentsForScheduledCharges(
+        Collections.emptyMap(),
+        disbursementFees,
+        maximumBalanceSize,
+        new SimulatedRunningBalances(),
+        BigDecimal.ZERO, //Contractual repayment not determined yet here.
+        disbursementSize,
+        BigDecimal.ZERO,
+        interest,
+        minorCurrencyUnitDigits,
+        false
+        );
+    final BigDecimal finalDisbursementSize = paymentBuilder.getBalanceAdjustment(AccountDesignators.CUSTOMER_LOAN).negate();
+
     final MonetaryAmount presentValue = AnnuityPayment.calculate(
-        Money.of(startingBalance, "XXX"),
+        Money.of(finalDisbursementSize, "XXX"),
         Rate.of(geometricMeanAccrualRate),
         periodCount);
     return BigDecimal.valueOf(presentValue.getNumber().doubleValueExact()).setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
   }
 
-  private static void adjustBalances(
-      final Action action,
-      final ChargeDefinition chargeDefinition,
-      final BigDecimal chargeAmount,
-      final Map<String, BigDecimal> balanceAdjustments,
-      boolean accrualAccounting) {
-    if (accrualAccounting) {
-      if (chargeIsAccrued(chargeDefinition)) {
-        if (Action.valueOf(chargeDefinition.getAccrueAction()) == action) {
-          adjustBalance(chargeDefinition.getFromAccountDesignator(), chargeAmount.negate(), balanceAdjustments);
-          adjustBalance(chargeDefinition.getAccrualAccountDesignator(), chargeAmount, balanceAdjustments);
-        } else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
-          adjustBalance(chargeDefinition.getAccrualAccountDesignator(), chargeAmount.negate(), balanceAdjustments);
-          adjustBalance(chargeDefinition.getToAccountDesignator(), chargeAmount, balanceAdjustments);
-        }
-      } else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
-        adjustBalance(chargeDefinition.getFromAccountDesignator(), chargeAmount.negate(), balanceAdjustments);
-        adjustBalance(chargeDefinition.getToAccountDesignator(), chargeAmount, balanceAdjustments);
-      }
-    }
-    else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
-      adjustBalance(chargeDefinition.getFromAccountDesignator(), chargeAmount.negate(), balanceAdjustments);
-      adjustBalance(chargeDefinition.getToAccountDesignator(), chargeAmount, balanceAdjustments);
-    }
-  }
-
-  private static void adjustBalance(
-      final String designator,
-      final BigDecimal chargeAmount,
-      final Map<String, BigDecimal> balanceAdjustments) {
-    final BigDecimal balance = balanceAdjustments.computeIfAbsent(designator, (x) -> BigDecimal.ZERO);
-    final BigDecimal newBalance = balance.add(chargeAmount);
-    balanceAdjustments.put(designator, newBalance);
-  }
-
-  public static boolean chargeIsAccrued(final ChargeDefinition chargeDefinition) {
-    return chargeDefinition.getAccrualAccountDesignator() != null;
-  }
-
   private static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
     return chargeDefinition.getAccrueAction() != null &&
         chargeDefinition.getChargeAction().equals(action.name());
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java
deleted file mode 100644
index 83372f4..0000000
--- a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * 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;
-
-import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
-import io.mifos.portfolio.api.v1.domain.CostComponent;
-
-import java.math.BigDecimal;
-import java.util.Map;
-import java.util.stream.Stream;
-
-/**
- * @author Myrle Krantz
- */
-public class CostComponentsForRepaymentPeriod {
-  final private Map<ChargeDefinition, CostComponent> costComponents;
-  final private BigDecimal balanceAdjustment;
-
-  CostComponentsForRepaymentPeriod(
-      final Map<ChargeDefinition, CostComponent> costComponents,
-      final BigDecimal balanceAdjustment) {
-    this.costComponents = costComponents;
-    this.balanceAdjustment = balanceAdjustment;
-  }
-
-  Map<ChargeDefinition, CostComponent> getCostComponents() {
-    return costComponents;
-  }
-
-  public Stream<Map.Entry<ChargeDefinition, CostComponent>> stream() {
-    return costComponents.entrySet().stream()
-        .filter(costComponentEntry -> costComponentEntry.getValue().getAmount().compareTo(BigDecimal.ZERO) != 0);
-  }
-
-  BigDecimal getBalanceAdjustment() {
-    return balanceAdjustment;
-  }
-}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java b/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java
index 2bd9e57..851bedd 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java
@@ -25,6 +25,7 @@
 
 import javax.annotation.Nonnull;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 
@@ -51,12 +52,16 @@
             productAccountAssignments.stream().map(ProductMapper::mapAccountAssignmentEntity));
   }
 
-  public String mapOrThrow(final @Nonnull String accountDesignator) {
+  public Optional<String> map(final @Nonnull String accountDesignator) {
     return allAccountAssignmentsAsStream()
-            .filter(x -> x.getDesignator().equals(accountDesignator))
-            .findFirst()
-            .map(AccountAssignment::getAccountIdentifier)
-            .orElseThrow(() -> ServiceException.badRequest("A required account designator was not set ''{0}''.", accountDesignator));
+        .filter(x -> x.getDesignator().equals(accountDesignator))
+        .findFirst()
+        .map(AccountAssignment::getAccountIdentifier);
+  }
+
+  public String mapOrThrow(final @Nonnull String accountDesignator) {
+    return map(accountDesignator).orElseThrow(() ->
+        ServiceException.badRequest("A required account designator was not set ''{0}''.", accountDesignator));
   }
 
   public Stream<AccountAssignment> getLedgersNeedingAccounts() {
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java b/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
index 62ddc51..25cd63d 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
@@ -57,6 +57,7 @@
 
     final BigDecimal loanPaymentSize = CostComponentService.getLoanPaymentSize(
         dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum(),
+        dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum(),
         dataContextOfAction.getInterest(),
         minorCurrencyUnitDigits,
         scheduledCharges);
@@ -123,41 +124,43 @@
         .sorted()
         .collect(Collector.of(ArrayList::new, List::add, (left, right) -> { left.addAll(right); return left; }));
 
-    BigDecimal balance = initialBalance.setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
+    final SimulatedRunningBalances balances = new SimulatedRunningBalances();
     final List<PlannedPayment> plannedPayments = new ArrayList<>();
     for (int i = 0; i < sortedRepaymentPeriods.size(); i++)
     {
       final Period repaymentPeriod = sortedRepaymentPeriods.get(i);
-      final BigDecimal currentLoanPaymentSize;
-      if (repaymentPeriod.isDefined()) {
-        // last repayment period: Force the proposed payment to "overhang". Cost component calculation
-        // corrects last loan payment downwards but not upwards.
-        if (i == sortedRepaymentPeriods.size() - 1)
-          currentLoanPaymentSize = loanPaymentSize.add(BigDecimal.valueOf(sortedRepaymentPeriods.size()));
-        else
-          currentLoanPaymentSize = loanPaymentSize;
+      final BigDecimal requestedRepayment;
+      final BigDecimal requestedDisbursal;
+      if (i == 0)
+      { //First "period" is actually just the OPEN/APPROVE/DISBURSAL action set.
+        requestedRepayment = BigDecimal.ZERO;
+        requestedDisbursal = initialBalance.setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
       }
-      else
-        currentLoanPaymentSize = BigDecimal.ZERO;
+      else if (i == sortedRepaymentPeriods.size() - 1)
+      { //Last repayment period: Fill the proposed payment out to the remaining balance of the loan.
+        requestedRepayment = loanPaymentSize; //TODO: wrong doesn't include last period of interest.
+        requestedDisbursal = BigDecimal.ZERO;
+      }
+      else {
+        requestedRepayment = loanPaymentSize;
+        requestedDisbursal = BigDecimal.ZERO;
+      }
 
       final SortedSet<ScheduledCharge> scheduledChargesInPeriod = orderedScheduledChargesGroupedByPeriod.get(repaymentPeriod);
-      final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+      final PaymentBuilder paymentBuilder =
               CostComponentService.getCostComponentsForScheduledCharges(
                   Collections.emptyMap(),
                   scheduledChargesInPeriod,
                   initialBalance,
-                  balance,
-                  currentLoanPaymentSize,
+                  balances,
+                  loanPaymentSize,
+                  requestedDisbursal,
+                  requestedRepayment,
                   interest,
                   minorCurrencyUnitDigits,
                   false);
 
-      final PlannedPayment plannedPayment = new PlannedPayment();
-      plannedPayment.setCostComponents(new ArrayList<>(costComponentsForRepaymentPeriod.getCostComponents().values()));
-      plannedPayment.setDate(repaymentPeriod.getEndDateAsString());
-      balance = balance.add(costComponentsForRepaymentPeriod.getBalanceAdjustment());
-      plannedPayment.setRemainingPrincipal(balance);
-      plannedPayments.add(plannedPayment);
+      plannedPayments.add(paymentBuilder.accumulatePlannedPayment(balances));
     }
     return plannedPayments;
   }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/PaymentBuilder.java b/service/src/main/java/io/mifos/individuallending/internal/service/PaymentBuilder.java
new file mode 100644
index 0000000..f158dee
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/PaymentBuilder.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * 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;
+
+import com.google.common.collect.Sets;
+import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPayment;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.api.v1.domain.Payment;
+import io.mifos.portfolio.service.internal.util.ChargeInstance;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+public class PaymentBuilder {
+  private final RunningBalances prePaymentBalances;
+  private final Map<ChargeDefinition, CostComponent> costComponents;
+  private final Map<String, BigDecimal> balanceAdjustments;
+  private final boolean accrualAccounting;
+
+  PaymentBuilder(final RunningBalances prePaymentBalances,
+                 final boolean accrualAccounting) {
+    this.prePaymentBalances = prePaymentBalances;
+    this.costComponents = new HashMap<>();
+    this.balanceAdjustments = new HashMap<>();
+    this.accrualAccounting = accrualAccounting;
+  }
+
+  public Payment buildPayment(final Action action, final Set<String> forAccountDesignators) {
+
+    if (!forAccountDesignators.isEmpty()) {
+      final Stream<Map.Entry<ChargeDefinition, CostComponent>> costComponentStream = stream()
+          .filter(costComponentEntry -> chargeReferencesAccountDesignators(
+              costComponentEntry.getKey(),
+              action,
+              forAccountDesignators));
+
+      final List<CostComponent> costComponentList = costComponentStream
+          .map(costComponentEntry -> new CostComponent(
+              costComponentEntry.getKey().getIdentifier(),
+              costComponentEntry.getValue().getAmount()))
+          .collect(Collectors.toList());
+
+      return new Payment(costComponentList, balanceAdjustments);
+    }
+    else {
+      return buildPayment();
+    }
+
+  }
+
+  private Payment buildPayment() {
+    final Stream<Map.Entry<ChargeDefinition, CostComponent>> costComponentStream  = stream();
+
+    final List<CostComponent> costComponentList = costComponentStream
+        .map(costComponentEntry -> new CostComponent(
+            costComponentEntry.getKey().getIdentifier(),
+            costComponentEntry.getValue().getAmount()))
+        .collect(Collectors.toList());
+
+    return new Payment(costComponentList, balanceAdjustments);
+  }
+
+  PlannedPayment accumulatePlannedPayment(final SimulatedRunningBalances balances) {
+    final Payment payment = buildPayment();
+    balanceAdjustments.forEach(balances::adjustBalance);
+    final Map<String, BigDecimal> balancesCopy = balances.snapshot();
+
+    return new PlannedPayment(payment, balancesCopy);
+  }
+
+  public List<ChargeInstance> buildCharges(
+      final Action action,
+      final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
+    return stream()
+        .map(entry -> mapCostComponentEntryToChargeInstance(action, entry, designatorToAccountIdentifierMapper))
+        .filter(Optional::isPresent)
+        .map(Optional::get)
+        .collect(Collectors.toList());
+  }
+
+  BigDecimal getBalanceAdjustment(final String accountDesignator) {
+    return balanceAdjustments.getOrDefault(accountDesignator, BigDecimal.ZERO);
+  }
+
+  void adjustBalances(
+      final Action action,
+      final ChargeDefinition chargeDefinition,
+      final BigDecimal chargeAmount) {
+    BigDecimal adjustedChargeAmount = BigDecimal.ZERO;
+    if (this.accrualAccounting) {
+      if (chargeIsAccrued(chargeDefinition)) {
+        if (Action.valueOf(chargeDefinition.getAccrueAction()) == action) {
+          final BigDecimal maxDebit = prePaymentBalances.getMaxDebit(chargeDefinition.getFromAccountDesignator(), chargeAmount);
+          adjustedChargeAmount = prePaymentBalances.getMaxCredit(chargeDefinition.getAccrualAccountDesignator(), maxDebit);
+
+          this.addToBalance(chargeDefinition.getFromAccountDesignator(), adjustedChargeAmount.negate());
+          this.addToBalance(chargeDefinition.getAccrualAccountDesignator(), adjustedChargeAmount);
+        } else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
+          final BigDecimal maxDebit = prePaymentBalances.getMaxDebit(chargeDefinition.getAccrualAccountDesignator(), chargeAmount);
+          adjustedChargeAmount = prePaymentBalances.getMaxCredit(chargeDefinition.getToAccountDesignator(), maxDebit);
+
+          this.addToBalance(chargeDefinition.getAccrualAccountDesignator(), adjustedChargeAmount.negate());
+          this.addToBalance(chargeDefinition.getToAccountDesignator(), adjustedChargeAmount);
+        }
+      } else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
+        final BigDecimal maxDebit = prePaymentBalances.getMaxDebit(chargeDefinition.getFromAccountDesignator(), chargeAmount);
+        adjustedChargeAmount = prePaymentBalances.getMaxCredit(chargeDefinition.getToAccountDesignator(), maxDebit);
+
+        this.addToBalance(chargeDefinition.getFromAccountDesignator(), adjustedChargeAmount.negate());
+        this.addToBalance(chargeDefinition.getToAccountDesignator(), adjustedChargeAmount);
+      }
+    }
+    else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
+      final BigDecimal maxDebit = prePaymentBalances.getMaxDebit(chargeDefinition.getFromAccountDesignator(), chargeAmount);
+      adjustedChargeAmount = prePaymentBalances.getMaxCredit(chargeDefinition.getToAccountDesignator(), maxDebit);
+
+      this.addToBalance(chargeDefinition.getFromAccountDesignator(), adjustedChargeAmount.negate());
+      this.addToBalance(chargeDefinition.getToAccountDesignator(), adjustedChargeAmount);
+    }
+
+
+    addToCostComponent(chargeDefinition, adjustedChargeAmount);
+  }
+
+  private static boolean chargeIsAccrued(final ChargeDefinition chargeDefinition) {
+    return chargeDefinition.getAccrualAccountDesignator() != null;
+  }
+
+  void addToBalance(
+      final String accountDesignator,
+      final BigDecimal chargeAmount) {
+    final BigDecimal currentAdjustment = balanceAdjustments.getOrDefault(accountDesignator, BigDecimal.ZERO);
+    final BigDecimal newAdjustment = currentAdjustment.add(chargeAmount);
+    balanceAdjustments.put(accountDesignator, newAdjustment);
+  }
+
+  void addToCostComponent(
+      final ChargeDefinition chargeDefinition,
+      final BigDecimal amount) {
+    final CostComponent costComponent = costComponents
+        .computeIfAbsent(chargeDefinition, PaymentBuilder::constructEmptyCostComponent);
+    costComponent.setAmount(costComponent.getAmount().add(amount));
+  }
+
+  private Stream<Map.Entry<ChargeDefinition, CostComponent>> stream() {
+    return costComponents.entrySet().stream()
+        .filter(costComponentEntry -> costComponentEntry.getValue().getAmount().compareTo(BigDecimal.ZERO) != 0);
+  }
+
+
+  private static boolean chargeReferencesAccountDesignators(
+      final ChargeDefinition chargeDefinition,
+      final Action action,
+      final Set<String> forAccountDesignators) {
+    final Set<String> accountsToCompare = Sets.newHashSet(
+        chargeDefinition.getFromAccountDesignator(),
+        chargeDefinition.getToAccountDesignator()
+    );
+    if (chargeDefinition.getAccrualAccountDesignator() != null)
+      accountsToCompare.add(chargeDefinition.getAccrualAccountDesignator());
+
+    return !Sets.intersection(accountsToCompare, forAccountDesignators).isEmpty();
+  }
+
+
+  private static CostComponent constructEmptyCostComponent(final ChargeDefinition chargeDefinition) {
+    final CostComponent ret = new CostComponent();
+    ret.setChargeIdentifier(chargeDefinition.getIdentifier());
+    ret.setAmount(BigDecimal.ZERO);
+    return ret;
+  }
+
+  private static Optional<ChargeInstance> mapCostComponentEntryToChargeInstance(
+      final Action action,
+      final Map.Entry<ChargeDefinition, CostComponent> costComponentEntry,
+      final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
+    final ChargeDefinition chargeDefinition = costComponentEntry.getKey();
+    final BigDecimal chargeAmount = costComponentEntry.getValue().getAmount();
+
+    if (chargeIsAccrued(chargeDefinition)) {
+      if (Action.valueOf(chargeDefinition.getAccrueAction()) == action)
+        return Optional.of(new ChargeInstance(
+            designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getFromAccountDesignator()),
+            designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getAccrualAccountDesignator()),
+            chargeAmount));
+      else if (Action.valueOf(chargeDefinition.getChargeAction()) == action)
+        return Optional.of(new ChargeInstance(
+            designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getAccrualAccountDesignator()),
+            designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getToAccountDesignator()),
+            chargeAmount));
+      else
+        return Optional.empty();
+    }
+    else if (Action.valueOf(chargeDefinition.getChargeAction()) == action)
+      return Optional.of(new ChargeInstance(
+          designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getFromAccountDesignator()),
+          designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getToAccountDesignator()),
+          chargeAmount));
+    else
+      return Optional.empty();
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/RealRunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/RealRunningBalances.java
new file mode 100644
index 0000000..761c963
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/RealRunningBalances.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import net.jodah.expiringmap.ExpirationPolicy;
+import net.jodah.expiringmap.ExpiringMap;
+
+import java.math.BigDecimal;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Myrle Krantz
+ */
+public class RealRunningBalances implements RunningBalances {
+  private final ExpiringMap<String, BigDecimal> realBalanceCache;
+
+
+  RealRunningBalances(
+      final AccountingAdapter accountingAdapter,
+      final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
+    this.realBalanceCache = ExpiringMap.builder()
+        .maxSize(20)
+        .expirationPolicy(ExpirationPolicy.CREATED)
+        .expiration(30,TimeUnit.SECONDS)
+        .entryLoader((String accountDesignator) -> {
+          final Optional<String> accountIdentifier;
+          if (accountDesignator.equals(AccountDesignators.ENTRY)) {
+            accountIdentifier = designatorToAccountIdentifierMapper.map(accountDesignator);
+          }
+          else {
+            accountIdentifier = Optional.of(designatorToAccountIdentifierMapper.mapOrThrow(accountDesignator));
+          }
+          return accountIdentifier.map(accountingAdapter::getCurrentBalance).orElse(BigDecimal.ZERO);
+        })
+        .build();
+  }
+
+  @Override
+  public BigDecimal getBalance(final String accountDesignator) {
+    return realBalanceCache.get(accountDesignator);
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/RunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/RunningBalances.java
new file mode 100644
index 0000000..08197f6
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/RunningBalances.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Myrle Krantz
+ */
+public interface RunningBalances {
+  Map<String, BigDecimal> ACCOUNT_SIGNS = new HashMap<String, BigDecimal>() {{
+    final BigDecimal negative = BigDecimal.valueOf(-1);
+    final BigDecimal positive = BigDecimal.valueOf(1);
+
+    this.put(AccountDesignators.CUSTOMER_LOAN, negative);
+    this.put(AccountDesignators.LOAN_FUNDS_SOURCE, negative);
+    this.put(AccountDesignators.PROCESSING_FEE_INCOME, positive);
+    this.put(AccountDesignators.ORIGINATION_FEE_INCOME, positive);
+    this.put(AccountDesignators.DISBURSEMENT_FEE_INCOME, positive);
+    this.put(AccountDesignators.INTEREST_INCOME, positive);
+    this.put(AccountDesignators.INTEREST_ACCRUAL, positive);
+    this.put(AccountDesignators.LATE_FEE_INCOME, positive);
+    this.put(AccountDesignators.LATE_FEE_ACCRUAL, positive);
+    this.put(AccountDesignators.ARREARS_ALLOWANCE, positive);
+    this.put(AccountDesignators.ENTRY, positive);
+  }};
+
+  BigDecimal getBalance(final String accountDesignator);
+
+  default BigDecimal getMaxDebit(final String accountDesignator, final BigDecimal amount) {
+    if (accountDesignator.equals(AccountDesignators.ENTRY))
+      return amount;
+
+    if (ACCOUNT_SIGNS.get(accountDesignator).signum() == -1)
+      return amount;
+    else
+      return amount.min(getBalance(accountDesignator));
+  }
+
+  default BigDecimal getMaxCredit(final String accountDesignator, final BigDecimal amount) {
+    if (accountDesignator.equals(AccountDesignators.ENTRY))
+      return amount; //don't guard the entry account.
+
+    if (ACCOUNT_SIGNS.get(accountDesignator).signum() != -1)
+      return amount;
+    else
+      return amount.min(getBalance(accountDesignator));
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/SimulatedRunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/SimulatedRunningBalances.java
new file mode 100644
index 0000000..3c4ce83
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/SimulatedRunningBalances.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Myrle Krantz
+ */
+public class SimulatedRunningBalances implements RunningBalances {
+  final private Map<String, BigDecimal> balances;
+
+  SimulatedRunningBalances() {
+    this.balances = new HashMap<>();
+  }
+
+  public BigDecimal getBalance(final String accountDesignator) {
+    return balances.getOrDefault(accountDesignator, BigDecimal.ZERO);
+  }
+
+  void adjustBalance(final String key, final BigDecimal amount) {
+    final BigDecimal sign = ACCOUNT_SIGNS.get(key);
+    final BigDecimal currentValue = balances.getOrDefault(key, BigDecimal.ZERO);
+    final BigDecimal newValue = currentValue.add(amount.multiply(sign));
+    balances.put(key, newValue);
+  }
+
+  Map<String, BigDecimal> snapshot() {
+    return new HashMap<>(balances);
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
index d09fab1..cff6143 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
@@ -90,10 +90,6 @@
 
   private static Boolean readOnlyLegacyMapper(final String identifier) {
     switch (identifier) {
-      case LOAN_FUNDS_ALLOCATION_ID:
-        return true;
-      case RETURN_DISBURSEMENT_ID:
-        return true;
       case INTEREST_ID:
         return false;
       case ALLOW_FOR_WRITE_OFF_ID:
@@ -104,16 +100,12 @@
         return false;
       case DISBURSE_PAYMENT_ID:
         return false;
-      case TRACK_DISBURSAL_PAYMENT_ID:
-        return false;
       case LOAN_ORIGINATION_FEE_ID:
         return true;
       case PROCESSING_FEE_ID:
         return true;
       case REPAYMENT_ID:
         return false;
-      case TRACK_RETURN_PRINCIPAL_ID:
-        return false;
       default:
         return false;
     }
@@ -126,14 +118,12 @@
       return from.getProportionalTo();
 
     switch (identifier) {
-      case LOAN_FUNDS_ALLOCATION_ID:
-        return ChargeProportionalDesignator.MAXIMUM_BALANCE_DESIGNATOR.getValue();
       case LOAN_ORIGINATION_FEE_ID:
         return ChargeProportionalDesignator.MAXIMUM_BALANCE_DESIGNATOR.getValue();
       case PROCESSING_FEE_ID:
         return ChargeProportionalDesignator.MAXIMUM_BALANCE_DESIGNATOR.getValue();
       case LATE_FEE_ID:
-        return ChargeProportionalDesignator.REPAYMENT_DESIGNATOR.getValue();
+        return ChargeProportionalDesignator.CONTRACTUAL_REPAYMENT_DESIGNATOR.getValue();
       default:
         return ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue();
     }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
index c004eb4..414d38e 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
@@ -18,7 +18,7 @@
 import io.mifos.core.lang.ServiceException;
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.api.v1.domain.CasePage;
-import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.api.v1.domain.Payment;
 import io.mifos.portfolio.service.internal.mapper.CaseMapper;
 import io.mifos.portfolio.service.internal.pattern.PatternFactoryRegistry;
 import io.mifos.portfolio.service.internal.repository.CaseEntity;
@@ -133,12 +133,12 @@
     return this.findByIdentifier(productIdentifier, caseIdentifier).isPresent();
   }
 
-  public List<CostComponent> getActionCostComponentsForCase(final String productIdentifier,
-                                                            final String caseIdentifier,
-                                                            final String actionIdentifier,
-                                                            final LocalDateTime localDateTime,
-                                                            final Set<String> forAccountDesignatorsList,
-                                                            final BigDecimal forPaymentSize) {
+  public Payment getActionCostComponentsForCase(final String productIdentifier,
+                                                final String caseIdentifier,
+                                                final String actionIdentifier,
+                                                final LocalDateTime localDateTime,
+                                                final Set<String> forAccountDesignatorsList,
+                                                final BigDecimal forPaymentSize) {
     return getPatternFactoryOrThrow(productIdentifier).getCostComponentsForAction(
         productIdentifier,
         caseIdentifier,
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
index c95b51e..df851db 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
@@ -156,10 +156,12 @@
   public BigDecimal getCurrentBalance(final String accountIdentifier) {
     try {
       final Account account = ledgerManager.findAccount(accountIdentifier);
+      if (account == null)
+        throw ServiceException.internalError("Could not find the account with identifier ''{0}''", accountIdentifier);
       return BigDecimal.valueOf(account.getBalance());
     }
     catch (final AccountNotFoundException e) {
-     throw ServiceException.internalError("Could not found the account with the identifier ''{0}''", accountIdentifier);
+     throw ServiceException.internalError("Could not find the account with identifier ''{0}''", accountIdentifier);
     }
   }
 
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
index a2471c2..c24e7de 100644
--- a/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
@@ -26,7 +26,7 @@
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.api.v1.domain.CasePage;
 import io.mifos.portfolio.api.v1.domain.Command;
-import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.api.v1.domain.Payment;
 import io.mifos.portfolio.service.internal.checker.CaseChecker;
 import io.mifos.portfolio.service.internal.command.ChangeCaseCommand;
 import io.mifos.portfolio.service.internal.command.CreateCaseCommand;
@@ -45,7 +45,6 @@
 import java.math.BigDecimal;
 import java.time.Clock;
 import java.time.LocalDateTime;
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -194,12 +193,13 @@
           produces = MediaType.APPLICATION_JSON_VALUE
   )
   @ResponseBody
-  List<CostComponent> getCostComponentsForAction(@PathVariable("productidentifier") final String productIdentifier,
-                                                 @PathVariable("caseidentifier") final String caseIdentifier,
-                                                 @PathVariable("actionidentifier") final String actionIdentifier,
-                                                 @RequestParam(value="fordatetime", required = false, defaultValue = "") final @ValidLocalDateTimeString String forDateTimeString,
-                                                 @RequestParam(value="touchingaccounts", required = false, defaultValue = "") final Set<String> forAccountDesignators,
-                                                 @RequestParam(value="forpaymentsize", required = false, defaultValue = "") final BigDecimal forPaymentSize)
+  Payment getCostComponentsForAction(
+      @PathVariable("productidentifier") final String productIdentifier,
+      @PathVariable("caseidentifier") final String caseIdentifier,
+      @PathVariable("actionidentifier") final String actionIdentifier,
+      @RequestParam(value="fordatetime", required = false, defaultValue = "") final @ValidLocalDateTimeString String forDateTimeString,
+      @RequestParam(value="touchingaccounts", required = false, defaultValue = "") final Set<String> forAccountDesignators,
+      @RequestParam(value="forpaymentsize", required = false, defaultValue = "") final BigDecimal forPaymentSize)
   {
     checkThatCaseExists(productIdentifier, caseIdentifier);
 
diff --git a/service/src/main/java/io/mifos/products/spi/PatternFactory.java b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
index dd0e555..9c153be 100644
--- a/service/src/main/java/io/mifos/products/spi/PatternFactory.java
+++ b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
@@ -16,10 +16,7 @@
 package io.mifos.products.spi;
 
 
-import io.mifos.portfolio.api.v1.domain.Case;
-import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
-import io.mifos.portfolio.api.v1.domain.CostComponent;
-import io.mifos.portfolio.api.v1.domain.Pattern;
+import io.mifos.portfolio.api.v1.domain.*;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
@@ -38,7 +35,7 @@
   void changeParameters(Long caseId, String parameters);
   Optional<String> getParameters(Long caseId, int minorCurrencyUnitDigits);
   Set<String> getNextActionsForState(Case.State state);
-  List<CostComponent> getCostComponentsForAction(
+  Payment getCostComponentsForAction(
       String productIdentifier,
       String caseIdentifier,
       String actionIdentifier,
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/CostComponentServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/CostComponentServiceTest.java
index f2aa460..4a639c9 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/CostComponentServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/CostComponentServiceTest.java
@@ -15,6 +15,7 @@
  */
 package io.mifos.individuallending.internal.service;
 
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import org.junit.Assert;
 import org.junit.Test;
@@ -22,11 +23,9 @@
 import org.junit.runners.Parameterized;
 
 import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
+import java.util.*;
 
-import static io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR;
+import static io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR;
 import static io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR;
 
 /**
@@ -40,7 +39,7 @@
     BigDecimal maximumBalance = BigDecimal.ZERO;
     BigDecimal runningBalance = BigDecimal.ZERO;
     BigDecimal loanPaymentSize = BigDecimal.ZERO;
-    BigDecimal expectedAmount = BigDecimal.ZERO;
+    BigDecimal expectedAmount = BigDecimal.ONE;
 
     private TestCase(String description) {
       this.description = description;
@@ -70,6 +69,18 @@
       this.expectedAmount = expectedAmount;
       return this;
     }
+
+    @Override
+    public String toString() {
+      return "TestCase{" +
+          "description='" + description + '\'' +
+          ", chargeProportionalDesignator=" + chargeProportionalDesignator +
+          ", maximumBalance=" + maximumBalance +
+          ", runningBalance=" + runningBalance +
+          ", loanPaymentSize=" + loanPaymentSize +
+          ", expectedAmount=" + expectedAmount +
+          '}';
+    }
   }
 
   @Parameterized.Parameters
@@ -77,9 +88,9 @@
     final Collection<CostComponentServiceTest.TestCase> ret = new ArrayList<>();
     ret.add(new TestCase("simple"));
     ret.add(new TestCase("distribution fee")
-        .chargeProportionalDesignator(PRINCIPAL_ADJUSTMENT_DESIGNATOR)
+        .chargeProportionalDesignator(REQUESTED_DISBURSEMENT_DESIGNATOR)
         .maximumBalance(BigDecimal.valueOf(2000))
-        .loanPaymentSize(BigDecimal.valueOf(-2000))
+        .loanPaymentSize(BigDecimal.valueOf(2000))
         .expectedAmount(BigDecimal.valueOf(2000)));
     ret.add(new TestCase("origination fee")
         .chargeProportionalDesignator(RUNNING_BALANCE_DESIGNATOR)
@@ -96,14 +107,18 @@
 
   @Test
   public void getAmountProportionalTo() {
+    final SimulatedRunningBalances runningBalances = new SimulatedRunningBalances();
+    runningBalances.adjustBalance(AccountDesignators.CUSTOMER_LOAN, testCase.runningBalance.negate());
     final BigDecimal amount = CostComponentService.getAmountProportionalTo(
         testCase.chargeProportionalDesignator,
         testCase.maximumBalance,
-        testCase.runningBalance,
+        runningBalances,
         testCase.loanPaymentSize,
-        Collections.emptyMap());
+        testCase.loanPaymentSize,
+        testCase.loanPaymentSize,
+        new PaymentBuilder(runningBalances, false));
 
-    Assert.assertEquals(testCase.expectedAmount, amount);
+    Assert.assertEquals(testCase.toString(), testCase.expectedAmount, amount);
   }
 
 }
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
index 4daaedc..a31f3e0 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
@@ -24,7 +24,10 @@
 import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
-import io.mifos.portfolio.api.v1.domain.*;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.api.v1.domain.PaymentCycle;
+import io.mifos.portfolio.api.v1.domain.TermRange;
 import io.mifos.portfolio.service.internal.repository.BalanceSegmentRepository;
 import io.mifos.portfolio.service.internal.repository.CaseEntity;
 import io.mifos.portfolio.service.internal.repository.ProductEntity;
@@ -94,14 +97,10 @@
     private BigDecimal interest;
     private Set<String> expectedChargeIdentifiers = new HashSet<>(Arrays.asList(
         PROCESSING_FEE_ID,
-        LOAN_FUNDS_ALLOCATION_ID,
-        RETURN_DISBURSEMENT_ID,
         LOAN_ORIGINATION_FEE_ID,
         INTEREST_ID,
         DISBURSEMENT_FEE_ID,
         REPAYMENT_ID,
-        TRACK_DISBURSAL_PAYMENT_ID,
-        TRACK_RETURN_PRINCIPAL_ID,
         DISBURSE_PAYMENT_ID,
         LATE_FEE_ID
         ));
@@ -189,8 +188,8 @@
     caseParameters.setTermRange(new TermRange(ChronoUnit.WEEKS, 3));
     caseParameters.setPaymentCycle(new PaymentCycle(ChronoUnit.WEEKS, 1, 0, null, null));
 
-    final ChargeDefinition processingFeeCharge = getFixedSingleChargeDefinition(10.0, Action.OPEN, PROCESSING_FEE_ID, AccountDesignators.PROCESSING_FEE_INCOME);
-    final ChargeDefinition loanOriginationFeeCharge = getFixedSingleChargeDefinition(100.0, Action.APPROVE, LOAN_ORIGINATION_FEE_ID, AccountDesignators.ORIGINATION_FEE_INCOME);
+    final ChargeDefinition processingFeeCharge = getFixedSingleChargeDefinition(10.0, Action.DISBURSE, PROCESSING_FEE_ID, AccountDesignators.PROCESSING_FEE_INCOME);
+    final ChargeDefinition loanOriginationFeeCharge = getFixedSingleChargeDefinition(100.0, Action.DISBURSE, LOAN_ORIGINATION_FEE_ID, AccountDesignators.ORIGINATION_FEE_INCOME);
     final List<ChargeDefinition> defaultChargesWithFeesReplaced =
     charges().stream().map(x -> {
       switch (x.getIdentifier()) {
@@ -209,9 +208,7 @@
         .initialDisbursementDate(initialDisbursementDate)
         .chargeDefinitions(defaultChargesWithFeesReplaced)
         .interest(BigDecimal.valueOf(1))
-        .expectChargeInstancesForActionDatePair(Action.OPEN, initialDisbursementDate, Collections.singletonList(processingFeeCharge))
-        .expectChargeInstancesForActionDatePair(Action.APPROVE, initialDisbursementDate,
-            Collections.singletonList(loanOriginationFeeCharge));
+        .expectChargeInstancesForActionDatePair(Action.DISBURSE, initialDisbursementDate, Arrays.asList(processingFeeCharge, loanOriginationFeeCharge));
   }
 
   private static TestCase yearLoanTestCase()
@@ -267,7 +264,7 @@
     ret.setChargeAction(action.name());
     ret.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
     ret.setProportionalTo(null);
-    ret.setFromAccountDesignator(AccountDesignators.ENTRY);
+    ret.setFromAccountDesignator(AccountDesignators.CUSTOMER_LOAN);
     ret.setToAccountDesignator(feeAccountDesignator);
     ret.setForCycleSizeUnit(null);
     return ret;
@@ -318,38 +315,42 @@
     final Set<BigDecimal> customerRepayments = Stream.iterate(1, x -> x + 1).limit(allPlannedPayments.size() - 1)
         .map(x ->
         {
-          final BigDecimal costComponentSum = allPlannedPayments.get(x).getCostComponents().stream()
-              .filter(this::includeCostComponentsInSumCheck)
+          final BigDecimal valueOfRepaymentCostComponent = allPlannedPayments.get(x).getPayment().getCostComponents().stream()
+              .filter(costComponent -> costComponent.getChargeIdentifier().equals(ChargeIdentifiers.REPAYMENT_ID))
               .map(CostComponent::getAmount)
               .reduce(BigDecimal::add)
               .orElse(BigDecimal.ZERO);
-          final BigDecimal valueOfPrincipleTrackingCostComponent = allPlannedPayments.get(x).getCostComponents().stream()
-              .filter(costComponent -> costComponent.getChargeIdentifier().equals(ChargeIdentifiers.TRACK_RETURN_PRINCIPAL_ID))
+          final BigDecimal valueOfInterestCostComponent = allPlannedPayments.get(x).getPayment().getCostComponents().stream()
+              .filter(costComponent -> costComponent.getChargeIdentifier().equals(ChargeIdentifiers.INTEREST_ID))
               .map(CostComponent::getAmount)
               .reduce(BigDecimal::add)
               .orElse(BigDecimal.ZERO);
-          final BigDecimal principalDifference = allPlannedPayments.get(x-1).getRemainingPrincipal().subtract(allPlannedPayments.get(x).getRemainingPrincipal());
-          Assert.assertEquals(valueOfPrincipleTrackingCostComponent, principalDifference);
+          final BigDecimal principalDifference = allPlannedPayments.get(x-1).getBalances().get(AccountDesignators.CUSTOMER_LOAN).subtract(allPlannedPayments.get(x).getBalances().get(AccountDesignators.CUSTOMER_LOAN));
+          Assert.assertEquals("Checking payment " + x, valueOfRepaymentCostComponent.subtract(valueOfInterestCostComponent), principalDifference);
           Assert.assertNotEquals("Remaining principle should always be positive or zero.",
-              allPlannedPayments.get(x).getRemainingPrincipal().signum(), -1);
-          final boolean containsLateFee = allPlannedPayments.get(x).getCostComponents().stream().anyMatch(y -> y.getChargeIdentifier().equals(LATE_FEE_ID));
+              allPlannedPayments.get(x).getBalances().get(AccountDesignators.CUSTOMER_LOAN).signum(), -1);
+          final boolean containsLateFee = allPlannedPayments.get(x).getPayment().getCostComponents().stream().anyMatch(y -> y.getChargeIdentifier().equals(LATE_FEE_ID));
           Assert.assertFalse("Late fee should not be included in planned payments", containsLateFee);
-          return costComponentSum;
+          return valueOfRepaymentCostComponent;
         }
     ).collect(Collectors.toSet());
 
     //All entries should have the correct scale.
     allPlannedPayments.forEach(x -> {
-      x.getCostComponents().forEach(y -> Assert.assertEquals(testCase.minorCurrencyUnitDigits, y.getAmount().scale()));
-      Assert.assertEquals(testCase.minorCurrencyUnitDigits, x.getRemainingPrincipal().scale());
-      final int uniqueChargeIdentifierCount = x.getCostComponents().stream()
+      x.getPayment().getCostComponents().forEach(y -> Assert.assertEquals(testCase.minorCurrencyUnitDigits, y.getAmount().scale()));
+      Assert.assertEquals(testCase.minorCurrencyUnitDigits, x.getBalances().get(AccountDesignators.CUSTOMER_LOAN).scale());
+      final int uniqueChargeIdentifierCount = x.getPayment().getCostComponents().stream()
           .map(CostComponent::getChargeIdentifier)
           .collect(Collectors.toSet())
           .size();
       Assert.assertEquals("There should be only one cost component per charge per planned payment.",
-          x.getCostComponents().size(), uniqueChargeIdentifierCount);
+          x.getPayment().getCostComponents().size(), uniqueChargeIdentifierCount);
     });
 
+    Assert.assertEquals("Final balance should be zero.",
+        BigDecimal.ZERO.setScale(testCase.minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN),
+        allPlannedPayments.get(allPlannedPayments.size()-1).getBalances().get(AccountDesignators.CUSTOMER_LOAN));
+
     //All customer payments should be within one percent of each other.
     final Optional<BigDecimal> maxPayment = customerRepayments.stream().max(BigDecimal::compareTo);
     final Optional<BigDecimal> minPayment = customerRepayments.stream().min(BigDecimal::compareTo);
@@ -359,10 +360,6 @@
     Assert.assertTrue("Percent difference = " + percentDifference + ", max = " + maxPayment.get() + ", min = " + minPayment.get(),
         percentDifference < 0.01);
 
-    //Final balance should be zero.
-    Assert.assertEquals(BigDecimal.ZERO.setScale(testCase.minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN),
-            allPlannedPayments.get(allPlannedPayments.size()-1).getRemainingPrincipal());
-
     //All charge identifiers should be associated with a name on the returned page.
     final Set<String> resultChargeIdentifiers = firstPage.getChargeNames().stream()
             .map(ChargeName::getIdentifier)
@@ -371,22 +368,6 @@
     Assert.assertEquals(testCase.expectedChargeIdentifiers, resultChargeIdentifiers);
   }
 
-  private boolean includeCostComponentsInSumCheck(CostComponent costComponent) {
-    switch (costComponent.getChargeIdentifier()) {
-      case ChargeIdentifiers.INTEREST_ID:
-      case ChargeIdentifiers.DISBURSEMENT_FEE_ID:
-      case ChargeIdentifiers.TRACK_DISBURSAL_PAYMENT_ID:
-      case ChargeIdentifiers.LATE_FEE_ID:
-      case ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID:
-      case ChargeIdentifiers.TRACK_RETURN_PRINCIPAL_ID:
-      case ChargeIdentifiers.PROCESSING_FEE_ID:
-        return true;
-      default:
-        return false;
-
-    }
-  }
-
   @Test
   public void getScheduledCharges() {
     final List<ScheduledAction> scheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(testCase.initialDisbursementDate, testCase.caseParameters);
@@ -423,7 +404,8 @@
                 new ActionDatePair(scheduledCharge.getScheduledAction().action, scheduledCharge.getScheduledAction().when),
                 Collectors.mapping(ScheduledCharge::getChargeDefinition, Collectors.toSet())));
 
-    testCase.chargeDefinitionsForActions.forEach((key, value) -> value.forEach(x -> Assert.assertTrue(searchableScheduledCharges.get(key).contains(x))));
+    testCase.chargeDefinitionsForActions.forEach((key, value) ->
+        value.forEach(x -> Assert.assertTrue(searchableScheduledCharges.get(key).contains(x))));
   }
 
   private double percentDifference(final BigDecimal maxPayment, final BigDecimal minPayment) {
diff --git a/shared.gradle b/shared.gradle
index 410d57d..19e1491 100644
--- a/shared.gradle
+++ b/shared.gradle
@@ -14,7 +14,8 @@
         mifosrhythm         : '0.1.0-BUILD-SNAPSHOT',
         mifoscustomer       : '0.1.0-BUILD-SNAPSHOT',
         validator    : '5.3.0.Final',
-        javamoneylib : '0.9-SNAPSHOT'
+        javamoneylib : '0.9-SNAPSHOT',
+        expiringmap         : '0.5.8'
 ]
 
 apply plugin: 'java'