From eee746a5d85000a157bf10948fec6206aaceee9d Mon Sep 17 00:00:00 2001 From: Vus Khoza Date: Mon, 16 Jun 2025 18:13:20 +0200 Subject: [PATCH] completed initial setup --- pom.xml | 20 ++ .../controllers/ConfirmationController.java | 18 ++ .../controllers/IntegrationController.java | 18 ++ .../controllers/TransactonController.java | 18 ++ .../qantra/tm/domain/enums/RequestType.java | 6 + .../tm/domain/models/ConfirmTransaction.java | 52 +++++ .../zw/qantra/tm/domain/models/Currency.java | 25 +++ .../domain/models/IntegrationProcessor.java | 25 +++ .../tm/domain/models/PaymentProcessor.java | 26 +++ .../zw/qantra/tm/domain/models/Provider.java | 25 +++ .../zw/qantra/tm/domain/models/Setting.java | 28 +++ .../qantra/tm/domain/models/Transaction.java | 49 +++++ .../ConfirmTransactionRepository.java | 11 + .../repositories/CurrencyRepository.java | 11 + .../IntegrationProcessorRepository.java | 11 + .../PaymentProcessorRepository.java | 11 + .../repositories/ProviderRepository.java | 11 + .../repositories/SettingRepository.java | 11 + .../repositories/TransactionRepository.java | 11 + .../services/ConfirmTransactionService.java | 11 + .../tm/domain/services/CurrencyService.java | 11 + .../services/IntegrationProcessorService.java | 11 + .../services/PaymentProcessorService.java | 11 + .../tm/domain/services/ProviderService.java | 11 + .../tm/domain/services/SettingService.java | 11 + .../domain/services/TransactionService.java | 21 ++ .../factories/PaymentProcessorFactory.java | 40 ++++ .../TransactionProcessorInterface.java | 8 + .../ZesaConfirmationProcessor.java | 25 +++ .../java/zw/qantra/tm/rest/RestService.java | 195 ++++++++++++++++++ .../tm/rest/RestTemplateInterceptor.java | 72 +++++++ src/main/resources/application-lab.properties | 2 +- src/main/resources/application.properties | 2 +- src/main/resources/liquibase-lab.properties | 2 +- 34 files changed, 817 insertions(+), 3 deletions(-) create mode 100644 src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java create mode 100644 src/main/java/zw/qantra/tm/domain/controllers/IntegrationController.java create mode 100644 src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java create mode 100644 src/main/java/zw/qantra/tm/domain/enums/RequestType.java create mode 100644 src/main/java/zw/qantra/tm/domain/models/ConfirmTransaction.java create mode 100644 src/main/java/zw/qantra/tm/domain/models/Currency.java create mode 100644 src/main/java/zw/qantra/tm/domain/models/IntegrationProcessor.java create mode 100644 src/main/java/zw/qantra/tm/domain/models/PaymentProcessor.java create mode 100644 src/main/java/zw/qantra/tm/domain/models/Provider.java create mode 100644 src/main/java/zw/qantra/tm/domain/models/Setting.java create mode 100644 src/main/java/zw/qantra/tm/domain/models/Transaction.java create mode 100644 src/main/java/zw/qantra/tm/domain/repositories/ConfirmTransactionRepository.java create mode 100644 src/main/java/zw/qantra/tm/domain/repositories/CurrencyRepository.java create mode 100644 src/main/java/zw/qantra/tm/domain/repositories/IntegrationProcessorRepository.java create mode 100644 src/main/java/zw/qantra/tm/domain/repositories/PaymentProcessorRepository.java create mode 100644 src/main/java/zw/qantra/tm/domain/repositories/ProviderRepository.java create mode 100644 src/main/java/zw/qantra/tm/domain/repositories/SettingRepository.java create mode 100644 src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/ConfirmTransactionService.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/CurrencyService.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/IntegrationProcessorService.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/PaymentProcessorService.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/ProviderService.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/SettingService.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/TransactionService.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/factories/PaymentProcessorFactory.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/TransactionProcessorInterface.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java create mode 100755 src/main/java/zw/qantra/tm/rest/RestService.java create mode 100755 src/main/java/zw/qantra/tm/rest/RestTemplateInterceptor.java diff --git a/pom.xml b/pom.xml index 24353a4..5df47dc 100644 --- a/pom.xml +++ b/pom.xml @@ -83,6 +83,26 @@ commons-collections4 4.4 + + jakarta.persistence + jakarta.persistence-api + 3.1.0 + + + com.googlecode.libphonenumber + libphonenumber + 8.12.10 + + + com.google.code.gson + gson + 2.10.1 + + + org.json + json + 20231013 + diff --git a/src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java b/src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java new file mode 100644 index 0000000..21e0ae0 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/controllers/ConfirmationController.java @@ -0,0 +1,18 @@ +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; + +@RestController +@RequestMapping("/confirm/transaction") +@RequiredArgsConstructor +public class ConfirmationController { + @PostMapping() + public ResponseEntity post(@RequestBody Object transactionRequest) { + return ResponseEntity.ok("Transaction endpoint is working"); + } +} diff --git a/src/main/java/zw/qantra/tm/domain/controllers/IntegrationController.java b/src/main/java/zw/qantra/tm/domain/controllers/IntegrationController.java new file mode 100644 index 0000000..ce37cd9 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/controllers/IntegrationController.java @@ -0,0 +1,18 @@ +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; + +@RestController +@RequestMapping("/integration/transaction") +@RequiredArgsConstructor +public class IntegrationController { + @PostMapping() + public ResponseEntity post(@RequestBody Object transactionRequest) { + return ResponseEntity.ok("Transaction endpoint is working"); + } +} diff --git a/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java b/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java new file mode 100644 index 0000000..0885d41 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java @@ -0,0 +1,18 @@ +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; + +@RestController +@RequestMapping("/transaction") +@RequiredArgsConstructor +public class TransactonController { + @PostMapping() + public ResponseEntity post(@RequestBody Object transactionRequest) { + return ResponseEntity.ok("Transaction endpoint is working"); + } +} diff --git a/src/main/java/zw/qantra/tm/domain/enums/RequestType.java b/src/main/java/zw/qantra/tm/domain/enums/RequestType.java new file mode 100644 index 0000000..b4386e0 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/enums/RequestType.java @@ -0,0 +1,6 @@ +package zw.qantra.tm.domain.enums; + +public enum RequestType { + REQUEST, + CONFIRM +} diff --git a/src/main/java/zw/qantra/tm/domain/models/ConfirmTransaction.java b/src/main/java/zw/qantra/tm/domain/models/ConfirmTransaction.java new file mode 100644 index 0000000..188fd5e --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/models/ConfirmTransaction.java @@ -0,0 +1,52 @@ +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/Currency.java b/src/main/java/zw/qantra/tm/domain/models/Currency.java new file mode 100644 index 0000000..6c350b0 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/models/Currency.java @@ -0,0 +1,25 @@ +package zw.qantra.tm.domain.models; + +import java.util.UUID; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Currency { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + private String isoCode; + private String name; + private String displayName; +} diff --git a/src/main/java/zw/qantra/tm/domain/models/IntegrationProcessor.java b/src/main/java/zw/qantra/tm/domain/models/IntegrationProcessor.java new file mode 100644 index 0000000..527132b --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/models/IntegrationProcessor.java @@ -0,0 +1,25 @@ +package zw.qantra.tm.domain.models; + +import java.util.UUID; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class IntegrationProcessor { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + private String name; + private String description; + private String type; +} diff --git a/src/main/java/zw/qantra/tm/domain/models/PaymentProcessor.java b/src/main/java/zw/qantra/tm/domain/models/PaymentProcessor.java new file mode 100644 index 0000000..49ef204 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/models/PaymentProcessor.java @@ -0,0 +1,26 @@ +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 java.util.UUID; + +@Entity +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PaymentProcessor { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + private String name; + private String description; + private String label; + private String type; +} diff --git a/src/main/java/zw/qantra/tm/domain/models/Provider.java b/src/main/java/zw/qantra/tm/domain/models/Provider.java new file mode 100644 index 0000000..3df2252 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/models/Provider.java @@ -0,0 +1,25 @@ +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 java.util.UUID; + +@Entity +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Provider { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + private String clientId; + private String name; + private String description; +} diff --git a/src/main/java/zw/qantra/tm/domain/models/Setting.java b/src/main/java/zw/qantra/tm/domain/models/Setting.java new file mode 100644 index 0000000..95ae24c --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/models/Setting.java @@ -0,0 +1,28 @@ +package zw.qantra.tm.domain.models; + +import java.util.UUID; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Setting { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + private String name; + private String value; +} \ 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 new file mode 100644 index 0000000..d0d95c2 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/models/Transaction.java @@ -0,0 +1,49 @@ +package zw.qantra.tm.domain.models; + +import jakarta.persistence.*; +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 Transaction { + @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/repositories/ConfirmTransactionRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/ConfirmTransactionRepository.java new file mode 100644 index 0000000..75edb6a --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/repositories/ConfirmTransactionRepository.java @@ -0,0 +1,11 @@ +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/repositories/CurrencyRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/CurrencyRepository.java new file mode 100644 index 0000000..f71ac3c --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/repositories/CurrencyRepository.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import zw.qantra.tm.domain.models.Currency; + +import java.util.UUID; + +@Repository +public interface CurrencyRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/repositories/IntegrationProcessorRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/IntegrationProcessorRepository.java new file mode 100644 index 0000000..bef1e51 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/repositories/IntegrationProcessorRepository.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import zw.qantra.tm.domain.models.IntegrationProcessor; + +import java.util.UUID; + +@Repository +public interface IntegrationProcessorRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/repositories/PaymentProcessorRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/PaymentProcessorRepository.java new file mode 100644 index 0000000..a5e9cc0 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/repositories/PaymentProcessorRepository.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import zw.qantra.tm.domain.models.PaymentProcessor; + +import java.util.UUID; + +@Repository +public interface PaymentProcessorRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/repositories/ProviderRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/ProviderRepository.java new file mode 100644 index 0000000..ff6b980 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/repositories/ProviderRepository.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import zw.qantra.tm.domain.models.Provider; + +import java.util.UUID; + +@Repository +public interface ProviderRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/repositories/SettingRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/SettingRepository.java new file mode 100644 index 0000000..7345976 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/repositories/SettingRepository.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import zw.qantra.tm.domain.models.Setting; + +import java.util.UUID; + +@Repository +public interface SettingRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java new file mode 100644 index 0000000..2759c5c --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import zw.qantra.tm.domain.models.Transaction; + +import java.util.UUID; + +@Repository +public interface TransactionRepository 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 new file mode 100644 index 0000000..cef298e --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/ConfirmTransactionService.java @@ -0,0 +1,11 @@ +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/CurrencyService.java b/src/main/java/zw/qantra/tm/domain/services/CurrencyService.java new file mode 100644 index 0000000..b94af52 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/CurrencyService.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.services; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.repositories.CurrencyRepository; + +@Service +@RequiredArgsConstructor +public class CurrencyService { + private final CurrencyRepository currencyRepository; +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/IntegrationProcessorService.java b/src/main/java/zw/qantra/tm/domain/services/IntegrationProcessorService.java new file mode 100644 index 0000000..77e82c7 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/IntegrationProcessorService.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.services; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.repositories.IntegrationProcessorRepository; + +@Service +@RequiredArgsConstructor +public class IntegrationProcessorService { + private final IntegrationProcessorRepository integrationProcessorRepository; +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/PaymentProcessorService.java b/src/main/java/zw/qantra/tm/domain/services/PaymentProcessorService.java new file mode 100644 index 0000000..4b2bee4 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/PaymentProcessorService.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.services; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.repositories.PaymentProcessorRepository; + +@Service +@RequiredArgsConstructor +public class PaymentProcessorService { + private final PaymentProcessorRepository paymentProcessorRepository; +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/ProviderService.java b/src/main/java/zw/qantra/tm/domain/services/ProviderService.java new file mode 100644 index 0000000..17afbf8 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/ProviderService.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.services; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.repositories.ProviderRepository; + +@Service +@RequiredArgsConstructor +public class ProviderService { + private final ProviderRepository providerRepository; +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/SettingService.java b/src/main/java/zw/qantra/tm/domain/services/SettingService.java new file mode 100644 index 0000000..4b36c93 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/SettingService.java @@ -0,0 +1,11 @@ +package zw.qantra.tm.domain.services; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.repositories.SettingRepository; + +@Service +@RequiredArgsConstructor +public class SettingService { + private final SettingRepository settingRepository; +} \ 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 new file mode 100644 index 0000000..f15b228 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/TransactionService.java @@ -0,0 +1,21 @@ +package zw.qantra.tm.domain.services; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +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; + +@Service +@RequiredArgsConstructor +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); + 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 new file mode 100644 index 0000000..9a0c9b9 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/factories/PaymentProcessorFactory.java @@ -0,0 +1,40 @@ +package zw.qantra.tm.domain.services.factories; + + +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PaymentProcessorFactory implements ApplicationContextAware { + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + public Object getPaymentProcessor(String paymentProcessorName) { + Object paymentProcessor; + + try { + paymentProcessor = applicationContext.getBean(paymentProcessorName); + } catch (NoSuchBeanDefinitionException exception) { + logger.error("Couldn't find payment processor: {}", exception.getMessage()); + return null; + } catch (BeansException e) { + logger.error("Error retrieving payment processor bean: {}", e.getMessage()); + return null; + } + + 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 new file mode 100644 index 0000000..cb452de --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/TransactionProcessorInterface.java @@ -0,0 +1,8 @@ +package zw.qantra.tm.domain.services.processors; + +import zw.qantra.tm.domain.models.Transaction; + +public interface TransactionProcessorInterface { + Object process(Transaction transaction); + 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 new file mode 100644 index 0000000..300db30 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java @@ -0,0 +1,25 @@ +package zw.qantra.tm.domain.services.processors.confirmations; + +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("CONFIRM_POWERTEL_ZESA") +@RequiredArgsConstructor +public class ZesaConfirmationProcessor implements TransactionProcessorInterface { + private final RestService restService; + + + @Override + public Object process(Transaction transaction) { + + return transaction; + } + + @Override + public Object reverse(Transaction transaction) { + return transaction; + } +} diff --git a/src/main/java/zw/qantra/tm/rest/RestService.java b/src/main/java/zw/qantra/tm/rest/RestService.java new file mode 100755 index 0000000..c66a2b0 --- /dev/null +++ b/src/main/java/zw/qantra/tm/rest/RestService.java @@ -0,0 +1,195 @@ +package zw.qantra.tm.rest; + +import com.google.gson.Gson; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; +import zw.qantra.tm.exceptions.RestTemplateResponseErrorHandler; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/* +* Rest service that facilitates communication between services. Authentication +* is either based on client credentials, user credentials or is open. User credentials +* are only available for registered users +* */ + +@Service +@RequiredArgsConstructor +public class RestService { + Logger logger = LoggerFactory.getLogger(this.getClass().toString()); + + @Autowired + private Gson gson; + + public final RestTemplate restTemplate; + + public RestService() { + this.restTemplate = new RestTemplate( + new BufferingClientHttpRequestFactory( + new SimpleClientHttpRequestFactory() + ) + ); + + this.restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler()); + + // bootstrap rest template + List interceptors + = this.restTemplate.getInterceptors(); + if (CollectionUtils.isEmpty(interceptors)) { + interceptors = new ArrayList<>(); + } + interceptors.add(new RestTemplateInterceptor(gson)); + this.restTemplate.setInterceptors(interceptors); + } + + public String getUserToken(){ + String token = "Utils.getToken()"; + logger.info(token); + return token; + } + + // client token is retrieved from open ID identity provider and + // cached for 20 days for easy reuse. + + // todo: definitely not the best way to do this, tried implementing caching but + // that needs a bit of time to do correctly + public String getClientToken(){ + logger.info("Getting client token"); + MultiValueMap payload = new LinkedMultiValueMap<>(); + payload.add("grant_type", "client_credentials"); + Map tokenResponse = getToken(payload); + assert tokenResponse != null; + + String clientToken = (String) tokenResponse.get("access_token"); + logger.info("client access token", "log", clientToken); + return clientToken; + } + + public Map getToken(MultiValueMap payload){ + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + // build the request + HttpEntity entity = new HttpEntity<>(payload, headers); + return this.restTemplate.postForObject("tokenUrl", entity, Map.class); + } + + public T getAsClient(String path, Class clazz){ + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(getClientToken()); + HttpEntity entity = new HttpEntity<>(headers); + return this.restTemplate.getForObject(path, clazz, entity); + } + + public ResponseEntity getExchangeAsClient(String path, Class clazz){ + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(getClientToken()); + HttpEntity entity = new HttpEntity<>(headers); + return this.restTemplate.exchange(path, HttpMethod.GET, entity, clazz); + } + + public ResponseEntity postAsClient(String path, Object payload, Class clazz){ + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setBearerAuth(getClientToken()); + // build the request + HttpEntity entity = new HttpEntity<>(payload, headers); + ResponseEntity responseEntity = this.restTemplate.postForEntity(path, entity, clazz); + return responseEntity; + } + + public T postAs(String path, Object payload, HttpHeaders headers, Class clazz){ + // build the request + HttpEntity entity = new HttpEntity<>(payload, headers); + return this.restTemplate.postForObject(path, entity, clazz); + } + + public T post(String path, Object payload, Class clazz){ + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setBearerAuth(getUserToken()); + // build the request + HttpEntity entity = new HttpEntity<>(payload, headers); + T t = this.restTemplate.postForObject(path, entity, clazz); + return t; + } + + public T postWithToken(String token , String path, Object payload, Class clazz){ + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setBearerAuth(token); + // build the request + HttpEntity entity = new HttpEntity<>(payload, headers); + T t = this.restTemplate.postForObject(path, entity, clazz); + return t; + } + + public T postFormData(String path, MultiValueMap payload, Class clazz) + { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + + HttpEntity> request = new HttpEntity<>(payload, headers); + + return this.restTemplate.postForEntity(path, request, clazz).getBody(); + } + + public T get(String path, Type clazz){ + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(getUserToken()); + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity responseEntity = this.restTemplate.exchange(path, HttpMethod.GET, entity, String.class); + T response = gson.fromJson(responseEntity.getBody(), clazz); + return response; + } + + // make post request to endpoint without bearer auth + public T postNoAuth(String path, Object payload, Class clazz){ + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + // build the request + HttpEntity entity = new HttpEntity<>(payload, headers); + T t = this.restTemplate.postForObject(path, entity, clazz); + return t; + } + + // make post request to endpoint without bearer auth + public T postWithExternalAuth(String path, Object payload, HttpHeaders headers, Class clazz){ + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + // build the request + HttpEntity entity = new HttpEntity<>(payload, headers); + T t = this.restTemplate.postForObject(path, entity, clazz); + return t; + } + + // make get request to endpoint without bearer auth + public T getNoAuth(String path, Class clazz){ + return this.restTemplate.getForObject(path, clazz); + } + + public ResponseEntity exchangeGetNoAuth(String path, HttpEntity httpEntity, Class clazz, String pathVariable) { + return this.restTemplate.exchange(path, HttpMethod.GET, httpEntity, clazz, pathVariable); + } +} diff --git a/src/main/java/zw/qantra/tm/rest/RestTemplateInterceptor.java b/src/main/java/zw/qantra/tm/rest/RestTemplateInterceptor.java new file mode 100755 index 0000000..ec01b34 --- /dev/null +++ b/src/main/java/zw/qantra/tm/rest/RestTemplateInterceptor.java @@ -0,0 +1,72 @@ +package zw.qantra.tm.rest; + +import com.google.gson.Gson; +import lombok.RequiredArgsConstructor; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +public class RestTemplateInterceptor + implements ClientHttpRequestInterceptor { + private final Logger logger = LoggerFactory.getLogger(getClass().getName()); + private final Gson gson; + + @Override + public ClientHttpResponse intercept( + HttpRequest request, + byte[] reqBody, + ClientHttpRequestExecution execution) throws IOException { + String requestBody = new String(reqBody, StandardCharsets.UTF_8); + if(!isJSONValid(requestBody)) requestBody = toBasicJson(requestBody); + + // we don't log file data + if(request.getHeaders().getContentLength() < 10000){ + logger.info("Request: " + + request.getMethod() + ": " + + request.getURI(), "http", gson.fromJson(requestBody, HashMap.class)); + } + ClientHttpResponse response = execution.execute(request, reqBody); + InputStreamReader isr = new InputStreamReader(response.getBody()); + String body = new BufferedReader(isr).lines() + .collect(Collectors.joining("\n")); + try{ + logger.info("Response body: {}", "http", gson.fromJson(body, HashMap.class)); + }catch (Exception exception){ + logger.info("Problem serializing resp: {}", "http", exception.getMessage()); + } + return response; + } + + public String toBasicJson(String string){ + HashMap map = new HashMap(); + map.put("log", string); + return gson.toJson(map); + } + + public boolean isJSONValid(String test) { + try { + new JSONObject(test); + } catch (JSONException ex) { + try { + new JSONArray(test); + } catch (JSONException ex1) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/src/main/resources/application-lab.properties b/src/main/resources/application-lab.properties index 0567c1f..706aaeb 100644 --- a/src/main/resources/application-lab.properties +++ b/src/main/resources/application-lab.properties @@ -5,7 +5,7 @@ logging.level.org.springframework.boot.autoconfigure=INFO server.port=6950 -spring.jpa.hibernate.ddl-auto=validate +spring.jpa.hibernate.ddl-auto=update spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 0567c1f..706aaeb 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,7 +5,7 @@ logging.level.org.springframework.boot.autoconfigure=INFO server.port=6950 -spring.jpa.hibernate.ddl-auto=validate +spring.jpa.hibernate.ddl-auto=update spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect diff --git a/src/main/resources/liquibase-lab.properties b/src/main/resources/liquibase-lab.properties index 1b82a8e..98053c0 100644 --- a/src/main/resources/liquibase-lab.properties +++ b/src/main/resources/liquibase-lab.properties @@ -4,6 +4,6 @@ username=root password=password driver=com.mysql.cj.jdbc.Driver -referenceUrl=hibernate:spring:zw.qantra.tm.domain.models?dialect=org.hibernate.dialect.MySQL5Dialect&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy&hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy +referenceUrl=hibernate:spring:zw.qantra.tm.domain.models?dialect=org.hibernate.dialect.MySQLDialect&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy&hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy diffChangeLogFile=src/main/resources/liquibase-diff-changeLog.xml outputChangeLogFile=src/main/resources/liquibase-changeLog.xml