FINERACT-1968: Enhanced charge handling
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index a8614c9..796ccd3 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -779,14 +779,16 @@
                 .determineProcessor(this.transactionProcessingStrategyCode);
         final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>();
         List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
+        int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
+                .fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments);
         for (final LoanRepaymentScheduleInstallment installment : installments) {
-            boolean isDue = installment.isFirstPeriod()
+            boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)
                     ? charge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate())
                     : charge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate());
-            if (installmentNumber == null && isDue) {
+            if (installmentNumber == null && isFirstNormalInstallment) {
                 chargePaymentInstallments.add(installment);
                 break;
-            } else if (installmentNumber != null && installment.getInstallmentNumber().equals(installmentNumber)) {
+            } else if (installment.getInstallmentNumber().equals(installmentNumber)) {
                 chargePaymentInstallments.add(installment);
                 break;
             }
@@ -1347,7 +1349,8 @@
                 LocalDate transactionDateForRange = isBasedOnSubmittedOnDate
                         ? loanTransaction.getLoanChargesPaid().stream().findFirst().get().getLoanCharge().getDueDate()
                         : loanTransaction.getTransactionDate();
-                if (installment.isInPeriod(transactionDateForRange)) {
+                boolean isInPeriod = LoanRepaymentScheduleProcessingWrapper.isInPeriod(transactionDateForRange, installment, installments);
+                if (isInPeriod) {
                     interest = interest.plus(loanTransaction.getInterestPortion(getCurrency()));
                     fee = fee.plus(loanTransaction.getFeeChargesPortion(getCurrency()));
                     penality = penality.plus(loanTransaction.getPenaltyChargesPortion(getCurrency()));
@@ -6641,13 +6644,18 @@
         Money paidFromFutureInstallments = Money.zero(currency);
         Money fee = Money.zero(currency);
         Money penalty = Money.zero(currency);
+        int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
+                .fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments);
+
         for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
+            boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
             if (!DateUtils.isBefore(paymentDate, installment.getDueDate())) {
                 interest = interest.plus(installment.getInterestOutstanding(currency));
                 penalty = penalty.plus(installment.getPenaltyChargesOutstanding(currency));
                 fee = fee.plus(installment.getFeeChargesOutstanding(currency));
             } else if (DateUtils.isAfter(paymentDate, installment.getFromDate())) {
-                Money[] balancesForCurrentPeroid = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment);
+                Money[] balancesForCurrentPeroid = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment,
+                        isFirstNormalInstallment);
                 if (balancesForCurrentPeroid[0].isGreaterThan(balancesForCurrentPeroid[5])) {
                     interest = interest.plus(balancesForCurrentPeroid[0]).minus(balancesForCurrentPeroid[5]);
                 } else {
@@ -6680,7 +6688,7 @@
     }
 
     private Money[] fetchInterestFeeAndPenaltyTillDate(final LocalDate paymentDate, final MonetaryCurrency currency,
-            final LoanRepaymentScheduleInstallment installment) {
+            final LoanRepaymentScheduleInstallment installment, boolean isFirstNormalInstallment) {
         Money penaltyForCurrentPeriod = Money.zero(getCurrency());
         Money penaltyAccoutedForCurrentPeriod = Money.zero(getCurrency());
         Money feeForCurrentPeriod = Money.zero(getCurrency());
@@ -6694,7 +6702,7 @@
         interestAccountedForCurrentPeriod = installment.getInterestWaived(getCurrency()).plus(installment.getInterestPaid(getCurrency()));
         for (LoanCharge loanCharge : this.charges) {
             if (loanCharge.isActive() && !loanCharge.isDueAtDisbursement()) {
-                boolean isDue = installment.isFirstPeriod()
+                boolean isDue = isFirstNormalInstallment
                         ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), paymentDate)
                         : loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), paymentDate);
                 if (isDue) {
@@ -6734,7 +6742,10 @@
         Money[] balances = new Money[3];
         final MonetaryCurrency currency = getCurrency();
         balances[0] = balances[1] = balances[2] = Money.zero(currency);
+        int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
+                .fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments);
         for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
+            boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
             if (DateUtils.isEqual(paymentDate, installment.getDueDate())) {
                 Money interest = installment.getInterestCharged(currency);
                 Money fee = installment.getFeeChargesCharged(currency);
@@ -6745,7 +6756,7 @@
                 break;
             } else if (DateUtils.isAfter(paymentDate, installment.getFromDate())
                     && DateUtils.isBefore(paymentDate, installment.getDueDate())) {
-                balances = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment);
+                balances = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment, isFirstNormalInstallment);
                 break;
             }
         }
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 0e46345..1431c8a 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -960,15 +960,6 @@
         this.additional = true;
     }
 
-    public boolean isFirstPeriod() {
-        return (this.installmentNumber == 1);
-    }
-
-    public boolean isInPeriod(LocalDate date) {
-        return (isFirstPeriod() ? !DateUtils.isBefore(date, getFromDate()) : DateUtils.isAfter(date, getFromDate()))
-                && !DateUtils.isAfter(date, getDueDate());
-    }
-
     public Set<LoanTransactionToRepaymentScheduleMapping> getLoanTransactionToRepaymentScheduleMappings() {
         return this.loanTransactionToRepaymentScheduleMappings;
     }
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
index 4c032e3..6444da3 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
@@ -20,6 +20,7 @@
 
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
@@ -43,13 +44,14 @@
             totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
         }
         LocalDate startDate = disbursementDate;
+        LoanRepaymentScheduleInstallment firstNormalPeriod = repaymentPeriods.stream()
+                .sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber))
+                .filter(repaymentPeriod -> !repaymentPeriod.isDownPayment()).findFirst().orElseThrow();
         for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
 
             if (!period.isDownPayment()) {
 
-                boolean isFirstNonDownPaymentPeriod = repaymentPeriods.stream()
-                        .filter(repaymentPeriod -> repaymentPeriod.getInstallmentNumber() < period.getInstallmentNumber())
-                        .allMatch(LoanRepaymentScheduleInstallment::isDownPayment);
+                boolean isFirstNonDownPaymentPeriod = period.equals(firstNormalPeriod);
 
                 final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges,
                         currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(),
@@ -235,4 +237,18 @@
         return amount;
     }
 
+    public static int fetchFirstNormalInstallmentNumber(List<LoanRepaymentScheduleInstallment> installments) {
+        return installments.stream().sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber))
+                .filter(repaymentPeriod -> !repaymentPeriod.isDownPayment()).findFirst().orElseThrow().getInstallmentNumber();
+    }
+
+    public static boolean isInPeriod(LocalDate transactionDate, LoanRepaymentScheduleInstallment targetInstallment,
+            List<LoanRepaymentScheduleInstallment> installments) {
+        int firstPeriod = fetchFirstNormalInstallmentNumber(installments);
+        return (targetInstallment.getInstallmentNumber().equals(firstPeriod)
+                ? !DateUtils.isBefore(transactionDate, targetInstallment.getFromDate())
+                : DateUtils.isAfter(transactionDate, targetInstallment.getFromDate()))
+                && !DateUtils.isAfter(transactionDate, targetInstallment.getDueDate());
+    }
+
 }
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java
index c119dfb..506ae8a 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java
@@ -42,13 +42,11 @@
             totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
         }
         LocalDate startDate = disbursementDate;
+        int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(repaymentPeriods);
         for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
 
             if (!period.isDownPayment()) {
-
-                boolean isFirstNonDownPaymentPeriod = repaymentPeriods.stream()
-                        .filter(repaymentPeriod -> repaymentPeriod.getInstallmentNumber() < period.getInstallmentNumber())
-                        .allMatch(LoanRepaymentScheduleInstallment::isDownPayment);
+                boolean isFirstNonDownPaymentPeriod = period.getInstallmentNumber().equals(firstNormalInstallmentNumber);
 
                 final Money feeChargesDueForRepaymentPeriod = feeChargesDueWithin(startDate, period.getDueDate(), loanCharge, currency,
                         period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod);
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index 331fd88..0f8dc6b 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -102,9 +102,11 @@
                     }
                 }
                 LocalDate startDate = disbursementDate;
+                int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
                 for (final LoanRepaymentScheduleInstallment installment : installments) {
+                    boolean isFirstPeriod = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
                     for (final LoanCharge loanCharge : transferCharges) {
-                        boolean isDue = installment.isFirstPeriod()
+                        boolean isDue = isFirstPeriod
                                 ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate())
                                 : loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate());
                         if (isDue) {
@@ -635,15 +637,15 @@
         return penaltyCharges;
     }
 
-    protected void updateChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,
+    protected void updateChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money chargeAmount, final Set<LoanCharge> charges,
             final Integer installmentNumber) {
 
-        Money amountRemaining = feeCharges;
+        Money amountRemaining = chargeAmount;
         while (amountRemaining.isGreaterThanZero()) {
-            final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges, feeCharges.getCurrency());
-            Money feeAmount = feeCharges.zero();
+            final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges, chargeAmount.getCurrency());
+            Money feeAmount = chargeAmount.zero();
             if (loanTransaction.isChargePayment()) {
-                feeAmount = feeCharges;
+                feeAmount = chargeAmount;
             }
             if (unpaidCharge == null) {
                 break; // All are trache charges
@@ -669,6 +671,18 @@
 
     }
 
+    public interface ChargesPaidByFunction {
+
+        void accept(LoanTransaction loanTransaction, Money feeCharges, Set<LoanCharge> charges, Integer installmentNumber);
+    }
+
+    public ChargesPaidByFunction getChargesPaymentFunction(LoanRepaymentScheduleInstallment.PaymentAction action) {
+        return switch (action) {
+            case PAY -> this::updateChargesPaidAmountBy;
+            case UNPAY -> this::undoChargesPaidAmountBy;
+        };
+    }
+
     protected LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges, final MonetaryCurrency currency) {
         LoanCharge earliestUnpaidCharge = null;
         LoanCharge installemntCharge = null;
@@ -770,15 +784,15 @@
         loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
     }
 
-    protected void undoChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,
+    protected void undoChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money chargeAmount, final Set<LoanCharge> charges,
             final Integer installmentNumber) {
 
-        Money amountRemaining = feeCharges;
+        Money amountRemaining = chargeAmount;
         while (amountRemaining.isGreaterThanZero()) {
-            final LoanCharge paidCharge = findLatestPaidChargeFromUnOrderedSet(charges, feeCharges.getCurrency());
+            final LoanCharge paidCharge = findLatestPaidChargeFromUnOrderedSet(charges, chargeAmount.getCurrency());
 
             if (paidCharge != null) {
-                Money feeAmount = feeCharges.zero();
+                Money feeAmount = chargeAmount.zero();
 
                 final Money amountDeductedTowardsCharge = paidCharge.undoPaidOrPartiallyAmountBy(amountRemaining, installmentNumber,
                         feeAmount);
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index cedbadf..9d3adee 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -49,6 +49,7 @@
 import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
 import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
@@ -196,7 +197,7 @@
             for (Map.Entry<DueType, List<PaymentAllocationType>> paymentAllocationsEntry : paymentAllocationsMap.entrySet()) {
                 transactionAmountUnprocessed = refundTransactionHorizontally(loanTransaction, currency, installments,
                         transactionAmountUnprocessed, paymentAllocationsEntry.getValue(), futureInstallmentAllocationRule,
-                        transactionMappings, balances);
+                        transactionMappings, charges, balances);
                 if (!transactionAmountUnprocessed.isGreaterThanZero()) {
                     break;
                 }
@@ -205,7 +206,7 @@
                 .equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
             for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
                 transactionAmountUnprocessed = refundTransactionVertically(loanTransaction, currency, installments, zero,
-                        transactionMappings, transactionAmountUnprocessed, futureInstallmentAllocationRule, balances,
+                        transactionMappings, transactionAmountUnprocessed, futureInstallmentAllocationRule, charges, balances,
                         paymentAllocationType);
                 if (!transactionAmountUnprocessed.isGreaterThanZero()) {
                     break;
@@ -216,20 +217,6 @@
         loanTransaction.updateComponents(balances.getAggregatedPrincipalPortion(), balances.getAggregatedInterestPortion(),
                 balances.getAggregatedFeeChargesPortion(), balances.getAggregatedPenaltyChargesPortion());
         loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
-
-        final Set<LoanCharge> loanFees = extractFeeCharges(charges);
-        final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
-        Integer installmentNumber = null;
-
-        final Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
-        if (feeCharges.isGreaterThanZero()) {
-            undoChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber);
-        }
-
-        final Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
-        if (penaltyCharges.isGreaterThanZero()) {
-            undoChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber);
-        }
     }
 
     private void processSingleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
@@ -350,44 +337,39 @@
 
     private Money processPaymentAllocation(PaymentAllocationType paymentAllocationType, LoanRepaymentScheduleInstallment currentInstallment,
             LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
-            LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping, Balances balances,
-            LoanRepaymentScheduleInstallment.PaymentAction action) {
+            LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping, Set<LoanCharge> chargesOfInstallment,
+            Balances balances, LoanRepaymentScheduleInstallment.PaymentAction action) {
         LocalDate transactionDate = loanTransaction.getTransactionDate();
         Money zero = transactionAmountUnprocessed.zero();
-        return switch (paymentAllocationType.getAllocationType()) {
+
+        LoanRepaymentScheduleInstallment.PaymentFunction paymentFunction = currentInstallment
+                .getPaymentFunction(paymentAllocationType.getAllocationType(), action);
+        ChargesPaidByFunction chargesPaidByFunction = getChargesPaymentFunction(action);
+        Money portion = paymentFunction.accept(transactionDate, transactionAmountUnprocessed);
+
+        switch (paymentAllocationType.getAllocationType()) {
             case PENALTY -> {
-                LoanRepaymentScheduleInstallment.PaymentFunction paymentFunction = currentInstallment
-                        .getPaymentFunction(paymentAllocationType.getAllocationType(), action);
-                Money portion = paymentFunction.accept(transactionDate, transactionAmountUnprocessed);
                 balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().add(portion));
                 addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, zero, portion);
-                yield portion;
+                Set<LoanCharge> penalties = chargesOfInstallment.stream().filter(LoanCharge::isPenaltyCharge).collect(Collectors.toSet());
+                chargesPaidByFunction.accept(loanTransaction, portion, penalties, currentInstallment.getInstallmentNumber());
             }
             case FEE -> {
-                LoanRepaymentScheduleInstallment.PaymentFunction functional = currentInstallment
-                        .getPaymentFunction(paymentAllocationType.getAllocationType(), action);
-                Money portion = functional.accept(transactionDate, transactionAmountUnprocessed);
                 balances.setAggregatedFeeChargesPortion(balances.getAggregatedFeeChargesPortion().add(portion));
                 addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, portion, zero);
-                yield portion;
+                Set<LoanCharge> fees = chargesOfInstallment.stream().filter(LoanCharge::isFeeCharge).collect(Collectors.toSet());
+                chargesPaidByFunction.accept(loanTransaction, portion, fees, currentInstallment.getInstallmentNumber());
             }
             case INTEREST -> {
-                LoanRepaymentScheduleInstallment.PaymentFunction functional = currentInstallment
-                        .getPaymentFunction(paymentAllocationType.getAllocationType(), action);
-                Money portion = functional.accept(transactionDate, transactionAmountUnprocessed);
                 balances.setAggregatedInterestPortion(balances.getAggregatedInterestPortion().add(portion));
                 addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, portion, zero, zero);
-                yield portion;
             }
             case PRINCIPAL -> {
-                LoanRepaymentScheduleInstallment.PaymentFunction functional = currentInstallment
-                        .getPaymentFunction(paymentAllocationType.getAllocationType(), action);
-                Money portion = functional.accept(transactionDate, transactionAmountUnprocessed);
                 balances.setAggregatedPrincipalPortion(balances.getAggregatedPrincipalPortion().add(portion));
                 addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, portion, zero, zero, zero);
-                yield portion;
             }
-        };
+        }
+        return portion;
     }
 
     private void addToTransactionMapping(LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping,
@@ -427,7 +409,7 @@
                 principalPortion = principalPortion.plus(currentInstallment.getPrincipalOutstanding(currency));
                 interestPortion = interestPortion.plus(currentInstallment.getInterestOutstanding(currency));
                 feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(currency));
-                penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency));
+                penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesOutstanding(currency));
             }
         }
 
@@ -450,8 +432,9 @@
         LocalDate startDate = loanTransaction.getLoan().getDisbursementDate();
 
         Money unprocessed = loanTransaction.getAmount(currency);
+        int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
         for (final LoanRepaymentScheduleInstallment installment : installments) {
-            boolean isDue = installment.isFirstPeriod()
+            boolean isDue = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)
                     ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate())
                     : loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate());
             if (isDue) {
@@ -487,7 +470,7 @@
     private Money refundTransactionHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
             List<PaymentAllocationType> paymentAllocationTypes, FutureInstallmentAllocationRule futureInstallmentAllocationRule,
-            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Balances balances) {
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges, Balances balances) {
         Money zero = Money.zero(currency);
         Money refundedPortion;
         outerLoop: do {
@@ -498,15 +481,18 @@
             List<LoanRepaymentScheduleInstallment> inAdvanceInstallments = getFutureInstallmentsForRefund(loanTransaction, currency,
                     installments, futureInstallmentAllocationRule, zero);
 
+            int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
             for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
                 switch (paymentAllocationType.getDueType()) {
                     case PAST_DUE -> {
                         if (latestPastDueInstallment != null) {
+                            Set<LoanCharge> oldestPastDueInstallmentCharges = getLoanChargesOfInstallment(charges, latestPastDueInstallment,
+                                    firstNormalInstallmentNumber);
                             LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                     transactionMappings, loanTransaction, latestPastDueInstallment, currency);
                             refundedPortion = processPaymentAllocation(paymentAllocationType, latestPastDueInstallment, loanTransaction,
-                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
-                                    LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
+                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping,
+                                    oldestPastDueInstallmentCharges, balances, LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
                             transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
                         } else {
                             break outerLoop;
@@ -514,11 +500,13 @@
                     }
                     case DUE -> {
                         if (dueInstallment != null) {
+                            Set<LoanCharge> dueInstallmentCharges = getLoanChargesOfInstallment(charges, dueInstallment,
+                                    firstNormalInstallmentNumber);
                             LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                     transactionMappings, loanTransaction, dueInstallment, currency);
                             refundedPortion = processPaymentAllocation(paymentAllocationType, dueInstallment, loanTransaction,
-                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
-                                    LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
+                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges,
+                                    balances, LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
                             transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
                         } else {
                             break outerLoop;
@@ -530,13 +518,15 @@
                             Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getRoundingMode());
                             Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
                             for (LoanRepaymentScheduleInstallment inAdvanceInstallment : inAdvanceInstallments) {
+                                Set<LoanCharge> inAdvanceInstallmentCharges = getLoanChargesOfInstallment(charges, inAdvanceInstallment,
+                                        firstNormalInstallmentNumber);
                                 if (inAdvanceInstallment.equals(inAdvanceInstallments.get(numberOfInstallments - 1))) {
                                     evenPortion = evenPortion.add(balanceAdjustment);
                                 }
                                 LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                         transactionMappings, loanTransaction, inAdvanceInstallment, currency);
                                 refundedPortion = processPaymentAllocation(paymentAllocationType, inAdvanceInstallment, loanTransaction,
-                                        evenPortion, loanTransactionToRepaymentScheduleMapping, balances,
+                                        evenPortion, loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges, balances,
                                         LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
                                 transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
                             }
@@ -554,30 +544,35 @@
     private Money refundTransactionVertically(LoanTransaction loanTransaction, MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments, Money zero,
             List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Money transactionAmountUnprocessed,
-            FutureInstallmentAllocationRule futureInstallmentAllocationRule, Balances balances,
+            FutureInstallmentAllocationRule futureInstallmentAllocationRule, Set<LoanCharge> charges, Balances balances,
             PaymentAllocationType paymentAllocationType) {
         LoanRepaymentScheduleInstallment currentInstallment = null;
         Money refundedPortion = zero;
+        int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
         do {
             switch (paymentAllocationType.getDueType()) {
                 case PAST_DUE -> {
                     currentInstallment = getLatestPastDueInstallmentForRefund(loanTransaction, currency, installments, zero);
                     if (currentInstallment != null) {
+                        Set<LoanCharge> oldestPastDueInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
+                                firstNormalInstallmentNumber);
                         LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                 transactionMappings, loanTransaction, currentInstallment, currency);
                         refundedPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
-                                transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
-                                LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
+                                transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, oldestPastDueInstallmentCharges,
+                                balances, LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
                         transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
                     }
                 }
                 case DUE -> {
                     currentInstallment = getDueInstallmentForRefund(loanTransaction, currency, installments, zero);
                     if (currentInstallment != null) {
+                        Set<LoanCharge> dueInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
+                                firstNormalInstallmentNumber);
                         LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                 transactionMappings, loanTransaction, currentInstallment, currency);
                         refundedPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
-                                transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
+                                transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges, balances,
                                 LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
                         transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
                     }
@@ -592,14 +587,16 @@
                         Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
                         for (LoanRepaymentScheduleInstallment internalCurrentInstallment : currentInstallments) {
                             currentInstallment = internalCurrentInstallment;
+                            Set<LoanCharge> inAdvanceInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
+                                    firstNormalInstallmentNumber);
                             if (internalCurrentInstallment.equals(currentInstallments.get(numberOfInstallments - 1))) {
                                 evenPortion = evenPortion.add(balanceAdjustment);
                             }
                             LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                     transactionMappings, loanTransaction, currentInstallment, currency);
                             Money internalUnpaidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment,
-                                    loanTransaction, evenPortion, loanTransactionToRepaymentScheduleMapping, balances,
-                                    LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
+                                    loanTransaction, evenPortion, loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges,
+                                    balances, LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
                             if (internalUnpaidPortion.isGreaterThanZero()) {
                                 refundedPortion = internalUnpaidPortion;
                             }
@@ -666,25 +663,23 @@
         if (LoanScheduleProcessingType.HORIZONTAL
                 .equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
             transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, currency, installments, transactionAmountUnprocessed,
-                    paymentAllocationRule, transactionMappings, balances);
+                    paymentAllocationRule, transactionMappings, charges, balances);
         } else if (LoanScheduleProcessingType.VERTICAL
                 .equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
             transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, currency, installments, transactionAmountUnprocessed,
-                    paymentAllocationRule, transactionMappings, balances);
+                    paymentAllocationRule, transactionMappings, charges, balances);
         }
         loanTransaction.updateComponents(balances.getAggregatedPrincipalPortion(), balances.getAggregatedInterestPortion(),
                 balances.getAggregatedFeeChargesPortion(), balances.getAggregatedPenaltyChargesPortion());
         loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
 
-        payAdditionalCharges(loanTransaction, currency, charges);
-
         handleOverpayment(transactionAmountUnprocessed, loanTransaction);
     }
 
     private Money processPeriodsHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
             LoanPaymentAllocationRule paymentAllocationRule, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
-            Balances balances) {
+            Set<LoanCharge> charges, Balances balances) {
         LinkedHashMap<DueType, List<PaymentAllocationType>> paymentAllocationsMap = paymentAllocationRule.getAllocationTypes().stream()
                 .collect(Collectors.groupingBy(PaymentAllocationType::getDueType, LinkedHashMap::new,
                         mapping(Function.identity(), toList())));
@@ -692,7 +687,7 @@
         for (Map.Entry<DueType, List<PaymentAllocationType>> paymentAllocationsEntry : paymentAllocationsMap.entrySet()) {
             transactionAmountUnprocessed = processAllocationsHorizontally(loanTransaction, currency, installments,
                     transactionAmountUnprocessed, paymentAllocationsEntry.getValue(),
-                    paymentAllocationRule.getFutureInstallmentAllocationRule(), transactionMappings, balances);
+                    paymentAllocationRule.getFutureInstallmentAllocationRule(), transactionMappings, charges, balances);
         }
         return transactionAmountUnprocessed;
     }
@@ -700,7 +695,7 @@
     private Money processAllocationsHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
             List<PaymentAllocationType> paymentAllocationTypes, FutureInstallmentAllocationRule futureInstallmentAllocationRule,
-            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Balances balances) {
+            List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges, Balances balances) {
         Money paidPortion;
         boolean exit = false;
         do {
@@ -727,15 +722,19 @@
                         .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
             }
 
+            int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
+
             for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
                 switch (paymentAllocationType.getDueType()) {
                     case PAST_DUE -> {
                         if (oldestPastDueInstallment != null) {
+                            Set<LoanCharge> oldestPastDueInstallmentCharges = getLoanChargesOfInstallment(charges, oldestPastDueInstallment,
+                                    firstNormalInstallmentNumber);
                             LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                     transactionMappings, loanTransaction, oldestPastDueInstallment, currency);
                             paidPortion = processPaymentAllocation(paymentAllocationType, oldestPastDueInstallment, loanTransaction,
-                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
-                                    LoanRepaymentScheduleInstallment.PaymentAction.PAY);
+                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping,
+                                    oldestPastDueInstallmentCharges, balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
                             transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
                         } else {
                             exit = true;
@@ -743,11 +742,13 @@
                     }
                     case DUE -> {
                         if (dueInstallment != null) {
+                            Set<LoanCharge> dueInstallmentCharges = getLoanChargesOfInstallment(charges, dueInstallment,
+                                    firstNormalInstallmentNumber);
                             LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                     transactionMappings, loanTransaction, dueInstallment, currency);
                             paidPortion = processPaymentAllocation(paymentAllocationType, dueInstallment, loanTransaction,
-                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
-                                    LoanRepaymentScheduleInstallment.PaymentAction.PAY);
+                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges,
+                                    balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
                             transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
                         } else {
                             exit = true;
@@ -762,6 +763,8 @@
                             // Adjustment might be needed due to the divide operation and the rounding mode
                             Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
                             for (LoanRepaymentScheduleInstallment inAdvanceInstallment : inAdvanceInstallments) {
+                                Set<LoanCharge> inAdvanceInstallmentCharges = getLoanChargesOfInstallment(charges, inAdvanceInstallment,
+                                        firstNormalInstallmentNumber);
                                 // Adjust the portion for the last installment
                                 if (inAdvanceInstallment.equals(inAdvanceInstallments.get(numberOfInstallments - 1))) {
                                     evenPortion = evenPortion.add(balanceAdjustment);
@@ -769,7 +772,7 @@
                                 LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                         transactionMappings, loanTransaction, inAdvanceInstallment, currency);
                                 paidPortion = processPaymentAllocation(paymentAllocationType, inAdvanceInstallment, loanTransaction,
-                                        evenPortion, loanTransactionToRepaymentScheduleMapping, balances,
+                                        evenPortion, loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges, balances,
                                         LoanRepaymentScheduleInstallment.PaymentAction.PAY);
                                 transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
                             }
@@ -787,10 +790,21 @@
         return transactionAmountUnprocessed;
     }
 
+    @NotNull
+    private static Set<LoanCharge> getLoanChargesOfInstallment(Set<LoanCharge> charges, LoanRepaymentScheduleInstallment currentInstallment,
+            int firstNormalInstallmentNumber) {
+        return charges.stream().filter(loanCharge -> currentInstallment.getInstallmentNumber().equals(firstNormalInstallmentNumber)
+                ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(currentInstallment.getFromDate(),
+                        currentInstallment.getDueDate())
+                : loanCharge.isDueForCollectionFromAndUpToAndIncluding(currentInstallment.getFromDate(), currentInstallment.getDueDate()))
+                .collect(Collectors.toSet());
+    }
+
     private Money processPeriodsVertically(LoanTransaction loanTransaction, MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
             LoanPaymentAllocationRule paymentAllocationRule, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
-            Balances balances) {
+            Set<LoanCharge> charges, Balances balances) {
+        int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
         for (PaymentAllocationType paymentAllocationType : paymentAllocationRule.getAllocationTypes()) {
             FutureInstallmentAllocationRule futureInstallmentAllocationRule = paymentAllocationRule.getFutureInstallmentAllocationRule();
             LoanRepaymentScheduleInstallment currentInstallment = null;
@@ -802,11 +816,13 @@
                         currentInstallment = installments.stream().filter(predicate).filter(e -> loanTransaction.isAfter(e.getDueDate()))
                                 .min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
                         if (currentInstallment != null) {
+                            Set<LoanCharge> oldestPastDueInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
+                                    firstNormalInstallmentNumber);
                             LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                     transactionMappings, loanTransaction, currentInstallment, currency);
                             paidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
-                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
-                                    LoanRepaymentScheduleInstallment.PaymentAction.PAY);
+                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping,
+                                    oldestPastDueInstallmentCharges, balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
                             transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
                         }
                     }
@@ -814,11 +830,13 @@
                         currentInstallment = installments.stream().filter(predicate).filter(e -> loanTransaction.isOn(e.getDueDate()))
                                 .min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
                         if (currentInstallment != null) {
+                            Set<LoanCharge> dueInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
+                                    firstNormalInstallmentNumber);
                             LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                     transactionMappings, loanTransaction, currentInstallment, currency);
                             paidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
-                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
-                                    LoanRepaymentScheduleInstallment.PaymentAction.PAY);
+                                    transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges,
+                                    balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
                             transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
                         }
                     }
@@ -848,6 +866,8 @@
                             Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
                             for (LoanRepaymentScheduleInstallment internalCurrentInstallment : currentInstallments) {
                                 currentInstallment = internalCurrentInstallment;
+                                Set<LoanCharge> inAdvanceInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
+                                        firstNormalInstallmentNumber);
                                 // Adjust the portion for the last installment
                                 if (internalCurrentInstallment.equals(currentInstallments.get(numberOfInstallments - 1))) {
                                     evenPortion = evenPortion.add(balanceAdjustment);
@@ -855,8 +875,8 @@
                                 LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
                                         transactionMappings, loanTransaction, currentInstallment, currency);
                                 Money internalPaidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment,
-                                        loanTransaction, evenPortion, loanTransactionToRepaymentScheduleMapping, balances,
-                                        LoanRepaymentScheduleInstallment.PaymentAction.PAY);
+                                        loanTransaction, evenPortion, loanTransactionToRepaymentScheduleMapping,
+                                        inAdvanceInstallmentCharges, balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
                                 // Some extra logic to allocate as much as possible across the installments if the
                                 // outstanding balances are different
                                 if (internalPaidPortion.isGreaterThanZero()) {
@@ -887,43 +907,6 @@
         };
     }
 
-    private void payAdditionalCharges(LoanTransaction loanTransaction, MonetaryCurrency currency, Set<LoanCharge> charges) {
-        final Set<LoanCharge> paidFeeCharges = loanTransaction.getLoanChargesPaid().stream() //
-                .map(LoanChargePaidBy::getLoanCharge) //
-                .filter(LoanCharge::isFeeCharge).collect(Collectors.toSet());
-        final Set<LoanCharge> paidPenaltyCharges = loanTransaction.getLoanChargesPaid().stream() //
-                .map(LoanChargePaidBy::getLoanCharge) //
-                .filter(LoanCharge::isPenaltyCharge).collect(Collectors.toSet());
-        // TODO: rewrite to provide sorted list
-        final Set<LoanCharge> loanFees = extractFeeCharges(charges);
-        final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
-        loanFees.removeAll(paidFeeCharges);
-        loanPenalties.removeAll(paidPenaltyCharges);
-
-        BigDecimal sumFeePaidAmount = paidFeeCharges.stream() //
-                .map(paidCharge -> paidCharge.getAmountPaid(currency)) //
-                .map(Money::getAmount) //
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
-
-        BigDecimal sumPenaltyPaidAmount = paidPenaltyCharges.stream() //
-                .map(paidCharge -> paidCharge.getAmountPaid(currency)) //
-                .map(Money::getAmount) //
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
-
-        if (loanTransaction.isNotWaiver() && !loanTransaction.isAccrual()) {
-            Money feeCharges = loanTransaction.getFeeChargesPortion(currency).minus(sumFeePaidAmount);
-            Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency).minus(sumPenaltyPaidAmount);
-
-            if (feeCharges.isGreaterThanZero()) {
-                updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, null);
-            }
-
-            if (penaltyCharges.isGreaterThanZero()) {
-                updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, null);
-            }
-        }
-    }
-
     @AllArgsConstructor
     @Getter
     @Setter
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java
index 34f677e..5c5a4a0 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java
@@ -44,7 +44,11 @@
 
     private LocalDate getEffectiveDate() {
         if (loanCharge.isPresent()) {
-            return loanCharge.get().getDueDate();
+            if (isBackdatedCharge()) {
+                return loanCharge.get().getDueDate();
+            } else {
+                return loanCharge.get().getSubmittedOnDate();
+            }
         } else if (loanTransaction.isPresent()) {
             return loanTransaction.get().getTransactionDate();
         } else {
@@ -52,6 +56,10 @@
         }
     }
 
+    private boolean isBackdatedCharge() {
+        return loanCharge.get().getDueDate().isBefore(loanCharge.get().getSubmittedOnDate());
+    }
+
     private LocalDate getSubmittedOnDate() {
         if (loanCharge.isPresent()) {
             return loanCharge.get().getSubmittedOnDate();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index 7b04162..48a13be 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -612,15 +612,17 @@
         ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
         CurrencyData currencyData = applicationCurrency.toData();
         Set<LoanCharge> loanCharges = loan.getActiveCharges();
+        int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
 
         for (LoanRepaymentScheduleInstallment installment : installments) {
             if (DateUtils.isAfter(installment.getDueDate(), loan.getMaturityDate())) {
                 accruedTill = DateUtils.getBusinessLocalDate();
             }
             if (!isOrganisationDateEnabled || DateUtils.isBefore(organisationStartDate, installment.getDueDate())) {
+                boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
                 generateLoanScheduleAccrualData(accruedTill, loanScheduleAccrualList, loanId, officeId, accrualStartDate,
                         repaymentFrequency, repayEvery, interestCalculatedFrom, loanProductId, currency, currencyData, loanCharges,
-                        installment);
+                        installment, isFirstNormalInstallment);
             }
         }
 
@@ -639,7 +641,8 @@
             final Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas, final Long loanId, Long officeId,
             final LocalDate accrualStartDate, final PeriodFrequencyType repaymentFrequency, final Integer repayEvery,
             final LocalDate interestCalculatedFrom, final Long loanProductId, final MonetaryCurrency currency,
-            final CurrencyData currencyData, final Set<LoanCharge> loanCharges, final LoanRepaymentScheduleInstallment installment) {
+            final CurrencyData currencyData, final Set<LoanCharge> loanCharges, final LoanRepaymentScheduleInstallment installment,
+            boolean isFirstNormalInstallment) {
 
         if (!DateUtils.isBefore(accruedTill, installment.getDueDate()) || (DateUtils.isAfter(accruedTill, installment.getFromDate())
                 && !DateUtils.isAfter(accruedTill, installment.getDueDate()))) {
@@ -651,7 +654,7 @@
             }
 
             for (final LoanCharge loanCharge : loanCharges) {
-                boolean isDue = installment.isFirstPeriod()
+                boolean isDue = isFirstNormalInstallment
                         ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), chargesTillDate)
                         : loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), chargesTillDate);
                 if (isDue) {
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
index 3856ced..7cffe27 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
@@ -109,7 +109,6 @@
         Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
         Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
         Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
-        Mockito.when(installment.isFirstPeriod()).thenReturn(true);
         Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
                 .thenReturn(true);
         Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
@@ -154,7 +153,6 @@
         Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
         Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
         Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
-        Mockito.when(installment.isFirstPeriod()).thenReturn(true);
         Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
                 .thenReturn(true);
         Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
@@ -204,7 +202,6 @@
         Mockito.when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
         Mockito.when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
         Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
-        Mockito.when(installment.isFirstPeriod()).thenReturn(true);
         Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
                 .thenReturn(true);
         Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransactionTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransactionTest.java
index 892fc65..20ac223 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransactionTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransactionTest.java
@@ -41,6 +41,14 @@
     }
 
     @Test
+    public void testCompareToEqualBackdatedCharge() {
+        ChargeOrTransaction charge = createCharge("2023-10-16", "2023-10-17", "2023-10-17T10:15:30+01:00");
+        ChargeOrTransaction transaction = createTransaction("2023-10-16", "2023-10-17", "2023-10-17T10:15:30+01:00");
+        Assertions.assertTrue(charge.compareTo(transaction) == 0);
+        Assertions.assertTrue(transaction.compareTo(charge) == 0);
+    }
+
+    @Test
     public void testCompareToCreatedDateTime() {
         ChargeOrTransaction charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:31+01:00");
         ChargeOrTransaction transaction = createTransaction("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
@@ -77,7 +85,7 @@
     }
 
     @Test
-    public void testComparatorOnSameDay() {
+    public void testComparatorOnSameDayBackdatedCharge() {
         ChargeOrTransaction cot1 = createCharge("2023-10-17", "2023-10-19", "2023-10-19T10:15:31+01:00");
         ChargeOrTransaction cot2 = createTransaction("2023-10-17", "2023-10-19", "2023-10-19T10:15:33+01:00");
         ChargeOrTransaction cot3 = createCharge("2023-10-17", "2023-10-19", "2023-10-19T10:15:32+01:00");
@@ -88,6 +96,18 @@
         }
     }
 
+    @Test
+    public void testComparatorOnSameDay() {
+        ChargeOrTransaction cot1 = createCharge("2023-10-24", "2023-10-19", "2023-10-19T10:15:31+01:00");
+        ChargeOrTransaction cot2 = createTransaction("2023-10-19", "2023-10-19", "2023-10-19T10:15:33+01:00");
+        ChargeOrTransaction cot3 = createCharge("2023-10-24", "2023-10-19", "2023-10-19T10:15:32+01:00");
+        Collection<List<ChargeOrTransaction>> permutations = Collections2.permutations(List.of(cot1, cot2, cot3));
+        List<ChargeOrTransaction> expected = List.of(cot1, cot3, cot2);
+        for (List<ChargeOrTransaction> permutation : permutations) {
+            Assertions.assertEquals(expected, permutation.stream().sorted().toList());
+        }
+    }
+
     private ChargeOrTransaction createCharge(String effectiveDate, String submittedDate, String creationDateTime) {
         LoanCharge charge = Mockito.mock(LoanCharge.class);
         Mockito.when(charge.getDueDate()).thenReturn(LocalDate.parse(effectiveDate));
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
index baa5bf3..64c51a6 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
@@ -39,6 +39,7 @@
 import java.util.stream.Collectors;
 import org.apache.fineract.client.models.AdvancedPaymentData;
 import org.apache.fineract.client.models.BusinessDateRequest;
+import org.apache.fineract.client.models.GetLoansLoanIdLoanChargeData;
 import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.client.models.PaymentAllocationOrder;
@@ -2522,6 +2523,445 @@
         }
     }
 
+    // UC112: Advanced payment allocation, horizontal repayment processing
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Disburse the loan (1000)
+    // 2. Add charge after maturity date
+    // 3. Pay 1st installment
+    // 4. Pay 2nd installment
+    // 5. Add charge to 3rd installment
+    // 6. Add charge to 4th installment
+    // 7. Do goodwill credit (in advance payment)
+    @Test
+    public void uc112() {
+        try {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
+            businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en"));
+
+            final Account assetAccount = accountHelper.createAssetAccount();
+            final Account incomeAccount = accountHelper.createIncomeAccount();
+            final Account expenseAccount = accountHelper.createExpenseAccount();
+            final Account overpaymentAccount = accountHelper.createLiabilityAccount();
+            Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE,
+                    LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
+            final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId,
+                    BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023");
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("01 September 2023").locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("01 September 2023").dateFormat(DATETIME_PATTERN)
+                            .transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 1000.0, 0.0, 1000.0, 0.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            // Add Charge Penalty
+            Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
+                    ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", true));
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 October 2023", "20"));
+
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 1020.0, 0.0, 1000.0, 0.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("01 September 2023").locale("en").transactionAmount(250.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 770.0, 250.0, 750.0, 250.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2023.09.16").dateFormat("yyyy.MM.dd").locale("en"));
+
+            loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(250.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 520.0, 500.0, 500.0, 500.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 September 2023", "20"));
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "16 October 2023", "20"));
+
+            loanTransactionHelper.makeGoodwillCredit(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(50.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 510.0, 550.0, 490.0, 510.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 10.0, 240.0, 0.0, 0.0, 0.0, 20.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 30.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 20.0, 0.0, 0.0, 0.0,
+                    0.0, 20.0, 0.0);
+
+            validateLoanCharge(loanDetails, 0, LocalDate.of(2023, 9, 17), 20.0, 0.0, 20.0);
+            validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 20.0, 0.0);
+            validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 20.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+        } finally {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+        }
+    }
+
+    // UC113: Advanced payment allocation, vertical repayment processing
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Disburse the loan (1000)
+    // 2. Add charge after maturity date
+    // 3. Pay 1st installment
+    // 4. Pay 2nd installment
+    // 5. Add charge to 3rd installment
+    // 6. Add charge to 4th installment
+    // 7. Do goodwill credit (in advance payment)
+    @Test
+    public void uc113() {
+        try {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
+            businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en"));
+
+            final Account assetAccount = accountHelper.createAssetAccount();
+            final Account incomeAccount = accountHelper.createIncomeAccount();
+            final Account expenseAccount = accountHelper.createExpenseAccount();
+            final Account overpaymentAccount = accountHelper.createLiabilityAccount();
+            Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE,
+                    LoanScheduleProcessingType.VERTICAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
+            final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId,
+                    BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023");
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("01 September 2023").locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("01 September 2023").dateFormat(DATETIME_PATTERN)
+                            .transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 1000.0, 0.0, 1000.0, 0.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            // Add Charge Penalty
+            Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
+                    ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", true));
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 October 2023", "20"));
+
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 1020.0, 0.0, 1000.0, 0.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("01 September 2023").locale("en").transactionAmount(250.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 770.0, 250.0, 750.0, 250.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2023.09.16").dateFormat("yyyy.MM.dd").locale("en"));
+
+            loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(250.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 520.0, 500.0, 500.0, 500.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 September 2023", "20"));
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "16 October 2023", "20"));
+
+            loanTransactionHelper.makeGoodwillCredit(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(50.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 510.0, 550.0, 500.0, 500.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0, 0.0,
+                    0.0, 10.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 20.0, 0.0, 0.0, 0.0,
+                    0.0, 20.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 20.0, 0.0, 0.0, 0.0,
+                    0.0, 20.0, 0.0);
+            validateLoanCharge(loanDetails, 0, LocalDate.of(2023, 9, 17), 20.0, 10.0, 10.0);
+            validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 20.0, 0.0);
+            validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 20.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+        } finally {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+        }
+    }
+
+    // UC114: Advanced payment allocation, horizontal repayment processing
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Disburse the loan (1000)
+    // 2. Add charge after maturity date
+    // 3. Pay 1st installment
+    // 4. Pay 2nd installment
+    // 5. Add charge to 3rd installment
+    // 6. Add charge to 4th installment
+    // 7. Do merchant issued refund (in advance payment)
+    @Test
+    public void uc114() {
+        try {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
+            businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en"));
+
+            final Account assetAccount = accountHelper.createAssetAccount();
+            final Account incomeAccount = accountHelper.createIncomeAccount();
+            final Account expenseAccount = accountHelper.createExpenseAccount();
+            final Account overpaymentAccount = accountHelper.createLiabilityAccount();
+            Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE,
+                    LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
+            final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId,
+                    BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023");
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("01 September 2023").locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("01 September 2023").dateFormat(DATETIME_PATTERN)
+                            .transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 1000.0, 0.0, 1000.0, 0.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            // Add Charge Penalty
+            Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
+                    ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", true));
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 October 2023", "20"));
+
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 1020.0, 0.0, 1000.0, 0.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("01 September 2023").locale("en").transactionAmount(250.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 770.0, 250.0, 750.0, 250.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2023.09.16").dateFormat("yyyy.MM.dd").locale("en"));
+
+            loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(250.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 520.0, 500.0, 500.0, 500.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 September 2023", "20"));
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "16 October 2023", "20"));
+
+            loanTransactionHelper.makeMerchantIssuedRefund(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(30.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 530.0, 530.0, 500.0, 500.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0, 0.0,
+                    0.0, 10.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0,
+                    0.0, 0.0, 10.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0, 0.0,
+                    0.0, 10.0, 0.0);
+            validateLoanCharge(loanDetails, 0, LocalDate.of(2023, 9, 17), 20.0, 10.0, 10.0);
+            validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 10.0, 10.0);
+            validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 10.0, 10.0);
+            assertTrue(loanDetails.getStatus().getActive());
+        } finally {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+        }
+    }
+
+    // UC115: Advanced payment allocation, vertical repayment processing
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Disburse the loan (1000)
+    // 2. Add charge after maturity date
+    // 3. Pay 1st installment
+    // 4. Pay 2nd installment
+    // 5. Add charge to 3rd installment
+    // 6. Add charge to 4th installment
+    // 7. Do merchant issued refund (in advance payment)
+    @Test
+    public void uc115() {
+        try {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
+            businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en"));
+
+            final Account assetAccount = accountHelper.createAssetAccount();
+            final Account incomeAccount = accountHelper.createIncomeAccount();
+            final Account expenseAccount = accountHelper.createExpenseAccount();
+            final Account overpaymentAccount = accountHelper.createLiabilityAccount();
+            Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE,
+                    LoanScheduleProcessingType.VERTICAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
+            final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId,
+                    BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023");
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("01 September 2023").locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("01 September 2023").dateFormat(DATETIME_PATTERN)
+                            .transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 1000.0, 0.0, 1000.0, 0.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            // Add Charge Penalty
+            Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
+                    ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", true));
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 October 2023", "20"));
+
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 1020.0, 0.0, 1000.0, 0.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("01 September 2023").locale("en").transactionAmount(250.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 770.0, 250.0, 750.0, 250.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2023.09.16").dateFormat("yyyy.MM.dd").locale("en"));
+
+            loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(250.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 520.0, 500.0, 500.0, 500.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
+                    0.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 September 2023", "20"));
+            loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
+                    LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "16 October 2023", "20"));
+            loanTransactionHelper.makeMerchantIssuedRefund(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(30.0));
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 530.0, 530.0, 500.0, 500.0, null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0, 0.0,
+                    0.0, 10.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0,
+                    0.0, 0.0, 10.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0, 0.0,
+                    0.0, 10.0, 0.0);
+            validateLoanCharge(loanDetails, 0, LocalDate.of(2023, 9, 17), 20.0, 10.0, 10.0);
+            validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 10.0, 10.0);
+            validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 10.0, 10.0);
+            assertTrue(loanDetails.getStatus().getActive());
+        } finally {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+        }
+    }
+
     private static void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment,
             Double principalOutstanding, Double principalPaid, Double totalOverpaid) {
         assertEquals(totalOutstanding, loanDetails.getSummary().getTotalOutstanding());
@@ -2723,4 +3163,13 @@
         assertEquals(overPaidPortion, loanDetails.getTransactions().get(index).getOverpaymentPortion());
         assertEquals(loanBalance, loanDetails.getTransactions().get(index).getOutstandingLoanBalance());
     }
+
+    private void validateLoanCharge(GetLoansLoanIdResponse loanDetails, int index, LocalDate dueDate, double charged, double paid,
+            double outstanding) {
+        GetLoansLoanIdLoanChargeData chargeData = loanDetails.getCharges().get(index);
+        assertEquals(dueDate, chargeData.getDueDate());
+        assertEquals(charged, chargeData.getAmount());
+        assertEquals(paid, chargeData.getAmountPaid());
+        assertEquals(outstanding, chargeData.getAmountOutstanding());
+    }
 }