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


The following commit(s) were added to refs/heads/develop by this push:
     new 40b6eb50f Chargeback Loan Transaction
40b6eb50f is described below

commit 40b6eb50f6b17b3f5bd00512bfc883384c83843d
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Sun Sep 18 18:33:41 2022 -0500

    Chargeback Loan Transaction
---
 .../main/avro/payment/v1/PaymentTypeDataV1.avsc    |  16 +++
 .../commands/service/CommandWrapperBuilder.java    |   9 ++
 .../LoanChargebackTransactionBusinessEvent.java    |  40 ++++++
 .../interoperation/service/InteropServiceImpl.java |   4 +-
 .../service/NotificationDomainServiceImpl.java     |  17 +++
 .../fineract/portfolio/charge/domain/Charge.java   |   2 +-
 .../loanaccount/api/LoanApiConstants.java          |   5 +
 .../api/LoanTransactionsApiResource.java           |  11 +-
 .../loanaccount/data/LoanTransactionData.java      |   6 +
 .../data/LoanTransactionRelationData.java}         |  23 ++--
 .../portfolio/loanaccount/domain/Loan.java         |  18 ++-
 .../portfolio/loanaccount/domain/LoanEvent.java    |   3 +-
 .../loanaccount/domain/LoanTransaction.java        |  24 +++-
 .../domain/LoanTransactionRelation.java            |  65 ++++++++++
 .../domain/LoanTransactionRelationRepository.java} |   9 +-
 .../domain/LoanTransactionRelationTypeEnum.java    |  54 ++++++++
 .../loanaccount/domain/LoanTransactionType.java    |   5 +
 .../LoanRepaymentChargebackCommandHandler.java     |  43 +++++++
 .../mapper/LoanTransactionRelationMapper.java      |  39 ++++++
 .../serialization/LoanEventApiJsonValidator.java   |  44 ++++++-
 .../service/LoanReadPlatformService.java           |   4 +
 .../service/LoanReadPlatformServiceImpl.java       | 100 +++++++--------
 .../service/LoanWritePlatformService.java          |   2 +
 .../LoanWritePlatformServiceJpaRepositoryImpl.java | 104 +++++++++++++++-
 .../paymentdetail/domain/PaymentDetail.java        |   2 +-
 .../paymenttype/api/PaymentTypeApiResource.java    |  31 ++---
 .../api/PaymentTypeApiResourceConstants.java       |   2 +
 .../api/PaymentTypeApiResourceSwagger.java         |  16 +++
 .../paymenttype/data/PaymentTypeData.java          |  14 ++-
 .../paymenttype/data/PaymentTypeDataValidator.java |  34 ++++-
 .../portfolio/paymenttype/domain/PaymentType.java  |  34 ++---
 .../paymenttype/domain/PaymentTypeRepository.java  |   5 +
 .../domain/PaymentTypeRepositoryWrapper.java       |  13 +-
 .../PaymentTypeMapper.java}                        |  20 ++-
 .../service/PaymentTypeReadPlatformService.java    |   2 +
 .../PaymentTypeReadPlatformServiceImpl.java        |  58 +++------
 .../service/PaymentTypeWriteServiceImpl.java       |  19 ++-
 .../portfolio/savings/domain/SavingsAccount.java   |   4 +-
 .../SavingsAccountReadPlatformServiceImpl.java     |   3 +-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 .../parts/0052_loan_transaction_chargeback.xml     | 134 ++++++++++++++++++++
 .../LoanTransactionChargebackTest.java             | 138 +++++++++++++++++++++
 .../integrationtests/common/PaymentTypeHelper.java |  13 +-
 .../common/loans/LoanTransactionHelper.java        |   9 ++
 44 files changed, 1006 insertions(+), 193 deletions(-)

diff --git 
a/fineract-avro-schemas/src/main/avro/payment/v1/PaymentTypeDataV1.avsc 
b/fineract-avro-schemas/src/main/avro/payment/v1/PaymentTypeDataV1.avsc
index a1a02919c..0c96a5b7e 100644
--- a/fineract-avro-schemas/src/main/avro/payment/v1/PaymentTypeDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/payment/v1/PaymentTypeDataV1.avsc
@@ -42,6 +42,22 @@
                 "null",
                 "long"
             ]
+        },
+        {
+            "default": null,
+            "name": "codeName",
+            "type": [
+                "null",
+                "string"
+            ]
+        },
+        {
+            "default": null,
+            "name": "isSystemDefined",
+            "type": [
+                "null",
+                "boolean"
+            ]
         }
     ]
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 952da0cec..28d523242 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -905,6 +905,15 @@ public class CommandWrapperBuilder {
         return this;
     }
 
+    public CommandWrapperBuilder chargebackTransaction(final Long loanId, 
final Long transactionId) {
+        this.actionName = "CHARGEBACK";
+        this.entityName = "LOAN";
+        this.entityId = transactionId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions/" + transactionId;
+        return this;
+    }
+
     public CommandWrapperBuilder loanForeclosure(final Long loanId) {
         this.actionName = "FORECLOSURE";
         this.entityName = "LOAN";
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanChargebackTransactionBusinessEvent.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanChargebackTransactionBusinessEvent.java
new file mode 100644
index 000000000..6cc63c795
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanChargebackTransactionBusinessEvent.java
@@ -0,0 +1,40 @@
+/**
+ * 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.infrastructure.event.business.domain.loan;
+
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+
+public class LoanChargebackTransactionBusinessEvent extends 
LoanTransactionBusinessEvent {
+
+    public static final String LOAN_CHARGEBACK_TRANSACTION_BUSINESS_EVENT = 
"LoanChargebackTransactionBusinessEvent";
+    public static final String LOAN_CHARGEBACK_TRANSACTION_PERMISSION = 
"LOAN_CHARGEBACK";
+    public static final String LOAN_CHARGEBACK_TRANSACTION_OBJECT_TYPE = 
"loanTransaction";
+    public static final String LOAN_CHARGEBACK_TRANSACTION_EVENT_TYPE = 
"loanTransactionChargeback";
+    public static final String LOAN_CHARGEBACK_TRANSACTION_NOTIFICATION = 
"Loan Transaction has been chargeback";
+
+    public LoanChargebackTransactionBusinessEvent(LoanTransaction 
transactionToChargeback) {
+        super(transactionToChargeback);
+    }
+
+    @Override
+    public String getType() {
+        return LOAN_CHARGEBACK_TRANSACTION_BUSINESS_EVENT;
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java
index 81691513c..c045c09aa 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java
@@ -332,7 +332,7 @@ public class InteropServiceImpl implements InteropService {
         InteropTransactionRequestData request = 
dataValidator.validateAndParseCreateRequest(command);
 
         // TODO: error handling
-        SavingsAccount savingsAccount = validateAndGetSavingAccount(request);
+        validateAndGetSavingAccount(request);
 
         return 
InteropTransactionRequestResponseData.build(command.commandId(), 
request.getTransactionCode(), InteropActionState.ACCEPTED,
                 request.getExpiration(), request.getExtensionList(), 
request.getRequestCode());
@@ -617,7 +617,7 @@ public class InteropServiceImpl implements InteropService {
     PaymentType findPaymentType() {
         List<PaymentType> paymentTypes = paymentTypeRepository.findAll();
         for (PaymentType paymentType : paymentTypes) {
-            if (!paymentType.isCashPayment()) {
+            if (!paymentType.getIsCashPayment()) {
                 return paymentType;
             }
             // TODO: for now first not cash is retured:
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java
index e68eaeb30..9e607f73e 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java
@@ -34,6 +34,7 @@ import 
org.apache.fineract.infrastructure.event.business.domain.deposit.Recurrin
 import 
org.apache.fineract.infrastructure.event.business.domain.group.CentersCreateBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.group.GroupsCreateBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanApprovedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanChargebackTransactionBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseAsRescheduleBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanCreatedBusinessEvent;
@@ -53,6 +54,7 @@ import org.apache.fineract.notification.data.NotificationData;
 import 
org.apache.fineract.notification.eventandlistener.NotificationEventPublisher;
 import org.apache.fineract.portfolio.client.domain.Client;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
 import org.apache.fineract.portfolio.savings.DepositAccountType;
 import org.apache.fineract.portfolio.savings.domain.FixedDepositAccount;
@@ -92,6 +94,8 @@ public class NotificationDomainServiceImpl implements 
NotificationDomainService
         
businessEventNotifierService.addPostBusinessEventListener(LoanCreatedBusinessEvent.class,
 new LoanCreatedListener());
         
businessEventNotifierService.addPostBusinessEventListener(LoanApprovedBusinessEvent.class,
 new LoanApprovedListener());
         
businessEventNotifierService.addPostBusinessEventListener(LoanCloseBusinessEvent.class,
 new LoanClosedListener());
+        
businessEventNotifierService.addPostBusinessEventListener(LoanChargebackTransactionBusinessEvent.class,
+                new LoanChargebackTransactionListener());
         
businessEventNotifierService.addPostBusinessEventListener(LoanCloseAsRescheduleBusinessEvent.class,
                 new LoanCloseAsRescheduledListener());
         
businessEventNotifierService.addPostBusinessEventListener(LoanTransactionMakeRepaymentPostBusinessEvent.class,
@@ -247,6 +251,19 @@ public class NotificationDomainServiceImpl implements 
NotificationDomainService
         }
     }
 
+    private class LoanChargebackTransactionListener implements 
BusinessEventListener<LoanChargebackTransactionBusinessEvent> {
+
+        @Override
+        public void onBusinessEvent(LoanChargebackTransactionBusinessEvent 
event) {
+            LoanTransaction loanTransaction = event.get();
+            
buildNotification(LoanChargebackTransactionBusinessEvent.LOAN_CHARGEBACK_TRANSACTION_PERMISSION,
+                    
LoanChargebackTransactionBusinessEvent.LOAN_CHARGEBACK_TRANSACTION_OBJECT_TYPE, 
loanTransaction.getId(),
+                    
LoanChargebackTransactionBusinessEvent.LOAN_CHARGEBACK_TRANSACTION_NOTIFICATION,
+                    
LoanChargebackTransactionBusinessEvent.LOAN_CHARGEBACK_TRANSACTION_EVENT_TYPE, 
context.authenticatedUser().getId(),
+                    loanTransaction.getLoan().getOfficeId());
+        }
+    }
+
     private class LoanMakeRepaymentListener implements 
BusinessEventListener<LoanTransactionMakeRepaymentPostBusinessEvent> {
 
         @Override
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
index 8d2bf69d8..45270e9d1 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
@@ -670,7 +670,7 @@ public class Charge extends AbstractPersistableCustom {
 
         PaymentTypeData paymentTypeData = null;
         if (this.paymentType != null) {
-            paymentTypeData = PaymentTypeData.instance(paymentType.getId(), 
paymentType.getPaymentName());
+            paymentTypeData = PaymentTypeData.instance(paymentType.getId(), 
paymentType.getName());
         }
 
         final CurrencyData currency = new CurrencyData(this.currencyCode, 
null, 0, 0, null, null);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
index 47eb7d89e..5c3281ee3 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
@@ -150,4 +150,9 @@ public interface LoanApiConstants {
 
     String DISALLOW_EXPECTED_DISBURSEMENTS = "disallowExpectedDisbursements";
 
+    String TRANSACTION_AMOUNT_PARAMNAME = "transactionAmount";
+    String PAYMENT_TYPE_PARAMNAME = "paymentTypeId";
+
+    // Commands
+    String CHARGEBACK_TRANSACTION_COMMAND = "chargeback";
 }
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 62bc82943..a15b976a2 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
@@ -275,10 +275,17 @@ public class LoanTransactionsApiResource {
             @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsTransactionIdResponse.class)))
 })
     public String adjustLoanTransaction(@PathParam("loanId") 
@Parameter(description = "loanId") final Long loanId,
             @PathParam("transactionId") @Parameter(description = 
"transactionId") final Long transactionId,
-            @Parameter(hidden = true) final String apiRequestBodyAsJson) {
+            @Parameter(hidden = true) final String apiRequestBodyAsJson,
+            @QueryParam("command") @Parameter(description = "command") final 
String commandParam) {
 
         final CommandWrapperBuilder builder = new 
CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
-        final CommandWrapper commandRequest = 
builder.adjustTransaction(loanId, transactionId).build();
+
+        CommandWrapper commandRequest = null;
+        if (CommandParameterUtil.is(commandParam, 
LoanApiConstants.CHARGEBACK_TRANSACTION_COMMAND)) {
+            commandRequest = builder.chargebackTransaction(loanId, 
transactionId).build();
+        } else { // Default to adjust the Loan Transaction
+            commandRequest = builder.adjustTransaction(loanId, 
transactionId).build();
+        }
 
         final CommandProcessingResult result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
index 1d1006855..e32065d36 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
@@ -91,6 +91,8 @@ public class LoanTransactionData {
     private String reversalExternalId;
     private LocalDate reversedOnDate;
 
+    private List<LoanTransactionRelationData> transactionRelations;
+
     public static LoanTransactionData importInstance(BigDecimal 
repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId,
             Integer rowIndex, String locale, String dateFormat) {
         return new LoanTransactionData(repaymentAmount, lastRepaymentDate, 
repaymentTypeId, rowIndex, locale, dateFormat);
@@ -348,4 +350,8 @@ public class LoanTransactionData {
     public void setLoanChargePaidByList(Collection<LoanChargePaidByData> 
loanChargePaidByList) {
         this.loanChargePaidByList = loanChargePaidByList;
     }
+
+    public void setLoanTransactionRelations(List<LoanTransactionRelationData> 
transactionRelations) {
+        this.transactionRelations = transactionRelations;
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionRelationData.java
similarity index 58%
copy from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionRelationData.java
index b3aead6d3..aa848ed72 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionRelationData.java
@@ -16,15 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.paymenttype.service;
+package org.apache.fineract.portfolio.loanaccount.data;
 
-import java.util.Collection;
-import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
 
-public interface PaymentTypeReadPlatformService {
+@Getter
+@Setter
+@AllArgsConstructor
+public class LoanTransactionRelationData implements Serializable {
 
-    Collection<PaymentTypeData> retrieveAllPaymentTypes();
-
-    PaymentTypeData retrieveOne(Long paymentTypeId);
+    private Long fromLoanTransaction;
+    private Long toLoanTransaction;
+    private LoanTransactionRelationTypeEnum relationType;
+    private BigDecimal amount;
+    private String paymentType;
 
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index f3d72a97d..d70c6ab28 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -1338,7 +1338,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
             final Money principal = 
this.loanRepaymentScheduleDetail.getPrincipal();
             this.summary.updateSummary(loanCurrency(), principal, 
getRepaymentScheduleInstallments(), this.loanSummaryWrapper,
                     isDisbursed(), this.charges);
-            updateLoanOutstandingBalaces();
+            updateLoanOutstandingBalances();
         }
     }
 
@@ -2850,7 +2850,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
             chargesPayment.updateComponentsAndTotal(zero, zero, 
disbursentMoney, zero);
             chargesPayment.updateLoan(this);
             addLoanTransaction(chargesPayment);
-            updateLoanOutstandingBalaces();
+            updateLoanOutstandingBalances();
         }
 
         if (getApprovedOnDate() != null && 
disbursedOn.isBefore(getApprovedOnDate())) {
@@ -2897,7 +2897,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         chargesPayment.updateComponents(zero, zero, 
charge.getAmount(getCurrency()), zero);
         chargesPayment.updateLoan(this);
         addLoanTransaction(chargesPayment);
-        updateLoanOutstandingBalaces();
+        updateLoanOutstandingBalances();
         charge.markAsFullyPaid();
         return chargesPayment;
     }
@@ -3426,7 +3426,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
                 addLoanTransaction(finalAccrual);
             }
         }
-        updateLoanOutstandingBalaces();
+        updateLoanOutstandingBalances();
     }
 
     private void determineCumulativeIncomeFromInstallments(HashMap<String, 
BigDecimal> cumulativeIncomeFromInstallments) {
@@ -3898,6 +3898,12 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         return changedTransactionDetail;
     }
 
+    public void handleChargebackTransaction(LoanTransaction 
chargebackTransaction,
+            final LoanLifecycleStateMachine loanLifecycleStateMachine) {
+        chargebackTransaction.updateLoan(this);
+        // TODO To be updated in the repayment schedule
+    }
+
     /**
      * Behaviour added to comply with capability of previous mifos product to 
support easier transition to fineract
      * platform.
@@ -5422,7 +5428,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
                 addLoanTransaction(accrual);
             }
         }
-        updateLoanOutstandingBalaces();
+        updateLoanOutstandingBalances();
     }
 
     private void determineFeeDetails(LocalDate fromDate, LocalDate toDate, 
HashMap<String, Object> feeDetails) {
@@ -5665,7 +5671,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         return interestRecalculatedOn;
     }
 
-    private void updateLoanOutstandingBalaces() {
+    private void updateLoanOutstandingBalances() {
         Money outstanding = Money.zero(getCurrency());
         List<LoanTransaction> loanTransactions = 
retreiveListOfTransactionsExcludeAccruals();
         for (LoanTransaction loanTransaction : loanTransactions) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
index 9d7f1dfba..e0c11e21f 100755
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
@@ -48,5 +48,6 @@ public enum LoanEvent {
     LOAN_INITIATE_TRANSFER, //
     LOAN_REJECT_TRANSFER, //
     LOAN_WITHDRAW_TRANSFER, //
-    LOAN_CREDIT_BALANCE_REFUND;
+    LOAN_CREDIT_BALANCE_REFUND, //
+    LOAN_CHARGEBACK;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index a4d5d3e00..22313cc2b 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -130,6 +130,9 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = 
FetchType.EAGER, mappedBy = "loanTransaction")
     private Set<LoanTransactionToRepaymentScheduleMapping> 
loanTransactionToRepaymentScheduleMappings = new HashSet<>();
 
+    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = 
FetchType.LAZY, mappedBy = "fromTransaction")
+    private Set<LoanTransactionRelation> loanTransactionRelations = new 
HashSet<>();
+
     protected LoanTransaction() {}
 
     public static LoanTransaction incomePosting(final Loan loan, final Office 
office, final LocalDate dateOf, final BigDecimal amount,
@@ -155,6 +158,14 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
         return new LoanTransaction(null, office, 
LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, 
externalId);
     }
 
+    public static LoanTransaction chargeback(final Loan loan, final Money 
amount, final PaymentDetail paymentDetail,
+            final LocalDate paymentDate, final String externalId) {
+        LoanTransaction loanTransaction = new LoanTransaction(loan, 
loan.getOffice(), LoanTransactionType.CHARGEBACK, paymentDetail,
+                amount.getAmount(), paymentDate, externalId);
+        loanTransaction.principalPortion = amount.getAmount();
+        return loanTransaction;
+    }
+
     public static LoanTransaction repaymentType(final LoanTransactionType 
repaymentType, final Office office, final Money amount,
             final PaymentDetail paymentDetail, final LocalDate paymentDate, 
final String externalId, final String chargeRefundChargeType) {
         return new LoanTransaction(null, office, repaymentType, paymentDetail, 
amount.getAmount(), paymentDate, externalId,
@@ -170,7 +181,6 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
                 break;
             }
         }
-
     }
 
     public static LoanTransaction recoveryRepayment(final Office office, final 
Money amount, final PaymentDetail paymentDetail,
@@ -589,6 +599,10 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
         return getTypeOf().isChargePayment() && isNotReversed();
     }
 
+    public boolean isChargeback() {
+        return getTypeOf().isChargeback() && isNotReversed();
+    }
+
     public boolean isPenaltyPayment() {
         boolean isPenalty = false;
         if (isChargePayment()) {
@@ -849,6 +863,14 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
         return submittedOnDate;
     }
 
+    public Set<LoanTransactionRelation> getLoanTransactionRelations() {
+        return loanTransactionRelations;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
     // TODO missing hashCode(), equals(Object obj), but probably OK as long as
     // this is never stored in a Collection.
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelation.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelation.java
new file mode 100644
index 000000000..07cc3a57c
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelation.java
@@ -0,0 +1,65 @@
+/**
+ * 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.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.JoinColumn;
+import javax.persistence.Table;
+import javax.persistence.Version;
+import javax.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+import 
org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "m_loan_transaction_relation")
+public class LoanTransactionRelation extends 
AbstractAuditableWithUTCDateTimeCustom {
+
+    @JoinColumn(name = "from_loan_transaction_id", nullable = false)
+    private LoanTransaction fromTransaction;
+
+    @JoinColumn(name = "to_loan_transaction_id", nullable = false)
+    private LoanTransaction toTransaction;
+
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "relation_type_enum", nullable = false)
+    private LoanTransactionRelationTypeEnum relationType;
+
+    @Version
+    private Long version;
+
+    protected LoanTransactionRelation() {}
+
+    protected LoanTransactionRelation(@NotNull LoanTransaction 
fromTransaction, @NotNull LoanTransaction toTransaction,
+            LoanTransactionRelationTypeEnum relationType) {
+        this.fromTransaction = fromTransaction;
+        this.toTransaction = toTransaction;
+        this.relationType = relationType;
+    }
+
+    public static LoanTransactionRelation instance(@NotNull LoanTransaction 
fromTransaction, @NotNull LoanTransaction toTransaction,
+            LoanTransactionRelationTypeEnum relation) {
+        return new LoanTransactionRelation(fromTransaction, toTransaction, 
relation);
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationRepository.java
similarity index 69%
copy from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationRepository.java
index d4119bdf2..6eeec08ea 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationRepository.java
@@ -16,11 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.paymenttype.domain;
+package org.apache.fineract.portfolio.loanaccount.domain;
 
+import java.util.List;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 
-public interface PaymentTypeRepository extends JpaRepository<PaymentType, 
Long>, JpaSpecificationExecutor<PaymentType> {
+public interface LoanTransactionRelationRepository
+        extends JpaRepository<LoanTransactionRelation, Long>, 
JpaSpecificationExecutor<LoanTransactionRelation> {
+
+    List<LoanTransactionRelation> 
findByFromTransactionAndRelationType(LoanTransaction loanTransaction,
+            LoanTransactionRelationTypeEnum relationType);
 
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationTypeEnum.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationTypeEnum.java
new file mode 100644
index 000000000..d07987798
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRelationTypeEnum.java
@@ -0,0 +1,54 @@
+/**
+ * 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.domain;
+
+public enum LoanTransactionRelationTypeEnum {
+
+    INVALID(0, "loanTransactionType.invalid"), //
+    CHARGEBACK(1, "loanTransactionRelationType.chargeback");
+
+    private final Integer value;
+    private final String code;
+
+    LoanTransactionRelationTypeEnum(final Integer value, final String code) {
+        this.value = value;
+        this.code = code;
+    }
+
+    public Integer getValue() {
+        return this.value;
+    }
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public static LoanTransactionRelationTypeEnum fromInt(final Integer 
transactionType) {
+
+        if (transactionType == null) {
+            return LoanTransactionRelationTypeEnum.INVALID;
+        }
+
+        return switch (transactionType) {
+            case 1 -> LoanTransactionRelationTypeEnum.CHARGEBACK;
+            default -> LoanTransactionRelationTypeEnum.INVALID;
+        };
+    }
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
index 1515d656e..7fee78b88 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
@@ -173,4 +173,9 @@ public enum LoanTransactionType {
     public boolean isIncomePosting() {
         return 
this.value.equals(LoanTransactionType.INCOME_POSTING.getValue());
     }
+
+    public boolean isChargeback() {
+        return this.value.equals(LoanTransactionType.CHARGEBACK.getValue());
+    }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentChargebackCommandHandler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentChargebackCommandHandler.java
new file mode 100644
index 000000000..58991fac3
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanRepaymentChargebackCommandHandler.java
@@ -0,0 +1,43 @@
+/**
+ * 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", action = "CHARGEBACK")
+public class LoanRepaymentChargebackCommandHandler implements 
NewCommandSourceHandler {
+
+    private final LoanWritePlatformService writePlatformService;
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return 
this.writePlatformService.chargebackLoanTransaction(command.getLoanId(), 
command.entityId(), command);
+    }
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionRelationMapper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionRelationMapper.java
new file mode 100644
index 000000000..37f524e50
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionRelationMapper.java
@@ -0,0 +1,39 @@
+/**
+ * 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.mapper;
+
+import java.util.List;
+import 
org.apache.fineract.portfolio.loanaccount.data.LoanTransactionRelationData;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface LoanTransactionRelationMapper {
+
+    @Mapping(target = "fromLoanTransaction", source = 
"source.fromTransaction.id")
+    @Mapping(target = "toLoanTransaction", source = "source.toTransaction.id")
+    @Mapping(target = "relationType", source = "source.relationType")
+    @Mapping(target = "amount", source = "source.toTransaction.amount")
+    @Mapping(target = "paymentType", source = 
"source.toTransaction.paymentDetail.paymentType.name")
+    LoanTransactionRelationData map(LoanTransactionRelation source);
+
+    List<LoanTransactionRelationData> map(List<LoanTransactionRelation> 
sources);
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
index 1e0ac6c90..1da8a069f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
@@ -212,7 +212,37 @@ public final class LoanEventApiJsonValidator {
         
baseDataValidator.reset().parameter(LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME).ignoreIfNull().value(reversalExternalId)
                 .notExceedingLengthOf(100);
 
-        validatePaymentDetails(baseDataValidator, element);
+        validatePaymentDetails(baseDataValidator, element, false);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateChargebackTransaction(final String json) {
+
+        if (StringUtils.isBlank(json)) {
+            throw new InvalidJsonException();
+        }
+
+        final Set<String> transactionParameters = new 
HashSet<>(Arrays.asList(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME,
+                LoanApiConstants.localeParameterName, 
LoanApiConstants.externalIdParameterName, LoanApiConstants.noteParameterName,
+                LoanApiConstants.PAYMENT_TYPE_PARAMNAME));
+
+        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(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME,
 element);
+        
baseDataValidator.reset().parameter(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME).value(transactionAmount).notNull()
+                .zeroOrPositiveAmount();
+
+        final String note = 
this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.noteParameterName, 
element);
+        
baseDataValidator.reset().parameter(LoanApiConstants.noteParameterName).value(note).notExceedingLengthOf(1000);
+
+        validatePaymentDetails(baseDataValidator, element, true);
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
     }
 
@@ -262,9 +292,19 @@ public final class LoanEventApiJsonValidator {
     }
 
     private void validatePaymentDetails(final DataValidatorBuilder 
baseDataValidator, final JsonElement element) {
+        final boolean paymentDetailRequired = false; // Default value for 
backward compatibility
+        validatePaymentDetails(baseDataValidator, element, 
paymentDetailRequired);
+    }
+
+    private void validatePaymentDetails(final DataValidatorBuilder 
baseDataValidator, final JsonElement element,
+            final boolean paymentDetailRequired) {
         // Validate all string payment detail fields for max length
         final Integer paymentTypeId = 
this.fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
-        
baseDataValidator.reset().parameter("paymentTypeId").value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
+        if (paymentDetailRequired) {
+            
baseDataValidator.reset().parameter("paymentTypeId").value(paymentTypeId).notNull().integerGreaterThanZero();
+        } else {
+            
baseDataValidator.reset().parameter("paymentTypeId").value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
+        }
         final Set<String> paymentDetailParameters = new HashSet<>(
                 Arrays.asList("accountNumber", "checkNumber", "routingCode", 
"receiptNumber", "bankNumber"));
         for (final String paymentDetailParameterName : 
paymentDetailParameters) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
index f52f20163..b47ff7547 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
@@ -36,6 +36,7 @@ import 
org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
 import 
org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+import 
org.apache.fineract.portfolio.loanaccount.data.LoanTransactionRelationData;
 import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
 import 
org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -157,4 +158,7 @@ public interface LoanReadPlatformService {
     List<LoanRepaymentScheduleInstallmentData> getRepaymentDataResponse(Long 
loanId);
 
     CollectionData retrieveLoanCollectionData(Long loanId);
+
+    List<LoanTransactionRelationData> 
retrieveLoanTransactionRelationsByLoanTransactionId(Long loanTransactionId);
+
 }
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 9c0d3266e..7005b8adb 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
@@ -34,6 +34,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
+import lombok.AllArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.accounting.common.AccountingRuleType;
 import org.apache.fineract.infrastructure.codes.data.CodeValueData;
@@ -97,6 +98,7 @@ import 
org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+import 
org.apache.fineract.portfolio.loanaccount.data.LoanTransactionRelationData;
 import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
 import 
org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
 import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
@@ -108,12 +110,17 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
+import 
org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionRelationMapper;
 import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
 import 
org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
 import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
@@ -125,7 +132,6 @@ import 
org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
 import 
org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.jetbrains.annotations.NotNull;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.DataAccessException;
 import org.springframework.dao.EmptyResultDataAccessException;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -136,6 +142,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
+@AllArgsConstructor
 @Service
 @Transactional(readOnly = true)
 public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@@ -154,7 +161,6 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
     private final CalendarReadPlatformService calendarReadPlatformService;
     private final StaffReadPlatformService staffReadPlatformService;
     private final PaginationHelper paginationHelper;
-    private final LoanMapper loanMapper;
     private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
     private final PaymentTypeReadPlatformService 
paymentTypeReadPlatformService;
     private final LoanRepaymentScheduleTransactionProcessorFactory 
loanRepaymentScheduleTransactionProcessorFactory;
@@ -165,48 +171,9 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
     private final ColumnValidator columnValidator;
     private final DatabaseSpecificSQLGenerator sqlGenerator;
     private final DelinquencyReadPlatformService 
delinquencyReadPlatformService;
-
-    @Autowired
-    public LoanReadPlatformServiceImpl(final PlatformSecurityContext context,
-            final ApplicationCurrencyRepositoryWrapper 
applicationCurrencyRepository,
-            final LoanProductReadPlatformService 
loanProductReadPlatformService, final ClientReadPlatformService 
clientReadPlatformService,
-            final GroupReadPlatformService groupReadPlatformService, final 
LoanDropdownReadPlatformService loanDropdownReadPlatformService,
-            final FundReadPlatformService fundReadPlatformService, final 
ChargeReadPlatformService chargeReadPlatformService,
-            final CodeValueReadPlatformService codeValueReadPlatformService, 
final JdbcTemplate jdbcTemplate,
-            final NamedParameterJdbcTemplate namedParameterJdbcTemplate, final 
CalendarReadPlatformService calendarReadPlatformService,
-            final StaffReadPlatformService staffReadPlatformService, final 
PaymentTypeReadPlatformService paymentTypeReadPlatformService,
-            final LoanRepaymentScheduleTransactionProcessorFactory 
loanRepaymentScheduleTransactionProcessorFactory,
-            final FloatingRatesReadPlatformService 
floatingRatesReadPlatformService, final LoanUtilService loanUtilService,
-            final ConfigurationDomainService configurationDomainService,
-            final DelinquencyReadPlatformService 
delinquencyReadPlatformService,
-            final AccountDetailsReadPlatformService 
accountDetailsReadPlatformService, final LoanRepositoryWrapper 
loanRepositoryWrapper,
-            final ColumnValidator columnValidator, 
DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
-        this.context = context;
-        this.loanRepositoryWrapper = loanRepositoryWrapper;
-        this.applicationCurrencyRepository = applicationCurrencyRepository;
-        this.loanProductReadPlatformService = loanProductReadPlatformService;
-        this.clientReadPlatformService = clientReadPlatformService;
-        this.groupReadPlatformService = groupReadPlatformService;
-        this.loanDropdownReadPlatformService = loanDropdownReadPlatformService;
-        this.fundReadPlatformService = fundReadPlatformService;
-        this.chargeReadPlatformService = chargeReadPlatformService;
-        this.codeValueReadPlatformService = codeValueReadPlatformService;
-        this.calendarReadPlatformService = calendarReadPlatformService;
-        this.staffReadPlatformService = staffReadPlatformService;
-        this.jdbcTemplate = jdbcTemplate;
-        this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
-        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
-        this.loanRepaymentScheduleTransactionProcessorFactory = 
loanRepaymentScheduleTransactionProcessorFactory;
-        this.floatingRatesReadPlatformService = 
floatingRatesReadPlatformService;
-        this.loanUtilService = loanUtilService;
-        this.configurationDomainService = configurationDomainService;
-        this.accountDetailsReadPlatformService = 
accountDetailsReadPlatformService;
-        this.columnValidator = columnValidator;
-        this.loanMapper = new LoanMapper(sqlGenerator, 
delinquencyReadPlatformService);
-        this.sqlGenerator = sqlGenerator;
-        this.paginationHelper = paginationHelper;
-        this.delinquencyReadPlatformService = delinquencyReadPlatformService;
-    }
+    private final LoanTransactionRepository loanTransactionRepository;
+    private final LoanTransactionRelationRepository 
loanTransactionRelationRepository;
+    private final LoanTransactionRelationMapper loanTransactionRelationMapper;
 
     @Override
     public LoanAccountData retrieveOne(final Long loanId) {
@@ -296,9 +263,16 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
             /***
              * TODO Vishwas: Remove references to "Contra" from the codebase
              ***/
-            final String sql = "select " + rm.loanPaymentsSchema()
-                    + " where tr.loan_id = ? and tr.transaction_type_enum not 
in (0, 3) and  (tr.is_reversed=false or tr.manually_adjusted_or_reversed = 
true) order by tr.transaction_date ASC,id ";
-            return this.jdbcTemplate.query(sql, rm, loanId); // NOSONAR
+            final String sql = "select " + rm.loanPaymentsSchema() + " where 
tr.loan_id = ? and tr.transaction_type_enum not in (0, 3) "
+                    + " and (tr.is_reversed=false or 
tr.manually_adjusted_or_reversed = true) " + " order by tr.transaction_date 
ASC,id ";
+            Collection<LoanTransactionData> loanTransactionData = 
this.jdbcTemplate.query(sql, rm, loanId); // NOSONAR
+            for (LoanTransactionData loanTransaction : loanTransactionData) {
+                if (loanTransaction.getType().isRepaymentType()) {
+                    loanTransaction
+                            
.setLoanTransactionRelations(this.retrieveLoanTransactionRelationsByLoanTransactionId(loanTransaction.getId()));
+                }
+            }
+            return loanTransactionData;
         } catch (final EmptyResultDataAccessException e) {
             return null;
         }
@@ -310,10 +284,11 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
         final AppUser currentUser = this.context.authenticatedUser();
         final String hierarchy = currentUser.getOffice().getHierarchy();
         final String hierarchySearchString = hierarchy + "%";
+        final LoanMapper loanMapper = new LoanMapper(sqlGenerator, 
delinquencyReadPlatformService);
 
         final StringBuilder sqlBuilder = new StringBuilder(200);
         sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
-        sqlBuilder.append(this.loanMapper.loanSchema());
+        sqlBuilder.append(loanMapper.loanSchema());
 
         // TODO - for time being this will data scope list of loans returned to
         // only loans that have a client associated.
@@ -377,7 +352,7 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
         }
         final Object[] objectArray = extraCriterias.toArray();
         final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
-        return this.paginationHelper.fetchPage(this.jdbcTemplate, 
sqlBuilder.toString(), finalObjectArray, this.loanMapper);
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, 
sqlBuilder.toString(), finalObjectArray, loanMapper);
     }
 
     @Override
@@ -588,7 +563,9 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
         try {
             final LoanTransactionsMapper rm = new 
LoanTransactionsMapper(sqlGenerator);
             final String sql = "select " + rm.loanPaymentsSchema() + " where 
l.id = ? and tr.id = ? ";
-            return this.jdbcTemplate.queryForObject(sql, rm, loanId, 
transactionId); // NOSONAR
+            LoanTransactionData loanTransactionData = 
this.jdbcTemplate.queryForObject(sql, rm, loanId, transactionId); // NOSONAR
+            
loanTransactionData.setLoanTransactionRelations(this.retrieveLoanTransactionRelationsByLoanTransactionId(transactionId));
+            return loanTransactionData;
         } catch (final EmptyResultDataAccessException e) {
             throw new LoanTransactionNotFoundException(transactionId, e);
         }
@@ -1310,10 +1287,10 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
         public String loanPaymentsSchema() {
 
             return " tr.id as id, tr.transaction_type_enum as transactionType, 
tr.transaction_date as " + sqlGenerator.escape("date")
-                    + ", tr.amount as total, " + " 
tr.principal_portion_derived as principal, tr.interest_portion_derived as 
interest, "
-                    + " tr.fee_charges_portion_derived as fees, 
tr.penalty_charges_portion_derived as penalties, "
+                    + ", tr.amount as total, tr.principal_portion_derived as 
principal, tr.interest_portion_derived as interest, "
+                    + " tr.fee_charges_portion_derived as fees, 
tr.penalty_charges_portion_derived as penalties,  "
                     + " tr.overpayment_portion_derived as overpayment, 
tr.outstanding_loan_balance_derived as outstandingLoanBalance, "
-                    + " tr.unrecognized_income_portion as unrecognizedIncome," 
+ " tr.submitted_on_date as submittedOnDate, "
+                    + " tr.unrecognized_income_portion as unrecognizedIncome, 
tr.submitted_on_date as submittedOnDate, "
                     + " tr.manually_adjusted_or_reversed as manuallyReversed, 
tr.reversal_external_id as reversalExternalId, tr.reversed_on_date as 
reversedOnDate, "
                     + " pd.payment_type_id as paymentType,pd.account_number as 
accountNumber,pd.check_number as checkNumber, "
                     + " pd.receipt_number as receiptNumber, pd.bank_number as 
bankNumber,pd.routing_code as routingCode, l.net_disbursal_amount as 
netDisbursalAmount,"
@@ -1323,10 +1300,10 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
                     + " pt.value as paymentTypeName, tr.external_id as 
externalId, tr.office_id as officeId, office.name as officeName, "
                     + " fromtran.id as fromTransferId, fromtran.is_reversed as 
fromTransferReversed,"
                     + " fromtran.transaction_date as fromTransferDate, 
fromtran.amount as fromTransferAmount,"
-                    + " fromtran.description as fromTransferDescription,"
-                    + " totran.id as toTransferId, totran.is_reversed as 
toTransferReversed,"
+                    + " fromtran.description as fromTransferDescription, "
+                    + " totran.id as toTransferId, totran.is_reversed as 
toTransferReversed, "
                     + " totran.transaction_date as toTransferDate, 
totran.amount as toTransferAmount,"
-                    + " totran.description as toTransferDescription " + " from 
m_loan l join m_loan_transaction tr on tr.loan_id = l.id"
+                    + " totran.description as toTransferDescription from 
m_loan l join m_loan_transaction tr on tr.loan_id = l.id "
                     + " join m_currency rc on rc." + 
sqlGenerator.escape("code") + " = l.currency_code "
                     + " left JOIN m_payment_detail pd ON tr.payment_detail_id 
= pd.id"
                     + " left join m_payment_type pt on pd.payment_type_id = 
pt.id" + " left join m_office office on office.id=tr.office_id"
@@ -1405,6 +1382,7 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
                 transfer = 
AccountTransferData.transferBasicDetails(toTransferId, currencyData, 
toTransferAmount, toTransferDate,
                         toTransferDescription, toTransferReversed);
             }
+
             return new LoanTransactionData(id, officeId, officeName, 
transactionType, paymentDetailData, currencyData, date, totalAmount,
                     netDisbursalAmount, principalPortion, interestPortion, 
feeChargesPortion, penaltyChargesPortion, overPaymentPortion,
                     unrecognizedIncomePortion, externalId, transfer, null, 
outstandingLoanBalance, submittedOnDate, manuallyReversed,
@@ -2066,7 +2044,7 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
         public String schema() {
 
             return " tr.id as id, tr.transaction_type_enum as transactionType, 
tr.transaction_date as " + sqlGenerator.escape("date")
-                    + ", tr.amount as total, " + " 
tr.principal_portion_derived as principal, tr.interest_portion_derived as 
interest, "
+                    + ", tr.amount as total, tr.principal_portion_derived as 
principal, tr.interest_portion_derived as interest, "
                     + " tr.fee_charges_portion_derived as fees, 
tr.penalty_charges_portion_derived as penalties, "
                     + " tr.overpayment_portion_derived as overpayment, 
tr.outstanding_loan_balance_derived as outstandingLoanBalance, "
                     + " tr.unrecognized_income_portion as unrecognizedIncome " 
+ " from m_loan_transaction tr ";
@@ -2492,4 +2470,12 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
                     delinquentAmount, lastPaymentDate, lastPaymentAmount);
         }
     }
+
+    @Override
+    public List<LoanTransactionRelationData> 
retrieveLoanTransactionRelationsByLoanTransactionId(Long loanTransactionId) {
+        final LoanTransaction loanTransaction = 
this.loanTransactionRepository.getReferenceById(loanTransactionId);
+        List<LoanTransactionRelation> loanTransactionRelations = 
this.loanTransactionRelationRepository
+                .findByFromTransactionAndRelationType(loanTransaction, 
LoanTransactionRelationTypeEnum.CHARGEBACK);
+        return loanTransactionRelationMapper.map(loanTransactionRelations);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
index d8e8a29c1..fb419edd7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java
@@ -50,6 +50,8 @@ public interface LoanWritePlatformService {
 
     CommandProcessingResult adjustLoanTransaction(Long loanId, Long 
transactionId, JsonCommand command);
 
+    CommandProcessingResult chargebackLoanTransaction(Long loanId, Long 
transactionId, JsonCommand command);
+
     CommandProcessingResult waiveInterestOnLoan(Long loanId, JsonCommand 
command);
 
     CommandProcessingResult writeOff(Long loanId, JsonCommand command);
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 7115b2d90..7d010b191 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
@@ -59,6 +59,7 @@ import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanAcceptT
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanApplyOverdueChargeBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanChargebackTransactionBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseAsRescheduleBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanDisbursalBusinessEvent;
@@ -180,6 +181,9 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
 import 
org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException;
@@ -237,6 +241,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
     private final LoanAccountDomainService loanAccountDomainService;
     private final NoteRepository noteRepository;
     private final LoanTransactionRepository loanTransactionRepository;
+    private final LoanTransactionRelationRepository 
loanTransactionRelationRepository;
     private final LoanAssembler loanAssembler;
     private final ChargeRepositoryWrapper chargeRepository;
     private final LoanChargeRepository loanChargeRepository;
@@ -392,7 +397,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         final Map<String, Object> changes = new LinkedHashMap<>();
 
         final PaymentDetail paymentDetail = 
this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, 
changes);
-        if (paymentDetail != null && paymentDetail.getPaymentType() != null && 
paymentDetail.getPaymentType().isCashPayment()) {
+        if (paymentDetail != null && paymentDetail.getPaymentType() != null && 
paymentDetail.getPaymentType().getIsCashPayment()) {
             BigDecimal transactionAmount = 
command.bigDecimalValueOfParameterNamed("transactionAmount");
             
this.cashierTransactionDataValidator.validateOnLoanDisbursal(currentUser, 
loan.getCurrencyCode(), transactionAmount);
         }
@@ -1184,6 +1189,103 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
                 .with(changes).build();
     }
 
+    @Transactional
+    @Override
+    public CommandProcessingResult chargebackLoanTransaction(final Long 
loanId, final Long transactionId, final JsonCommand command) {
+        
this.loanEventApiJsonValidator.validateChargebackTransaction(command.json());
+
+        Loan loan = this.loanAssembler.assembleFrom(loanId);
+        if 
(this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, 
PortfolioAccountType.LOAN)) {
+            throw new 
PlatformServiceUnavailableException("error.msg.loan.transfer.transaction.update.not.allowed",
+                    "Loan transaction:" + transactionId + " chargeback not 
allowed as it involves in account transfer", transactionId);
+        }
+        if (loan.isClosedWrittenOff()) {
+            throw new 
PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
+                    "Loan transaction:" + transactionId + " chargeback not 
allowed as loan status is written off", transactionId);
+        }
+        if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+            throw new 
PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
+                    "Loan transaction:" + transactionId + " chargeback not 
allowed as loan product is interest recalculation enabled",
+                    transactionId);
+        }
+
+        final List<Long> existingTransactionIds = new ArrayList<>();
+        final List<Long> existingReversedTransactionIds = new ArrayList<>();
+
+        checkClientOrGroupActive(loan);
+
+        LoanTransaction loanTransaction = 
this.loanTransactionRepository.findById(transactionId)
+                .orElseThrow(() -> new 
LoanTransactionNotFoundException(transactionId));
+
+        if (!loanTransaction.isRepayment()) {
+            throw new 
PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
+                    "Loan transaction:" + transactionId + " chargeback not 
allowed as loan transaction is not repayment", transactionId);
+        }
+
+        businessEventNotifierService.notifyPreBusinessEvent(new 
LoanChargebackTransactionBusinessEvent(loanTransaction));
+
+        final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
+        final BigDecimal transactionAmount = 
command.bigDecimalValueOfParameterNamed(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME);
+        final String txnExternalId = 
command.stringValueOfParameterNamedAllowingNull(LoanApiConstants.externalIdParameterName);
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+        changes.put("transactionAmount", 
command.stringValueOfParameterNamed(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME));
+        changes.put("locale", command.locale());
+        changes.put("dateFormat", command.dateFormat());
+        changes.put("paymentTypeId", 
command.stringValueOfParameterNamed(LoanApiConstants.PAYMENT_TYPE_PARAMNAME));
+
+        final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), 
transactionAmount);
+        final PaymentDetail paymentDetail = 
this.paymentDetailWritePlatformService.createPaymentDetail(command, changes);
+        LoanTransaction newTransaction = LoanTransaction.chargeback(loan, 
transactionAmountAsMoney, paymentDetail, transactionDate,
+                txnExternalId);
+
+        validateLoanTransactionAmountChargeBack(loanTransaction, 
newTransaction);
+
+        
this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
+
+        loan.handleChargebackTransaction(newTransaction, 
defaultLoanLifecycleStateMachine());
+
+        loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+
+        // Store the Loan Transaction Relation
+        LoanTransactionRelation loanTransactionRelation = 
LoanTransactionRelation.instance(loanTransaction, newTransaction,
+                LoanTransactionRelationTypeEnum.CHARGEBACK);
+        this.loanTransactionRelationRepository.save(loanTransactionRelation);
+
+        this.loanTransactionRepository.saveAndFlush(newTransaction);
+
+        final String noteText = 
command.stringValueOfParameterNamed(LoanApiConstants.noteParamName);
+        if (StringUtils.isNotBlank(noteText)) {
+            changes.put("note", noteText);
+            Note note = Note.loanTransactionNote(loan, newTransaction, 
noteText);
+            this.noteRepository.save(note);
+        }
+
+        postJournalEntries(loan, existingTransactionIds, 
existingReversedTransactionIds);
+        this.loanAccountDomainService.setLoanDelinquencyTag(loan, 
DateUtils.getBusinessLocalDate());
+
+        businessEventNotifierService.notifyPostBusinessEvent(new 
LoanChargebackTransactionBusinessEvent(loanTransaction));
+
+        return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(transactionId)
+                
.withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()).withGroupId(loan.getGroupId()).withLoanId(loanId)
+                .with(changes).build();
+    }
+
+    private void validateLoanTransactionAmountChargeBack(LoanTransaction 
loanTransaction, LoanTransaction chargebackTransaction) {
+        BigDecimal actualAmount = BigDecimal.ZERO;
+        for (LoanTransactionRelation loanTransactionRelation : 
loanTransaction.getLoanTransactionRelations()) {
+            if 
(loanTransactionRelation.getRelationType().equals(LoanTransactionRelationTypeEnum.CHARGEBACK))
 {
+                actualAmount = 
actualAmount.add(loanTransactionRelation.getToTransaction().getPrincipalPortion());
+            }
+        }
+        actualAmount = 
actualAmount.add(chargebackTransaction.getPrincipalPortion());
+        if (actualAmount.compareTo(loanTransaction.getPrincipalPortion()) > 0) 
{
+            throw new 
PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
+                    "Loan transaction:" + loanTransaction.getId() + " 
chargeback not allowed as loan transaction amount is not enough",
+                    loanTransaction.getId());
+        }
+    }
+
     @Transactional
     @Override
     public CommandProcessingResult waiveInterestOnLoan(final Long loanId, 
final JsonCommand command) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
index 8c0cbaecf..b737c7631 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
@@ -103,7 +103,7 @@ public final class PaymentDetail extends 
AbstractPersistableCustom {
     }
 
     public PaymentDetailData toData() {
-        final PaymentTypeData paymentTypeData = this.paymentType.toData();
+        final PaymentTypeData paymentTypeData = null; // 
this.paymentType.toData();
         final PaymentDetailData paymentDetailData = new 
PaymentDetailData(getId(), paymentTypeData, this.accountNumber, 
this.checkNumber,
                 this.routingCode, this.receiptNumber, this.bankNumber);
         return paymentDetailData;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java
index f967457cf..aeb359e77 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java
@@ -36,9 +36,11 @@ import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.UriInfo;
+import lombok.AllArgsConstructor;
 import org.apache.fineract.commands.domain.CommandWrapper;
 import org.apache.fineract.commands.service.CommandWrapperBuilder;
 import 
org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -50,9 +52,9 @@ import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityConte
 import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
 import 
org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
 import 
org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+@AllArgsConstructor
 @Path("/paymenttypes")
 @Component
 
@@ -64,34 +66,23 @@ public class PaymentTypeApiResource {
     private final PaymentTypeReadPlatformService readPlatformService;
     private final PortfolioCommandSourceWritePlatformService 
commandWritePlatformService;
     private final ApiRequestParameterHelper apiRequestParameterHelper;
-    // private final String resourceNameForPermissions = "PAYMENT_TYPE";
     private final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper;
 
-    // private final Set<String> RESPONSE_DATA_PARAMETERS = new
-    // HashSet<>(Arrays.asList("id", "value", "description", "isCashPayment"));
-
-    @Autowired
-    public PaymentTypeApiResource(PlatformSecurityContext securityContext, 
DefaultToApiJsonSerializer<PaymentTypeData> jsonSerializer,
-            PaymentTypeReadPlatformService readPlatformService, 
PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper,
-            ApiRequestParameterHelper apiRequestParameterHelper, 
PortfolioCommandSourceWritePlatformService commandWritePlatformService) {
-
-        this.securityContext = securityContext;
-        this.jsonSerializer = jsonSerializer;
-        this.readPlatformService = readPlatformService;
-        this.paymentTypeRepositoryWrapper = paymentTypeRepositoryWrapper;
-        this.apiRequestParameterHelper = apiRequestParameterHelper;
-        this.commandWritePlatformService = commandWritePlatformService;
-    }
-
     @GET
     @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
     @Produces(MediaType.APPLICATION_JSON)
     @Operation(summary = "Retrieve all Payment Types", description = "Retrieve 
list of payment types")
     @ApiResponses({
             @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(array = @ArraySchema(schema = @Schema(implementation = 
PaymentTypeApiResourceSwagger.GetPaymentTypesResponse.class)))) })
-    public String getAllPaymentTypes(@Context final UriInfo uriInfo) {
+    public String getAllPaymentTypes(@Context final UriInfo uriInfo,
+            @QueryParam("onlyWithCode") @Parameter(description = 
"onlyWithCode") final boolean onlyWithCode) {
         
this.securityContext.authenticatedUser().validateHasReadPermission(PaymentTypeApiResourceConstants.resourceNameForPermissions);
-        final Collection<PaymentTypeData> paymentTypes = 
this.readPlatformService.retrieveAllPaymentTypes();
+        Collection<PaymentTypeData> paymentTypes = null;
+        if (onlyWithCode) {
+            paymentTypes = 
this.readPlatformService.retrieveAllPaymentTypesWithCode();
+        } else {
+            paymentTypes = this.readPlatformService.retrieveAllPaymentTypes();
+        }
         final ApiRequestJsonSerializationSettings settings = 
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
         return this.jsonSerializer.serialize(settings, paymentTypes, 
PaymentTypeApiResourceConstants.RESPONSE_DATA_PARAMETERS);
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceConstants.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceConstants.java
index 8d8440983..be7b4f00f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceConstants.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceConstants.java
@@ -37,6 +37,8 @@ public final class PaymentTypeApiResourceConstants {
     public static final String DESCRIPTION = "description";
     public static final String ISCASHPAYMENT = "isCashPayment";
     public static final String POSITION = "position";
+    public static final String CODE_NAME = "code_name";
+    public static final String IS_SYSTEM_DEFINED = "system_defined";
 
     static final Set<String> RESPONSE_DATA_PARAMETERS = new 
HashSet<>(Arrays.asList(ID, NAME, DESCRIPTION, ISCASHPAYMENT));
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java
index ead8bd51d..8813ec286 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java
@@ -42,6 +42,10 @@ final class PaymentTypeApiResourceSwagger {
         public Boolean isCashPayment;
         @Schema(example = "0")
         public Integer position;
+        @Schema(example = "REPAYMENT_REFUND")
+        public String codeName;
+        @Schema(example = "false")
+        public Boolean isSystemDefined;
     }
 
     @Schema(description = "GetPaymentTypesPaymentTypeIdResponse")
@@ -59,6 +63,10 @@ final class PaymentTypeApiResourceSwagger {
         public Boolean isCashPayment;
         @Schema(example = "1")
         public Integer position;
+        @Schema(example = "REPAYMENT_REFUND")
+        public String codeName;
+        @Schema(example = "false")
+        public Boolean isSystemDefined;
     }
 
     @Schema(description = "PostPaymentTypesRequest")
@@ -74,6 +82,10 @@ final class PaymentTypeApiResourceSwagger {
         public Boolean isCashPayment;
         @Schema(example = "1")
         public Integer position;
+        @Schema(example = "REPAYMENT_REFUND")
+        public String codeName;
+        @Schema(example = "false")
+        public Boolean isSystemDefined;
     }
 
     @Schema(description = "PostPaymentTypesResponse")
@@ -98,6 +110,10 @@ final class PaymentTypeApiResourceSwagger {
         public Boolean isCashPayment;
         @Schema(example = "3")
         public Integer position;
+        @Schema(example = "REPAYMENT_REFUND")
+        public String codeName;
+        @Schema(example = "false")
+        public Boolean isSystemDefined;
     }
 
     @Schema(description = "PutPaymentTypesPaymentTypeIdResponse")
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeData.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeData.java
index ee18a19ff..b626469bb 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeData.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeData.java
@@ -22,8 +22,12 @@ import java.io.Serializable;
 import lombok.AllArgsConstructor;
 import lombok.EqualsAndHashCode;
 import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
 
+@ToString
 @Getter
+@Setter
 @EqualsAndHashCode
 @AllArgsConstructor
 public class PaymentTypeData implements Serializable {
@@ -33,16 +37,22 @@ public class PaymentTypeData implements Serializable {
     private String description;
     private Boolean isCashPayment;
     private Long position;
+    private String codeName;
+    private Boolean isSystemDefined;
 
     public static PaymentTypeData instance(final Long id, final String name, 
final String description, final Boolean isCashPayment,
             final Long position) {
-        return new PaymentTypeData(id, name, description, isCashPayment, 
position);
+        String codeName = null;
+        Boolean isSystemDefined = false;
+        return new PaymentTypeData(id, name, description, isCashPayment, 
position, codeName, isSystemDefined);
     }
 
     public static PaymentTypeData instance(final Long id, final String name) {
         String description = null;
         Boolean isCashPayment = null;
         Long position = null;
-        return new PaymentTypeData(id, name, description, isCashPayment, 
position);
+        String codeName = null;
+        Boolean isSystemDefined = false;
+        return new PaymentTypeData(id, name, description, isCashPayment, 
position, codeName, isSystemDefined);
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeDataValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeDataValidator.java
index b735af4a2..23a570242 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeDataValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/data/PaymentTypeDataValidator.java
@@ -43,11 +43,13 @@ public class PaymentTypeDataValidator {
     private final FromJsonHelper fromApiJsonHelper;
     private static final Set<String> 
CREATE_PAYMENT_TYPE_REQUEST_DATA_PARAMETERS = new HashSet<>(
             Arrays.asList(PaymentTypeApiResourceConstants.NAME, 
PaymentTypeApiResourceConstants.DESCRIPTION,
-                    PaymentTypeApiResourceConstants.ISCASHPAYMENT, 
PaymentTypeApiResourceConstants.POSITION));
+                    PaymentTypeApiResourceConstants.ISCASHPAYMENT, 
PaymentTypeApiResourceConstants.POSITION,
+                    PaymentTypeApiResourceConstants.CODE_NAME, 
PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED));
 
     private static final Set<String> 
UPDATE_PAYMENT_TYPE_REQUEST_DATA_PARAMETERS = new HashSet<>(
             Arrays.asList(PaymentTypeApiResourceConstants.NAME, 
PaymentTypeApiResourceConstants.DESCRIPTION,
-                    PaymentTypeApiResourceConstants.ISCASHPAYMENT, 
PaymentTypeApiResourceConstants.POSITION));
+                    PaymentTypeApiResourceConstants.ISCASHPAYMENT, 
PaymentTypeApiResourceConstants.POSITION,
+                    PaymentTypeApiResourceConstants.CODE_NAME, 
PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED));
 
     @Autowired
     public PaymentTypeDataValidator(final FromJsonHelper fromApiJsonHelper) {
@@ -93,6 +95,19 @@ public class PaymentTypeDataValidator {
                     .integerZeroOrGreater();
         }
 
+        if 
(this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.CODE_NAME,
 element)) {
+            final String codeName = 
this.fromApiJsonHelper.extractStringNamed(PaymentTypeApiResourceConstants.CODE_NAME,
 element);
+            
baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.CODE_NAME).value(codeName).ignoreIfNull()
+                    .notExceedingLengthOf(100);
+        }
+
+        if 
(this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED,
 element)) {
+            final Boolean isSystemDefined = 
this.fromApiJsonHelper.extractBooleanNamed(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED,
+                    element);
+            
baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED).value(isSystemDefined)
+                    .validateForBooleanValue();
+        }
+
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
     }
 
@@ -142,7 +157,20 @@ public class PaymentTypeDataValidator {
                     .integerZeroOrGreater();
         }
 
-        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+        if 
(this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.CODE_NAME,
 element)) {
+            final String codeName = 
this.fromApiJsonHelper.extractStringNamed(PaymentTypeApiResourceConstants.CODE_NAME,
 element);
+            
baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.CODE_NAME).value(codeName).ignoreIfNull()
+                    .notExceedingLengthOf(100);
+        }
 
+        if 
(this.fromApiJsonHelper.parameterExists(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED,
 element)) {
+            final Boolean isSystemDefined = 
this.fromApiJsonHelper.extractBooleanNamed(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED,
+                    element);
+            
baseDataValidator.reset().parameter(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED).value(isSystemDefined)
+                    .validateForBooleanValue();
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
     }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentType.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentType.java
index 1207eba64..ab1bd383b 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentType.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentType.java
@@ -23,14 +23,19 @@ import java.util.Map;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import 
org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
 import 
org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceConstants;
-import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
 
+@Getter
+@Setter
 @Entity
 @Table(name = "m_payment_type")
+@AllArgsConstructor
 public class PaymentType extends AbstractPersistableCustom {
 
     @Column(name = "value")
@@ -45,18 +50,13 @@ public class PaymentType extends AbstractPersistableCustom {
     @Column(name = "order_position")
     private Long position;
 
-    protected PaymentType() {}
+    @Column(name = "code_name")
+    private String codeName;
 
-    public PaymentType(final String name, final String description, final 
Boolean isCashPayment, final Long position) {
-        this.name = name;
-        this.description = description;
-        this.isCashPayment = isCashPayment;
-        this.position = position;
-    }
+    @Column(name = "is_system_defined")
+    private Boolean isSystemDefined;
 
-    public static PaymentType create(String name, String description, Boolean 
isCashPayment, Long position) {
-        return new PaymentType(name, description, isCashPayment, position);
-    }
+    protected PaymentType() {}
 
     public Map<String, Object> update(final JsonCommand command) {
 
@@ -89,16 +89,4 @@ public class PaymentType extends AbstractPersistableCustom {
         return actualChanges;
     }
 
-    public PaymentTypeData toData() {
-        return PaymentTypeData.instance(getId(), this.name, this.description, 
this.isCashPayment, this.position);
-    }
-
-    public Boolean isCashPayment() {
-        return isCashPayment;
-    }
-
-    public String getPaymentName() {
-        return name;
-    }
-
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
index d4119bdf2..eaa8ed285 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepository.java
@@ -18,9 +18,14 @@
  */
 package org.apache.fineract.portfolio.paymenttype.domain;
 
+import java.util.List;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 
 public interface PaymentTypeRepository extends JpaRepository<PaymentType, 
Long>, JpaSpecificationExecutor<PaymentType> {
 
+    List<PaymentType> findAllByOrderByPositionAsc();
+
+    List<PaymentType> findAllByCodeNameIsNotNullOrderByPositionAsc();
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepositoryWrapper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepositoryWrapper.java
index 6b03bdb71..8bcf81d02 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepositoryWrapper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/domain/PaymentTypeRepositoryWrapper.java
@@ -18,18 +18,23 @@
  */
 package org.apache.fineract.portfolio.paymenttype.domain;
 
+import java.util.List;
+import lombok.RequiredArgsConstructor;
 import 
org.apache.fineract.portfolio.paymenttype.exception.PaymentTypeNotFoundException;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 @Service
+@RequiredArgsConstructor
 public class PaymentTypeRepositoryWrapper {
 
     private final PaymentTypeRepository repository;
 
-    @Autowired
-    public PaymentTypeRepositoryWrapper(final PaymentTypeRepository 
repository) {
-        this.repository = repository;
+    public List<PaymentType> findAll() {
+        return this.repository.findAllByOrderByPositionAsc();
+    }
+
+    public List<PaymentType> findAllWithCodeName() {
+        return this.repository.findAllByCodeNameIsNotNullOrderByPositionAsc();
     }
 
     public PaymentType findOneWithNotFoundDetection(final Long id) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/mapper/PaymentTypeMapper.java
similarity index 53%
copy from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/mapper/PaymentTypeMapper.java
index b3aead6d3..af48e1336 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/mapper/PaymentTypeMapper.java
@@ -16,15 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.paymenttype.service;
+package org.apache.fineract.portfolio.paymenttype.mapper;
 
-import java.util.Collection;
+import java.util.List;
 import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
 
-public interface PaymentTypeReadPlatformService {
+@Mapper(componentModel = "spring")
+public interface PaymentTypeMapper {
 
-    Collection<PaymentTypeData> retrieveAllPaymentTypes();
+    @Mapping(target = "id", source = "source.id")
+    @Mapping(target = "name", source = "source.name")
+    @Mapping(target = "description", source = "source.description")
+    @Mapping(target = "isCashPayment", source = "source.isCashPayment")
+    @Mapping(target = "codeName", source = "source.codeName")
+    @Mapping(target = "isSystemDefined", source = "source.isSystemDefined")
+    PaymentTypeData map(PaymentType source);
 
-    PaymentTypeData retrieveOne(Long paymentTypeId);
+    List<PaymentTypeData> map(List<PaymentType> sources);
 
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
index b3aead6d3..072f5a903 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformService.java
@@ -25,6 +25,8 @@ public interface PaymentTypeReadPlatformService {
 
     Collection<PaymentTypeData> retrieveAllPaymentTypes();
 
+    Collection<PaymentTypeData> retrieveAllPaymentTypesWithCode();
+
     PaymentTypeData retrieveOne(Long paymentTypeId);
 
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
index 65fd525b2..a7edbd5d4 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeReadPlatformServiceImpl.java
@@ -18,28 +18,24 @@
  */
 package org.apache.fineract.portfolio.paymenttype.service;
 
-import java.sql.ResultSet;
-import java.sql.SQLException;
 import java.util.Collection;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
 import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
+import 
org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
+import org.apache.fineract.portfolio.paymenttype.mapper.PaymentTypeMapper;
 import org.springframework.cache.annotation.Cacheable;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.core.RowMapper;
 import org.springframework.stereotype.Service;
 
 @Service
+@RequiredArgsConstructor
 public class PaymentTypeReadPlatformServiceImpl implements 
PaymentTypeReadPlatformService {
 
-    private final JdbcTemplate jdbcTemplate;
     private final PlatformSecurityContext context;
-
-    @Autowired
-    public PaymentTypeReadPlatformServiceImpl(final PlatformSecurityContext 
context, final JdbcTemplate jdbcTemplate) {
-        this.context = context;
-        this.jdbcTemplate = jdbcTemplate;
-    }
+    private final PaymentTypeMapper paymentTypeMapper;
+    private final PaymentTypeRepositoryWrapper paymentTypeRepository;
 
     @Override
     @Cacheable(value = "payment_types", key = 
"T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('payment_types')")
@@ -47,41 +43,27 @@ public class PaymentTypeReadPlatformServiceImpl implements 
PaymentTypeReadPlatfo
         // TODO Auto-generated method stub
         this.context.authenticatedUser();
 
-        final PaymentTypeMapper ptm = new PaymentTypeMapper();
-        final String sql = "select " + ptm.schema() + "order by position";
-
-        return this.jdbcTemplate.query(sql, ptm); // NOSONAR
+        List<PaymentType> paymentType = this.paymentTypeRepository.findAll();
+        return this.paymentTypeMapper.map(paymentType);
     }
 
     @Override
-    public PaymentTypeData retrieveOne(Long paymentTypeId) {
+    @Cacheable(value = "paymentTypesWithCode", key = 
"T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('payment_types')")
+    public Collection<PaymentTypeData> retrieveAllPaymentTypesWithCode() {
         // TODO Auto-generated method stub
         this.context.authenticatedUser();
 
-        final PaymentTypeMapper ptm = new PaymentTypeMapper();
-        final String sql = "select " + ptm.schema() + "where pt.id = ?";
-
-        return this.jdbcTemplate.queryForObject(sql, ptm, new Object[] { 
paymentTypeId }); // NOSONAR
+        List<PaymentType> paymentType = 
this.paymentTypeRepository.findAllWithCodeName();
+        return this.paymentTypeMapper.map(paymentType);
     }
 
-    private static final class PaymentTypeMapper implements 
RowMapper<PaymentTypeData> {
-
-        public String schema() {
-            return " pt.id as id, pt.value as name, pt.description as 
description,pt.is_cash_payment as isCashPayment,pt.order_position as position 
from m_payment_type pt ";
-        }
-
-        @Override
-        public PaymentTypeData mapRow(final ResultSet rs, 
@SuppressWarnings("unused") final int rowNum) throws SQLException {
-
-            final Long id = rs.getLong("id");
-            final String name = rs.getString("name");
-            final String description = rs.getString("description");
-            final boolean isCashPayment = rs.getBoolean("isCashPayment");
-            final Long position = rs.getLong("position");
-
-            return PaymentTypeData.instance(id, name, description, 
isCashPayment, position);
-        }
+    @Override
+    public PaymentTypeData retrieveOne(Long paymentTypeId) {
+        // TODO Auto-generated method stub
+        this.context.authenticatedUser();
 
+        final PaymentType paymentType = 
this.paymentTypeRepository.findOneWithNotFoundDetection(paymentTypeId);
+        return this.paymentTypeMapper.map(paymentType);
     }
 
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
index 0fcf4f26c..aa5a893c6 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/service/PaymentTypeWriteServiceImpl.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.portfolio.paymenttype.service;
 
 import java.util.Map;
+import lombok.RequiredArgsConstructor;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
@@ -28,28 +29,19 @@ import 
org.apache.fineract.portfolio.paymenttype.data.PaymentTypeDataValidator;
 import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
 import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepository;
 import 
org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.orm.jpa.JpaSystemException;
 import org.springframework.stereotype.Service;
 
 @Service
+@RequiredArgsConstructor
 public class PaymentTypeWriteServiceImpl implements PaymentTypeWriteService {
 
     private final PaymentTypeRepository repository;
     private final PaymentTypeRepositoryWrapper repositoryWrapper;
     private final PaymentTypeDataValidator fromApiJsonDeserializer;
 
-    @Autowired
-    public PaymentTypeWriteServiceImpl(PaymentTypeRepository repository, 
PaymentTypeRepositoryWrapper repositoryWrapper,
-            PaymentTypeDataValidator fromApiJsonDeserializer) {
-        this.repository = repository;
-        this.repositoryWrapper = repositoryWrapper;
-        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
-
-    }
-
     @Override
     @CacheEvict(value = "payment_types", key = 
"T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('payment_types')")
     public CommandProcessingResult createPaymentType(JsonCommand command) {
@@ -58,8 +50,13 @@ public class PaymentTypeWriteServiceImpl implements 
PaymentTypeWriteService {
         String description = 
command.stringValueOfParameterNamed(PaymentTypeApiResourceConstants.DESCRIPTION);
         Boolean isCashPayment = 
command.booleanObjectValueOfParameterNamed(PaymentTypeApiResourceConstants.ISCASHPAYMENT);
         Long position = 
command.longValueOfParameterNamed(PaymentTypeApiResourceConstants.POSITION);
+        String codeName = 
command.stringValueOfParameterNamed(PaymentTypeApiResourceConstants.CODE_NAME);
+        Boolean isSystemDefined = 
command.booleanObjectValueOfParameterNamed(PaymentTypeApiResourceConstants.IS_SYSTEM_DEFINED);
+        if (isSystemDefined == null) {
+            isSystemDefined = false;
+        }
 
-        PaymentType newPaymentType = PaymentType.create(name, description, 
isCashPayment, position);
+        PaymentType newPaymentType = new PaymentType(name, description, 
isCashPayment, position, codeName, isSystemDefined);
         this.repository.saveAndFlush(newPaymentType);
         return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(newPaymentType.getId()).build();
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
index 459fac396..ae10f099a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
@@ -1322,11 +1322,11 @@ public class SavingsAccount extends 
AbstractPersistableCustom {
 
                 if (charge.isEnablePaymentType() && 
charge.isEnableFreeWithdrawal()) { // discount transaction to
                                                                                
        // specific paymentType
-                    if 
(paymentDetail.getPaymentType().getPaymentName().equals(charge.getCharge().getPaymentType().getPaymentName()))
 {
+                    if 
(paymentDetail.getPaymentType().getName().equals(charge.getCharge().getPaymentType().getName()))
 {
                         resetFreeChargeDaysCount(charge, transactionAmount, 
transactionDate, user, refNo);
                     }
                 } else if (charge.isEnablePaymentType()) { // normal 
charge-transaction to specific paymentType
-                    if 
(paymentDetail.getPaymentType().getPaymentName().equals(charge.getCharge().getPaymentType().getPaymentName()))
 {
+                    if 
(paymentDetail.getPaymentType().getName().equals(charge.getCharge().getPaymentType().getName()))
 {
                         charge.updateWithdralFeeAmount(transactionAmount);
                         this.payCharge(charge, 
charge.getAmountOutstanding(this.getCurrency()), transactionDate, user,
                                 backdatedTxnsAllowedTill, refNo);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
index a6fd7cb16..7a8d19d9d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
@@ -616,7 +616,8 @@ public class SavingsAccountReadPlatformServiceImpl 
implements SavingsAccountRead
                         final Long paymentTypeId = JdbcSupport.getLong(rs, 
"paymentType");
                         if (paymentTypeId != null) {
                             final String typeName = 
rs.getString("paymentTypeName");
-                            final PaymentTypeData paymentTypeData = new 
PaymentTypeData(paymentTypeId, typeName, null, false, null);
+                            final PaymentTypeData paymentTypeData = new 
PaymentTypeData(paymentTypeId, typeName, null, false, null, null,
+                                    false);
                             paymentDetailData = new PaymentDetailData(id, 
paymentTypeData, null, null, null, null, null);
                         }
                     }
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 6ec9cf739..ba4e3bfc4 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -71,4 +71,5 @@
     <include file="parts/0049_add_send_asynchronous_events_job.xml" 
relativeToChangelogFile="true"/>
     <include file="parts/0050_add_reverse_flag_disbursement_details.xml" 
relativeToChangelogFile="true"/>
     <include file="parts/0051_external_event_table_category_info.xml" 
relativeToChangelogFile="true"/>
+    <include file="parts/0052_loan_transaction_chargeback.xml" 
relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0052_loan_transaction_chargeback.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0052_loan_transaction_chargeback.xml
new file mode 100644
index 000000000..638f126fe
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0052_loan_transaction_chargeback.xml
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd";>
+    <changeSet author="fineract" id="1" context="mysql">
+        <createTable tableName="m_loan_transaction_relation">
+            <column autoIncrement="true" name="id" type="BIGINT">
+                <constraints nullable="false" primaryKey="true"/>
+            </column>
+            <column name="from_loan_transaction_id" type="BIGINT">
+                <constraints nullable="false" />
+            </column>
+            <column name="to_loan_transaction_id" type="BIGINT">
+                <constraints nullable="false" />
+            </column>
+            <column name="relation_type_enum" type="INT">
+                <constraints nullable="false"/>
+            </column>
+            <column name="created_by" type="BIGINT">
+                <constraints nullable="false" />
+            </column>
+            <column name="created_on_utc" type="DATETIME">
+                <constraints nullable="false" />
+            </column>
+            <column name="version" type="BIGINT">
+                <constraints nullable="false"/>
+            </column>
+            <column name="last_modified_by" type="BIGINT">
+                <constraints nullable="false" />
+            </column>
+            <column name="last_modified_on_utc" type="DATETIME">
+                <constraints nullable="false" />
+            </column>
+        </createTable>
+    </changeSet>
+    <changeSet author="fineract" id="1" context="postgresql">
+        <createTable tableName="m_loan_transaction_relation">
+            <column autoIncrement="true" name="id" type="BIGINT">
+                <constraints nullable="false" primaryKey="true"/>
+            </column>
+            <column name="from_loan_transaction_id" type="BIGINT">
+                <constraints nullable="false" />
+            </column>
+            <column name="to_loan_transaction_id" type="BIGINT">
+                <constraints nullable="false" />
+            </column>
+            <column name="relation_type_enum" type="INT">
+                <constraints nullable="false"/>
+            </column>
+            <column name="created_by" type="BIGINT">
+                <constraints nullable="false" />
+            </column>
+            <column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE">
+                <constraints nullable="false" />
+            </column>
+            <column name="version" type="BIGINT">
+                <constraints nullable="false"/>
+            </column>
+            <column name="last_modified_by" type="BIGINT">
+                <constraints nullable="false" />
+            </column>
+            <column name="last_modified_on_utc" type="TIMESTAMP WITH TIME 
ZONE">
+                <constraints nullable="false" />
+            </column>
+        </createTable>
+    </changeSet>
+    <changeSet author="fineract" id="2">
+        <addForeignKeyConstraint baseColumnNames="from_loan_transaction_id" 
baseTableName="m_loan_transaction_relation"
+                                 
constraintName="FK_m_loan_transaction_from_relation" deferrable="false"
+                                 initiallyDeferred="false" onDelete="RESTRICT" 
onUpdate="RESTRICT"
+                                 referencedColumnNames="id" 
referencedTableName="m_loan_transaction" validate="true"/>
+        <addForeignKeyConstraint baseColumnNames="to_loan_transaction_id" 
baseTableName="m_loan_transaction_relation"
+                                 
constraintName="FK_m_loan_transaction_to_relation" deferrable="false"
+                                 initiallyDeferred="false" onDelete="RESTRICT" 
onUpdate="RESTRICT"
+                                 referencedColumnNames="id" 
referencedTableName="m_loan_transaction" validate="true"/>
+    </changeSet>
+    <changeSet author="fineract" id="3">
+        <insert tableName="m_permission">
+            <column name="grouping" value="transaction_loan"/>
+            <column name="code" value="CHARGEBACK_LOAN"/>
+            <column name="entity_name" value="LOAN"/>
+            <column name="action_name" value="CHARGEBACK"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+    <changeSet author="fineract" id="4">
+        <addColumn tableName="m_payment_type">
+            <column name="code_name" type="VARCHAR(100)"/>
+        </addColumn>
+        <addColumn tableName="m_payment_type">
+            <column name="is_system_defined" type="boolean" 
defaultValueComputed="false">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="5">
+        <insert tableName="m_payment_type">
+            <column name="value" value="Repayment Adjustment Chargeback"/>
+            <column name="description" value="Repayment Adjustment 
Chargeback"/>
+            <column name="is_cash_payment" valueBoolean="false"/>
+            <column name="order_position" valueNumeric="1"/>
+            <column name="code_name" value="REPAYMENT_ADJUSTMENT_CHARGEBACK"/>
+            <column name="is_system_defined" valueNumeric="true"/>
+        </insert>
+        <insert tableName="m_payment_type">
+            <column name="value" value="Repayment Adjustment Refund"/>
+            <column name="description" value="Repayment Adjustment Refund"/>
+            <column name="is_cash_payment" valueBoolean="false"/>
+            <column name="order_position" valueNumeric="1"/>
+            <column name="code_name" value="REPAYMENT_ADJUSTMENT_REFUND"/>
+            <column name="is_system_defined" valueBoolean="true"/>
+        </insert>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java
new file mode 100644
index 000000000..cd07d376d
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java
@@ -0,0 +1,138 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.google.gson.Gson;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.GetPaymentTypesResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import 
org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.PaymentTypeHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import 
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@Slf4j
+public class LoanTransactionChargebackTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+    }
+
+    @Test
+    public void applyLoanTransactionChargeback() {
+        final LoanTransactionHelper loanTransactionHelper = new 
LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+        // Client and Loan account creation
+        final Integer clientId = ClientHelper.createClient(this.requestSpec, 
this.responseSpec, "01 January 2012");
+        final GetLoanProductsProductIdResponse getLoanProductsProductResponse 
= createLoanProduct(loanTransactionHelper, null);
+        assertNotNull(getLoanProductsProductResponse);
+        log.info("Loan Product Bucket Name: {}", 
getLoanProductsProductResponse.getDelinquencyBucket().getName());
+
+        final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+        // Older date to have more than one overdue installment
+        final LocalDate transactionDate = todaysDate.minusDays(45);
+        String operationDate = Utils.dateFormatter.format(transactionDate);
+
+        final Integer loanId = createLoanAccount(loanTransactionHelper, 
clientId.toString(),
+                getLoanProductsProductResponse.getId().toString(), 
operationDate, "1000");
+
+        GetLoansLoanIdResponse getLoansLoanIdResponse = 
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+        assertNotNull(getLoansLoanIdResponse);
+
+        loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
+
+        operationDate = Utils.dateFormatter.format(todaysDate);
+        Float amount = Float.valueOf("1100.0");
+        PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = 
loanTransactionHelper.makeLoanRepayment(operationDate, amount,
+                loanId);
+        assertNotNull(loanIdTransactionsResponse);
+        final Integer transactionId = 
loanIdTransactionsResponse.getResourceId();
+
+        List<GetPaymentTypesResponse> paymentTypeList = 
PaymentTypeHelper.getSystemPaymentType(requestSpec, responseSpec);
+        assertTrue(!paymentTypeList.isEmpty());
+
+        amount = Float.valueOf("800.0");
+        final String payload = createChargebackPayload(amount.toString(), 
paymentTypeList.get(0).getId());
+        PostLoansLoanIdTransactionsTransactionIdRequest 
postLoansTransactionCommandRequest = loanTransactionHelper
+                .applyLoanTransactionCommand(loanId, transactionId, 
"chargeback", payload);
+        assertNotNull(postLoansTransactionCommandRequest);
+
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        assertNotNull(getLoansLoanIdResponse);
+    }
+
+    private String createChargebackPayload(final String transactionAmount, 
final Integer paymentTypeId) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("transactionAmount", transactionAmount);
+        map.put("paymentTypeId", paymentTypeId);
+        map.put("locale", CommonConstants.LOCALE);
+        final String chargebackPayload = new Gson().toJson(map);
+        log.info("{}", chargebackPayload);
+        return chargebackPayload;
+    }
+
+    private GetLoanProductsProductIdResponse createLoanProduct(final 
LoanTransactionHelper loanTransactionHelper,
+            final Integer delinquencyBucketId) {
+        final HashMap<String, Object> loanProductMap = new 
LoanProductTestBuilder().build(null, delinquencyBucketId);
+        final Integer loanProductId = 
loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
+        return loanTransactionHelper.getLoanProduct(loanProductId);
+    }
+
+    private Integer createLoanAccount(final LoanTransactionHelper 
loanTransactionHelper, final String clientId, final String loanProductId,
+            final String operationDate, final String principalAmount) {
+        final String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal(principalAmount).withLoanTermFrequency("12")
+                
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1")
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("2") //
+                .withExpectedDisbursementDate(operationDate) //
+                .withInterestTypeAsDecliningBalance() //
+                .withSubmittedOnDate(operationDate) //
+                .build(clientId, loanProductId, null);
+        final Integer loanId = 
loanTransactionHelper.getLoanId(loanApplicationJSON);
+        loanTransactionHelper.approveLoan(operationDate, principalAmount, 
loanId, null);
+        
loanTransactionHelper.disburseLoanWithNetDisbursalAmount(operationDate, loanId, 
principalAmount);
+        return loanId;
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java
index 9a54fa5e2..0f2c396dc 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java
@@ -24,7 +24,10 @@ import com.google.common.reflect.TypeToken;
 import com.google.gson.Gson;
 import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
 import java.util.HashMap;
+import org.apache.fineract.client.models.GetPaymentTypesResponse;
 
 @SuppressWarnings({ "rawtypes", "unchecked" })
 public final class PaymentTypeHelper {
@@ -33,8 +36,16 @@ public final class PaymentTypeHelper {
 
     }
 
-    private static final String CREATE_PAYMENTTYPE_URL = 
"/fineract-provider/api/v1/paymenttypes?" + Utils.TENANT_IDENTIFIER;
     private static final String PAYMENTTYPE_URL = 
"/fineract-provider/api/v1/paymenttypes";
+    private static final String CREATE_PAYMENTTYPE_URL = PAYMENTTYPE_URL + "?" 
+ Utils.TENANT_IDENTIFIER;
+
+    public static ArrayList<GetPaymentTypesResponse> 
getSystemPaymentType(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        String response = Utils.performServerGet(requestSpec, responseSpec,
+                PAYMENTTYPE_URL + "?onlyWithCode=true&" + 
Utils.TENANT_IDENTIFIER);
+        Type paymentTypeList = new 
TypeToken<ArrayList<GetPaymentTypesResponse>>() {}.getType();
+        return new Gson().fromJson(response, paymentTypeList);
+    }
 
     public static Integer createPaymentType(final RequestSpecification 
requestSpec, final ResponseSpecification responseSpec,
             final String name, final String description, final Boolean 
isCashPayment, final Integer position) {
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 96c51a0f5..aeb79b8d9 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -47,6 +47,7 @@ import 
org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import 
org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
 import org.apache.fineract.client.models.PutLoansLoanIdResponse;
 import org.apache.fineract.client.util.JSON;
 import org.apache.fineract.integrationtests.common.CommonConstants;
@@ -239,6 +240,14 @@ public class LoanTransactionHelper {
         return Utils.performServerGet(requestSpec, responseSpec, 
GET_REPAYMENTS_URL, "loanRepaymentScheduleInstallments");
     }
 
+    public PostLoansLoanIdTransactionsTransactionIdRequest 
applyLoanTransactionCommand(final Integer loanId, final Integer transactionId,
+            final String command, final String payload) {
+        final String LOAN_TRANSACTION_URL = "/fineract-provider/api/v1/loans/" 
+ loanId + "/transactions/" + transactionId + "?command="
+                + command + "&" + Utils.TENANT_IDENTIFIER;
+        final String response = Utils.performServerPost(requestSpec, 
responseSpec, LOAN_TRANSACTION_URL, payload, null);
+        return GSON.fromJson(response, 
PostLoansLoanIdTransactionsTransactionIdRequest.class);
+    }
+
     public HashMap approveLoan(final String approvalDate, final Integer 
loanID) {
         String loanApprovalCommand = 
createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID);
         String loanApprovalRequest = getApproveLoanAsJSON(approvalDate);

Reply via email to