This is an automated email from the ASF dual-hosted git repository.

aleks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit c2e19bb68faf637f4aa473f0278e122c9230a42c
Author: mariiaKraievska <[email protected]>
AuthorDate: Fri Jul 18 19:03:18 2025 +0300

    FINERACT-2327: Allow to create Interest Refund transaction manually
---
 .../commands/service/CommandWrapperBuilder.java    |   9 +
 .../apache/fineract/test/data/TransactionType.java |   1 +
 .../fineract/test/helper/ErrorMessageHelper.java   |   4 +
 .../test/messaging/event/EventCheckHelper.java     |   5 +-
 .../LoanTransactionInterestRefundPostEvent.java    |  27 +++
 .../fineract/test/stepdef/loan/LoanStepDef.java    |  74 ++++++++
 .../fineract/test/support/TestContextKey.java      |   1 +
 .../features/LoanMerchantIssuedRefund.feature      | 200 ++++++++++++++++++++-
 .../resources/features/LoanPayoutRefund.feature    | 200 ++++++++++++++++++++-
 .../domain/LoanAccountDomainService.java           |   3 +
 .../loanaccount/domain/LoanTransaction.java        |   5 +
 .../ManualInterestRefundCommandHandler.java        |  42 +++++
 .../serialization/LoanTransactionValidator.java    |   2 +
 .../service/LoanReadPlatformService.java           |   2 +
 .../service/LoanWritePlatformService.java          |   2 +
 .../ProgressiveLoanTransactionValidatorImpl.java   |   5 +
 .../api/LoanTransactionsApiResource.java           |  11 +-
 .../domain/LoanAccountDomainServiceJpa.java        |   6 +
 .../LoanTransactionValidatorImpl.java              |  31 ++++
 .../service/LoanReadPlatformServiceImpl.java       |  36 ++++
 .../LoanWritePlatformServiceJpaRepositoryImpl.java | 134 +++++++++++++-
 .../starter/LoanAccountConfiguration.java          |   7 +-
 22 files changed, 799 insertions(+), 8 deletions(-)

diff --git 
a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
 
b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 42f23b6bac..f0321581b6 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -3885,4 +3885,13 @@ public class CommandWrapperBuilder {
         this.href = "/loans/" + loanId;
         return this;
     }
+
+    public CommandWrapperBuilder manualInterestRefund(final Long loanId, final 
Long transactionId) {
+        this.actionName = "MANUAL_INTEREST_REFUND";
+        this.entityName = "LOAN_TRANSACTION";
+        this.loanId = loanId;
+        this.entityId = transactionId;
+        this.href = "/loans/" + loanId + "/transactions/" + transactionId + 
"?command=interest-refund";
+        return this;
+    }
 }
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java
index 74c0330554..8029dd6d74 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java
@@ -41,6 +41,7 @@ public enum TransactionType {
     BUY_DOWN_FEE("buyDownFee"), //
     BUY_DOWN_FEE_ADJUSTMENT("buyDownFeeAdjustment"), //
     BUY_DOWN_FEE_AMORTIZATION("buyDownFeeAmortization"), //
+    INTEREST_REFUND("interestRefund"), //
     ;
 
     public final String value;
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
index 595abad3ef..dd0a911663 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
@@ -68,6 +68,10 @@ public final class ErrorMessageHelper {
         return "Loan can't be disbursed, disburse amount is exceeding approved 
principal.";
     }
 
+    public static String addManualInterestRefundIfAlreadyExistsFailure() {
+        return "Interest Refund already exists for this transaction";
+    }
+
     public static String addDisbursementExceedMaxAppliedAmountFailure(String 
totalDisbAmount, String maxDisbursalAmount) {
         return String.format("Loan disbursal amount can't be greater than 
maximum applied loan amount calculation. "
                 + "Total disbursed amount: %s  Maximum disbursal amount: %s", 
totalDisbAmount, maxDisbursalAmount);
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java
index f7e0ea509f..d3f876f008 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java
@@ -76,6 +76,7 @@ import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanDisbursalTr
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanRefundPostBusinessEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionGoodwillCreditPostEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionInterestPaymentWaiverPostEvent;
+import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionInterestRefundPostEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionMakeRepaymentPostEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionMerchantIssuedRefundPostEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionPayoutRefundPostEvent;
@@ -308,7 +309,8 @@ public class EventCheckHelper {
             Response<PostLoansLoanIdTransactionsResponse> transactionResponse, 
TransactionType transactionType, String externalOwnerId)
             throws IOException {
         Long loanId = transactionResponse.body().getLoanId();
-        Long transactionId = transactionResponse.body().getResourceId();
+        Long transactionId = 
transactionType.equals(TransactionType.INTEREST_REFUND) ? 
transactionResponse.body().getSubResourceId()
+                : transactionResponse.body().getResourceId();
         Response<GetLoansLoanIdResponse> loanDetailsResponse = 
loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
         List<GetLoansLoanIdTransactions> transactions = 
loanDetailsResponse.body().getTransactions();
         GetLoansLoanIdTransactions transactionFound = transactions//
@@ -324,6 +326,7 @@ public class EventCheckHelper {
             case MERCHANT_ISSUED_REFUND -> 
LoanTransactionMerchantIssuedRefundPostEvent.class;
             case REFUND_BY_CASH -> LoanRefundPostBusinessEvent.class;
             case INTEREST_PAYMENT_WAIVER -> 
LoanTransactionInterestPaymentWaiverPostEvent.class;
+            case INTEREST_REFUND -> 
LoanTransactionInterestRefundPostEvent.class;
             default -> throw new 
IllegalStateException(String.format("transaction type %s cannot be found", 
transactionType.getValue()));
         };
 
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionInterestRefundPostEvent.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionInterestRefundPostEvent.java
new file mode 100644
index 0000000000..8be3a75e6d
--- /dev/null
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionInterestRefundPostEvent.java
@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.test.messaging.event.loan.transaction;
+
+public class LoanTransactionInterestRefundPostEvent extends 
AbstractLoanTransactionEvent {
+
+    @Override
+    public String getEventName() {
+        return "LoanTransactionInterestRefundPostBusinessEvent";
+    }
+}
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
index 8e82eb3d10..a4c667045f 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
@@ -433,6 +433,63 @@ public class LoanStepDef extends AbstractStepDef {
         eventCheckHelper.loanBalanceChangedEventCheck(loanId);
     }
 
+    @When("Admin manually adds Interest Refund for {string} transaction made 
on {string} with {double} EUR interest refund amount")
+    public void addInterestRefundTransactionManually(final String 
transactionTypeInput, final String transactionDate, final double amount)
+            throws IOException {
+        final Response<PostLoansResponse> loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        final long loanId = loanResponse.body().getLoanId();
+        final TransactionType transactionType = 
TransactionType.valueOf(transactionTypeInput);
+
+        final Response<GetLoansLoanIdResponse> loanDetailsResponse = 
loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
+        ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
+
+        assert loanDetailsResponse.body() != null;
+        final List<GetLoansLoanIdTransactions> transactions = 
loanDetailsResponse.body().getTransactions();
+        assert transactions != null;
+        final GetLoansLoanIdTransactions refundTransaction = 
transactions.stream()
+                .filter(t -> t.getType() != null
+                        && 
(transactionType.equals(TransactionType.PAYOUT_REFUND) ? "Payout Refund" : 
"Merchant Issued Refund")
+                                .equals(t.getType().getValue())
+                        && t.getDate() != null && 
transactionDate.equals(FORMATTER.format(t.getDate())))
+                .findFirst().orElseThrow(() -> new IllegalStateException("No 
refund transaction found for loan " + loanId));
+
+        final Response<PostLoansLoanIdTransactionsResponse> adjustmentResponse 
= addInterestRefundTransaction(amount,
+                refundTransaction.getId());
+        testContext().set(TestContextKey.LOAN_INTEREST_REFUND_RESPONSE, 
adjustmentResponse);
+        ErrorHelper.checkSuccessfulApiCall(adjustmentResponse);
+        eventCheckHelper.transactionEventCheck(adjustmentResponse, 
TransactionType.INTEREST_REFUND, null);
+        eventCheckHelper.loanBalanceChangedEventCheck(loanId);
+    }
+
+    @Then("Admin fails to add duplicate Interest Refund for {string} 
transaction made on {string} with {double} EUR interest refund amount")
+    public void failToAddManualInterestRefundIfAlreadyExists(final String 
transactionTypeInput, final String transactionDate,
+            final double amount) throws IOException {
+        final Response<PostLoansResponse> loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        final long loanId = loanResponse.body().getLoanId();
+        final TransactionType transactionType = 
TransactionType.valueOf(transactionTypeInput);
+
+        final Response<GetLoansLoanIdResponse> loanDetailsResponse = 
loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
+        ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
+
+        assert loanDetailsResponse.body() != null;
+        final List<GetLoansLoanIdTransactions> transactions = 
loanDetailsResponse.body().getTransactions();
+        assert transactions != null;
+        final GetLoansLoanIdTransactions refundTransaction = 
transactions.stream()
+                .filter(t -> t.getType() != null
+                        && 
(transactionType.equals(TransactionType.PAYOUT_REFUND) ? "Payout Refund" : 
"Merchant Issued Refund")
+                                .equals(t.getType().getValue())
+                        && t.getDate() != null && 
transactionDate.equals(FORMATTER.format(t.getDate())))
+                .findFirst().orElseThrow(() -> new IllegalStateException("No 
refund transaction found for loan " + loanId));
+
+        final Response<PostLoansLoanIdTransactionsResponse> adjustmentResponse 
= addInterestRefundTransaction(amount,
+                refundTransaction.getId());
+        testContext().set(TestContextKey.LOAN_INTEREST_REFUND_RESPONSE, 
adjustmentResponse);
+        final ErrorResponse errorDetails = 
ErrorResponse.from(adjustmentResponse);
+        
assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.addManualInterestRefundIfAlreadyExistsFailure()).isEqualTo(403);
+        assertThat(errorDetails.getSingleError().getDeveloperMessage())
+                
.isEqualTo(ErrorMessageHelper.addManualInterestRefundIfAlreadyExistsFailure());
+    }
+
     private void 
createTransactionWithAutoIdempotencyKeyAndWithExternalOwner(String 
transactionTypeInput, String transactionPaymentType,
             String transactionDate, double transactionAmount, String 
externalOwnerId) throws IOException {
         eventStore.reset();
@@ -4984,4 +5041,21 @@ public class LoanStepDef extends AbstractStepDef {
         }
         
assertThat(developerMessage).isEqualTo(ErrorMessageHelper.updateApprovedLoanLessMinAllowedAmountFailure());
     }
+
+    private Response<PostLoansLoanIdTransactionsResponse> 
addInterestRefundTransaction(final double amount, final Long transactionId)
+            throws IOException {
+        final Response<PostLoansResponse> loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        assert loanResponse.body() != null;
+        final long loanId = loanResponse.body().getLoanId();
+
+        final DefaultPaymentType paymentType = DefaultPaymentType.AUTOPAY;
+        final Long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
+
+        final PostLoansLoanIdTransactionsTransactionIdRequest 
interestRefundRequest = new PostLoansLoanIdTransactionsTransactionIdRequest()
+                .dateFormat("dd MMMM 
yyyy").locale("en").transactionAmount(amount).paymentTypeId(paymentTypeValue)
+                .externalId("EXT-INT-REF-" + UUID.randomUUID()).note("");
+
+        return loanTransactionsApi.adjustLoanTransaction(loanId, 
transactionId, interestRefundRequest, "interest-refund").execute();
+    }
+
 }
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
index 9b7c554cc9..a70ddc138a 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
@@ -241,6 +241,7 @@ public abstract class TestContextKey {
     public static final String LOAN_DISBURSEMENT_DETAIL_RESPONSE = 
"loanDisbursementDetailResponse";
     public static final String LOAN_CAPITALIZED_INCOME_AMORTIZATION_ID = 
"loanCapitalizedIncomeAmortizationId";
     public static final String LOAN_CAPITALIZED_INCOME_ADJUSTMENT_RESPONSE = 
"loanCapitalizedIncomeAdjustmentResponse";
+    public static final String LOAN_INTEREST_REFUND_RESPONSE = 
"loanInterestRefundResponse";
     public static final String 
DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION
 = 
"loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTermination";
     public static final String LOAN_CONTRACT_TERMINATION_RESPONSE = 
"loanContractTerminationResponse";
     public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = 
"loanUndoContractTerminationResponse";
diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature
 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature
index 89c1536a34..1a9aacd2b8 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature
@@ -333,4 +333,202 @@ Feature: MerchantIssuedRefund
       | 01 August 2024   | Accrual                | 8.33    | 0.0       | 8.33 
    | 0.0  | 0.0       | 0.0          | false    | false    |
       | 01 August 2024   | Accrual Activity       | 8.33    | 0.0       | 8.33 
    | 0.0  | 0.0       | 0.0          | false    | false    |
       | 05 August 2024   | Merchant Issued Refund | 10.0    | 0.0       | 0.0  
    | 0.0  | 0.0       | 0.0          | false    | false    |
-    Then Loan status will be "OVERPAID"
\ No newline at end of file
+    Then Loan status will be "OVERPAID"
+
+  @TestRailId:C3873
+  Scenario: Manual Interest Refund creation for Merchant Issued Refund with 
interestRefundCalculation = false
+    When Admin sets the business date to "01 July 2024"
+    And Admin creates a client with random data
+    And Admin creates a fully customized loan with the following data:
+      | LoanProduct                                                   | 
submitted on date | with Principal | ANNUAL interest rate % | interest type     
| interest calculation period | amortization type  | loanTermFrequency | 
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | 
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | 
interest free period | Payment strategy            |
+      | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 
July 2024      | 1000           | 10                     | DECLINING_BALANCE | 
DAILY                       | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 July 2024" with "1000" 
amount and expected disbursement date on "01 July 2024"
+    And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR 
transaction amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid | In advance | Late 
| Outstanding |
+      |    |      | 01 July 2024      |           | 1000.0          |          
     |          | 0.0  |           | 0.0    | 0.0  |            |      |        
     |
+      | 1  | 31   | 01 August 2024    |           | 669.43          | 330.57   
     | 8.33     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 2  | 31   | 01 September 2024 |           | 336.11          | 333.32   
     | 5.58     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 3  | 30   | 01 October 2024   |           | 0.0             | 336.11   
     | 2.8      | 0.0  | 0.0       | 338.91 | 0.0  | 0.0        | 0.0  | 338.91 
     |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid | In 
advance | Late | Outstanding |
+      | 1000.0        | 16.71    | 0.0  | 0.0       | 1016.71 | 0.0  | 0.0     
   | 0.0  | 1016.71     |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+    When Admin sets the business date to "10 July 2024"
+    And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on 
"10 July 2024" with 338.9 EUR transaction amount and system-generated 
Idempotency key
+    When Admin sets the business date to "15 July 2024"
+    And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" 
payment type on "15 July 2024" with 50 EUR transaction amount and 
system-generated Idempotency key and interestRefundCalculation false
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid  | In advance | 
Late | Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0    | 0.0   |            |      |    
         |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9  | 338.9 | 338.9      | 0.0 
 | 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 333.42          | 330.1 
        | 8.8      | 0.0  | 0.0       | 338.9  | 0.0   | 0.0        | 0.0  | 
338.9       |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
333.42        | 2.36     | 0.0  | 0.0       | 335.78 | 50.0  | 50.0       | 0.0 
 | 285.78      |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 13.58    | 0.0  | 0.0       | 1013.58 | 388.9  | 388.9 
     | 0.0  | 624.68      |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type       | Amount | Principal | 
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement           | 1000.0 | 0.0       | 0.0   
   | 0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment              | 338.9  | 336.48    | 2.42  
   | 0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Merchant Issued Refund | 50.0   | 50.0      | 0.0   
   | 0.0  | 0.0       | 613.52       | false    | false    |
+    When Admin manually adds Interest Refund for "MERCHANT_ISSUED_REFUND" 
transaction made on "15 July 2024" with 0.19 EUR interest refund amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid  | In advance | 
Late | Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0    | 0.0   |            |      |    
         |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9  | 338.9 | 338.9      | 0.0 
 | 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 333.42          | 330.1 
        | 8.8      | 0.0  | 0.0       | 338.9  | 0.19  | 0.19       | 0.0  | 
338.71      |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
333.42        | 2.36     | 0.0  | 0.0       | 335.78 | 50.0  | 50.0       | 0.0 
 | 285.78      |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 13.58    | 0.0  | 0.0       | 1013.58 | 389.09 | 
389.09     | 0.0  | 624.49      |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type       | Amount | Principal | 
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement           | 1000.0 | 0.0       | 0.0   
   | 0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment              | 338.9  | 336.48    | 2.42  
   | 0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Merchant Issued Refund | 50.0   | 50.0      | 0.0   
   | 0.0  | 0.0       | 613.52       | false    | false    |
+      | 15 July 2024     | Interest Refund        | 0.19   | 0.0       | 0.19  
   | 0.0  | 0.0       | 613.52       | false    | false    |
+    Then Loan Transactions tab has a "MERCHANT_ISSUED_REFUND" transaction with 
date "15 July 2024" which has the following Journal entries:
+      | Type      | Account code | Account name              | Debit | Credit |
+      | ASSET     | 112601       | Loans Receivable          |       | 50.0   |
+      | LIABILITY | 145023       | Suspense/Clearing account | 50.0  |        |
+    Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date 
"15 July 2024" which has the following Journal entries:
+      | Type   | Account code | Account name            | Debit | Credit |
+      | ASSET  | 112603       | Interest/Fee Receivable |       | 0.19   |
+      | INCOME | 404000       | Interest Income         | 0.19  |        |
+
+  @TestRailId:C3874
+  Scenario: Undo Merchant Issued Refund with manual Interest Refund, both 
transactions are reversed
+    When Admin sets the business date to "01 July 2024"
+    And Admin creates a client with random data
+    And Admin creates a fully customized loan with the following data:
+      | LoanProduct                                                   | 
submitted on date | with Principal | ANNUAL interest rate % | interest type     
| interest calculation period | amortization type  | loanTermFrequency | 
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | 
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | 
interest free period | Payment strategy            |
+      | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 
July 2024      | 1000           | 10                     | DECLINING_BALANCE | 
DAILY                       | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 July 2024" with "1000" 
amount and expected disbursement date on "01 July 2024"
+    And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR 
transaction amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid | In advance | Late 
| Outstanding |
+      |    |      | 01 July 2024      |           | 1000.0          |          
     |          | 0.0  |           | 0.0    | 0.0  |            |      |        
     |
+      | 1  | 31   | 01 August 2024    |           | 669.43          | 330.57   
     | 8.33     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 2  | 31   | 01 September 2024 |           | 336.11          | 333.32   
     | 5.58     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 3  | 30   | 01 October 2024   |           | 0.0             | 336.11   
     | 2.8      | 0.0  | 0.0       | 338.91 | 0.0  | 0.0        | 0.0  | 338.91 
     |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid | In 
advance | Late | Outstanding |
+      | 1000.0        | 16.71    | 0.0  | 0.0       | 1016.71 | 0.0  | 0.0     
   | 0.0  | 1016.71     |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+    When Admin sets the business date to "10 July 2024"
+    And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on 
"10 July 2024" with 338.9 EUR transaction amount and system-generated 
Idempotency key
+    When Admin sets the business date to "15 July 2024"
+    And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" 
payment type on "15 July 2024" with 50 EUR transaction amount and 
system-generated Idempotency key and interestRefundCalculation false
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid  | In advance | 
Late | Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0    | 0.0   |            |      |    
         |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9  | 338.9 | 338.9      | 0.0 
 | 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 333.42          | 330.1 
        | 8.8      | 0.0  | 0.0       | 338.9  | 0.0   | 0.0        | 0.0  | 
338.9       |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
333.42        | 2.36     | 0.0  | 0.0       | 335.78 | 50.0  | 50.0       | 0.0 
 | 285.78      |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 13.58    | 0.0  | 0.0       | 1013.58 | 388.9  | 388.9 
     | 0.0  | 624.68      |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type       | Amount | Principal | 
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement           | 1000.0 | 0.0       | 0.0   
   | 0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment              | 338.9  | 336.48    | 2.42  
   | 0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Merchant Issued Refund | 50.0   | 50.0      | 0.0   
   | 0.0  | 0.0       | 613.52       | false    | false    |
+    When Admin manually adds Interest Refund for "MERCHANT_ISSUED_REFUND" 
transaction made on "15 July 2024" with 0.19 EUR interest refund amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid  | In advance | 
Late | Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0    | 0.0   |            |      |    
         |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9  | 338.9 | 338.9      | 0.0 
 | 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 333.42          | 330.1 
        | 8.8      | 0.0  | 0.0       | 338.9  | 0.19  | 0.19       | 0.0  | 
338.71      |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
333.42        | 2.36     | 0.0  | 0.0       | 335.78 | 50.0  | 50.0       | 0.0 
 | 285.78      |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 13.58    | 0.0  | 0.0       | 1013.58 | 389.09 | 
389.09     | 0.0  | 624.49      |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type       | Amount | Principal | 
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement           | 1000.0 | 0.0       | 0.0   
   | 0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment              | 338.9  | 336.48    | 2.42  
   | 0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Merchant Issued Refund | 50.0   | 50.0      | 0.0   
   | 0.0  | 0.0       | 613.52       | false    | false    |
+      | 15 July 2024     | Interest Refund        | 0.19   | 0.0       | 0.19  
   | 0.0  | 0.0       | 613.52       | false    | false    |
+    Then Loan Transactions tab has a "MERCHANT_ISSUED_REFUND" transaction with 
date "15 July 2024" which has the following Journal entries:
+      | Type      | Account code | Account name              | Debit | Credit |
+      | ASSET     | 112601       | Loans Receivable          |       | 50.0   |
+      | LIABILITY | 145023       | Suspense/Clearing account | 50.0  |        |
+    Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date 
"15 July 2024" which has the following Journal entries:
+      | Type   | Account code | Account name            | Debit | Credit |
+      | ASSET  | 112603       | Interest/Fee Receivable |       | 0.19   |
+      | INCOME | 404000       | Interest Income         | 0.19  |        |
+    When Customer undo "1"th transaction made on "15 July 2024"
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid  | In advance | 
Late | Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0    | 0.0   |            |      |    
         |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9  | 338.9 | 338.9      | 0.0 
 | 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 334.07          | 
329.45        | 9.45     | 0.0  | 0.0       | 338.9  | 0.0   | 0.0        | 0.0 
 | 338.9       |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
334.07        | 2.78     | 0.0  | 0.0       | 336.85 | 0.0   | 0.0        | 0.0 
 | 336.85      |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 14.65    | 0.0  | 0.0       | 1014.65 | 338.9  | 338.9 
     | 0.0  | 675.75      |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type       | Amount | Principal | 
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement           | 1000.0 | 0.0       | 0.0   
   | 0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment              | 338.9  | 336.48    | 2.42  
   | 0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Merchant Issued Refund | 50.0   | 50.0      | 0.0   
   | 0.0  | 0.0       | 613.52       | true     | false    |
+      | 15 July 2024     | Interest Refund        | 0.19   | 0.0       | 0.19  
   | 0.0  | 0.0       | 613.52       | true     | false    |
+    Then Loan Transactions tab has a "MERCHANT_ISSUED_REFUND" transaction with 
date "15 July 2024" which has the following Journal entries:
+      | Type      | Account code | Account name              | Debit | Credit |
+      | ASSET     | 112601       | Loans Receivable          |       | 50.0   |
+      | LIABILITY | 145023       | Suspense/Clearing account | 50.0  |        |
+      | ASSET     | 112601       | Loans Receivable          | 50.0  |        |
+      | LIABILITY | 145023       | Suspense/Clearing account |       | 50.0   |
+    Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date 
"15 July 2024" which has the following Journal entries:
+      | Type   | Account code | Account name            | Debit | Credit |
+      | ASSET  | 112603       | Interest/Fee Receivable |       | 0.19   |
+      | INCOME | 404000       | Interest Income         | 0.19  |        |
+      | ASSET  | 112603       | Interest/Fee Receivable | 0.19  |        |
+      | INCOME | 404000       | Interest Income         |       | 0.19   |
+
+  @TestRailId:C3875
+  Scenario: Prevent manual Interest Refund creation if 
interestRefundCalculation = true and Interest Refund already exists
+    When Admin sets the business date to "01 July 2024"
+    And Admin creates a client with random data
+    And Admin creates a fully customized loan with the following data:
+      | LoanProduct                                                   | 
submitted on date | with Principal | ANNUAL interest rate % | interest type     
| interest calculation period | amortization type  | loanTermFrequency | 
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | 
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | 
interest free period | Payment strategy            |
+      | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 
July 2024      | 1000           | 10                     | DECLINING_BALANCE | 
DAILY                       | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 July 2024" with "1000" 
amount and expected disbursement date on "01 July 2024"
+    And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR 
transaction amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid | In advance | Late 
| Outstanding |
+      |    |      | 01 July 2024      |           | 1000.0          |          
     |          | 0.0  |           | 0.0    | 0.0  |            |      |        
     |
+      | 1  | 31   | 01 August 2024    |           | 669.43          | 330.57   
     | 8.33     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 2  | 31   | 01 September 2024 |           | 336.11          | 333.32   
     | 5.58     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 3  | 30   | 01 October 2024   |           | 0.0             | 336.11   
     | 2.8      | 0.0  | 0.0       | 338.91 | 0.0  | 0.0        | 0.0  | 338.91 
     |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid | In 
advance | Late | Outstanding |
+      | 1000.0        | 16.71    | 0.0  | 0.0       | 1016.71 | 0.0  | 0.0     
   | 0.0  | 1016.71     |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+    When Admin sets the business date to "10 July 2024"
+    And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on 
"10 July 2024" with 338.9 EUR transaction amount and system-generated 
Idempotency key
+    When Admin sets the business date to "15 July 2024"
+    And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" 
payment type on "15 July 2024" with 50 EUR transaction amount and 
system-generated Idempotency key and interestRefundCalculation true
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid  | In advance | 
Late | Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0    | 0.0   |            |      |    
         |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9  | 338.9 | 338.9      | 0.0 
 | 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 333.42          | 330.1 
        | 8.8      | 0.0  | 0.0       | 338.9  | 0.19  | 0.19       | 0.0  | 
338.71      |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
333.42        | 2.36     | 0.0  | 0.0       | 335.78 | 50.0  | 50.0       | 0.0 
 | 285.78      |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 13.58    | 0.0  | 0.0       | 1013.58 | 389.09 | 
389.09     | 0.0  | 624.49      |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type       | Amount | Principal | 
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement           | 1000.0 | 0.0       | 0.0   
   | 0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment              | 338.9  | 336.48    | 2.42  
   | 0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Merchant Issued Refund | 50.0   | 50.0      | 0.0   
   | 0.0  | 0.0       | 613.52       | false    | false    |
+      | 15 July 2024     | Interest Refund        | 0.19   | 0.0       | 0.19  
   | 0.0  | 0.0       | 613.52       | false    | false    |
+    When Admin fails to add duplicate Interest Refund for 
"MERCHANT_ISSUED_REFUND" transaction made on "15 July 2024" with 0.19 EUR 
interest refund amount
\ No newline at end of file
diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature
 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature
index 820a30e6d7..4de834faee 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature
@@ -192,4 +192,202 @@ Feature: PayoutRefund
       | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
       | 10 July 2024     | Payout Refund    | 50.0   | 50.0      | 0.0      | 
0.0  | 0.0       | 950.0        | true     | false    |
       | 10 July 2024     | Interest Refund  | 0.12   | 0.0       | 0.12     | 
0.0  | 0.0       | 950.0        | true     | false    |
-      | 15 July 2024     | Repayment        | 100.0  | 96.24     | 3.76     | 
0.0  | 0.0       | 903.76        | false    | true     |
\ No newline at end of file
+      | 15 July 2024     | Repayment        | 100.0  | 96.24     | 3.76     | 
0.0  | 0.0       | 903.76       | false    | true     |
+
+  @TestRailId:C3870
+  Scenario: Manual Interest Refund creation for Payout Refund with 
interestRefundCalculation = false
+    When Admin sets the business date to "01 July 2024"
+    And Admin creates a client with random data
+    And Admin creates a fully customized loan with the following data:
+      | LoanProduct                                                   | 
submitted on date | with Principal | ANNUAL interest rate % | interest type     
| interest calculation period | amortization type  | loanTermFrequency | 
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | 
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | 
interest free period | Payment strategy            |
+      | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 
July 2024      | 1000           | 10                     | DECLINING_BALANCE | 
DAILY                       | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 July 2024" with "1000" 
amount and expected disbursement date on "01 July 2024"
+    And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR 
transaction amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid | In advance | Late 
| Outstanding |
+      |    |      | 01 July 2024      |           | 1000.0          |          
     |          | 0.0  |           | 0.0    | 0.0  |            |      |        
     |
+      | 1  | 31   | 01 August 2024    |           | 669.43          | 330.57   
     | 8.33     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 2  | 31   | 01 September 2024 |           | 336.11          | 333.32   
     | 5.58     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 3  | 30   | 01 October 2024   |           | 0.0             | 336.11   
     | 2.8      | 0.0  | 0.0       | 338.91 | 0.0  | 0.0        | 0.0  | 338.91 
     |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid | In 
advance | Late | Outstanding |
+      | 1000.0        | 16.71    | 0.0  | 0.0       | 1016.71 | 0.0  | 0.0     
   | 0.0  | 1016.71     |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+    When Admin sets the business date to "10 July 2024"
+    And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on 
"10 July 2024" with 338.9 EUR transaction amount and system-generated 
Idempotency key
+    When Admin sets the business date to "15 July 2024"
+    And Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type 
on "15 July 2024" with 50 EUR transaction amount and system-generated 
Idempotency key and interestRefundCalculation false
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0   | 0.0   |            |      |     
        |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9 | 338.9 | 338.9      | 0.0  
| 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 333.42          | 330.1 
        | 8.8      | 0.0  | 0.0       | 338.9 | 50.0  | 50.0       | 0.0  | 
288.9       |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
333.42        | 2.78     | 0.0  | 0.0       | 336.2 | 0.0   | 0.0        | 0.0  
| 336.2       |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 14.0     | 0.0  | 0.0       | 1014.0 | 388.9  | 388.9  
    | 0.0  | 625.1       |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment        | 338.9  | 336.48    | 2.42     | 
0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Payout Refund    | 50.0   | 50.0      | 0.0      | 
0.0  | 0.0       | 613.52       | false    | false    |
+    When Admin manually adds Interest Refund for "PAYOUT_REFUND" transaction 
made on "15 July 2024" with 0.19 EUR interest refund amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0   | 0.0   |            |      |     
        |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9 | 338.9 | 338.9      | 0.0  
| 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 333.42          | 330.1 
        | 8.8      | 0.0  | 0.0       | 338.9 | 50.19 | 50.19      | 0.0  | 
288.71      |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
333.42        | 2.78     | 0.0  | 0.0       | 336.2 | 0.0   | 0.0        | 0.0  
| 336.2       |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 14.0     | 0.0  | 0.0       | 1014.0 | 389.09 | 389.09 
    | 0.0  | 624.91      |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment        | 338.9  | 336.48    | 2.42     | 
0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Payout Refund    | 50.0   | 50.0      | 0.0      | 
0.0  | 0.0       | 613.52       | false    | false    |
+      | 15 July 2024     | Interest Refund  | 0.19   | 0.0       | 0.19     | 
0.0  | 0.0       | 613.52       | false    | false    |
+    Then Loan Transactions tab has a "PAYOUT_REFUND" transaction with date "15 
July 2024" which has the following Journal entries:
+      | Type      | Account code | Account name              | Debit | Credit |
+      | ASSET     | 112601       | Loans Receivable          |       | 50.0   |
+      | LIABILITY | 145023       | Suspense/Clearing account | 50.0  |        |
+    Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date 
"15 July 2024" which has the following Journal entries:
+      | Type   | Account code | Account name            | Debit | Credit |
+      | ASSET  | 112603       | Interest/Fee Receivable |       | 0.19   |
+      | INCOME | 404000       | Interest Income         | 0.19  |        |
+
+  @TestRailId:C3871
+  Scenario: Undo Payout Refund with manual Interest Refund, both transactions 
are reversed
+    When Admin sets the business date to "01 July 2024"
+    And Admin creates a client with random data
+    And Admin creates a fully customized loan with the following data:
+      | LoanProduct                                                   | 
submitted on date | with Principal | ANNUAL interest rate % | interest type     
| interest calculation period | amortization type  | loanTermFrequency | 
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | 
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | 
interest free period | Payment strategy            |
+      | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 
July 2024      | 1000           | 10                     | DECLINING_BALANCE | 
DAILY                       | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 July 2024" with "1000" 
amount and expected disbursement date on "01 July 2024"
+    And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR 
transaction amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid | In advance | Late 
| Outstanding |
+      |    |      | 01 July 2024      |           | 1000.0          |          
     |          | 0.0  |           | 0.0    | 0.0  |            |      |        
     |
+      | 1  | 31   | 01 August 2024    |           | 669.43          | 330.57   
     | 8.33     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 2  | 31   | 01 September 2024 |           | 336.11          | 333.32   
     | 5.58     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 3  | 30   | 01 October 2024   |           | 0.0             | 336.11   
     | 2.8      | 0.0  | 0.0       | 338.91 | 0.0  | 0.0        | 0.0  | 338.91 
     |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid | In 
advance | Late | Outstanding |
+      | 1000.0        | 16.71    | 0.0  | 0.0       | 1016.71 | 0.0  | 0.0     
   | 0.0  | 1016.71     |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+    When Admin sets the business date to "10 July 2024"
+    And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on 
"10 July 2024" with 338.9 EUR transaction amount and system-generated 
Idempotency key
+    When Admin sets the business date to "15 July 2024"
+    And Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type 
on "15 July 2024" with 50 EUR transaction amount and system-generated 
Idempotency key and interestRefundCalculation false
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0   | 0.0   |            |      |     
        |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9 | 338.9 | 338.9      | 0.0  
| 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 333.42          | 330.1 
        | 8.8      | 0.0  | 0.0       | 338.9 | 50.0  | 50.0       | 0.0  | 
288.9       |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
333.42        | 2.78     | 0.0  | 0.0       | 336.2 | 0.0   | 0.0        | 0.0  
| 336.2       |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 14.0     | 0.0  | 0.0       | 1014.0 | 388.9  | 388.9  
    | 0.0  | 625.1       |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment        | 338.9  | 336.48    | 2.42     | 
0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Payout Refund    | 50.0   | 50.0      | 0.0      | 
0.0  | 0.0       | 613.52       | false    | false    |
+    When Admin manually adds Interest Refund for "PAYOUT_REFUND" transaction 
made on "15 July 2024" with 0.19 EUR interest refund amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0   | 0.0   |            |      |     
        |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9 | 338.9 | 338.9      | 0.0  
| 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 333.42          | 330.1 
        | 8.8      | 0.0  | 0.0       | 338.9 | 50.19 | 50.19      | 0.0  | 
288.71      |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
333.42        | 2.78     | 0.0  | 0.0       | 336.2 | 0.0   | 0.0        | 0.0  
| 336.2       |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 14.0     | 0.0  | 0.0       | 1014.0 | 389.09 | 389.09 
    | 0.0  | 624.91      |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment        | 338.9  | 336.48    | 2.42     | 
0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Payout Refund    | 50.0   | 50.0      | 0.0      | 
0.0  | 0.0       | 613.52       | false    | false    |
+      | 15 July 2024     | Interest Refund  | 0.19   | 0.0       | 0.19     | 
0.0  | 0.0       | 613.52       | false    | false    |
+    Then Loan Transactions tab has a "PAYOUT_REFUND" transaction with date "15 
July 2024" which has the following Journal entries:
+      | Type      | Account code | Account name              | Debit | Credit |
+      | ASSET     | 112601       | Loans Receivable          |       | 50.0   |
+      | LIABILITY | 145023       | Suspense/Clearing account | 50.0  |        |
+    Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date 
"15 July 2024" which has the following Journal entries:
+      | Type   | Account code | Account name            | Debit | Credit |
+      | ASSET  | 112603       | Interest/Fee Receivable |       | 0.19   |
+      | INCOME | 404000       | Interest Income         | 0.19  |        |
+    When Customer undo "1"th transaction made on "15 July 2024"
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid  | In advance | 
Late | Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0    | 0.0   |            |      |    
         |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9  | 338.9 | 338.9      | 0.0 
 | 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 334.07          | 
329.45        | 9.45     | 0.0  | 0.0       | 338.9  | 0.0   | 0.0        | 0.0 
 | 338.9       |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
334.07        | 2.78     | 0.0  | 0.0       | 336.85 | 0.0   | 0.0        | 0.0 
 | 336.85      |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 14.65    | 0.0  | 0.0       | 1014.65 | 338.9  | 338.9 
     | 0.0  | 675.75      |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment        | 338.9  | 336.48    | 2.42     | 
0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Payout Refund    | 50.0   | 50.0      | 0.0      | 
0.0  | 0.0       | 613.52       | true     | false    |
+      | 15 July 2024     | Interest Refund  | 0.19   | 0.0       | 0.19     | 
0.0  | 0.0       | 613.52       | true     | false    |
+    Then Loan Transactions tab has a "PAYOUT_REFUND" transaction with date "15 
July 2024" which has the following Journal entries:
+      | Type      | Account code | Account name              | Debit | Credit |
+      | ASSET     | 112601       | Loans Receivable          |       | 50.0   |
+      | LIABILITY | 145023       | Suspense/Clearing account | 50.0  |        |
+      | ASSET     | 112601       | Loans Receivable          | 50.0  |        |
+      | LIABILITY | 145023       | Suspense/Clearing account |       | 50.0   |
+    Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date 
"15 July 2024" which has the following Journal entries:
+      | Type   | Account code | Account name            | Debit | Credit |
+      | ASSET  | 112603       | Interest/Fee Receivable |       | 0.19   |
+      | INCOME | 404000       | Interest Income         | 0.19  |        |
+      | ASSET  | 112603       | Interest/Fee Receivable | 0.19  |        |
+      | INCOME | 404000       | Interest Income         |       | 0.19   |
+
+  @TestRailId:C3872
+  Scenario: Prevent manual Interest Refund creation if 
interestRefundCalculation = true and Interest Refund already exists
+    When Admin sets the business date to "01 July 2024"
+    And Admin creates a client with random data
+    And Admin creates a fully customized loan with the following data:
+      | LoanProduct                                                   | 
submitted on date | with Principal | ANNUAL interest rate % | interest type     
| interest calculation period | amortization type  | loanTermFrequency | 
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | 
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | 
interest free period | Payment strategy            |
+      | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 
July 2024      | 1000           | 10                     | DECLINING_BALANCE | 
DAILY                       | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 July 2024" with "1000" 
amount and expected disbursement date on "01 July 2024"
+    And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR 
transaction amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid | In advance | Late 
| Outstanding |
+      |    |      | 01 July 2024      |           | 1000.0          |          
     |          | 0.0  |           | 0.0    | 0.0  |            |      |        
     |
+      | 1  | 31   | 01 August 2024    |           | 669.43          | 330.57   
     | 8.33     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 2  | 31   | 01 September 2024 |           | 336.11          | 333.32   
     | 5.58     | 0.0  | 0.0       | 338.9  | 0.0  | 0.0        | 0.0  | 338.9  
     |
+      | 3  | 30   | 01 October 2024   |           | 0.0             | 336.11   
     | 2.8      | 0.0  | 0.0       | 338.91 | 0.0  | 0.0        | 0.0  | 338.91 
     |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due     | Paid | In 
advance | Late | Outstanding |
+      | 1000.0        | 16.71    | 0.0  | 0.0       | 1016.71 | 0.0  | 0.0     
   | 0.0  | 1016.71     |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+    When Admin sets the business date to "10 July 2024"
+    And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on 
"10 July 2024" with 338.9 EUR transaction amount and system-generated 
Idempotency key
+    When Admin sets the business date to "15 July 2024"
+    And Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type 
on "15 July 2024" with 50 EUR transaction amount and system-generated 
Idempotency key and interestRefundCalculation true
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 01 July 2024      |              | 1000.0          |       
        |          | 0.0  |           | 0.0   | 0.0   |            |      |     
        |
+      | 1  | 31   | 01 August 2024    | 10 July 2024 | 663.52          | 
336.48        | 2.42     | 0.0  | 0.0       | 338.9 | 338.9 | 338.9      | 0.0  
| 0.0         |
+      | 2  | 31   | 01 September 2024 |              | 333.42          | 330.1 
        | 8.8      | 0.0  | 0.0       | 338.9 | 50.19 | 50.19      | 0.0  | 
288.71      |
+      | 3  | 30   | 01 October 2024   |              | 0.0             | 
333.42        | 2.78     | 0.0  | 0.0       | 336.2 | 0.0   | 0.0        | 0.0  
| 336.2       |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid   | In 
advance | Late | Outstanding |
+      | 1000.0        | 14.0     | 0.0  | 0.0       | 1014.0 | 389.09 | 389.09 
    | 0.0  | 624.91      |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 01 July 2024     | Disbursement     | 1000.0 | 0.0       | 0.0      | 
0.0  | 0.0       | 1000.0       | false    | false    |
+      | 10 July 2024     | Repayment        | 338.9  | 336.48    | 2.42     | 
0.0  | 0.0       | 663.52       | false    | false    |
+      | 15 July 2024     | Payout Refund    | 50.0   | 50.0      | 0.0      | 
0.0  | 0.0       | 613.52       | false    | false    |
+      | 15 July 2024     | Interest Refund  | 0.19   | 0.0       | 0.19     | 
0.0  | 0.0       | 613.52       | false    | false    |
+    When Admin fails to add duplicate Interest Refund for "PAYOUT_REFUND" 
transaction made on "15 July 2024" with 0.19 EUR interest refund amount
\ No newline at end of file
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
index 1cc87f2b16..ad8d5d0fa6 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
@@ -97,4 +97,7 @@ public interface LoanAccountDomainService {
     LoanTransaction applyInterestRefund(Loan loan, LoanRefundRequestData 
loanRefundRequest);
 
     void updateAndSaveLoanCollateralTransactionsForIndividualAccounts(Loan 
loan, LoanTransaction transaction);
+
+    LoanTransaction createManualInterestRefundWithAmount(Loan loan, 
LoanTransaction targetTransaction, BigDecimal amount,
+            PaymentDetail paymentDetail, ExternalId txnExternalId);
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index d8dc5940b3..4b692da5f7 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -195,6 +195,11 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom<Long
         return new LoanTransaction(loan, loan.getOffice(), 
LoanTransactionType.INTEREST_REFUND, null, amount, date, externalId);
     }
 
+    public static LoanTransaction interestRefund(final Loan loan, final 
BigDecimal amount, final LocalDate date,
+            final PaymentDetail paymentDetail, final ExternalId externalId) {
+        return new LoanTransaction(loan, loan.getOffice(), 
LoanTransactionType.INTEREST_REFUND, paymentDetail, amount, date, externalId);
+    }
+
     public static LoanTransaction chargeAdjustment(final Loan loan, final 
BigDecimal amount, final LocalDate transactionDate,
             final ExternalId externalId, PaymentDetail paymentDetail) {
         return new LoanTransaction(loan, loan.getOffice(), 
LoanTransactionType.CHARGE_ADJUSTMENT, paymentDetail, amount, transactionDate,
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ManualInterestRefundCommandHandler.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ManualInterestRefundCommandHandler.java
new file mode 100644
index 0000000000..09c9abc093
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ManualInterestRefundCommandHandler.java
@@ -0,0 +1,42 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.handler;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import 
org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@CommandType(entity = "LOAN_TRANSACTION", action = "MANUAL_INTEREST_REFUND")
+public class ManualInterestRefundCommandHandler implements 
NewCommandSourceHandler {
+
+    private final LoanWritePlatformService loanWritePlatformService;
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return 
loanWritePlatformService.makeManualInterestRefund(command.getLoanId(), 
command.entityId(), command);
+    }
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
index 1e77207dea..d3592f8a0d 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
@@ -94,4 +94,6 @@ public interface LoanTransactionValidator {
     void validateExternalId(DataValidatorBuilder baseDataValidator, 
JsonElement element);
 
     void validateReversalExternalId(DataValidatorBuilder baseDataValidator, 
JsonElement element);
+
+    void validateManualInterestRefundTransaction(String json);
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
index ed2d7176a1..cf8c4eb08f 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
@@ -155,4 +155,6 @@ public interface LoanReadPlatformService {
     List<Long> retrieveLoanIdsByExternalIds(List<ExternalId> externalIds);
 
     boolean existsByLoanId(Long loanId);
+
+    LoanTransactionData retrieveManualInterestRefundTemplate(Long loanId, Long 
targetTransactionId);
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
index f64f2d71ae..59cf3054df 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
@@ -125,4 +125,6 @@ public interface LoanWritePlatformService {
     CommandProcessingResult undoChargeOff(JsonCommand command);
 
     CommandProcessingResult makeRefund(Long loanId, LoanTransactionType 
loanTransactionType, JsonCommand command);
+
+    CommandProcessingResult makeManualInterestRefund(Long loanId, Long 
transactionId, JsonCommand command);
 }
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
index a1a0155afc..02e4739d30 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
@@ -572,6 +572,11 @@ public class ProgressiveLoanTransactionValidatorImpl 
implements ProgressiveLoanT
         loanTransactionValidator.validateReversalExternalId(baseDataValidator, 
element);
     }
 
+    @Override
+    public void validateManualInterestRefundTransaction(final String json) {
+        loanTransactionValidator.validateManualInterestRefundTransaction(json);
+    }
+
     private Set<String> getCapitalizedIncomeParameters() {
         return new HashSet<>(
                 Arrays.asList("transactionDate", "dateFormat", "locale", 
"transactionAmount", "paymentTypeId", "note", "externalId"));
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index 3d0ef8d180..b88965b999 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -93,6 +93,7 @@ public class LoanTransactionsApiResource {
     public static final String CAPITALIZED_INCOME = "capitalizedIncome";
     public static final String CAPITALIZED_INCOME_ADJUSTMENT = 
"capitalizedIncomeAdjustment";
     public static final String CONTRACT_TERMINATION = "contractTermination";
+    public static final String INTEREST_REFUND_COMMAND_VALUE = 
"interest-refund";
     private final Set<String> responseDataParameters = new 
HashSet<>(Arrays.asList("id", "type", "date", "currency", "amount", 
"externalId",
             LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME, 
LoanApiConstants.REVERSED_ON_DATE_PARAMNAME));
 
@@ -121,7 +122,8 @@ public class LoanTransactionsApiResource {
             + "loans/1/transactions/template?command=refundbycash" + "\n" + 
"loans/1/transactions/template?command=refundbytransfer" + "\n"
             + "loans/1/transactions/template?command=foreclosure" + "\n" + 
"loans/1/transactions/template?command=interestPaymentWaiver"
             + "\n" + 
"loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' 
field will have the overpaid value)"
-            + "\n" + "loans/1/transactions/template?command=charge-off" + "\n" 
+ "loans/1/transactions/template?command=downPayment" + "\n")
+            + "\n" + "loans/1/transactions/template?command=charge-off" + "\n" 
+ "loans/1/transactions/template?command=downPayment" + "\n"
+            + "loans/1/transactions/template?command=interest-refund")
     @ApiResponses({
             @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class)))
 })
     public String retrieveTransactionTemplate(@PathParam("loanId") 
@Parameter(description = "loanId", required = true) final Long loanId,
@@ -151,7 +153,8 @@ public class LoanTransactionsApiResource {
             + "loans/1/transactions/template?command=refundbycash" + "\n" + 
"loans/1/transactions/template?command=refundbytransfer" + "\n"
             + "loans/1/transactions/template?command=foreclosure" + "\n" + 
"loans/1/transactions/template?command=interestPaymentWaiver"
             + "\n" + 
"loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' 
field will have the overpaid value)"
-            + "\n" + "loans/1/transactions/template?command=charge-off" + "\n" 
+ "loans/1/transactions/template?command=downPayment" + "\n")
+            + "\n" + "loans/1/transactions/template?command=charge-off" + "\n" 
+ "loans/1/transactions/template?command=downPayment" + "\n"
+            + "loans/1/transactions/template?command=interest-refund")
     @ApiResponses({
             @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class)))
 })
     public String retrieveTransactionTemplate(
@@ -694,6 +697,8 @@ public class LoanTransactionsApiResource {
         } else if (CommandParameterUtil.is(commandParam, 
LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) {
             transactionData = 
this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId,
                     LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT, 
transactionId);
+        } else if (CommandParameterUtil.is(commandParam, 
INTEREST_REFUND_COMMAND_VALUE)) {
+            transactionData = 
this.loanReadPlatformService.retrieveManualInterestRefundTemplate(resolvedLoanId,
 transactionId);
         } else {
             throw new UnrecognizedQueryParamException("command", commandParam);
         }
@@ -718,6 +723,8 @@ public class LoanTransactionsApiResource {
             commandRequest = 
builder.capitalizedIncomeAdjustment(resolvedLoanId, 
resolvedTransactionId).build();
         } else if (CommandParameterUtil.is(commandParam, 
LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) {
             commandRequest = builder.buyDownFeeAdjustment(resolvedLoanId, 
resolvedTransactionId).build();
+        } else if (CommandParameterUtil.is(commandParam, 
INTEREST_REFUND_COMMAND_VALUE)) {
+            commandRequest = builder.manualInterestRefund(resolvedLoanId, 
resolvedTransactionId).build();
         } else { // Default to adjust the Loan Transaction
             commandRequest = builder.adjustTransaction(resolvedLoanId, 
resolvedTransactionId).build();
         }
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 20cf3535af..f528c505c4 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
@@ -206,7 +206,13 @@ public class LoanAccountDomainServiceJpa implements 
LoanAccountDomainService {
         final ExternalId txnExternalId = externalIdFactory.create();
         businessEventNotifierService.notifyPreBusinessEvent(new 
LoanTransactionInterestRefundPreBusinessEvent(loan));
         return LoanTransaction.interestRefund(loan, interestRefundAmount, 
refundTransaction.getDateOf(), txnExternalId);
+    }
 
+    @Override
+    public LoanTransaction createManualInterestRefundWithAmount(final Loan 
loan, final LoanTransaction targetTransaction,
+            final BigDecimal interestRefundAmount, final PaymentDetail 
paymentDetail, final ExternalId txnExternalId) {
+        businessEventNotifierService.notifyPreBusinessEvent(new 
LoanTransactionInterestRefundPreBusinessEvent(loan));
+        return LoanTransaction.interestRefund(loan, interestRefundAmount, 
targetTransaction.getDateOf(), paymentDetail, txnExternalId);
     }
 
     @Transactional
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
index 4847176e4f..f75e69daf4 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
@@ -1089,4 +1089,35 @@ public final class LoanTransactionValidatorImpl 
implements LoanTransactionValida
             
baseDataValidator.reset().parameter("reversalExternalId").value(reversalExternalId).notExceedingLengthOf(100);
         }
     }
+
+    @Override
+    public void validateManualInterestRefundTransaction(final String json) {
+        if (StringUtils.isBlank(json)) {
+            throw new InvalidJsonException();
+        }
+
+        final Set<String> transactionParameters = new HashSet<>(
+                Arrays.asList("transactionAmount", "externalId", "note", 
"locale", "dateFormat", "paymentTypeId", "accountNumber",
+                        "checkNumber", "routingCode", "receiptNumber", 
"bankNumber", "loanId", "numberOfRepayments"));
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() 
{}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, 
transactionParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new 
DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final BigDecimal transactionAmount = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", 
element);
+        
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
+
+        final String note = this.fromApiJsonHelper.extractStringNamed("note", 
element);
+        
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
+
+        final String externalId = 
this.fromApiJsonHelper.extractStringNamed("externalId", element);
+        
baseDataValidator.reset().parameter("externalId").value(externalId).ignoreIfNull().notExceedingLengthOf(100);
+
+        validatePaymentDetails(baseDataValidator, element);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 95113011ca..b956534c20 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -46,8 +46,10 @@ import 
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
 import org.apache.fineract.infrastructure.core.api.ApiFacingEnum;
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
+import 
org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import 
org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
 import org.apache.fineract.infrastructure.core.service.MathUtil;
@@ -192,6 +194,7 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
     private final LoanBalanceService loanBalanceService;
     private final LoanCapitalizedIncomeBalanceRepository 
loanCapitalizedIncomeBalanceRepository;
     private final LoanBuyDownFeeBalanceRepository 
loanBuyDownFeeBalanceRepository;
+    private final InterestRefundServiceDelegate interestRefundServiceDelegate;
 
     @Override
     public LoanAccountData retrieveOne(final Long loanId) {
@@ -2448,6 +2451,39 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
         return loanRepositoryWrapper.existsByLoanId(loanId);
     }
 
+    @Override
+    public LoanTransactionData retrieveManualInterestRefundTemplate(final Long 
loanId, final Long targetTransactionId) {
+        final Loan loan = 
loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
+        final LoanTransaction targetTxn = loan.getLoanTransaction(txn -> 
txn.getId() != null && txn.getId().equals(targetTransactionId));
+        if (targetTxn == null) {
+            throw new LoanTransactionNotFoundException(targetTransactionId);
+        }
+        if (!(targetTxn.isMerchantIssuedRefund() || 
targetTxn.isPayoutRefund())) {
+            throw new 
GeneralPlatformDomainRuleException("error.msg.loan.transaction.not.refund.type",
 "Only for refund transactions");
+        }
+        if (targetTxn.isReversed()) {
+            throw new 
GeneralPlatformDomainRuleException("error.msg.loan.transaction.reversed", 
"Refund transaction is reversed");
+        }
+        final boolean alreadyExists = 
loan.getLoanTransactions().stream().anyMatch(txn -> txn.isInterestRefund() && 
!txn
+                .getLoanTransactionRelations(rel -> rel.getToTransaction() != 
null && rel.getToTransaction().equals(targetTxn)).isEmpty());
+        if (alreadyExists) {
+            throw new 
GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.already.exists",
+                    "Interest Refund already exists for this refund");
+        }
+
+        final InterestRefundService interestRefundService = 
interestRefundServiceDelegate.lookupInterestRefundService(loan);
+        final Money totalInterest = 
interestRefundService.totalInterestByTransactions(null, loan.getId(), 
targetTxn.getTransactionDate(),
+                List.of(), 
loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList());
+        final Money newTotalInterest = 
interestRefundService.totalInterestByTransactions(null, loan.getId(), 
targetTxn.getTransactionDate(),
+                List.of(targetTxn), 
loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList());
+        final BigDecimal interestRefundAmount = 
totalInterest.minus(newTotalInterest).getAmount();
+        final Collection<PaymentTypeData> paymentTypeOptions = 
paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+        final LoanTransactionEnumData transactionType = 
LoanEnumerations.transactionType(LoanTransactionType.INTEREST_REFUND);
+
+        return 
LoanTransactionData.loanTransactionDataForCreditTemplate(transactionType, 
targetTxn.getTransactionDate(),
+                interestRefundAmount, paymentTypeOptions, 
loan.getCurrency().toData());
+    }
+
     private LoanTransaction deriveDefaultInterestWaiverTransaction(final Loan 
loan) {
         final Money totalInterestOutstanding = 
loan.getTotalInterestOutstandingOnLoan();
         Money possibleInterestToWaive = totalInterestOutstanding.copy();
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index af8ee32567..57e3a17368 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -98,6 +98,7 @@ import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPreBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestRefundPostBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoChargeOffBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWaiveInterestBusinessEvent;
@@ -188,6 +189,7 @@ import 
org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactio
 import 
org.apache.fineract.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanForeclosureException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanMultiDisbursementException;
+import 
org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
@@ -210,6 +212,7 @@ import 
org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommand
 import 
org.apache.fineract.portfolio.loanaccount.service.adjustment.LoanAdjustmentParameter;
 import 
org.apache.fineract.portfolio.loanaccount.service.adjustment.LoanAdjustmentService;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import 
org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
 import 
org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
 import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
 import org.apache.fineract.portfolio.note.domain.Note;
@@ -3010,6 +3013,136 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
                 .build();
     }
 
+    @Transactional
+    @Override
+    public CommandProcessingResult makeManualInterestRefund(final Long loanId, 
final Long transactionId, final JsonCommand command) {
+        
loanTransactionValidator.validateManualInterestRefundTransaction(command.json());
+        Loan loan = loanAssembler.assembleFrom(loanId);
+        if (loan == null) {
+            throw new LoanNotFoundException(loanId);
+        }
+        final LoanTransaction targetTransaction = 
this.loanTransactionRepository.findByIdAndLoanId(transactionId, loanId)
+                .orElseThrow(() -> new 
LoanTransactionNotFoundException(transactionId, loanId));
+
+        final LocalDate transactionDate = targetTransaction.getDateOf();
+        if (!(targetTransaction.isMerchantIssuedRefund() || 
targetTransaction.isPayoutRefund())) {
+            throw new 
GeneralPlatformDomainRuleException("error.msg.loan.transaction.not.refund.type",
+                    "Target transaction must be Merchant Issued Refund or 
Payout Refund");
+        }
+        if (targetTransaction.isReversed()) {
+            throw new 
GeneralPlatformDomainRuleException("error.msg.loan.transaction.reversed", 
"Target transaction is already reversed");
+        }
+        boolean alreadyHasInterestRefund = loan.getLoanTransactions().stream()
+                .anyMatch(txn -> txn.isInterestRefund()
+                        && !txn.getLoanTransactionRelations(rel -> 
rel.getToTransaction().equals(targetTransaction)).isEmpty()
+                        && !txn.isReversed());
+        if (alreadyHasInterestRefund) {
+            throw new 
GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.already.exists",
+                    "Interest Refund already exists for this transaction");
+        }
+        final BigDecimal amount = 
command.bigDecimalValueOfParameterNamed("transactionAmount");
+        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new 
GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.amount.invalid",
+                    "Amount must be provided and positive");
+        }
+
+        final boolean shouldCreateInterestRefundTransaction = 
loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
+                .map(LoanSupportedInterestRefundTypes::getTransactionType)
+                .anyMatch(transactionType -> 
transactionType.equals(targetTransaction.getTypeOf()));
+        if (!shouldCreateInterestRefundTransaction) {
+            throw new 
GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.not.supported",
+                    "Interest Refund calculation is not supported for this 
loan");
+        }
+        final ExternalId txnExternalId = 
externalIdFactory.createFromCommand(command, 
LoanApiConstants.externalIdParameterName);
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        changes.put("transactionAmount", 
command.stringValueOfParameterNamed("transactionAmount"));
+        changes.put("locale", command.locale());
+        changes.put("dateFormat", command.dateFormat());
+        changes.put(LoanApiConstants.externalIdParameterName, txnExternalId);
+
+        final LocalDate recalculateFrom = 
loan.isInterestBearingAndInterestRecalculationEnabled() ? transactionDate : 
null;
+        final ScheduleGeneratorDTO scheduleGeneratorDTO = 
this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, null);
+
+        this.loanTransactionValidator.validateRefund(loan, 
LoanTransactionType.INTEREST_REFUND, transactionDate, scheduleGeneratorDTO);
+
+        final PaymentDetail paymentDetail = 
this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, 
changes);
+
+        createNote(loan, command, changes);
+
+        final List<Long> existingTransactionIds = 
loanTransactionRepository.findTransactionIdsByLoan(loan);
+        final List<Long> existingReversedTransactionIds = 
loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
+
+        final LoanTransaction interestRefundTxn = 
loanAccountDomainService.createManualInterestRefundWithAmount(loan, 
targetTransaction,
+                amount, paymentDetail, txnExternalId);
+
+        interestRefundTxn.getLoanTransactionRelations().add(
+                LoanTransactionRelation.linkToTransaction(interestRefundTxn, 
targetTransaction, LoanTransactionRelationTypeEnum.RELATED));
+
+        final boolean isTransactionChronologicallyLatest = 
loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan,
+                interestRefundTxn);
+        final LoanRepaymentScheduleInstallment currentInstallment = 
loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate);
+
+        final boolean processLatest = isTransactionChronologicallyLatest //
+                && !loan.isForeclosure() //
+                && 
!loan.hasChargesAffectedByBackdatedRepaymentLikeTransaction(interestRefundTxn) 
//
+                && 
loanTransactionProcessingService.canProcessLatestTransactionOnly(loan, 
interestRefundTxn, currentInstallment); //
+        if (processLatest) {
+            
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(),
 interestRefundTxn,
+                    new TransactionCtx(loan.getCurrency(), 
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
+                            new MoneyHolder(loan.getTotalOverpaidAsMoney()), 
null));
+            loan.addLoanTransaction(interestRefundTxn);
+        } else {
+            if (loan.isCumulativeSchedule() && 
loan.isInterestBearingAndInterestRecalculationEnabled()) {
+                
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, 
scheduleGeneratorDTO);
+            } else if (loan.isProgressiveSchedule() && 
((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
+                    || loan.hasContractTerminationTransaction())) {
+                loanScheduleService.regenerateRepaymentSchedule(loan, 
scheduleGeneratorDTO);
+            }
+            loan.addLoanTransaction(interestRefundTxn);
+            reprocessLoanTransactionsService.reprocessTransactions(loan);
+        }
+
+        // Update outstanding loan balances
+        loanBalanceService.updateLoanOutstandingBalances(loan);
+
+        
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTxn);
+        businessEventNotifierService.notifyPostBusinessEvent(new 
LoanTransactionInterestRefundPostBusinessEvent(interestRefundTxn));
+
+        // Accrual reprocessing
+        if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
+            loanAccrualsProcessingService.reprocessExistingAccruals(loan);
+            
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
+        }
+
+        loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+        loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate);
+        
loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
+
+        
loanAccountDomainService.updateAndSavePostDatedChecksForIndividualAccount(loan, 
interestRefundTxn);
+        
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan,
 interestRefundTxn);
+
+        
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, 
loan.isInterestBearingAndInterestRecalculationEnabled(),
+                false);
+
+        journalEntryPoster.postJournalEntries(loan, existingTransactionIds, 
existingReversedTransactionIds);
+        
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan,
 existingTransactionIds);
+        businessEventNotifierService.notifyPostBusinessEvent(new 
LoanBalanceChangedBusinessEvent(loan));
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withLoanId(loan.getId()) //
+                .withEntityId(targetTransaction.getId()) //
+                .withEntityExternalId(targetTransaction.getExternalId()) //
+                .withSubEntityId(interestRefundTxn.getId()) //
+                .withSubEntityExternalId(interestRefundTxn.getExternalId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .with(changes) //
+                .build();
+    }
+
     public void handleChargebackTransaction(final Loan loan, LoanTransaction 
chargebackTransaction) {
         
loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction);
 
@@ -3626,5 +3759,4 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
             loan.setExpectedMaturityDate(latestRepaymentDate);
         }
     }
-
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
index 70866c25c4..cdfcd8de02 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
@@ -110,6 +110,7 @@ import 
org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlat
 import 
org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformServiceImpl;
 import 
org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService;
 import 
org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl;
+import 
org.apache.fineract.portfolio.loanaccount.service.InterestRefundServiceDelegate;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanAccountServiceImpl;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanAccrualEventService;
@@ -177,6 +178,7 @@ import 
org.apache.fineract.portfolio.savings.service.GSIMReadPlatformService;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.jdbc.core.JdbcTemplate;
 
 @Configuration
@@ -344,7 +346,8 @@ public class LoanAccountConfiguration {
             LoanForeclosureValidator loanForeclosureValidator, 
LoanTransactionMapper loanTransactionMapper,
             LoanTransactionProcessingService loanTransactionProcessingService, 
LoanBalanceService loanBalanceService,
             LoanCapitalizedIncomeBalanceRepository 
loanCapitalizedIncomeBalanceRepository,
-            LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository) {
+            LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository,
+            @Lazy InterestRefundServiceDelegate interestRefundServiceDelegate) 
{
         return new LoanReadPlatformServiceImpl(jdbcTemplate, context, 
loanRepositoryWrapper, applicationCurrencyRepository,
                 loanProductReadPlatformService, clientReadPlatformService, 
groupReadPlatformService, loanDropdownReadPlatformService,
                 fundReadPlatformService, chargeReadPlatformService, 
codeValueReadPlatformService, calendarReadPlatformService,
@@ -352,7 +355,7 @@ public class LoanAccountConfiguration {
                 loanUtilService, configurationDomainService, 
accountDetailsReadPlatformService, columnValidator, sqlGenerator,
                 delinquencyReadPlatformService, loanTransactionRepository, 
loanChargePaidByReadService, loanTransactionRelationReadService,
                 loanForeclosureValidator, loanTransactionMapper, 
loanTransactionProcessingService, loanBalanceService,
-                loanCapitalizedIncomeBalanceRepository, 
loanBuyDownFeeBalanceRepository);
+                loanCapitalizedIncomeBalanceRepository, 
loanBuyDownFeeBalanceRepository, interestRefundServiceDelegate);
     }
 
     @Bean

Reply via email to