From 285b03a72490e97d6f1039491ce8a7b958c25aa1 Mon Sep 17 00:00:00 2001 From: Vus Khoza Date: Mon, 1 Sep 2025 23:53:29 +0200 Subject: [PATCH] updates on transaction structure --- .../controllers/ConfirmationController.java | 22 ---- .../controllers/TransactonController.java | 13 ++- .../tm/domain/models/AdditionalData.java | 6 + .../qantra/tm/domain/models/Transaction.java | 1 + .../AdditionalDataRepository.java | 3 + .../TransactionEventRepository.java | 2 +- .../domain/services/TransactionService.java | 105 +++++++++++------- .../EconetConfirmationProcessor.java | 1 + .../ZesaConfirmationProcessor.java | 1 + .../handlers/ConfirmationLabelHandler.java | 33 ++++++ .../StewardIntegrationProcessor.java | 26 ++--- .../payments/MPGSWebPaymentProcessor.java | 2 +- 12 files changed, 133 insertions(+), 82 deletions(-) delete mode 100644 src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/handlers/ConfirmationLabelHandler.java diff --git a/src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java b/src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java deleted file mode 100644 index 528efa6..0000000 --- a/src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java +++ /dev/null @@ -1,22 +0,0 @@ -package zw.qantra.tm.domain.controllers; - -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import zw.qantra.tm.domain.models.Transaction; -import zw.qantra.tm.domain.services.TransactionService; - -@RestController -@RequestMapping("/public/confirm/transaction") -@RequiredArgsConstructor -public class ConfirmationController { - private final TransactionService transactionService; - - @PostMapping() - public ResponseEntity post(@RequestBody Transaction transaction) { - return ResponseEntity.ok(transactionService.execute(transaction)); - } -} diff --git a/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java b/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java index eb08453..82a1989 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java @@ -69,10 +69,19 @@ public class TransactonController { return ResponseEntity.ok(transactionService.poll(id)); } + @PostMapping("/request") + public ResponseEntity request(@RequestBody Transaction transaction) { + LogUtils.logPrettyJson(logger, "incoming transaction", transaction); + Transaction createdTransaction = transactionService.request(transaction); + LogUtils.logPrettyJson(logger, "outgoing transaction", createdTransaction); + return ResponseEntity.ok(createdTransaction); + } + + @PostMapping public ResponseEntity create(@RequestBody Transaction transaction) { LogUtils.logPrettyJson(logger, "incoming transaction", transaction); - Transaction createdTransaction = transactionService.execute(transaction); + Transaction createdTransaction = transactionService.confirm(transaction); LogUtils.logPrettyJson(logger, "outgoing transaction", createdTransaction); return ResponseEntity.ok(createdTransaction); } @@ -83,7 +92,7 @@ public class TransactonController { return ResponseEntity.notFound().build(); } transaction.setId(id); - return ResponseEntity.ok(transactionService.execute(transaction)); + return ResponseEntity.ok(transactionService.getTransactionRepository().save(transaction)); } @DeleteMapping("/{id}") diff --git a/src/main/java/zw/qantra/tm/domain/models/AdditionalData.java b/src/main/java/zw/qantra/tm/domain/models/AdditionalData.java index 848da4c..c66ce1a 100644 --- a/src/main/java/zw/qantra/tm/domain/models/AdditionalData.java +++ b/src/main/java/zw/qantra/tm/domain/models/AdditionalData.java @@ -3,6 +3,8 @@ package zw.qantra.tm.domain.models; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; import lombok.AllArgsConstructor; @@ -10,6 +12,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import zw.qantra.tm.domain.enums.RequestType; @Getter @Setter @@ -24,4 +27,7 @@ public class AdditionalData extends BaseEntity { @Column(columnDefinition = "TEXT") private String jsonString; + + @Enumerated(EnumType.STRING) + private RequestType requestType; } diff --git a/src/main/java/zw/qantra/tm/domain/models/Transaction.java b/src/main/java/zw/qantra/tm/domain/models/Transaction.java index 6c0ec50..4bc5ecb 100644 --- a/src/main/java/zw/qantra/tm/domain/models/Transaction.java +++ b/src/main/java/zw/qantra/tm/domain/models/Transaction.java @@ -30,6 +30,7 @@ public class Transaction extends BaseEntity { private String productUid; private String paymentProcessorLabel; private String integrationProcessorLabel; + private String confirmationProcessorLabel; private String providerLabel; private String rrn; private String channelName; diff --git a/src/main/java/zw/qantra/tm/domain/repositories/AdditionalDataRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/AdditionalDataRepository.java index 2bb3df5..d71db9a 100644 --- a/src/main/java/zw/qantra/tm/domain/repositories/AdditionalDataRepository.java +++ b/src/main/java/zw/qantra/tm/domain/repositories/AdditionalDataRepository.java @@ -2,6 +2,8 @@ package zw.qantra.tm.domain.repositories; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; + +import zw.qantra.tm.domain.enums.RequestType; import zw.qantra.tm.domain.models.AdditionalData; import java.util.Optional; @@ -10,4 +12,5 @@ import java.util.UUID; @Repository public interface AdditionalDataRepository extends JpaRepository { Optional findByTransactionId(UUID transactionId); + Optional findByTransactionIdAndRequestType(UUID transactionId, RequestType requestType); } \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/repositories/TransactionEventRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/TransactionEventRepository.java index 3bfbd1c..cedd548 100644 --- a/src/main/java/zw/qantra/tm/domain/repositories/TransactionEventRepository.java +++ b/src/main/java/zw/qantra/tm/domain/repositories/TransactionEventRepository.java @@ -6,5 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import zw.qantra.tm.domain.models.TransactionEvent; public interface TransactionEventRepository extends JpaRepository { - List findByTransactionId(UUID transactionId); + List findByTransactionId(String transactionId); } diff --git a/src/main/java/zw/qantra/tm/domain/services/TransactionService.java b/src/main/java/zw/qantra/tm/domain/services/TransactionService.java index 6fcd32d..4f6db75 100644 --- a/src/main/java/zw/qantra/tm/domain/services/TransactionService.java +++ b/src/main/java/zw/qantra/tm/domain/services/TransactionService.java @@ -3,7 +3,6 @@ package zw.qantra.tm.domain.services; import lombok.Getter; import lombok.RequiredArgsConstructor; -import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -23,6 +22,7 @@ import zw.qantra.tm.domain.repositories.TransactionRepository; import zw.qantra.tm.domain.services.factories.PaymentProcessorFactory; import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface; import zw.qantra.tm.domain.services.processors.handlers.CalculateChargesHandler; +import zw.qantra.tm.domain.services.processors.handlers.ConfirmationLabelHandler; import zw.qantra.tm.domain.services.processors.handlers.ConfirmationLogicHandler; import zw.qantra.tm.domain.services.processors.handlers.FormattingHandler; import zw.qantra.tm.domain.services.processors.handlers.LogicPipeline; @@ -31,7 +31,6 @@ import zw.qantra.tm.domain.services.processors.handlers.ValidationHandler; import zw.qantra.tm.domain.services.processors.handlers.erp.PurchaseInvoiceHandler; import zw.qantra.tm.domain.services.processors.handlers.erp.SalesInvoiceHandler; import zw.qantra.tm.domain.models.TransactionEvent; -import zw.qantra.tm.domain.services.TransactionEventService; import zw.qantra.tm.exceptions.ApiException; import zw.qantra.tm.utils.Utils; @@ -78,6 +77,8 @@ public class TransactionService { try { transactionProcessorInterface.process(transaction); + transaction.setIntegrationStatus(transaction.getStatus()); + transactionRepository.save(transaction); }catch (Exception e){ e.printStackTrace(); transaction.setStatus(Status.FAILED); @@ -106,7 +107,7 @@ public class TransactionService { Transaction transaction = transactionRepository.findById(uuid).get(); // if the transaction is already successful, return it - if(transaction.getStatus() == Status.SUCCESS){ + if(transaction.getIntegrationStatus() == Status.SUCCESS){ return transaction; } @@ -118,7 +119,7 @@ public class TransactionService { transactionEventService.save(transactionEvent); // no persistence happens during polling unless the transaction status changes - String label = getLabel(transaction); + String label = "REQUEST_" + transaction.getPaymentProcessorLabel() + "_" + transaction.getAuthType(); logger.info("label: {}", label); TransactionProcessorInterface transactionProcessorInterface = @@ -131,6 +132,9 @@ public class TransactionService { transactionEvent.setMessage(transaction.getErrorMessage()); transactionEventService.save(transactionEvent); + transaction.setPollingStatus(transaction.getStatus()); + transactionRepository.save(transaction); + // if tran was successful push integration to the aggregator if(transaction.getStatus() == Status.SUCCESS){ transaction.setType(RequestType.INTEGRATION); @@ -146,15 +150,25 @@ public class TransactionService { return transaction; } - public Transaction execute(Transaction incomingTransaction) { + public Transaction request(Transaction incomingTransaction) { // if payment request (not confirm), then fetch already existing transaction record Transaction transaction = incomingTransaction; if(incomingTransaction.getId() != null){ transaction = getTransactionRepository().findById(incomingTransaction.getId()).orElseThrow(() -> new ApiException("Transaction not found for ID: " + incomingTransaction.getId())); transaction.setType(incomingTransaction.getType()); // type should be REQUEST here + transaction.setAuthType(incomingTransaction.getAuthType()); } + // run pre transaction logic + LogicPipeline preTransactionLogic = + new LogicPipeline<>(new CalculateChargesHandler(chargeService)) + .addHandler(new FormattingHandler()) + .addHandler(new ValidationHandler()); + transaction = preTransactionLogic.execute(transaction); + + transactionRepository.save(transaction); + TransactionEvent transactionEvent = TransactionEvent.builder() .transactionId(transaction.getId().toString()) .event(transaction.getType().name()) @@ -162,15 +176,7 @@ public class TransactionService { .build(); transactionEventService.save(transactionEvent); - // run pre transaction logic - LogicPipeline preTransactionLogic = - new LogicPipeline<>(new CalculateChargesHandler(chargeService)) - .addHandler(new ConfirmationLogicHandler()) - .addHandler(new FormattingHandler()) - .addHandler(new ValidationHandler()); - transaction = preTransactionLogic.execute(transaction); - - String label = getLabel(transaction); + String label = transaction.getType() + "_" + transaction.getPaymentProcessorLabel() + "_" + transaction.getAuthType(); logger.info("label: {}", label); TransactionProcessorInterface transactionProcessorInterface = @@ -179,12 +185,7 @@ public class TransactionService { try { // MAIN TRANSACTION LOGIC! transactionProcessorInterface.process(transaction); - - if(transaction.getType() == RequestType.CONFIRM){ - transaction.setConfirmationStatus(transaction.getStatus()); - }else{ - transaction.setPaymentStatus(transaction.getStatus()); - } + transaction.setPaymentStatus(transaction.getStatus()); }catch (Exception e){ e.printStackTrace(); @@ -208,25 +209,52 @@ public class TransactionService { return transaction; } - private String getLabel(Transaction transaction){ - String label = ""; - if(transaction.getType() == RequestType.CONFIRM){ - // get processor label from billClientId - Provider provider = providerService.getProviderRepository() - .findByClientId(transaction.getBillClientId()); - if(provider == null){ - throw new ApiException("Provider not found for billClientId: " + transaction.getBillClientId()); - } - label = transaction.getType() + "_" + - provider.getLabel(); + public Transaction confirm(Transaction transaction) { + // run pre transaction logic + LogicPipeline preTransactionLogic = + new LogicPipeline<>(new CalculateChargesHandler(chargeService)) + .addHandler(new ConfirmationLabelHandler(providerService)) + .addHandler(new ConfirmationLogicHandler()) + .addHandler(new FormattingHandler()) + .addHandler(new ValidationHandler()); + transaction = preTransactionLogic.execute(transaction); + transactionRepository.save(transaction); - transaction.setIntegrationProcessorLabel(provider.getIntegrationProcessorLabel()); - }else{ - label = transaction.getType() + "_" + - transaction.getPaymentProcessorLabel() + "_" + - transaction.getAuthType(); + TransactionEvent transactionEvent = TransactionEvent.builder() + .transactionId(transaction.getId().toString()) + .event(transaction.getType().name()) + .status(Status.PROCESSING) + .build(); + transactionEventService.save(transactionEvent); + + TransactionProcessorInterface transactionProcessorInterface = + (TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor( + transaction.getConfirmationProcessorLabel()); + + try { + // MAIN TRANSACTION LOGIC! + transactionProcessorInterface.process(transaction); + transaction.setConfirmationStatus(transaction.getStatus()); + + }catch (Exception e){ + e.printStackTrace(); + transaction.setStatus(Status.FAILED); + transaction.setResponseCode("01"); + transaction.setErrorMessage(e.getMessage()); + transactionRepository.save(transaction); + return transaction; } - return label; + + // run post transaction logic + LogicPipeline postTransactionLogic = + new LogicPipeline<>(new RecipientsUpdateHandler(recipientService)); + transaction = postTransactionLogic.execute(transaction); + + transactionEvent.setStatus(transaction.getStatus()); + transactionEvent.setMessage(transaction.getErrorMessage()); + transactionEventService.save(transactionEvent); + + return transaction; } public Transaction findById(UUID id) { @@ -234,7 +262,8 @@ public class TransactionService { try { AdditionalData additionalData = additionalDataService.getAdditionalDataRepository() - .findByTransactionId(id).orElseThrow(() -> new ApiException("Additional data not found for transaction ID: " + id)); + .findByTransactionIdAndRequestType(id, RequestType.INTEGRATION).orElseThrow(() -> + new ApiException("Additional data not found for transaction ID: " + id + " and request type: " + RequestType.INTEGRATION)); transaction.setAdditionalData(Utils.fromJson(additionalData.getJsonString(), List.class)); } catch (ApiException e) { // TODO Auto-generated catch block diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/EconetConfirmationProcessor.java b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/EconetConfirmationProcessor.java index d4bbba9..24a6d53 100644 --- a/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/EconetConfirmationProcessor.java +++ b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/EconetConfirmationProcessor.java @@ -70,6 +70,7 @@ public class EconetConfirmationProcessor implements TransactionProcessorInterfac AdditionalData additionalData = AdditionalData.builder() .transaction(transaction) + .requestType(RequestType.CONFIRM) .jsonString(Utils.toJson(body.get("additionalData"))) .build(); additionalDataService.getAdditionalDataRepository().save(additionalData); diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java index 410c6f3..52d9441 100644 --- a/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java +++ b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java @@ -70,6 +70,7 @@ public class ZesaConfirmationProcessor implements TransactionProcessorInterface AdditionalData additionalData = AdditionalData.builder() .transaction(transaction) + .requestType(RequestType.CONFIRM) .jsonString(Utils.toJson(body.get("additionalData"))) .build(); additionalDataService.getAdditionalDataRepository().save(additionalData); diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ConfirmationLabelHandler.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ConfirmationLabelHandler.java new file mode 100644 index 0000000..6b945c0 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ConfirmationLabelHandler.java @@ -0,0 +1,33 @@ +package zw.qantra.tm.domain.services.processors.handlers; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import zw.qantra.tm.domain.models.Provider; +import zw.qantra.tm.domain.models.Transaction; +import zw.qantra.tm.domain.services.ProviderService; +import zw.qantra.tm.exceptions.ApiException; + +@Service +@RequiredArgsConstructor +public class ConfirmationLabelHandler implements LogicHandler { + private final Logger logger = LoggerFactory.getLogger(ConfirmationLabelHandler.class); + + private final ProviderService providerService; + + @Override + public Transaction process(Transaction transaction) { + Provider provider = providerService.getProviderRepository() + .findByClientId(transaction.getBillClientId()); + if(provider == null){ + throw new ApiException("Provider not found for billClientId: " + transaction.getBillClientId()); + } + String label = transaction.getType() + "_" + provider.getLabel(); + + transaction.setConfirmationProcessorLabel(label); + logger.info("label: {}", label); + return transaction; + } +} diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/integrations/StewardIntegrationProcessor.java b/src/main/java/zw/qantra/tm/domain/services/processors/integrations/StewardIntegrationProcessor.java index c69e784..de17543 100644 --- a/src/main/java/zw/qantra/tm/domain/services/processors/integrations/StewardIntegrationProcessor.java +++ b/src/main/java/zw/qantra/tm/domain/services/processors/integrations/StewardIntegrationProcessor.java @@ -6,7 +6,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import jakarta.transaction.TransactionScoped; import jakarta.transaction.Transactional; import zw.qantra.tm.domain.dtos.BillPaymentDto; import zw.qantra.tm.domain.enums.RequestType; @@ -19,7 +18,6 @@ import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface; import zw.qantra.tm.rest.RestService; import zw.qantra.tm.utils.Utils; -import java.beans.Transient; import java.util.LinkedHashMap; @Service("INTEGRATION_STEWARD") @@ -45,10 +43,15 @@ public class StewardIntegrationProcessor implements TransactionProcessorInterfac logger.info("processing integration"); String token = restService.getMerchantToken(); + String debitRef = transaction.getDebitRef(); + if(debitRef == null || debitRef.isEmpty()){ + debitRef = Utils.getUid(); + } + BillPaymentDto request = BillPaymentDto.builder() .type(RequestType.REQUEST) .billClientId(transaction.getBillClientId()) - .debitRef(transaction.getDebitRef()) + .debitRef(debitRef) .debitCurrency(transaction.getDebitCurrency()) .amount(transaction.getAmount()) .aggregatorId(aggregatorClientId) @@ -77,25 +80,12 @@ public class StewardIntegrationProcessor implements TransactionProcessorInterfac transaction.setErrorMessage(null); // clear any previous error message transactionService.save(transaction); - AdditionalData additionalData = additionalDataService.getAdditionalDataRepository() - .findByTransactionId(transaction.getId()).orElse(new AdditionalData()); - + AdditionalData additionalData = new AdditionalData(); additionalData.setTransaction(transaction); + additionalData.setRequestType(RequestType.INTEGRATION); additionalData.setJsonString(Utils.toJson(body.get("additionalData"))); additionalDataService.getAdditionalDataRepository().save(additionalData); - // fetch the corresponding request transaction if it exists - Transaction requestTransaction = transactionService.getTransactionRepository() - .findByTraceAndType(transaction.getTrace(), RequestType.REQUEST); - if (requestTransaction != null) { - // create a new additional data for the request transaction - AdditionalData requestAdditionalData = additionalDataService.getAdditionalDataRepository() - .findByTransactionId(requestTransaction.getId()).orElse(new AdditionalData()); - requestAdditionalData.setTransaction(requestTransaction); - requestAdditionalData.setJsonString(Utils.toJson(body.get("additionalData"))); - additionalDataService.getAdditionalDataRepository().save(requestAdditionalData); - } - } else { transaction.setStatus(Status.FAILED); transaction.setResponseCode((String) body.get("responseCode")); diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/payments/MPGSWebPaymentProcessor.java b/src/main/java/zw/qantra/tm/domain/services/processors/payments/MPGSWebPaymentProcessor.java index ce80bde..f4f4489 100644 --- a/src/main/java/zw/qantra/tm/domain/services/processors/payments/MPGSWebPaymentProcessor.java +++ b/src/main/java/zw/qantra/tm/domain/services/processors/payments/MPGSWebPaymentProcessor.java @@ -138,7 +138,7 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface { transaction.setTargetUrl((String) body.get("targetUrl")); LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction"); - transaction.setDebitRef((String) transactionData.get("debitReference")); + transaction.setDebitRef((String) transactionData.get("creditReference")); transactionService.save(transaction);