diff --git a/src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java b/src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java index 21e0ae0..6f19563 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java @@ -6,13 +6,17 @@ 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("/confirm/transaction") @RequiredArgsConstructor public class ConfirmationController { + private final TransactionService transactionService; + @PostMapping() - public ResponseEntity post(@RequestBody Object transactionRequest) { - return ResponseEntity.ok("Transaction endpoint is working"); + public ResponseEntity post(@RequestBody Transaction transaction) { + return ResponseEntity.ok(transactionService.execute(transaction)); } } diff --git a/src/main/java/zw/qantra/tm/domain/enums/ChargeConditionType.java b/src/main/java/zw/qantra/tm/domain/enums/ChargeConditionType.java new file mode 100644 index 0000000..0033a8a --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/enums/ChargeConditionType.java @@ -0,0 +1,7 @@ +package zw.qantra.tm.domain.enums; + +public enum ChargeConditionType { + PROVIDER, // bill provider conditions + GATEWAY, // payment gateway conditions + TRANSACTION // qantra conditions +} diff --git a/src/main/java/zw/qantra/tm/domain/enums/ChargeLabelEnum.java b/src/main/java/zw/qantra/tm/domain/enums/ChargeLabelEnum.java new file mode 100644 index 0000000..fe66546 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/enums/ChargeLabelEnum.java @@ -0,0 +1,7 @@ +package zw.qantra.tm.domain.enums; + +public enum ChargeLabelEnum { + FEE, + TAX, + GATEWAY_FEE +} diff --git a/src/main/java/zw/qantra/tm/domain/enums/CurrencyType.java b/src/main/java/zw/qantra/tm/domain/enums/CurrencyType.java new file mode 100644 index 0000000..f3fac75 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/enums/CurrencyType.java @@ -0,0 +1,9 @@ +package zw.qantra.tm.domain.enums; + +public enum CurrencyType { + ZWL, + ZWG, + USD, + ZiG, + ZAR +} diff --git a/src/main/java/zw/qantra/tm/domain/enums/Status.java b/src/main/java/zw/qantra/tm/domain/enums/Status.java new file mode 100755 index 0000000..324b27b --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/enums/Status.java @@ -0,0 +1,9 @@ +package zw.qantra.tm.domain.enums; + +public enum Status { + SUCCESS, + PENDING, + FAILED, + CANCELLED, + REVERSED +} diff --git a/src/main/java/zw/qantra/tm/domain/enums/TargetEvent.java b/src/main/java/zw/qantra/tm/domain/enums/TargetEvent.java new file mode 100644 index 0000000..4105532 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/enums/TargetEvent.java @@ -0,0 +1,6 @@ +package zw.qantra.tm.domain.enums; + +public enum TargetEvent { + INCLUDE, + EXCLUDE +} diff --git a/src/main/java/zw/qantra/tm/domain/models/Charge.java b/src/main/java/zw/qantra/tm/domain/models/Charge.java new file mode 100644 index 0000000..3e317b5 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/models/Charge.java @@ -0,0 +1,67 @@ +package zw.qantra.tm.domain.models; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.*; +import lombok.*; +import zw.qantra.tm.domain.enums.ChargeLabelEnum; +import zw.qantra.tm.domain.enums.CurrencyType; +import zw.qantra.tm.domain.enums.TargetEvent; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Entity +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Charge { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + private String description; + + // % rate to charge + private BigDecimal percentageRate; + private BigDecimal flat; + private boolean composite; // composite is % plus flat + private boolean global; + + private BigDecimal min; + private BigDecimal max; + private BigDecimal minimumAmount; + private BigDecimal maximumAmount; + + @Enumerated(EnumType.STRING) + private ChargeLabelEnum chargeLabel; + @Enumerated(EnumType.STRING) + private CurrencyType currency; + private String account; + private String target; + private String targetValue; + @Enumerated(EnumType.STRING) + private TargetEvent targetEvent; // include / exclude + + @JsonIgnore + @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinTable( + name = "charge_include_products", + joinColumns = @JoinColumn(name = "charge_id"), + inverseJoinColumns = @JoinColumn(name = "charge_condition_id") + ) + private Set includes = new HashSet<>(); + + @JsonIgnore + @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinTable( + name = "charge_exclude_products", + joinColumns = @JoinColumn(name = "charge_id"), + inverseJoinColumns = @JoinColumn(name = "charge_condition_id") + ) + private Set excludes = new HashSet<>(); + +} diff --git a/src/main/java/zw/qantra/tm/domain/models/ChargeCondition.java b/src/main/java/zw/qantra/tm/domain/models/ChargeCondition.java new file mode 100644 index 0000000..80a5ee1 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/models/ChargeCondition.java @@ -0,0 +1,31 @@ +package zw.qantra.tm.domain.models; + +import jakarta.persistence.*; +import lombok.*; +import zw.qantra.tm.domain.enums.ChargeConditionType; + +import java.util.List; +import java.util.UUID; + +@Entity +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ChargeCondition { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + private String name; + @Enumerated(EnumType.STRING) + private ChargeConditionType type; + @Column(columnDefinition = "longtext") + private String codes; + + @ManyToMany(mappedBy = "includes") + List includeCharges; + @ManyToMany(mappedBy = "excludes") + List excludeCharges; +} diff --git a/src/main/java/zw/qantra/tm/domain/models/ConfirmTransaction.java b/src/main/java/zw/qantra/tm/domain/models/ConfirmTransaction.java deleted file mode 100644 index 188fd5e..0000000 --- a/src/main/java/zw/qantra/tm/domain/models/ConfirmTransaction.java +++ /dev/null @@ -1,52 +0,0 @@ -package zw.qantra.tm.domain.models; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import lombok.*; -import zw.qantra.tm.domain.enums.RequestType; - -import java.math.BigDecimal; -import java.util.UUID; - -@Entity -@Getter -@Setter -@Builder -@AllArgsConstructor -@NoArgsConstructor -public class ConfirmTransaction { - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private UUID id; - - private String trace; - private BigDecimal amount; - private String reference; - private String providerUid; - private String productUid; - private String paymentProcessorLabel; - private String integrationProcessorLabel; - private String rrn; - private String channelName; - private String channel; - private String debitPhone; - private String debitAccount; - private String debitCurrency; - private String debitName; - private String debitCard; - private String debitRef; - private String creditPhone; - private String creditAccount; - private String creditCurrency; - private String creditName; - private String creditCard; - private String creditRef; - private String billClientId; - private String clientSecret; - private String aggregatorId; - private String billName; - private String billProductName; - private RequestType type; -} \ No newline at end of file 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 d0d95c2..c4044c9 100644 --- a/src/main/java/zw/qantra/tm/domain/models/Transaction.java +++ b/src/main/java/zw/qantra/tm/domain/models/Transaction.java @@ -1,10 +1,14 @@ package zw.qantra.tm.domain.models; +import com.fasterxml.jackson.annotation.JsonInclude; import jakarta.persistence.*; import lombok.*; +import zw.qantra.tm.domain.enums.CurrencyType; import zw.qantra.tm.domain.enums.RequestType; +import zw.qantra.tm.domain.enums.Status; import java.math.BigDecimal; +import java.util.List; import java.util.UUID; @Entity @@ -13,6 +17,7 @@ import java.util.UUID; @Builder @AllArgsConstructor @NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) public class Transaction { @Id @GeneratedValue(strategy = GenerationType.UUID) @@ -20,6 +25,10 @@ public class Transaction { private String trace; private BigDecimal amount; + private BigDecimal charge; + private BigDecimal gatewayCharge; + private BigDecimal tax; + private BigDecimal totalAmount; private String reference; private String providerUid; private String productUid; @@ -30,13 +39,13 @@ public class Transaction { private String channel; private String debitPhone; private String debitAccount; - private String debitCurrency; + private CurrencyType debitCurrency; private String debitName; private String debitCard; private String debitRef; private String creditPhone; private String creditAccount; - private String creditCurrency; + private CurrencyType creditCurrency; private String creditName; private String creditCard; private String creditRef; @@ -46,4 +55,11 @@ public class Transaction { private String billName; private String billProductName; private RequestType type; + + private String errorMessage; + private String responseCode; + private Status status; + @Transient + private List additionalData; + } \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/repositories/ConfirmTransactionRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/ConfirmTransactionRepository.java deleted file mode 100644 index 75edb6a..0000000 --- a/src/main/java/zw/qantra/tm/domain/repositories/ConfirmTransactionRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package zw.qantra.tm.domain.repositories; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import zw.qantra.tm.domain.models.ConfirmTransaction; - -import java.util.UUID; - -@Repository -public interface ConfirmTransactionRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/ConfirmTransactionService.java b/src/main/java/zw/qantra/tm/domain/services/ConfirmTransactionService.java deleted file mode 100644 index cef298e..0000000 --- a/src/main/java/zw/qantra/tm/domain/services/ConfirmTransactionService.java +++ /dev/null @@ -1,11 +0,0 @@ -package zw.qantra.tm.domain.services; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import zw.qantra.tm.domain.repositories.ConfirmTransactionRepository; - -@Service -@RequiredArgsConstructor -public class ConfirmTransactionService { - private final ConfirmTransactionRepository confirmTransactionRepository; -} \ No newline at end of file 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 f15b228..e50c0dc 100644 --- a/src/main/java/zw/qantra/tm/domain/services/TransactionService.java +++ b/src/main/java/zw/qantra/tm/domain/services/TransactionService.java @@ -2,10 +2,15 @@ package zw.qantra.tm.domain.services; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.enums.Status; import zw.qantra.tm.domain.models.PaymentProcessor; import zw.qantra.tm.domain.models.Transaction; 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.LogicPipeline; +import zw.qantra.tm.domain.services.processors.handlers.ValidationHandler; @Service @RequiredArgsConstructor @@ -13,9 +18,26 @@ public class TransactionService { private final TransactionRepository transactionRepository; private final PaymentProcessorFactory paymentProcessorFactory; - private Transaction bootstrap(Transaction transaction) { - String label = transaction.getType() + "_" + transaction.getPaymentProcessorLabel(); -// PaymentProcessor paymentProcessor = paymentProcessorFactory.getPaymentProcessor(label); + public Transaction execute(Transaction transaction) { + LogicPipeline transactionLogic = + new LogicPipeline<>(new CalculateChargesHandler()) + .addHandler(new ValidationHandler()); + transaction = transactionLogic.execute(transaction); + + String label = transaction.getType() + "_" + transaction.getIntegrationProcessorLabel(); + TransactionProcessorInterface transactionProcessorInterface = + (TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(label); + + try { + transactionProcessorInterface.process(transaction); + }catch (Exception e){ + e.printStackTrace(); + transaction.setStatus(Status.FAILED); + transaction.setResponseCode("01"); + transaction.setErrorMessage(e.getMessage()); + transactionRepository.save(transaction); + return transaction; + } return transaction; } } \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/factories/PaymentProcessorFactory.java b/src/main/java/zw/qantra/tm/domain/services/factories/PaymentProcessorFactory.java index 9a0c9b9..5f1927f 100644 --- a/src/main/java/zw/qantra/tm/domain/services/factories/PaymentProcessorFactory.java +++ b/src/main/java/zw/qantra/tm/domain/services/factories/PaymentProcessorFactory.java @@ -9,6 +9,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; +import zw.qantra.tm.exceptions.ApiException; @Component @RequiredArgsConstructor @@ -28,11 +29,13 @@ public class PaymentProcessorFactory implements ApplicationContextAware { try { paymentProcessor = applicationContext.getBean(paymentProcessorName); } catch (NoSuchBeanDefinitionException exception) { + exception.printStackTrace(); logger.error("Couldn't find payment processor: {}", exception.getMessage()); - return null; + throw new ApiException("Couldn't find payment processor"); } catch (BeansException e) { + e.printStackTrace(); logger.error("Error retrieving payment processor bean: {}", e.getMessage()); - return null; + throw new ApiException("Error retrieving payment processor bean"); } return paymentProcessor; diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/TransactionProcessorInterface.java b/src/main/java/zw/qantra/tm/domain/services/processors/TransactionProcessorInterface.java index cb452de..c7f8596 100644 --- a/src/main/java/zw/qantra/tm/domain/services/processors/TransactionProcessorInterface.java +++ b/src/main/java/zw/qantra/tm/domain/services/processors/TransactionProcessorInterface.java @@ -3,6 +3,6 @@ package zw.qantra.tm.domain.services.processors; import zw.qantra.tm.domain.models.Transaction; public interface TransactionProcessorInterface { - Object process(Transaction transaction); + Object process(Transaction transaction) throws Exception; Object reverse(Transaction transaction); } 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 300db30..7bb28e0 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 @@ -1,21 +1,25 @@ package zw.qantra.tm.domain.services.processors.confirmations; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import zw.qantra.tm.domain.models.Transaction; import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface; import zw.qantra.tm.rest.RestService; -@Service("CONFIRM_POWERTEL_ZESA") +@Service("CONFIRM_ZESA") @RequiredArgsConstructor public class ZesaConfirmationProcessor implements TransactionProcessorInterface { private final RestService restService; + @Value("${sbz.aggregator.url}") + private String aggregatorUrl; @Override - public Object process(Transaction transaction) { - - return transaction; + public Object process(Transaction transaction) throws Exception { + System.out.println("processing transaction"); + throw new Exception("Challenge encountered, please contact support."); +// return transaction; } @Override diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/CalculateChargesHandler.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/CalculateChargesHandler.java new file mode 100644 index 0000000..b3afe66 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/CalculateChargesHandler.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.services.processors.handlers; + +import zw.qantra.tm.domain.models.Transaction; + +public class CalculateChargesHandler implements LogicHandler { + @Override + public Transaction process(Transaction transaction) { + System.out.println("processing charges"); + return transaction; + } +} diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/LogicHandler.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/LogicHandler.java new file mode 100644 index 0000000..95d86e9 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/LogicHandler.java @@ -0,0 +1,5 @@ +package zw.qantra.tm.domain.services.processors.handlers; + +interface LogicHandler { + O process(I input); +} diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/LogicPipeline.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/LogicPipeline.java new file mode 100644 index 0000000..23624b8 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/LogicPipeline.java @@ -0,0 +1,20 @@ +package zw.qantra.tm.domain.services.processors.handlers; + +import org.springframework.stereotype.Service; + +public class LogicPipeline { + + private final LogicHandler currentHandler; + + public LogicPipeline(LogicHandler currentHandler) { + this.currentHandler = currentHandler; + } + + public LogicPipeline addHandler(LogicHandler newHandler) { + return new LogicPipeline<>(input -> newHandler.process(currentHandler.process(input))); + } + + public O execute(I input) { + return currentHandler.process(input); + } +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ValidationHandler.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ValidationHandler.java new file mode 100644 index 0000000..9336040 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ValidationHandler.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.services.processors.handlers; + +import zw.qantra.tm.domain.models.Transaction; + +public class ValidationHandler implements LogicHandler { + @Override + public Transaction process(Transaction transaction) { + System.out.println("processing validation"); + return transaction; + } +} diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/integrations/ZesaIntegrationProcessor.java b/src/main/java/zw/qantra/tm/domain/services/processors/integrations/ZesaIntegrationProcessor.java new file mode 100644 index 0000000..64d1002 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/integrations/ZesaIntegrationProcessor.java @@ -0,0 +1,28 @@ +package zw.qantra.tm.domain.services.processors.integrations; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.models.Transaction; +import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface; +import zw.qantra.tm.rest.RestService; + +@Service("REQUEST_ZESA") +@RequiredArgsConstructor +public class ZesaIntegrationProcessor implements TransactionProcessorInterface { + private final RestService restService; + + @Value("${sbz.aggregator.url}") + private String aggregatorUrl; + + @Override + public Object process(Transaction transaction) throws Exception { + + return transaction; + } + + @Override + public Object reverse(Transaction transaction) { + return transaction; + } +} diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/payments/StewardWebPaymentProcessor.java b/src/main/java/zw/qantra/tm/domain/services/processors/payments/StewardWebPaymentProcessor.java new file mode 100644 index 0000000..8de2411 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/payments/StewardWebPaymentProcessor.java @@ -0,0 +1,24 @@ +package zw.qantra.tm.domain.services.processors.payments; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.models.Transaction; +import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface; +import zw.qantra.tm.rest.RestService; + +@Service("STEWARD_WEB") +@RequiredArgsConstructor +public class StewardWebPaymentProcessor implements TransactionProcessorInterface { + private final RestService restService; + + + @Override + public Object process(Transaction transaction) throws Exception { + return transaction; + } + + @Override + public Object reverse(Transaction transaction) { + return transaction; + } +} diff --git a/src/main/resources/application-lab.properties b/src/main/resources/application-lab.properties index 706aaeb..ccb7dad 100644 --- a/src/main/resources/application-lab.properties +++ b/src/main/resources/application-lab.properties @@ -12,3 +12,5 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect spring.datasource.url=jdbc:mysql://localhost:3306/qpay?useLegacyDatetimeCode=false spring.datasource.username=root spring.datasource.password=password + +sbz.aggregator.url=http://192.168.16.29:24050/v1 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 706aaeb..5939ee0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,8 @@ spring.application.name=tm debug=true + +server.servlet.context-path=/api + logging.level.root=INFO logging.level.org.springframework.boot.autoconfigure=INFO @@ -12,3 +15,5 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect spring.datasource.url=jdbc:mysql://localhost:3306/qpay?useLegacyDatetimeCode=false spring.datasource.username=root spring.datasource.password=password + +sbz.aggregator.url=http://192.168.16.29:24050/v1 \ No newline at end of file