updating mpgs related changes

This commit is contained in:
2025-08-26 00:12:11 +02:00
parent be01f7c199
commit 96d75c3b94
14 changed files with 273 additions and 8466 deletions

View File

@@ -0,0 +1,107 @@
package zw.qantra.tm.domain.dtos;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ErpNextSalesInvoiceDto {
@JsonProperty("doctype")
private String doctype = "Sales Invoice";
@JsonProperty("company")
private String company;
@JsonProperty("customer")
private String customer;
@JsonProperty("posting_date")
private String postingDate;
@JsonProperty("due_date")
private String dueDate;
@JsonProperty("reference_no")
private String referenceNo;
@JsonProperty("remarks")
private String remarks;
@JsonProperty("currency")
private String currency;
@JsonProperty("conversion_rate")
private Double conversionRate = 1.0;
@JsonProperty("items")
private List<ErpNextInvoiceItemDto> items;
@JsonProperty("taxes")
private List<ErpNextTaxDto> taxes;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ErpNextInvoiceItemDto {
@JsonProperty("doctype")
private String doctype = "Sales Invoice Item";
@JsonProperty("item_code")
private String itemCode;
@JsonProperty("item_name")
private String itemName;
@JsonProperty("description")
private String description;
@JsonProperty("qty")
private Double qty = 1.0;
@JsonProperty("rate")
private Double rate;
@JsonProperty("amount")
private Double amount;
@JsonProperty("income_account")
private String incomeAccount;
@JsonProperty("cost_center")
private String costCenter;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ErpNextTaxDto {
@JsonProperty("doctype")
private String doctype = "Sales Taxes and Charges";
@JsonProperty("charge_type")
private String chargeType = "On Net Total";
@JsonProperty("account_head")
private String accountHead;
@JsonProperty("description")
private String description;
@JsonProperty("rate")
private Double rate;
@JsonProperty("tax_amount")
private Double taxAmount;
}
}

View File

@@ -5,14 +5,12 @@ import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
@@ -20,7 +18,6 @@ import zw.qantra.tm.domain.enums.RequestType;
import zw.qantra.tm.domain.enums.Status;
import zw.qantra.tm.domain.models.AdditionalData;
import zw.qantra.tm.domain.models.Provider;
import zw.qantra.tm.domain.models.Recipient;
import zw.qantra.tm.domain.models.Transaction;
import zw.qantra.tm.domain.repositories.TransactionRepository;
import zw.qantra.tm.domain.services.factories.PaymentProcessorFactory;
@@ -31,6 +28,8 @@ import zw.qantra.tm.domain.services.processors.handlers.FormattingHandler;
import zw.qantra.tm.domain.services.processors.handlers.LogicPipeline;
import zw.qantra.tm.domain.services.processors.handlers.RecipientsUpdateHandler;
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.exceptions.ApiException;
import zw.qantra.tm.utils.Utils;
@@ -76,6 +75,12 @@ public class TransactionService {
transactionRepository.save(transaction);
return transaction;
}
// run post integration logic
LogicPipeline<Transaction, Transaction> postTransactionLogic =
new LogicPipeline<>(new PurchaseInvoiceHandler());
transaction = postTransactionLogic.execute(transaction);
return transaction;
}
@@ -85,6 +90,10 @@ public class TransactionService {
}
Transaction transaction = transactionRepository.findById(uuid).get();
// if the transaction is already successful, return it
if(transaction.getStatus() == Status.SUCCESS){
return transaction;
}
// no persistence happens during polling unless the transaction status changes
String label = getLabel(transaction);
@@ -97,11 +106,12 @@ public class TransactionService {
transactionProcessorInterface.poll(transaction);
// if tran was successful deep copy transaction & push integration to the aggregator
Transaction transactionCopy = Utils.deepCopy(transaction, Transaction.class);
transactionCopy.setType(RequestType.INTEGRATION);
transactionCopy.setId(null);
integration(transactionCopy);
if(transaction.getStatus() == Status.SUCCESS){
Transaction transactionCopy = Utils.deepCopy(transaction, Transaction.class);
transactionCopy.setType(RequestType.INTEGRATION);
transactionCopy.setId(null);
integration(transactionCopy);
}
} catch (Exception e) {
e.printStackTrace();
transaction.setStatus(Status.FAILED);
@@ -143,7 +153,8 @@ public class TransactionService {
// run post transaction logic
LogicPipeline<Transaction, Transaction> postTransactionLogic =
new LogicPipeline<>(new RecipientsUpdateHandler(recipientService));
new LogicPipeline<>(new RecipientsUpdateHandler(recipientService))
.addHandler(new SalesInvoiceHandler());
transaction = postTransactionLogic.execute(transaction);
return transaction;

View File

@@ -3,7 +3,6 @@ package zw.qantra.tm.domain.services.processors.handlers;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import zw.qantra.tm.domain.models.Charge;
import zw.qantra.tm.domain.models.ChargeCondition;

View File

@@ -1,5 +1,5 @@
package zw.qantra.tm.domain.services.processors.handlers;
interface LogicHandler<I, O> {
public interface LogicHandler<I, O> {
O process(I input);
}

View File

@@ -1,7 +1,5 @@
package zw.qantra.tm.domain.services.processors.handlers;
import org.springframework.stereotype.Service;
public class LogicPipeline<I, O> {
private final LogicHandler<I, O> currentHandler;

View File

@@ -0,0 +1,21 @@
package zw.qantra.tm.domain.services.processors.handlers.erp;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
import zw.qantra.tm.domain.models.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
@RequiredArgsConstructor
public class PurchaseInvoiceHandler implements LogicHandler<Transaction, Transaction>{
Logger logger = LoggerFactory.getLogger(PurchaseInvoiceHandler.class);
@Override
public Transaction process(Transaction transaction) {
logger.info("processing purchase invoice");
return transaction;
}
}

View File

@@ -0,0 +1,40 @@
package zw.qantra.tm.domain.services.processors.handlers.erp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import zw.qantra.tm.domain.enums.Status;
import zw.qantra.tm.domain.models.Transaction;
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
@Service
@RequiredArgsConstructor
public class SalesInvoiceHandler implements LogicHandler<Transaction, Transaction>{
Logger logger = LoggerFactory.getLogger(SalesInvoiceHandler.class);
@Override
public Transaction process(Transaction transaction) {
logger.info("Processing sales invoice for transaction: {}", transaction.getReference());
if(transaction.getStatus() == Status.SUCCESS){
try {
logger.info("Creating sales invoice in ERPNext for successful transaction");
logger.info("Sales invoice created successfully in ERPNext");
} catch (Exception e) {
logger.error("Failed to create sales invoice in ERPNext: {}", e.getMessage(), e);
// Don't fail the transaction, just log the error
// The ERP invoice creation is a side effect, not critical to the payment flow
}
} else {
logger.info("Transaction status is not SUCCESS ({}), skipping sales invoice creation", transaction.getStatus());
}
return transaction;
}
}

View File

@@ -5,6 +5,9 @@ import org.slf4j.Logger;
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;
import zw.qantra.tm.domain.enums.Status;
@@ -16,6 +19,7 @@ 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")
@@ -36,6 +40,7 @@ public class StewardIntegrationProcessor implements TransactionProcessorInterfac
private String aggregatorAccount;
@Override
@Transactional
public Object process(Transaction transaction) throws Exception {
logger.info("processing integration");
String token = restService.getMerchantToken();
@@ -69,12 +74,14 @@ public class StewardIntegrationProcessor implements TransactionProcessorInterfac
transaction.setResponseCode((String) body.get("responseCode"));
transaction.setCreditRef((String) body.get("creditReference"));
transaction.setAdditionalData(body.get("additionalData"));
transaction.setErrorMessage(null); // clear any previous error message
transactionService.save(transaction);
AdditionalData additionalData = AdditionalData.builder()
.transaction(transaction)
.jsonString(Utils.toJson(body.get("additionalData")))
.build();
AdditionalData additionalData = additionalDataService.getAdditionalDataRepository()
.findByTransactionId(transaction.getId()).orElse(new AdditionalData());
additionalData.setTransaction(transaction);
additionalData.setJsonString(Utils.toJson(body.get("additionalData")));
additionalDataService.getAdditionalDataRepository().save(additionalData);
// fetch the corresponding request transaction if it exists
@@ -82,16 +89,15 @@ public class StewardIntegrationProcessor implements TransactionProcessorInterfac
.findByTraceAndType(transaction.getTrace(), RequestType.REQUEST);
if (requestTransaction != null) {
// create a new additional data for the request transaction
AdditionalData requestAdditionalData = AdditionalData.builder()
.transaction(requestTransaction)
.jsonString(Utils.toJson(body.get("additionalData")))
.build();
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.setErrorMessage((String) body.get("message"));
transaction.setResponseCode((String) body.get("responseCode"));
transaction.setErrorMessage((String) body.get("errorMessage"));
}

View File

@@ -88,7 +88,11 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
transaction.setStatus(Status.FAILED);
transaction.setResponseCode("01");
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
transaction.setErrorMessage((String) transactionData.get("errorMessage"));
if (transactionData != null) {
transaction.setErrorMessage((String) transactionData.get("errorMessage"));
} else {
transaction.setErrorMessage(body.get("errorMessage").toString());
}
}
} catch (Exception e) {
logger.error("Network error during payment processing: {}", e.getMessage(), e);
@@ -117,16 +121,24 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
LinkedHashMap body = (LinkedHashMap) response.get("body");
// only update tran status if polling returns a success
if(body.get("status").equals("SUCCESS")){
transaction.setStatus(Status.SUCCESS);
transaction.setResponseCode("00");
transaction.setTargetUrl((String) body.get("targetUrl"));
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
transaction.setDebitRef((String) transactionData.get("debitReference"));
if(!body.get("status").equals("SUCCESS")){
logger.error(body.get("errorMessage").toString());
transaction.setStatus(Status.FAILED);
transaction.setResponseCode(body.get("responseCode").toString());
transaction.setErrorMessage(body.get("errorMessage").toString());
transactionService.save(transaction);
return transaction;
}
transaction.setStatus(Status.SUCCESS);
transaction.setResponseCode("00");
transaction.setTargetUrl((String) body.get("targetUrl"));
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
transaction.setDebitRef((String) transactionData.get("debitReference"));
transactionService.save(transaction);
} catch (Exception e) {
logger.error("Network error during payment processing: {}", e.getMessage(), e);
transaction.setStatus(Status.FAILED);