From 1ba7a8cda4fecfec14dc4549430793bc8ef870cf Mon Sep 17 00:00:00 2001 From: Vus Khoza Date: Fri, 11 Jul 2025 12:25:46 +0200 Subject: [PATCH] poc completed --- .giltab-ci.yml | 22 +++ Dockerfile-lab | 7 + .../tm/configs/SpecificationConfig.java | 17 +++ .../domain/controllers/ConfigController.java | 17 +++ .../controllers/ProviderController.java | 12 +- .../controllers/RecipientController.java | 73 +++++++++ .../controllers/TransactonController.java | 46 +++++- .../qantra/tm/domain/dtos/BillPaymentDto.java | 3 + .../qantra/tm/domain/models/BaseEntity.java | 12 ++ .../zw/qantra/tm/domain/models/Provider.java | 2 + .../zw/qantra/tm/domain/models/Recipient.java | 21 +++ .../qantra/tm/domain/models/Transaction.java | 7 + .../repositories/RecipientRepository.java | 17 +++ .../repositories/TransactionRepository.java | 6 +- .../tm/domain/services/ProviderService.java | 33 +++- .../tm/domain/services/RecipientService.java | 51 +++++++ .../domain/services/TransactionService.java | 141 ++++++++++++++---- .../EconetConfirmationProcessor.java | 98 ++++++++++++ .../ZesaConfirmationProcessor.java | 1 + .../handlers/CalculateChargesHandler.java | 10 +- .../handlers/ConfirmationLogicHandler.java | 15 ++ .../handlers/FormattingHandler.java | 59 ++++++++ .../handlers/RecipientsUpdateHandler.java | 55 +++++++ .../StewardIntegrationProcessor.java | 112 ++++++++++++++ .../ZesaIntegrationProcessor.java | 34 ----- .../payments/MPGSWebPaymentProcessor.java | 11 +- src/main/java/zw/qantra/tm/utils/Utils.java | 5 + src/main/resources/application-lab.properties | 8 +- 28 files changed, 807 insertions(+), 88 deletions(-) create mode 100644 .giltab-ci.yml create mode 100644 Dockerfile-lab create mode 100644 src/main/java/zw/qantra/tm/configs/SpecificationConfig.java create mode 100644 src/main/java/zw/qantra/tm/domain/controllers/RecipientController.java create mode 100644 src/main/java/zw/qantra/tm/domain/models/Recipient.java create mode 100644 src/main/java/zw/qantra/tm/domain/repositories/RecipientRepository.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/RecipientService.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/confirmations/EconetConfirmationProcessor.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/handlers/ConfirmationLogicHandler.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/handlers/FormattingHandler.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/handlers/RecipientsUpdateHandler.java create mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/integrations/StewardIntegrationProcessor.java delete mode 100644 src/main/java/zw/qantra/tm/domain/services/processors/integrations/ZesaIntegrationProcessor.java diff --git a/.giltab-ci.yml b/.giltab-ci.yml new file mode 100644 index 0000000..297e599 --- /dev/null +++ b/.giltab-ci.yml @@ -0,0 +1,22 @@ +image: docker:19.03.1 + +variables: + REGISTRY: 144.91.121.112:8082 + DEV_TAG: q-cbs-transaction-manager-spring-boot:develop + TAG: q-cbs-transaction-manager-spring-boot:0.0.0-RELEASE + DOCKER_OPTS: "--insecure-registry=$REGISTRY" + +stages: + - package_test + +before_script: + - docker info + - docker login -u admin -p 1aae6202-7d42-44e4-bdc7-d988a95fdfe2 $REGISTRY + +package_test: + stage: package_test + script: + - docker build -f Dockerfile-lab -t "$REGISTRY/$DEV_TAG" . + - docker push "$REGISTRY/$DEV_TAG" + only: + - develop diff --git a/Dockerfile-lab b/Dockerfile-lab new file mode 100644 index 0000000..94de2be --- /dev/null +++ b/Dockerfile-lab @@ -0,0 +1,7 @@ +FROM 144.91.121.112:8082/java-17 +RUN mkdir /app +COPY . /app +WORKDIR /app +RUN mvn clean compile -P develop package +ENTRYPOINT ["/usr/bin/dumb-init", "--"] +CMD ["java", "-Duser.timezone=CAT", "-Dspring.profiles.active=lab", "-jar", "/app/target/cbs-transaction-manager-0.0.1-SNAPSHOT.jar"] diff --git a/src/main/java/zw/qantra/tm/configs/SpecificationConfig.java b/src/main/java/zw/qantra/tm/configs/SpecificationConfig.java new file mode 100644 index 0000000..878be48 --- /dev/null +++ b/src/main/java/zw/qantra/tm/configs/SpecificationConfig.java @@ -0,0 +1,17 @@ +package zw.qantra.tm.configs; + +import net.kaczmarzyk.spring.data.jpa.web.SpecificationArgumentResolver; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +public class SpecificationConfig implements WebMvcConfigurer { + + @Override + public void addArgumentResolvers(List argumentResolvers) { + argumentResolvers.add(new SpecificationArgumentResolver()); + } +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java b/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java index 84cb852..59dc883 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java @@ -11,10 +11,12 @@ import zw.qantra.tm.domain.enums.CurrencyType; import zw.qantra.tm.domain.models.Category; import zw.qantra.tm.domain.models.Charge; import zw.qantra.tm.domain.models.ChargeCondition; +import zw.qantra.tm.domain.models.IntegrationProcessor; import zw.qantra.tm.domain.models.PaymentProcessor; import zw.qantra.tm.domain.models.Provider; import zw.qantra.tm.domain.services.ChargeConditionService; import zw.qantra.tm.domain.services.ChargeService; +import zw.qantra.tm.domain.services.IntegrationProcessorService; import zw.qantra.tm.domain.services.CategoryService; import zw.qantra.tm.domain.services.PaymentProcessorService; import zw.qantra.tm.domain.services.ProviderService; @@ -33,9 +35,20 @@ public class ConfigController { private final PaymentProcessorService paymentProcessorService; private final ProviderService providerService; private final CategoryService categoryService; + private final IntegrationProcessorService integrationProcessorService; @GetMapping("/seed") public ResponseEntity charges(){ + + integrationProcessorService.getIntegrationProcessorRepository().deleteAll(); + integrationProcessorService.getIntegrationProcessorRepository().saveAll(Arrays.asList( + IntegrationProcessor.builder() + .name("STEWARD") + .label("STEWARD") + .description("Steward Integration Processor") + .build() + )); + // clear existing categories before seeding categoryService.getCategoryRepository().deleteAll(); // seed categories @@ -65,6 +78,7 @@ public class ConfigController { .category("AIRTIME") .image("econet.png") .externalId("78f1f497-c9eb-401c-b22c-884756e68e40") + .integrationProcessorLabel("STEWARD") .build(), Provider.builder() .description("NetOne Airtime") @@ -73,6 +87,7 @@ public class ConfigController { .category("AIRTIME") .image("netone.png") .externalId("95d485a7-39c9-4559-8a27-6521969abe8f") + .integrationProcessorLabel("STEWARD") .build(), Provider.builder() .description("Prepaid Zesa") @@ -81,6 +96,7 @@ public class ConfigController { .category("UTILITIES") .image("zesa.png") .externalId("0f67f7cc-b62b-4fb5-915f-6740b2afdba6") + .integrationProcessorLabel("STEWARD") .build(), Provider.builder() .description("Econet Broadband") @@ -89,6 +105,7 @@ public class ConfigController { .category("AIRTIME") .image("econet.png") .externalId("287863e2-a791-4106-b629-79dadc9a6779") + .integrationProcessorLabel("STEWARD") .build() ); providerService.getProviderRepository().saveAll(providers); diff --git a/src/main/java/zw/qantra/tm/domain/controllers/ProviderController.java b/src/main/java/zw/qantra/tm/domain/controllers/ProviderController.java index 3e857ba..829ff47 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/ProviderController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/ProviderController.java @@ -17,7 +17,7 @@ public class ProviderController { @GetMapping public ResponseEntity getProviders(@RequestParam(required = false) String category) { - return ResponseEntity.ok(providerService.getProviders(category)); + return ResponseEntity.ok(providerService.getProvidersByCategory(category)); } @GetMapping("/{id}/products") @@ -25,6 +25,16 @@ public class ProviderController { return ResponseEntity.ok(providerService.getProviderProducts(id)); } + @GetMapping("/{id}/products/{productId}") + public ResponseEntity getProducts(@PathVariable UUID id, @PathVariable UUID productId) { + return ResponseEntity.ok(providerService.getProviderProduct(id, productId)); + } + + @GetMapping("/client/{clientId}") + public ResponseEntity getProviderByClientId(@PathVariable String clientId) { + return ResponseEntity.ok(providerService.getProviderByClientId(clientId)); + } + @GetMapping("/{id}") public ResponseEntity getProvider(@PathVariable UUID id) { return providerService.getProvider(id) diff --git a/src/main/java/zw/qantra/tm/domain/controllers/RecipientController.java b/src/main/java/zw/qantra/tm/domain/controllers/RecipientController.java new file mode 100644 index 0000000..13d7972 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/controllers/RecipientController.java @@ -0,0 +1,73 @@ +package zw.qantra.tm.domain.controllers; + +import lombok.RequiredArgsConstructor; +import net.kaczmarzyk.spring.data.jpa.domain.Equal; +import net.kaczmarzyk.spring.data.jpa.domain.Like; +import net.kaczmarzyk.spring.data.jpa.web.annotation.Spec; +import net.kaczmarzyk.spring.data.jpa.web.annotation.Or; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import zw.qantra.tm.domain.models.Recipient; +import zw.qantra.tm.domain.services.RecipientService; +import net.kaczmarzyk.spring.data.jpa.web.annotation.And; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/recipients") +@RequiredArgsConstructor +public class RecipientController { + + private final RecipientService recipientService; + + @GetMapping + public ResponseEntity> getAllRecipients() { + return ResponseEntity.ok(recipientService.getAllRecipients()); + } + + @GetMapping("/{account}/user/{userId}") + public ResponseEntity getRecipient(@PathVariable String account, @PathVariable String userId) { + List recipients = recipientService.getRecipient(account, userId); + if(recipients.size() > 0){ + return ResponseEntity.ok(recipients.get(0)); + } + return ResponseEntity.notFound().build(); + } + + @PostMapping + public ResponseEntity createRecipient(@RequestBody Recipient recipient) { + return ResponseEntity.ok(recipientService.save(recipient)); + } + + @PutMapping("/{id}") + public ResponseEntity updateRecipient(@PathVariable UUID id, @RequestBody Recipient recipient) { + Recipient updatedRecipient = recipientService.updateRecipient(id, recipient); + if (updatedRecipient != null) { + return ResponseEntity.ok(updatedRecipient); + } + return ResponseEntity.notFound().build(); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteRecipient(@PathVariable UUID id) { + if (recipientService.deleteRecipient(id)) { + return ResponseEntity.ok("Recipient deleted successfully"); + } + return ResponseEntity.notFound().build(); + } + + @GetMapping("/search") + public ResponseEntity> searchRecipients( + @And({ + @Spec(path = "name", spec = Equal.class), + @Spec(path = "email", spec = Equal.class), + @Spec(path = "phoneNumber", spec = Equal.class), + @Spec(path = "account", spec = Equal.class), + @Spec(path = "latestProviderLabel", spec = Equal.class), + @Spec(path = "userId", spec = Equal.class) + }) Specification specification) { + return ResponseEntity.ok(recipientService.searchRecipients(specification)); + } +} \ No newline at end of file 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 e120136..35d7962 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java @@ -1,6 +1,12 @@ package zw.qantra.tm.domain.controllers; import lombok.RequiredArgsConstructor; +import net.kaczmarzyk.spring.data.jpa.domain.Equal; +import net.kaczmarzyk.spring.data.jpa.web.annotation.And; +import net.kaczmarzyk.spring.data.jpa.web.annotation.Or; +import net.kaczmarzyk.spring.data.jpa.web.annotation.Spec; +import net.kaczmarzyk.spring.data.jpa.web.annotation.Conjunction; +import org.springframework.data.jpa.domain.Specification; import zw.qantra.tm.domain.services.TransactionService; import zw.qantra.tm.domain.models.Transaction; @@ -8,6 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import zw.qantra.tm.utils.Utils; import java.util.List; import java.util.UUID; @@ -21,15 +28,40 @@ public class TransactonController { private final TransactionService transactionService; @GetMapping - public ResponseEntity> getAll() { - return ResponseEntity.ok(transactionService.getTransactionRepository().findAll()); + public ResponseEntity> getAll( + @Conjunction(value = { + @Or({ + @Spec(path = "status", spec = Equal.class), + @Spec(path = "createdAt", spec = Equal.class), + @Spec(path = "providerLabel", spec = Equal.class), + @Spec(path = "billClientId", spec = Equal.class), + @Spec(path = "productUid", spec = Equal.class), + @Spec(path = "productName", spec = Equal.class), + @Spec(path = "debitName", spec = Equal.class), + @Spec(path = "debitAccount", spec = Equal.class), + @Spec(path = "debitCurrency", spec = Equal.class), + @Spec(path = "debitPhone", spec = Equal.class), + @Spec(path = "creditName", spec = Equal.class), + @Spec(path = "creditAccount", spec = Equal.class), + @Spec(path = "creditCurrency", spec = Equal.class), + @Spec(path = "creditPhone", spec = Equal.class), + @Spec(path = "amount", spec = Equal.class), + @Spec(path = "reference", spec = Equal.class), + @Spec(path = "totalAmount", spec = Equal.class), + @Spec(path = "authType", spec = Equal.class), + @Spec(path = "sdkActionId", spec = Equal.class), + @Spec(path = "paymentProcessorLabel", spec = Equal.class), + }) + }, and = { + @Spec(path = "type", spec = Equal.class), + @Spec(path = "userId", spec = Equal.class), + }) Specification specification) { + return ResponseEntity.ok(transactionService.findAll(specification)); } @GetMapping("/{id}") public ResponseEntity getById(@PathVariable UUID id) { - return transactionService.getTransactionRepository().findById(id) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()); + return ResponseEntity.ok(transactionService.findById(id)); } @GetMapping("/poll/{id}") @@ -39,9 +71,9 @@ public class TransactonController { @PostMapping public ResponseEntity create(@RequestBody Transaction transaction) { - logger.info("incoming transaction: {}", transaction); + logger.info("incoming transaction: {}", Utils.toJson(transaction)); Transaction createdTransaction = transactionService.execute(transaction); - logger.info("outgoing transaction: {}", createdTransaction); + logger.info("outgoing transaction: {}", Utils.toJson(createdTransaction)); return ResponseEntity.ok(createdTransaction); } diff --git a/src/main/java/zw/qantra/tm/domain/dtos/BillPaymentDto.java b/src/main/java/zw/qantra/tm/domain/dtos/BillPaymentDto.java index 96029b6..7e970b1 100644 --- a/src/main/java/zw/qantra/tm/domain/dtos/BillPaymentDto.java +++ b/src/main/java/zw/qantra/tm/domain/dtos/BillPaymentDto.java @@ -5,6 +5,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import zw.qantra.tm.domain.enums.AuthType; import zw.qantra.tm.domain.enums.CurrencyType; import zw.qantra.tm.domain.enums.RequestType; @@ -17,6 +18,7 @@ import java.math.BigDecimal; @JsonIgnoreProperties(ignoreUnknown = true) public class BillPaymentDto { private RequestType type; + private AuthType authType; private String billClientId; private String debitRef; private CurrencyType debitCurrency; @@ -28,4 +30,5 @@ public class BillPaymentDto { private CurrencyType creditCurrency; private String creditPhone; private String billName; + private String trace; } \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java b/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java index 4870587..fc922cd 100644 --- a/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java +++ b/src/main/java/zw/qantra/tm/domain/models/BaseEntity.java @@ -6,10 +6,13 @@ import jakarta.persistence.Id; import jakarta.persistence.MappedSuperclass; import lombok.Getter; import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.type.SqlTypes; +import java.time.LocalDateTime; import java.util.UUID; @Getter @@ -25,4 +28,13 @@ public class BaseEntity { @JdbcTypeCode(SqlTypes.UUID) @Column(name = "id", updatable = false, nullable = false) private UUID id; + + @CreationTimestamp + @Column(name = "created_at", nullable = true, updatable = false) + private LocalDateTime createdAt; // Or Date, Timestamp + + @UpdateTimestamp + @Column(name = "updated_at", nullable = true) + private LocalDateTime updatedAt; // Or Date, Timestamp + } diff --git a/src/main/java/zw/qantra/tm/domain/models/Provider.java b/src/main/java/zw/qantra/tm/domain/models/Provider.java index 4d8bbd4..3ab23f1 100644 --- a/src/main/java/zw/qantra/tm/domain/models/Provider.java +++ b/src/main/java/zw/qantra/tm/domain/models/Provider.java @@ -17,4 +17,6 @@ public class Provider extends BaseEntity { private String externalId; private String image; private String category; + private String accountFieldName; + private String integrationProcessorLabel; } diff --git a/src/main/java/zw/qantra/tm/domain/models/Recipient.java b/src/main/java/zw/qantra/tm/domain/models/Recipient.java new file mode 100644 index 0000000..601412c --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/models/Recipient.java @@ -0,0 +1,21 @@ +package zw.qantra.tm.domain.models; + +import jakarta.persistence.Entity; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Recipient extends BaseEntity { + private String name; + private String email; + private String phoneNumber; + private String address; + private String account; + private String initials; + private String latestProviderLabel; + private String userId; +} 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 0833e4e..c7c63f4 100644 --- a/src/main/java/zw/qantra/tm/domain/models/Transaction.java +++ b/src/main/java/zw/qantra/tm/domain/models/Transaction.java @@ -18,6 +18,7 @@ import java.math.BigDecimal; @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) public class Transaction extends BaseEntity { + private String userId; private String trace; private BigDecimal amount; private BigDecimal charge; @@ -40,6 +41,7 @@ public class Transaction extends BaseEntity { private String debitName; private String debitCard; private String debitRef; + private String debitEmail; private String creditPhone; private String creditAccount; @Enumerated(EnumType.STRING) @@ -47,11 +49,15 @@ public class Transaction extends BaseEntity { private String creditName; private String creditCard; private String creditRef; + private String creditEmail; private String billClientId; private String clientSecret; private String aggregatorId; private String billName; private String billProductName; + private String paymentProcessorName; + private String providerImage; + private String paymentProcessorImage; @Enumerated(EnumType.STRING) private RequestType type; @Enumerated(EnumType.STRING) @@ -60,6 +66,7 @@ public class Transaction extends BaseEntity { @Column(columnDefinition = "TEXT") private String errorMessage; private String responseCode; + @Enumerated(EnumType.STRING) private Status status; @Column(columnDefinition = "TEXT") private String targetUrl; diff --git a/src/main/java/zw/qantra/tm/domain/repositories/RecipientRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/RecipientRepository.java new file mode 100644 index 0000000..6ffb9cc --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/repositories/RecipientRepository.java @@ -0,0 +1,17 @@ +package zw.qantra.tm.domain.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.stereotype.Repository; +import zw.qantra.tm.domain.models.Recipient; + +import java.util.List; +import java.util.UUID; + +@Repository +public interface RecipientRepository extends JpaRepository, JpaSpecificationExecutor { + List findByNameContainingIgnoreCase(String name); + List findByEmailContainingIgnoreCase(String email); + List findByPhoneNumberContaining(String phoneNumber); + List findByAccountAndUserId(String account, String userId); +} \ 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 index 2759c5c..653895c 100644 --- a/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java +++ b/src/main/java/zw/qantra/tm/domain/repositories/TransactionRepository.java @@ -1,11 +1,15 @@ package zw.qantra.tm.domain.repositories; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; + +import zw.qantra.tm.domain.enums.RequestType; import zw.qantra.tm.domain.models.Transaction; import java.util.UUID; @Repository -public interface TransactionRepository extends JpaRepository { +public interface TransactionRepository extends JpaRepository, JpaSpecificationExecutor { + Transaction findByTraceAndType(String trace, RequestType type); } \ 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 index c419bff..6ec062f 100644 --- a/src/main/java/zw/qantra/tm/domain/services/ProviderService.java +++ b/src/main/java/zw/qantra/tm/domain/services/ProviderService.java @@ -30,6 +30,13 @@ public class ProviderService { @Value("${sbz.aggregator.url}") private String aggregatorUrl; + public SBZProductDto getProviderProduct(UUID providerId, UUID productId) { + return getProviderProducts(providerId).stream() + .filter(product -> product.getUid().equals(productId)) + .findFirst() + .orElse(null); + } + public List getProviderProducts(UUID providerId) { String token = restService.getMerchantToken(); @@ -57,13 +64,15 @@ public class ProviderService { return products; } - public Object getProviders(String category) { - String token = restService.getMerchantToken(); + public Object getProviderByClientId(String clientId) { + Provider provider = providerRepository.findByClientId(clientId); + if (provider != null) { + return ((List)getProviders(Collections.singletonList(provider))).stream().findFirst().orElse(null); + } + return null; + } - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - headers.setBearerAuth(token); + public Object getProvidersByCategory(String category) { List existingProviders; if (category != null && !category.isEmpty()) { @@ -72,6 +81,17 @@ public class ProviderService { existingProviders = getProviderRepository().findAll(); } + return getProviders(existingProviders); + } + + public Object getProviders(List existingProviders) { + String token = restService.getMerchantToken(); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setBearerAuth(token); + List existingProviderIds = existingProviders.stream() .map(Provider::getClientId) .collect(Collectors.toList()); @@ -100,6 +120,7 @@ public class ProviderService { provider.put("image", existingProvider.getImage()); provider.put("label", existingProvider.getLabel()); provider.put("category", existingProvider.getCategory()); + provider.put("accountFieldName", existingProvider.getAccountFieldName()); } } } diff --git a/src/main/java/zw/qantra/tm/domain/services/RecipientService.java b/src/main/java/zw/qantra/tm/domain/services/RecipientService.java new file mode 100644 index 0000000..1a86bc9 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/RecipientService.java @@ -0,0 +1,51 @@ +package zw.qantra.tm.domain.services; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.models.Recipient; +import zw.qantra.tm.domain.repositories.RecipientRepository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class RecipientService { + @Getter + private final RecipientRepository recipientRepository; + + public List getAllRecipients() { + return recipientRepository.findAll(); + } + + public List getRecipient(String account, String userId) { + return recipientRepository.findByAccountAndUserId(account, userId); + } + + public Recipient save(Recipient recipient) { + return recipientRepository.save(recipient); + } + + public Recipient updateRecipient(UUID id, Recipient recipient) { + if (recipientRepository.existsById(id)) { + recipient.setId(id); + return recipientRepository.save(recipient); + } + return null; + } + + public boolean deleteRecipient(UUID id) { + if (recipientRepository.existsById(id)) { + recipientRepository.deleteById(id); + return true; + } + return false; + } + + public List searchRecipients(Specification specification) { + return recipientRepository.findAll(specification); + } +} \ 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 0108630..3ec756d 100644 --- a/src/main/java/zw/qantra/tm/domain/services/TransactionService.java +++ b/src/main/java/zw/qantra/tm/domain/services/TransactionService.java @@ -3,23 +3,34 @@ package zw.qantra.tm.domain.services; import lombok.Getter; 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.Sort; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; 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; 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.ConfirmationLogicHandler; +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.exceptions.ApiException; +import zw.qantra.tm.utils.Utils; @Service @RequiredArgsConstructor @@ -29,42 +40,25 @@ public class TransactionService { @Getter private final TransactionRepository transactionRepository; private final PaymentProcessorFactory paymentProcessorFactory; - private final CalculateChargesHandler calculateChargesHandler; private final ProviderService providerService; + private final ChargeService chargeService; + private final AdditionalDataService additionalDataService; + private final RecipientService recipientService; - public Transaction poll(UUID uuid){ - if(!transactionRepository.existsById(uuid)){ - throw new ApiException("Transaction not found for ID: " + uuid); - } - - Transaction transaction = transactionRepository.findById(uuid).get(); - - // no persistence happens during polling unless the transaction status changes - String label = getLabel(transaction); - logger.info("label: {}", label); - - TransactionProcessorInterface transactionProcessorInterface = - (TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(label); - try { - transactionProcessorInterface.poll(transaction); - } catch (Exception e) { - e.printStackTrace(); - transaction.setStatus(Status.FAILED); - transaction.setResponseCode("01"); - transaction.setErrorMessage(e.getMessage()); - } - return transaction; - } - - public Transaction execute(Transaction transaction) { - transaction.setReference(UUID.randomUUID().toString()); + public Transaction integration(Transaction transaction) { + transaction.setType(RequestType.INTEGRATION); LogicPipeline transactionLogic = - new LogicPipeline<>(calculateChargesHandler) - .addHandler(new ValidationHandler()); + new LogicPipeline<>(new ValidationHandler()); transaction = transactionLogic.execute(transaction); - String label = getLabel(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.getIntegrationProcessorLabel(); logger.info("label: {}", label); TransactionProcessorInterface transactionProcessorInterface = @@ -83,9 +77,80 @@ public class TransactionService { return transaction; } + public Transaction poll(UUID uuid){ + if(!transactionRepository.existsById(uuid)){ + throw new ApiException("Transaction not found for ID: " + uuid); + } + + Transaction transaction = transactionRepository.findById(uuid).get(); + + // no persistence happens during polling unless the transaction status changes + String label = getLabel(transaction); + logger.info("label: {}", label); + + TransactionProcessorInterface transactionProcessorInterface = + (TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(label); + try { + // find out if the gateway tran was successful + 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); + + } catch (Exception e) { + e.printStackTrace(); + transaction.setStatus(Status.FAILED); + transaction.setResponseCode("01"); + transaction.setErrorMessage(e.getMessage()); + } + return transaction; + } + + public Transaction execute(Transaction transaction) { + transaction.setReference(UUID.randomUUID().toString()); + + // 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); + logger.info("label: {}", label); + + TransactionProcessorInterface transactionProcessorInterface = + (TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(label); + + try { + // MAIN TRANSACTION LOGIC! + transactionProcessorInterface.process(transaction); + + }catch (Exception e){ + e.printStackTrace(); + transaction.setStatus(Status.FAILED); + transaction.setResponseCode("01"); + transaction.setErrorMessage(e.getMessage()); + transactionRepository.save(transaction); + return transaction; + } + + // run post transaction logic + LogicPipeline postTransactionLogic = + new LogicPipeline<>(new RecipientsUpdateHandler(recipientService)); + transaction = postTransactionLogic.execute(transaction); + + return transaction; + } + private String getLabel(Transaction transaction){ String label = ""; - if(transaction.getType() == RequestType.CONFIRM){ + List list = Arrays.asList(RequestType.CONFIRM); + if(list.contains(transaction.getType())){ // get processor label from billClientId Provider provider = providerService.getProviderRepository() .findByClientId(transaction.getBillClientId()); @@ -102,6 +167,20 @@ public class TransactionService { return label; } + public Transaction findById(UUID id) { + Transaction transaction = transactionRepository.findById(id).orElseThrow(() -> new ApiException("Transaction not found for ID: " + id)); + + AdditionalData additionalData = additionalDataService.getAdditionalDataRepository() + .findByTransactionId(id).orElseThrow(() -> new ApiException("Additional data not found for transaction ID: " + id)); + transaction.setAdditionalData(Utils.fromJson(additionalData.getJsonString(), List.class)); + + return transaction; + } + + public List findAll(Specification spec) { + return transactionRepository.findAll(spec, Sort.by(Sort.Direction.ASC, "createdAt")); + } + public Transaction save(Transaction transaction){ return transactionRepository.save(transaction); } 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 new file mode 100644 index 0000000..e26156f --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/EconetConfirmationProcessor.java @@ -0,0 +1,98 @@ +package zw.qantra.tm.domain.services.processors.confirmations; + +import lombok.RequiredArgsConstructor; + +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.dtos.BillPaymentDto; +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.Transaction; +import zw.qantra.tm.domain.services.AdditionalDataService; +import zw.qantra.tm.domain.services.TransactionService; +import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface; +import zw.qantra.tm.rest.RestService; +import zw.qantra.tm.utils.Utils; + +import java.util.LinkedHashMap; + +@Service("CONFIRM_ECONET") +@RequiredArgsConstructor +public class EconetConfirmationProcessor implements TransactionProcessorInterface { + Logger logger = LoggerFactory.getLogger(EconetConfirmationProcessor.class); + + private final RestService restService; + private final TransactionService transactionService; + private final AdditionalDataService additionalDataService; + + @Value("${sbz.aggregator.url}") + private String aggregatorUrl; + @Value("${sbz.aggregator.client-id}") + private String aggregatorClientId; + @Value("${sbz.aggregator.phone}") + private String aggregatorPhone; + @Value("${sbz.aggregator.account}") + private String aggregatorAccount; + + @Override + public Object process(Transaction transaction) throws Exception { + logger.info("processing transaction"); + String token = restService.getMerchantToken(); + + BillPaymentDto request = BillPaymentDto.builder() + .type(RequestType.CONFIRM) + .billClientId(transaction.getBillClientId()) + .debitRef(transaction.getDebitRef()) + .debitCurrency(transaction.getDebitCurrency()) + .amount(transaction.getAmount()) + .aggregatorId(aggregatorClientId) + .debitPhone(aggregatorPhone) // qantra mobile number + .debitAccount(aggregatorAccount) // qantra corporate account + .creditAccount(transaction.getCreditAccount()) // customer's phone number + .creditPhone(transaction.getCreditPhone()) // customer's contact phone number + .billName(transaction.getBillName()) + .build(); + + String url = aggregatorUrl + "/merchant/aggregation/transaction"; + + LinkedHashMap response = restService.postWithToken(token, url, request, LinkedHashMap.class); + + logger.info("response: {}", response.toString()); + + LinkedHashMap body = (LinkedHashMap) response.get("body"); + + if (body.get("status").equals("SUCCESS")) { + transaction.setStatus(Status.SUCCESS); + transaction.setResponseCode((String) body.get("responseCode")); + transaction.setAdditionalData(body.get("additionalData")); + transactionService.save(transaction); + + AdditionalData additionalData = AdditionalData.builder() + .transaction(transaction) + .jsonString(Utils.toJson(body.get("additionalData"))) + .build(); + additionalDataService.getAdditionalDataRepository().save(additionalData); + + } else { + transaction.setStatus(Status.FAILED); + transaction.setErrorMessage((String) body.get("message")); + transaction.setResponseCode((String) body.get("responseCode")); + transaction.setErrorMessage((String) body.get("errorMessage")); + } + + return transactionService.save(transaction); + } + + @Override + public Object reverse(Transaction transaction) { + return transaction; + } + + @Override + public Object poll(Transaction transaction) { + return 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 331f6a9..6d3c490 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 @@ -80,6 +80,7 @@ public class ZesaConfirmationProcessor implements TransactionProcessorInterface transaction.setStatus(Status.FAILED); transaction.setErrorMessage((String) body.get("message")); transaction.setResponseCode((String) body.get("responseCode")); + transaction.setErrorMessage((String) body.get("errorMessage")); } return transactionService.save(transaction); 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 index ac9bb4a..b1e51c2 100644 --- 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 @@ -1,5 +1,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; @@ -17,11 +18,11 @@ import java.util.List; @Service +@RequiredArgsConstructor public class CalculateChargesHandler implements LogicHandler { Logger logger = LoggerFactory.getLogger(CalculateChargesHandler.class); - @Autowired - private ChargeService chargeService; + private final ChargeService chargeService; @Override public Transaction process(Transaction transaction) { @@ -126,14 +127,17 @@ public class CalculateChargesHandler implements LogicHandler transaction.setCharge(chargeAmount); case TAX -> transaction.setTax(chargeAmount); diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ConfirmationLogicHandler.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ConfirmationLogicHandler.java new file mode 100644 index 0000000..43a448c --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/ConfirmationLogicHandler.java @@ -0,0 +1,15 @@ +package zw.qantra.tm.domain.services.processors.handlers; + +import zw.qantra.tm.domain.enums.RequestType; +import zw.qantra.tm.domain.models.Transaction; +import zw.qantra.tm.utils.Utils; + +public class ConfirmationLogicHandler implements LogicHandler { + @Override + public Transaction process(Transaction transaction) { + if(transaction.getType().equals(RequestType.CONFIRM)) { + transaction.setTrace(Utils.getUid()); + } + return transaction; + } +} diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/FormattingHandler.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/FormattingHandler.java new file mode 100644 index 0000000..4261aa0 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/FormattingHandler.java @@ -0,0 +1,59 @@ +package zw.qantra.tm.domain.services.processors.handlers; + +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; + +import zw.qantra.tm.domain.models.Transaction; + +public class FormattingHandler implements LogicHandler { + @Override + public Transaction process(Transaction transaction) { + System.out.println("processing formatting"); + + // strip credit account and make sure there's nothing funny + String creditAccount = transaction.getCreditAccount(); + if (creditAccount != null && !creditAccount.isEmpty()) { + // Remove all non-hexadecimal characters (keep only 0-9, a-f, A-F) + String strippedAccount = creditAccount.replaceAll("[^0-9a-fA-F]", ""); + + // Only set if the stripped result is not empty + if (!strippedAccount.isEmpty()) { + transaction.setCreditAccount(strippedAccount); + } else { + // If stripping results in empty string, set to null + transaction.setCreditAccount(null); + } + } + + + // sanitize phone number formatting + String debitPhoneNumber = transaction.getDebitPhone(); + String creditPhoneNumber = transaction.getCreditPhone(); + + PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); + + if(debitPhoneNumber != null && !debitPhoneNumber.isEmpty()){ + try { + // phone must begin with '+' + PhoneNumber zimPhoneNumber = phoneUtil.parse(debitPhoneNumber, ""); + String formattedPhoneNumber = phoneUtil.format(zimPhoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164); + transaction.setDebitPhone(formattedPhoneNumber); + } catch (NumberParseException e) { + System.err.println("NumberParseException was thrown: " + e.toString()); + } + } + + if(creditPhoneNumber != null && !creditPhoneNumber.isEmpty()){ + try { + PhoneNumber zimPhoneNumber = phoneUtil.parse(creditPhoneNumber, ""); + String formattedPhoneNumber = phoneUtil.format(zimPhoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164); + transaction.setCreditPhone(formattedPhoneNumber); + } catch (NumberParseException e) { + System.err.println("NumberParseException was thrown: " + e.toString()); + } + } + + return transaction; + } +} diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/RecipientsUpdateHandler.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/RecipientsUpdateHandler.java new file mode 100644 index 0000000..dcabe19 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/RecipientsUpdateHandler.java @@ -0,0 +1,55 @@ +package zw.qantra.tm.domain.services.processors.handlers; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.models.Transaction; +import zw.qantra.tm.domain.enums.Status; +import zw.qantra.tm.domain.models.Recipient; +import zw.qantra.tm.domain.services.RecipientService; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class RecipientsUpdateHandler implements LogicHandler { + + private final RecipientService recipientService; + + @Override + public Transaction process(Transaction transaction) { + System.out.println("processing recipients"); + + if(transaction.getStatus() == Status.SUCCESS){ + List recipients = recipientService.getRecipient(transaction.getCreditAccount(), transaction.getUserId()); + + Recipient recipient = null; + if(recipients.size() > 0){ + recipient = recipients.get(0); + }else{ + recipient = new Recipient(); + recipient.setAccount(transaction.getCreditAccount()); + recipient.setUserId(transaction.getUserId()); + } + + recipient.setLatestProviderLabel(transaction.getProviderLabel()); + if(transaction.getCreditName() != null && !transaction.getCreditName().isEmpty()){ + recipient.setName(transaction.getCreditName()); + } + if(transaction.getCreditEmail() != null && !transaction.getCreditEmail().isEmpty()){ + recipient.setEmail(transaction.getCreditEmail()); + } + if(transaction.getCreditPhone() != null && !transaction.getCreditPhone().isEmpty()){ + recipient.setPhoneNumber(transaction.getCreditPhone()); + } + + String stringToInitial = transaction.getCreditName() != null && !transaction.getCreditName().isEmpty() + ? transaction.getCreditName() : transaction.getBillName(); + + recipient.setInitials(stringToInitial.substring(0, 2).toUpperCase()); + recipientService.save(recipient); + + } + + return transaction; + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..db726e5 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/processors/integrations/StewardIntegrationProcessor.java @@ -0,0 +1,112 @@ +package zw.qantra.tm.domain.services.processors.integrations; + +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.dtos.BillPaymentDto; +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.Transaction; +import zw.qantra.tm.domain.services.AdditionalDataService; +import zw.qantra.tm.domain.services.TransactionService; +import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface; +import zw.qantra.tm.rest.RestService; +import zw.qantra.tm.utils.Utils; + +import java.util.LinkedHashMap; + +@Service("INTEGRATION_STEWARD") +@RequiredArgsConstructor +public class StewardIntegrationProcessor implements TransactionProcessorInterface { + Logger logger = LoggerFactory.getLogger(TransactionService.class); + private final RestService restService; + private final TransactionService transactionService; + private final AdditionalDataService additionalDataService; + + @Value("${sbz.aggregator.url}") + private String aggregatorUrl; + @Value("${sbz.aggregator.client-id}") + private String aggregatorClientId; + @Value("${sbz.aggregator.phone}") + private String aggregatorPhone; + @Value("${sbz.aggregator.account}") + private String aggregatorAccount; + + @Override + public Object process(Transaction transaction) throws Exception { + logger.info("processing integration"); + String token = restService.getMerchantToken(); + + BillPaymentDto request = BillPaymentDto.builder() + .type(RequestType.REQUEST) + .billClientId(transaction.getBillClientId()) + .debitRef(transaction.getDebitRef()) + .debitCurrency(transaction.getDebitCurrency()) + .amount(transaction.getAmount()) + .aggregatorId(aggregatorClientId) + .debitPhone(aggregatorPhone) // qantra mobile number + .debitAccount(aggregatorAccount) // qantra corporate account + .creditAccount(transaction.getCreditAccount()) // customer's zesa meter number + .creditPhone(transaction.getCreditPhone()) // customer's contact phone number + .billName(transaction.getBillName()) + .authType(transaction.getAuthType()) + .trace(transaction.getTrace()) + .build(); + + String url = aggregatorUrl + "/merchant/aggregation/transaction"; + + LinkedHashMap response = restService.postWithToken(token, url, request, LinkedHashMap.class); + + logger.info("response: {}", response.toString()); + + LinkedHashMap body = (LinkedHashMap) response.get("body"); + + if (body.get("status").equals("SUCCESS")) { + transaction.setStatus(Status.SUCCESS); + transaction.setResponseCode((String) body.get("responseCode")); + transaction.setAdditionalData(body.get("additionalData")); + transactionService.save(transaction); + + AdditionalData additionalData = AdditionalData.builder() + .transaction(transaction) + .jsonString(Utils.toJson(body.get("additionalData"))) + .build(); + 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 = AdditionalData.builder() + .transaction(requestTransaction) + .jsonString(Utils.toJson(body.get("additionalData"))) + .build(); + 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")); + } + + return transactionService.save(transaction); + + } + + @Override + public Object reverse(Transaction transaction) { + return transaction; + } + + @Override + public Object poll(Transaction transaction) { + 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 deleted file mode 100644 index 3c1b3af..0000000 --- a/src/main/java/zw/qantra/tm/domain/services/processors/integrations/ZesaIntegrationProcessor.java +++ /dev/null @@ -1,34 +0,0 @@ -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; - } - - @Override - public Object poll(Transaction transaction) { - return transaction; - } - -} 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 05c2b7c..5592676 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 @@ -14,6 +14,8 @@ import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface; import zw.qantra.tm.rest.RestService; import zw.qantra.tm.utils.Utils; +import java.math.BigDecimal; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -40,10 +42,13 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface { logger.info("processing transaction"); String token = restService.getMerchantToken(); + // send total less gateway charges + BigDecimal totalAmount = transaction.getAmount().add(transaction.getTax()).add(transaction.getCharge()); + String concat = clientId+ encryptionKey+ transaction.getCreditPhone()+ - transaction.getAmount(); + totalAmount; String hex = getHash(concat); @@ -51,7 +56,7 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface { .clientId(clientId) .clientSecret(clientSecret) .phone(transaction.getCreditPhone()) - .amount(transaction.getAmount().toString()) + .amount(totalAmount.toString()) .currency(transaction.getDebitCurrency().toString()) .hash(hex) .authType("WEB") @@ -59,7 +64,7 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface { .action("PAYMENT") .sandbox(true) .paymentProcessorLabel("MPGS") - .responseUrl("https://www.google.com") + .responseUrl("https://stewardbank.co.zw") .build(); HttpHeaders headers = new HttpHeaders(); diff --git a/src/main/java/zw/qantra/tm/utils/Utils.java b/src/main/java/zw/qantra/tm/utils/Utils.java index 312b2c2..d357ebd 100644 --- a/src/main/java/zw/qantra/tm/utils/Utils.java +++ b/src/main/java/zw/qantra/tm/utils/Utils.java @@ -12,6 +12,11 @@ import java.security.NoSuchAlgorithmException; import java.util.UUID; public class Utils { + public static T deepCopy(Object object, Class clazz) { + String json = toJson(object); + return fromJson(json, clazz); + } + public static String hashPassword(String password) { try { // Create a MessageDigest instance for SHA-256 diff --git a/src/main/resources/application-lab.properties b/src/main/resources/application-lab.properties index c8e0ca6..68e5439 100644 --- a/src/main/resources/application-lab.properties +++ b/src/main/resources/application-lab.properties @@ -14,11 +14,15 @@ spring.datasource.url=jdbc:postgresql://localhost:5432/qpay?useLegacyDatetimeCod spring.datasource.username=postgres spring.datasource.password=password -sbz.aggregator.url=http://192.168.16.29:24050/v1 + +sbz.merchant.url=http://192.168.16.29:24050/v1 +# sbz.merchant.url=https://localhost:24000/v1 +# sbz.merchant.url=https://api.stewardpay.co.zw/lab/v2 +sbz.aggregator.url=${sbz.merchant.url} sbz.aggregator.client-id=steward_pay sbz.aggregator.client-secret=XjkbBAJeJCnAplYDTbp5xOjeyySVnYGp sbz.aggregator.phone=263773591219 sbz.aggregator.account=1046737845 sbz.aggregator.encryption-key=735470ce-8efb-4c0f-ba3f-7298f805ab33 -steward.payment.processor.url=http://192.168.16.29:24050/v1 \ No newline at end of file +steward.payment.processor.url=${sbz.merchant.url}