diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dccf9be
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+
+.idea/
diff --git a/docker-compose.yml b/docker-compose.yml
index 6e9a9c5..939cc72 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,6 +7,13 @@ services:
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
+ redis:
+ image: "redis:latest"
+ ports:
+ - "6379:6379"
+ environment:
+ - ALLOW_EMPTY_PASSWORD=yes
+ - REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
zookeeper:
image: confluentinc/cp-zookeeper:5.5.3
environment:
diff --git a/microservices/.gitignore b/microservices/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/microservices/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/microservices/README.md b/microservices/README.md
new file mode 100644
index 0000000..2d761c3
--- /dev/null
+++ b/microservices/README.md
@@ -0,0 +1,37 @@
+## Composición del proyecto
+El presente proyecto esta conformado por tres módulos
+
+```
+1. transaction-gateway
+2. transaction-management
+3. fraud-evaluation
+```
+
+## Detalle de Módulos
+
+* transaction-gateway: Es el orquestador de las funcionalidades hacia el cliente. Utiliza GraphQL
+* transaction-management: Microservicio dedicado para el mantenimiento de las transacciones en la BD.
+* fraud-evaluation: Microservicio dedicado a la evaluación de fraudes de las transacciones registradas.
+
+## Pasos a seguir para compilar la aplicación
+
+1. Ejecutar el comando `docker-compose up -d` en la carpeta raiz del proyecto
+2. Crear base de datos **bd_challenge**
+3. Ejecutar script **01_init_query.sql** de la carpeta _/script_
+4. Compilar cada uno de los módulos listados previamente
+
+
+
+## Detalle de Funcionalidades
+
+El microservicio transaction-gateway expone las siguientes funcionalidades:
+
+1. Query
+ - getAllTransactions(limit, offset)
+ - getTransactionByCode(transactionCode)
+
+
+2. Mutation
+ - createTransaction(transactionBody)
+
+
diff --git a/microservices/fraud-evaluation/.gitignore b/microservices/fraud-evaluation/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/microservices/fraud-evaluation/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/microservices/fraud-evaluation/pom.xml b/microservices/fraud-evaluation/pom.xml
new file mode 100644
index 0000000..bf2243e
--- /dev/null
+++ b/microservices/fraud-evaluation/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+ com.yape
+ microservices
+ 1.0.0-SNAPSHOT
+
+ fraud-evaluation
+ 1.0.0-SNAPSHOT
+ fraud-evaluation
+ Fraud Evaluation Service
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+
+
+
+
\ No newline at end of file
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/FraudEvaluationApplication.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/FraudEvaluationApplication.java
new file mode 100644
index 0000000..f5c1b1d
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/FraudEvaluationApplication.java
@@ -0,0 +1,13 @@
+package com.yape.fraudevaluation;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class FraudEvaluationApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(FraudEvaluationApplication.class, args);
+ }
+
+}
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/config/KafkaConfig.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/config/KafkaConfig.java
new file mode 100644
index 0000000..0847746
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/config/KafkaConfig.java
@@ -0,0 +1,52 @@
+package com.yape.fraudevaluation.config;
+
+import com.yape.fraudevaluation.model.dto.TransactionDTO;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.core.*;
+import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer;
+import org.springframework.kafka.support.serializer.JsonDeserializer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class KafkaConfig {
+
+ @Value("${spring.kafka.bootstrap-servers}")
+ private String serverUrl;
+
+ @Value("${spring.kafka.consumer.group-id}")
+ private String groupId;
+
+ @Value("${spring.kafka.consumer.offset-mode}")
+ private String offsetMode;
+
+ @Bean
+ public ConsumerFactory consumerFactory() {
+ Map configProps = new HashMap<>();
+
+ configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, serverUrl);
+ configProps.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
+ configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
+ configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
+ configProps.put(ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS, StringDeserializer.class);
+ configProps.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, StringDeserializer.class);
+ configProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, offsetMode);
+
+ return new DefaultKafkaConsumerFactory<>(configProps, new ErrorHandlingDeserializer<>(), new ErrorHandlingDeserializer<>(new JsonDeserializer<>(TransactionDTO.class)));
+ }
+
+ @Bean
+ public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() {
+ ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>();
+ factory.setConsumerFactory(consumerFactory());
+
+ return factory;
+ }
+
+}
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/error/ErrorResponse.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/error/ErrorResponse.java
new file mode 100644
index 0000000..11c08ca
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/error/ErrorResponse.java
@@ -0,0 +1,13 @@
+package com.yape.fraudevaluation.error;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class ErrorResponse {
+
+ private Integer errorCode;
+ private String description;
+
+}
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/error/GlobalExceptionHandler.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/error/GlobalExceptionHandler.java
new file mode 100644
index 0000000..dcb1cf0
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/error/GlobalExceptionHandler.java
@@ -0,0 +1,23 @@
+package com.yape.fraudevaluation.error;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+@Slf4j
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(Throwable.class)
+ public ResponseEntity handle(Throwable ex) {
+ log.error("Ocurrió un error en la aplicación {}", ex.toString());
+ ex.printStackTrace();
+ return new ResponseEntity<>(ErrorResponse.builder()
+ .errorCode(HttpStatus.INTERNAL_SERVER_ERROR.value())
+ .description(ex.getMessage())
+ .build(), HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+}
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/consumer/FraudMessageConsumer.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/consumer/FraudMessageConsumer.java
new file mode 100644
index 0000000..1a7c539
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/consumer/FraudMessageConsumer.java
@@ -0,0 +1,9 @@
+package com.yape.fraudevaluation.kafka.consumer;
+
+import com.yape.fraudevaluation.model.dto.TransactionDTO;
+
+public interface FraudMessageConsumer {
+
+ void retrieveMessage(TransactionDTO object);
+
+}
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/consumer/impl/FraudMessageConsumerImpl.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/consumer/impl/FraudMessageConsumerImpl.java
new file mode 100644
index 0000000..045a239
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/consumer/impl/FraudMessageConsumerImpl.java
@@ -0,0 +1,39 @@
+package com.yape.fraudevaluation.kafka.consumer.impl;
+
+import com.yape.fraudevaluation.kafka.consumer.FraudMessageConsumer;
+import com.yape.fraudevaluation.model.dto.TransactionDTO;
+import com.yape.fraudevaluation.service.FraudEvaluationService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class FraudMessageConsumerImpl implements FraudMessageConsumer {
+
+ @Value("${fraud-evaluation.kafka-topics.transaction}")
+ private String transactionTopic;
+
+ private final FraudEvaluationService fraudEvaluationService;
+
+ @KafkaListener(
+ topics = "${fraud-evaluation.kafka-topics.transaction}",
+ groupId = "${spring.kafka.consumer.group-id}"
+ )
+ @Override
+ public void retrieveMessage(TransactionDTO object) {
+ try {
+ log.info("Retreived message from Topic {}. Message {}", transactionTopic, object.toString());
+
+ fraudEvaluationService.evaluateTransaction(object)
+ .subscribe();
+ } catch (Exception e) {
+ log.error("Exception on message send {}", e.toString());
+ throw new RuntimeException("Error occurs message retrieve");
+ }
+ }
+
+}
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/producer/FraudMessageProducer.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/producer/FraudMessageProducer.java
new file mode 100644
index 0000000..721984a
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/producer/FraudMessageProducer.java
@@ -0,0 +1,9 @@
+package com.yape.fraudevaluation.kafka.producer;
+
+import com.yape.fraudevaluation.model.dto.TransactionDTO;
+
+public interface FraudMessageProducer {
+
+ boolean sendMessage(TransactionDTO transactionDTO);
+
+}
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/producer/impl/FraudMessageProducerImpl.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/producer/impl/FraudMessageProducerImpl.java
new file mode 100644
index 0000000..4ba7aa1
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/kafka/producer/impl/FraudMessageProducerImpl.java
@@ -0,0 +1,35 @@
+package com.yape.fraudevaluation.kafka.producer.impl;
+
+import com.yape.fraudevaluation.kafka.producer.FraudMessageProducer;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import com.yape.fraudevaluation.model.dto.TransactionDTO;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class FraudMessageProducerImpl implements FraudMessageProducer {
+
+ private final KafkaTemplate kafkaTemplate;
+
+ @Value("${fraud-evaluation.kafka-topics.fraud-evaluation}")
+ private String fraudTopic;
+
+ @Override
+ public boolean sendMessage(TransactionDTO transactionDTO) {
+ try {
+ log.info("Sending message {} to Topic {}", transactionDTO.toString(), fraudTopic);
+
+ kafkaTemplate.send(fraudTopic, transactionDTO);
+
+ return true;
+ } catch (Exception e) {
+ log.error("Exception on message send {}", e.toString());
+ throw new RuntimeException("Error occurs on message send");
+ }
+ }
+
+}
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/model/constant/TransactionStatusEnum.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/model/constant/TransactionStatusEnum.java
new file mode 100644
index 0000000..6a3305c
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/model/constant/TransactionStatusEnum.java
@@ -0,0 +1,18 @@
+package com.yape.fraudevaluation.model.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum TransactionStatusEnum {
+
+ PENDING(1, "PENDING"),
+ APPROBED(2, "APPROBED"),
+ REJECTED(3, "REJECTED")
+ ;
+
+ private final Integer value;
+ private final String description;
+
+}
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/model/dto/TransactionDTO.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/model/dto/TransactionDTO.java
new file mode 100644
index 0000000..417bb43
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/model/dto/TransactionDTO.java
@@ -0,0 +1,22 @@
+package com.yape.fraudevaluation.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TransactionDTO implements Serializable {
+
+ private Long transactionId;
+ private String transactionCode;
+ private String transactionState;
+ private BigDecimal transactionValue;
+
+}
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/service/FraudEvaluationService.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/service/FraudEvaluationService.java
new file mode 100644
index 0000000..1d9b5ee
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/service/FraudEvaluationService.java
@@ -0,0 +1,10 @@
+package com.yape.fraudevaluation.service;
+
+import com.yape.fraudevaluation.model.dto.TransactionDTO;
+import reactor.core.publisher.Mono;
+
+public interface FraudEvaluationService {
+
+ Mono evaluateTransaction(TransactionDTO transactionDTO);
+
+}
diff --git a/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/service/impl/FraudEvaluationServiceImpl.java b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/service/impl/FraudEvaluationServiceImpl.java
new file mode 100644
index 0000000..8893c1e
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/java/com/yape/fraudevaluation/service/impl/FraudEvaluationServiceImpl.java
@@ -0,0 +1,40 @@
+package com.yape.fraudevaluation.service.impl;
+
+import com.yape.fraudevaluation.kafka.producer.FraudMessageProducer;
+import com.yape.fraudevaluation.model.constant.TransactionStatusEnum;
+import com.yape.fraudevaluation.model.dto.TransactionDTO;
+import com.yape.fraudevaluation.service.FraudEvaluationService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+import java.math.BigDecimal;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class FraudEvaluationServiceImpl implements FraudEvaluationService {
+
+ @Value("${fraud-evaluation.value-limit}")
+ private BigDecimal valueLimit;
+
+ private final FraudMessageProducer fraudMessageProducer;
+
+ @Override
+ public Mono evaluateTransaction(TransactionDTO transactionDTO) {
+ return Mono.just(transactionDTO)
+ .filter(transaction -> TransactionStatusEnum.PENDING.getDescription().equals(transactionDTO.getTransactionState()))
+ .doOnNext(this::evaluateValue)
+ .flatMap(transaction -> Mono.fromCallable(() -> fraudMessageProducer.sendMessage(transaction))
+ .then());
+ }
+
+ private void evaluateValue(TransactionDTO transaction) {
+ transaction.setTransactionState((transaction.getTransactionValue().compareTo(valueLimit) > 0)
+ ? TransactionStatusEnum.REJECTED.getDescription()
+ : TransactionStatusEnum.APPROBED.getDescription());
+ }
+
+}
diff --git a/microservices/fraud-evaluation/src/main/resources/application.yml b/microservices/fraud-evaluation/src/main/resources/application.yml
new file mode 100644
index 0000000..7f8f907
--- /dev/null
+++ b/microservices/fraud-evaluation/src/main/resources/application.yml
@@ -0,0 +1,37 @@
+application:
+ core:
+ api:
+ path: /api/v1.0/fraud-evaluation
+
+server:
+ port: 8094
+ address:
+ compression:
+ mime-types:
+
+spring:
+ kafka:
+ bootstrap-servers: 'localhost:9092'
+ consumer:
+ group-id: yape-group
+ offset-mode: 'earliest'
+ properties:
+ spring:
+ json:
+ trusted:
+ packages: com.yape.transactionmanagement.model.dto
+ producer:
+ properties:
+ spring:
+ json:
+ add:
+ type:
+ headers: false
+ key-serializer: org.springframework.kafka.support.serializer.JsonSerializer
+ value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
+
+fraud-evaluation:
+ kafka-topics:
+ fraud-evaluation: 'topic-fraud'
+ transaction: 'topic-transaction'
+ value-limit: 1000
\ No newline at end of file
diff --git a/microservices/pom.xml b/microservices/pom.xml
new file mode 100644
index 0000000..738a515
--- /dev/null
+++ b/microservices/pom.xml
@@ -0,0 +1,24 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.0
+
+
+
+ com.yape
+ microservices
+ 1.0.0-SNAPSHOT
+ pom
+
+
+ fraud-evaluation
+ transaction-management
+ transaction-gateway
+
+
+
\ No newline at end of file
diff --git a/microservices/transaction-gateway/.gitignore b/microservices/transaction-gateway/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/microservices/transaction-gateway/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/microservices/transaction-gateway/pom.xml b/microservices/transaction-gateway/pom.xml
new file mode 100644
index 0000000..3a508e2
--- /dev/null
+++ b/microservices/transaction-gateway/pom.xml
@@ -0,0 +1,74 @@
+
+
+ 4.0.0
+
+ com.yape
+ microservices
+ 1.0.0-SNAPSHOT
+
+ transaction-gateway
+ 1.0.0-SNAPSHOT
+ transaction-gateway
+ Transaction Gateway Service
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-graphql
+
+
+
+ com.graphql-java
+ graphql-java-extended-scalars
+ 22.0
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/TransactionGatewayApplication.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/TransactionGatewayApplication.java
new file mode 100644
index 0000000..18f07d1
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/TransactionGatewayApplication.java
@@ -0,0 +1,13 @@
+package com.yape.transactiongateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class TransactionGatewayApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(TransactionGatewayApplication.class, args);
+ }
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/config/GraphQLConfiguration.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/config/GraphQLConfiguration.java
new file mode 100644
index 0000000..abbb652
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/config/GraphQLConfiguration.java
@@ -0,0 +1,17 @@
+package com.yape.transactiongateway.config;
+
+import graphql.scalars.ExtendedScalars;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.graphql.execution.RuntimeWiringConfigurer;
+
+@Configuration
+public class GraphQLConfiguration {
+
+ @Bean
+ public RuntimeWiringConfigurer runtimeWiringConfigurer() {
+ return wiringBuilder -> wiringBuilder
+ .scalar(ExtendedScalars.GraphQLBigDecimal);
+ }
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/config/client/TransactionRestClient.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/config/client/TransactionRestClient.java
new file mode 100644
index 0000000..8811c6c
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/config/client/TransactionRestClient.java
@@ -0,0 +1,16 @@
+package com.yape.transactiongateway.config.client;
+
+import com.yape.transactiongateway.model.dto.RegisterTransactionRequest;
+import com.yape.transactiongateway.model.dto.TransactionResponse;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public interface TransactionRestClient {
+
+ Flux getAllTransactions();
+
+ Mono getTransactionByCode(String transactionCode);
+
+ Mono registerTransaction(RegisterTransactionRequest request);
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/config/client/impl/TransactionRestClientImpl.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/config/client/impl/TransactionRestClientImpl.java
new file mode 100644
index 0000000..a9644cc
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/config/client/impl/TransactionRestClientImpl.java
@@ -0,0 +1,89 @@
+package com.yape.transactiongateway.config.client.impl;
+
+import com.yape.transactiongateway.config.client.TransactionRestClient;
+import com.yape.transactiongateway.model.dto.RegisterTransactionRequest;
+import com.yape.transactiongateway.model.dto.TransactionResponse;
+import io.netty.channel.ChannelOption;
+import io.netty.handler.timeout.ReadTimeoutHandler;
+import io.netty.handler.timeout.WriteTimeoutHandler;
+import jakarta.annotation.PostConstruct;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.netty.http.client.HttpClient;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+public class TransactionRestClientImpl implements TransactionRestClient {
+
+ private WebClient restClient;
+
+ @Value("${http-client.transaction-management.base-url}")
+ private String baseUrl;
+
+ @Value("${http-client.transaction-management.read-timeout}")
+ private long readTimeout;
+
+ @Value("${http-client.transaction-management.write-timeout}")
+ private long writeTimeout;
+
+ @PostConstruct
+ public void init() {
+ HttpClient httpClient = HttpClient.create()
+ .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, ((Long) writeTimeout).intValue())
+ .responseTimeout(Duration.ofMillis(readTimeout))
+ .doOnConnected(conn ->
+ conn.addHandlerLast(new ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS))
+ .addHandlerLast(new WriteTimeoutHandler(writeTimeout, TimeUnit.MILLISECONDS)));
+
+ this.restClient = WebClient.builder()
+ .baseUrl(baseUrl)
+ .clientConnector(new ReactorClientHttpConnector(httpClient))
+ .build();
+ }
+
+ @Override
+ public Flux getAllTransactions() {
+ log.info("Begin HTTP Call [GET] transactions");
+ return restClient.get()
+ .uri("/transactions")
+ .retrieve()
+ .bodyToFlux(TransactionResponse.class)
+ .doAfterTerminate(() -> log.info("OK <-- Respuesta de Éxito en la petición HTTP"))
+ .doOnError(err -> log.error("Error <-- Ocurrió un error en la petición HTTP. Msg {}", err.toString()))
+ ;
+ }
+
+ @Override
+ public Mono getTransactionByCode(String transactionCode) {
+ log.info("Begin HTTP Call [GET] transactions/{txCode}");
+ return restClient.get()
+ .uri("/transactions/{transactionCode}", transactionCode)
+ .retrieve()
+ .bodyToMono(TransactionResponse.class)
+ .doOnSuccess(success -> log.info("OK <-- Respuesta de Éxito en la petición HTTP"))
+ .doOnError(err -> log.error("Error <-- Ocurrió un error en la petición HTTP. Msg {}", err.toString()))
+ ;
+ }
+
+ @Override
+ public Mono registerTransaction(RegisterTransactionRequest request) {
+ log.info("Begin HTTP Call [POST] transactions with request {}", request.toString());
+ return restClient.post()
+ .uri("/transactions")
+ .bodyValue(request)
+ .retrieve()
+ .bodyToMono(Void.class)
+ .doOnSuccess(success -> log.info("OK <-- Respuesta de Éxito en la petición HTTP"))
+ .doOnError(err -> log.error("Error <-- Ocurrió un error en la petición HTTP. Msg {}", err.toString()))
+ ;
+ }
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/error/ErrorResponse.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/error/ErrorResponse.java
new file mode 100644
index 0000000..43c4d3f
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/error/ErrorResponse.java
@@ -0,0 +1,13 @@
+package com.yape.transactiongateway.error;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class ErrorResponse {
+
+ private Integer errorCode;
+ private String description;
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/error/GlobalExceptionHandler.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/error/GlobalExceptionHandler.java
new file mode 100644
index 0000000..cc46f9c
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/error/GlobalExceptionHandler.java
@@ -0,0 +1,27 @@
+package com.yape.transactiongateway.error;
+
+import graphql.GraphQLError;
+import graphql.GraphqlErrorBuilder;
+import graphql.schema.DataFetchingEnvironment;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
+import org.springframework.graphql.execution.ErrorType;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+
+@Slf4j
+@ControllerAdvice
+public class GlobalExceptionHandler extends DataFetcherExceptionResolverAdapter {
+
+ @Override
+ protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
+ log.error("Ocurrió un error en la aplicación {}", ex.toString());
+ ex.printStackTrace();
+ return GraphqlErrorBuilder.newError()
+ .errorType(ErrorType.INTERNAL_ERROR)
+ .message(ex.getMessage())
+ .path(env.getExecutionStepInfo().getPath())
+ .location(env.getField().getSourceLocation())
+ .build();
+ }
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/common/TransactionState.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/common/TransactionState.java
new file mode 100644
index 0000000..7014386
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/common/TransactionState.java
@@ -0,0 +1,16 @@
+package com.yape.transactiongateway.model.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TransactionState {
+
+ private String name;
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/common/TransactionType.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/common/TransactionType.java
new file mode 100644
index 0000000..2808053
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/common/TransactionType.java
@@ -0,0 +1,16 @@
+package com.yape.transactiongateway.model.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TransactionType {
+
+ private String name;
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/dto/RegisterTransactionRequest.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/dto/RegisterTransactionRequest.java
new file mode 100644
index 0000000..b6ea96c
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/dto/RegisterTransactionRequest.java
@@ -0,0 +1,19 @@
+package com.yape.transactiongateway.model.dto;
+
+import lombok.*;
+
+import java.math.BigDecimal;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@ToString
+public class RegisterTransactionRequest {
+
+ private String accountExternalIdDebit;
+ private String accountExternalIdCredit;
+ private Integer tranferTypeId;
+ private BigDecimal value;
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/dto/TransactionResponse.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/dto/TransactionResponse.java
new file mode 100644
index 0000000..5d8a6f6
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/model/dto/TransactionResponse.java
@@ -0,0 +1,25 @@
+package com.yape.transactiongateway.model.dto;
+
+import com.yape.transactiongateway.model.common.TransactionState;
+import com.yape.transactiongateway.model.common.TransactionType;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TransactionResponse implements Serializable {
+
+ private String transactionExternalId;
+ private TransactionType transactionType;
+ private TransactionState transactionStatus;
+ private BigDecimal value;
+ private String createdAt;
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/service/TransactionService.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/service/TransactionService.java
new file mode 100644
index 0000000..8e708fa
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/service/TransactionService.java
@@ -0,0 +1,16 @@
+package com.yape.transactiongateway.service;
+
+import com.yape.transactiongateway.model.dto.RegisterTransactionRequest;
+import com.yape.transactiongateway.model.dto.TransactionResponse;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public interface TransactionService {
+
+ Flux getAllTransactions(Integer limit, Integer offset);
+
+ Mono getTransactionByCode(String transactionCode);
+
+ Mono createTransaction(RegisterTransactionRequest transactionBody);
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/service/impl/TransactionServiceImpl.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/service/impl/TransactionServiceImpl.java
new file mode 100644
index 0000000..29b562a
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/service/impl/TransactionServiceImpl.java
@@ -0,0 +1,41 @@
+package com.yape.transactiongateway.service.impl;
+
+import com.yape.transactiongateway.config.client.TransactionRestClient;
+import com.yape.transactiongateway.model.dto.RegisterTransactionRequest;
+import com.yape.transactiongateway.model.dto.TransactionResponse;
+import com.yape.transactiongateway.service.TransactionService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Objects;
+
+@Service
+@RequiredArgsConstructor
+public class TransactionServiceImpl implements TransactionService {
+
+ private final TransactionRestClient transactionRestClient;
+
+ @Override
+ public Flux getAllTransactions(Integer limit, Integer offset) {
+ Flux allTransactions = transactionRestClient.getAllTransactions();
+
+ if (Objects.nonNull(offset)) allTransactions = allTransactions.skip(offset);
+
+ if (Objects.nonNull(limit)) allTransactions = allTransactions.take(limit);
+
+ return allTransactions;
+ }
+
+ @Override
+ public Mono getTransactionByCode(String transactionCode) {
+ return transactionRestClient.getTransactionByCode(transactionCode);
+ }
+
+ @Override
+ public Mono createTransaction(RegisterTransactionRequest transactionBody) {
+ return transactionRestClient.registerTransaction(transactionBody);
+ }
+
+}
diff --git a/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/web/TransactionGraphController.java b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/web/TransactionGraphController.java
new file mode 100644
index 0000000..c08abf8
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/java/com/yape/transactiongateway/web/TransactionGraphController.java
@@ -0,0 +1,40 @@
+package com.yape.transactiongateway.web;
+
+import com.yape.transactiongateway.model.dto.RegisterTransactionRequest;
+import com.yape.transactiongateway.model.dto.TransactionResponse;
+import com.yape.transactiongateway.service.TransactionService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.graphql.data.method.annotation.Argument;
+import org.springframework.graphql.data.method.annotation.MutationMapping;
+import org.springframework.graphql.data.method.annotation.QueryMapping;
+import org.springframework.stereotype.Controller;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@Controller
+@RequiredArgsConstructor
+public class TransactionGraphController {
+
+ private final TransactionService transactionService;
+
+ @QueryMapping
+ public Flux getAllTransactions(@Argument Integer limit, @Argument Integer offset) {
+ log.info("Iniciando consulta de transacciones. Limit: {} Offset {}", limit, offset);
+ return transactionService.getAllTransactions(limit, offset);
+ }
+
+ @QueryMapping
+ public Mono getTransactionByCode(@Argument String transactionCode) {
+ log.info("Iniciando busqueda de la transaccion con codigo {}", transactionCode);
+ return transactionService.getTransactionByCode(transactionCode);
+ }
+
+ @MutationMapping
+ public Mono createTransaction(@Argument RegisterTransactionRequest transactionBody) {
+ log.info("Creando una nueva transaccion");
+ return transactionService.createTransaction(transactionBody);
+ }
+
+}
diff --git a/microservices/transaction-gateway/src/main/resources/application.yml b/microservices/transaction-gateway/src/main/resources/application.yml
new file mode 100644
index 0000000..30e3ef8
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/resources/application.yml
@@ -0,0 +1,14 @@
+spring:
+ graphql:
+ graphiql:
+ enabled: true
+ path: '/graphiql'
+
+server:
+ port: 8095
+
+http-client:
+ transaction-management:
+ base-url: 'http://localhost:8093/api/v1.0/transaction-management'
+ read-timeout: 15000
+ write-timeout: 15000
diff --git a/microservices/transaction-gateway/src/main/resources/graphql/schema.graphqls b/microservices/transaction-gateway/src/main/resources/graphql/schema.graphqls
new file mode 100644
index 0000000..80d02f7
--- /dev/null
+++ b/microservices/transaction-gateway/src/main/resources/graphql/schema.graphqls
@@ -0,0 +1,35 @@
+scalar BigDecimal
+
+type TransactionType {
+ name: String
+}
+
+type TransactionState {
+ name: String
+}
+
+type Transaction {
+ transactionExternalId: String!
+ transactionType: TransactionType!
+ transactionStatus: TransactionState!
+ value: BigDecimal!
+ createdAt: String!
+}
+
+input RegisterTransactionRequest {
+ accountExternalIdDebit: String!
+ accountExternalIdCredit: String!
+ tranferTypeId: Int!
+ value: BigDecimal!
+}
+
+
+
+type Query {
+ getAllTransactions(limit: Int, offset: Int): [Transaction]!
+ getTransactionByCode(transactionCode: String): Transaction
+}
+
+type Mutation {
+ createTransaction(transactionBody: RegisterTransactionRequest): Boolean
+}
\ No newline at end of file
diff --git a/microservices/transaction-management/.gitignore b/microservices/transaction-management/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/microservices/transaction-management/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/microservices/transaction-management/pom.xml b/microservices/transaction-management/pom.xml
new file mode 100644
index 0000000..9f323a3
--- /dev/null
+++ b/microservices/transaction-management/pom.xml
@@ -0,0 +1,96 @@
+
+
+ 4.0.0
+
+ com.yape
+ microservices
+ 1.0.0-SNAPSHOT
+
+ transaction-management
+ 1.0.0-SNAPSHOT
+ transaction-management
+ Transaction Management Service
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ redis.clients
+ jedis
+
+
+
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/TransactionManagementApplication.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/TransactionManagementApplication.java
new file mode 100644
index 0000000..ca7b6a6
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/TransactionManagementApplication.java
@@ -0,0 +1,13 @@
+package com.yape.transactionmanagement;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class TransactionManagementApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(TransactionManagementApplication.class, args);
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/builder/TransactionBuilder.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/builder/TransactionBuilder.java
new file mode 100644
index 0000000..a06eeb1
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/builder/TransactionBuilder.java
@@ -0,0 +1,52 @@
+package com.yape.transactionmanagement.builder;
+
+import com.yape.transactionmanagement.model.common.TransactionState;
+import com.yape.transactionmanagement.model.common.TransactionType;
+import com.yape.transactionmanagement.model.constant.TransactionStatusEnum;
+import com.yape.transactionmanagement.model.constant.TransactionTypeEnum;
+import com.yape.transactionmanagement.model.dto.TransactionDTO;
+import com.yape.transactionmanagement.model.request.RegisterTransactionRequest;
+import com.yape.transactionmanagement.model.response.TransactionResponse;
+import com.yape.transactionmanagement.model.entity.TransactionEntity;
+import com.yape.transactionmanagement.util.Utils;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+public class TransactionBuilder {
+
+ public static TransactionResponse buildTransactionResponse(TransactionEntity entity) {
+ return TransactionResponse.builder()
+ .transactionExternalId(entity.getTransactionCode())
+ .transactionType(TransactionType.builder()
+ .name(entity.getTransactionType()).build())
+ .transactionStatus(TransactionState.builder()
+ .name(entity.getTransactionStatus()).build())
+ .value(entity.getTransactionValue())
+ .createdAt(Utils.formatDateTime(entity.getCreatedAt()))
+ .build();
+ }
+
+ public static TransactionEntity buildTransactionEntity(RegisterTransactionRequest request) {
+ return TransactionEntity.builder()
+ .transactionCode(UUID.randomUUID().toString())
+ .accountExternalIdDebit(request.getAccountExternalIdDebit())
+ .accountExternalIdCredit(request.getAccountExternalIdCredit())
+ .transactionType(TransactionTypeEnum.parse(request.getTranferTypeId()).getDescription())
+ .transactionStatus(TransactionStatusEnum.PENDING.getDescription())
+ .transactionValue(request.getValue())
+ .createdAt(LocalDateTime.now())
+ .build();
+ }
+
+ public static TransactionDTO buildTransactionDTO(TransactionEntity entity) {
+ return TransactionDTO.builder()
+ .transactionId(entity.getTransactionId())
+ .transactionCode(entity.getTransactionCode())
+ .transactionState(entity.getTransactionStatus())
+ .transactionValue(entity.getTransactionValue())
+ .build();
+ }
+
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/config/KafkaConfig.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/config/KafkaConfig.java
new file mode 100644
index 0000000..35c774c
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/config/KafkaConfig.java
@@ -0,0 +1,52 @@
+package com.yape.transactionmanagement.config;
+
+import com.yape.transactionmanagement.model.dto.TransactionDTO;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.core.*;
+import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer;
+import org.springframework.kafka.support.serializer.JsonDeserializer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class KafkaConfig {
+
+ @Value("${spring.kafka.bootstrap-servers}")
+ private String serverUrl;
+
+ @Value("${spring.kafka.consumer.group-id}")
+ private String groupId;
+
+ @Value("${spring.kafka.consumer.offset-mode}")
+ private String offsetMode;
+
+ @Bean
+ public ConsumerFactory consumerFactory() {
+ Map configProps = new HashMap<>();
+
+ configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, serverUrl);
+ configProps.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
+ configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
+ configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
+ configProps.put(ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS, StringDeserializer.class);
+ configProps.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, StringDeserializer.class);
+ configProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, offsetMode);
+
+ return new DefaultKafkaConsumerFactory<>(configProps, new ErrorHandlingDeserializer<>(), new ErrorHandlingDeserializer<>(new JsonDeserializer<>(TransactionDTO.class)));
+ }
+
+ @Bean
+ public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() {
+ ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>();
+ factory.setConsumerFactory(consumerFactory());
+
+ return factory;
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/config/RedisConfig.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/config/RedisConfig.java
new file mode 100644
index 0000000..a7dc864
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/config/RedisConfig.java
@@ -0,0 +1,70 @@
+package com.yape.transactionmanagement.config;
+
+import jakarta.annotation.PostConstruct;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+import redis.clients.jedis.Jedis;
+
+import java.io.Serializable;
+import java.time.Duration;
+
+@Slf4j
+@Configuration
+@AutoConfigureAfter(RedisAutoConfiguration.class)
+@EnableCaching
+public class RedisConfig {
+
+ @Value("${redis.url}")
+ private String redisUrl;
+
+ @Value("${redis.ttl-secconds}")
+ private long ttlSeconds;
+
+ @Bean
+ public RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
+ RedisTemplate template = new RedisTemplate<>();
+ template.setKeySerializer(new StringRedisSerializer());
+ template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
+ template.setConnectionFactory(redisConnectionFactory);
+
+ return template;
+ }
+
+ @Bean
+ public CacheManager cacheManager(RedisConnectionFactory factory) {
+ RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
+
+ RedisCacheConfiguration redisCacheConfiguration = config
+ .serializeKeysWith(
+ RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
+ .serializeValuesWith(RedisSerializationContext.SerializationPair
+ .fromSerializer(new GenericJackson2JsonRedisSerializer()))
+ .entryTtl(Duration.ofSeconds(ttlSeconds));
+
+ return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration)
+ .build();
+ }
+
+ @PostConstruct
+ public void clearCache() {
+ log.info("Cleaning all cache entries. Test Only");
+ Jedis jedis = new Jedis(redisUrl);
+ jedis.flushAll();
+ jedis.close();
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/error/ErrorResponse.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/error/ErrorResponse.java
new file mode 100644
index 0000000..dd29d9f
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/error/ErrorResponse.java
@@ -0,0 +1,13 @@
+package com.yape.transactionmanagement.error;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class ErrorResponse {
+
+ private Integer errorCode;
+ private String description;
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/error/GlobalExceptionHandler.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/error/GlobalExceptionHandler.java
new file mode 100644
index 0000000..a837b67
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/error/GlobalExceptionHandler.java
@@ -0,0 +1,23 @@
+package com.yape.transactionmanagement.error;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+@Slf4j
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(Throwable.class)
+ public ResponseEntity handle(Throwable ex) {
+ log.error("Ocurrió un error en la aplicación {}", ex.toString());
+ ex.printStackTrace();
+ return new ResponseEntity<>(ErrorResponse.builder()
+ .errorCode(HttpStatus.INTERNAL_SERVER_ERROR.value())
+ .description(ex.getMessage())
+ .build(), HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/error/NotFoundException.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/error/NotFoundException.java
new file mode 100644
index 0000000..0d04388
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/error/NotFoundException.java
@@ -0,0 +1,9 @@
+package com.yape.transactionmanagement.error;
+
+public class NotFoundException extends RuntimeException {
+
+ public NotFoundException(String message) {
+ super(message);
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/consumer/TransactionMessageConsumer.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/consumer/TransactionMessageConsumer.java
new file mode 100644
index 0000000..109fe20
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/consumer/TransactionMessageConsumer.java
@@ -0,0 +1,9 @@
+package com.yape.transactionmanagement.kafka.consumer;
+
+import com.yape.transactionmanagement.model.dto.TransactionDTO;
+
+public interface TransactionMessageConsumer {
+
+ void retrieveMessage(TransactionDTO object);
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/consumer/impl/TransactionMessageConsumerImpl.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/consumer/impl/TransactionMessageConsumerImpl.java
new file mode 100644
index 0000000..304aa49
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/consumer/impl/TransactionMessageConsumerImpl.java
@@ -0,0 +1,44 @@
+package com.yape.transactionmanagement.kafka.consumer.impl;
+
+import com.yape.transactionmanagement.kafka.consumer.TransactionMessageConsumer;
+import com.yape.transactionmanagement.model.dto.TransactionDTO;
+import com.yape.transactionmanagement.repository.TransactionRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Caching;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class TransactionMessageConsumerImpl implements TransactionMessageConsumer {
+
+ @Value("${transaction-management.kafka-topics.fraud-evaluation}")
+ private String fraudTopic;
+
+ private final TransactionRepository transactionRepository;
+
+ @Caching(evict = {
+ @CacheEvict(value = "transactions", allEntries = true),
+ @CacheEvict(value = "transaction", key = "#object.transactionId")
+ })
+ @KafkaListener(
+ topics = "${transaction-management.kafka-topics.fraud-evaluation}",
+ groupId = "${spring.kafka.consumer.group-id}"
+ )
+ @Override
+ public void retrieveMessage(TransactionDTO object) {
+ try {
+ log.info("Retreived message from Topic {}. Message {}", fraudTopic, object.toString());
+
+ transactionRepository.updateTransactionState(object.getTransactionId(), object.getTransactionState());
+ } catch (Exception e) {
+ log.error("Exception on message send {}", e.toString());
+ throw new RuntimeException("Error occurs message retrieve");
+ }
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/producer/TransactionMessageProducer.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/producer/TransactionMessageProducer.java
new file mode 100644
index 0000000..1611160
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/producer/TransactionMessageProducer.java
@@ -0,0 +1,9 @@
+package com.yape.transactionmanagement.kafka.producer;
+
+import com.yape.transactionmanagement.model.dto.TransactionDTO;
+
+public interface TransactionMessageProducer {
+
+ boolean sendMessage(TransactionDTO transactionDTO);
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/producer/impl/TransactionMessageProducerImpl.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/producer/impl/TransactionMessageProducerImpl.java
new file mode 100644
index 0000000..0e83cc7
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/kafka/producer/impl/TransactionMessageProducerImpl.java
@@ -0,0 +1,35 @@
+package com.yape.transactionmanagement.kafka.producer.impl;
+
+import com.yape.transactionmanagement.kafka.producer.TransactionMessageProducer;
+import com.yape.transactionmanagement.model.dto.TransactionDTO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class TransactionMessageProducerImpl implements TransactionMessageProducer {
+
+ private final KafkaTemplate kafkaTemplate;
+
+ @Value("${transaction-management.kafka-topics.transaction}")
+ private String transactionTopic;
+
+ @Override
+ public boolean sendMessage(TransactionDTO transactionDTO) {
+ try {
+ log.info("Sending message {} to Topic {}", transactionDTO.toString(), transactionTopic);
+
+ kafkaTemplate.send(transactionTopic, transactionDTO);
+
+ return true;
+ } catch (Exception e) {
+ log.error("Exception on message send {}", e.toString());
+ throw new RuntimeException("Error occurs on message send");
+ }
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/common/TransactionState.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/common/TransactionState.java
new file mode 100644
index 0000000..141d2cb
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/common/TransactionState.java
@@ -0,0 +1,16 @@
+package com.yape.transactionmanagement.model.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TransactionState {
+
+ private String name;
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/common/TransactionType.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/common/TransactionType.java
new file mode 100644
index 0000000..4f5a9ac
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/common/TransactionType.java
@@ -0,0 +1,16 @@
+package com.yape.transactionmanagement.model.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TransactionType {
+
+ private String name;
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/constant/TransactionStatusEnum.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/constant/TransactionStatusEnum.java
new file mode 100644
index 0000000..bd6fb54
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/constant/TransactionStatusEnum.java
@@ -0,0 +1,19 @@
+package com.yape.transactionmanagement.model.constant;
+
+import com.yape.transactionmanagement.error.NotFoundException;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum TransactionStatusEnum {
+
+ PENDING(1, "PENDING"),
+ APPROBED(2, "APPROBED"),
+ REJECTED(3, "REJECTED")
+ ;
+
+ private final Integer value;
+ private final String description;
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/constant/TransactionTypeEnum.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/constant/TransactionTypeEnum.java
new file mode 100644
index 0000000..8beefe5
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/constant/TransactionTypeEnum.java
@@ -0,0 +1,26 @@
+package com.yape.transactionmanagement.model.constant;
+
+import com.yape.transactionmanagement.error.NotFoundException;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum TransactionTypeEnum {
+
+ OWN(1, "OWN"),
+ THIRDPARTY(2, "THIRDPARTY"),
+ INTERBANK(3, "EXTERNAL")
+ ;
+
+ private final Integer value;
+ private final String description;
+
+ public static TransactionTypeEnum parse(Integer value) {
+ for (TransactionTypeEnum state : TransactionTypeEnum.values()) {
+ if (value.equals(state.getValue())) return state;
+ }
+ throw new NotFoundException("Transaction type not found.");
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/dto/TransactionDTO.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/dto/TransactionDTO.java
new file mode 100644
index 0000000..e7f74b3
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/dto/TransactionDTO.java
@@ -0,0 +1,22 @@
+package com.yape.transactionmanagement.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TransactionDTO implements Serializable {
+
+ private Long transactionId;
+ private String transactionCode;
+ private String transactionState;
+ private BigDecimal transactionValue;
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/entity/TransactionEntity.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/entity/TransactionEntity.java
new file mode 100644
index 0000000..22d2242
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/entity/TransactionEntity.java
@@ -0,0 +1,52 @@
+package com.yape.transactionmanagement.model.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Entity(name = "transaction")
+@Table(name = "transaction", schema = "public")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TransactionEntity {
+
+ @Id
+ @Column(name = "id")
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long transactionId;
+
+ @Column(name ="code")
+ private String transactionCode;
+
+ @Column(name ="account_external_id_debit")
+ private String accountExternalIdDebit;
+
+ @Column(name ="account_external_id_credit")
+ private String accountExternalIdCredit;
+
+ @Column(name ="transfer_type")
+ private String transactionType;
+
+ @Column(name ="value")
+ private BigDecimal transactionValue;
+
+ @Column(name ="status")
+ private String transactionStatus;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Column(name = "created_at")
+ private LocalDateTime createdAt;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Column(name = "updated_at")
+ private LocalDateTime updatedAt;
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/request/RegisterTransactionRequest.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/request/RegisterTransactionRequest.java
new file mode 100644
index 0000000..555b208
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/request/RegisterTransactionRequest.java
@@ -0,0 +1,31 @@
+package com.yape.transactionmanagement.model.request;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class RegisterTransactionRequest {
+
+ @NotBlank(message = "El campo accountExternalIdDebit no puede enviarse vacio")
+ private String accountExternalIdDebit;
+
+ @NotBlank(message = "El campo accountExternalIdCredit no puede enviarse vacio")
+ private String accountExternalIdCredit;
+
+ @NotNull (message = "El campo tranferTypeId es obligatorio")
+ private Integer tranferTypeId;
+
+ @NotNull (message = "El campo tranferTypeId es obligatorio")
+ private BigDecimal value;
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/response/TransactionResponse.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/response/TransactionResponse.java
new file mode 100644
index 0000000..f041478
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/model/response/TransactionResponse.java
@@ -0,0 +1,25 @@
+package com.yape.transactionmanagement.model.response;
+
+import com.yape.transactionmanagement.model.common.TransactionState;
+import com.yape.transactionmanagement.model.common.TransactionType;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TransactionResponse implements Serializable {
+
+ private String transactionExternalId;
+ private TransactionType transactionType;
+ private TransactionState transactionStatus;
+ private BigDecimal value;
+ private String createdAt;
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/repository/TransactionRepository.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/repository/TransactionRepository.java
new file mode 100644
index 0000000..90f446c
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/repository/TransactionRepository.java
@@ -0,0 +1,29 @@
+package com.yape.transactionmanagement.repository;
+
+import com.yape.transactionmanagement.model.entity.TransactionEntity;
+import jakarta.transaction.Transactional;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface TransactionRepository extends JpaRepository {
+
+ @Query(value = "SELECT * FROM public.find_all_transactions_sp()", nativeQuery = true)
+ List callGetTransactionsSP();
+
+
+ @Query(value = "SELECT * FROM public.find_transaction_by_code_sp(:txCode)", nativeQuery = true)
+ TransactionEntity callGetTransactionByCode(@Param("txCode") String txCode);
+
+ @Transactional
+ @Modifying
+ @Query(value = "UPDATE public.transaction SET status = :txState, updated_at = CURRENT_TIMESTAMP WHERE id = :txId", nativeQuery = true)
+ void updateTransactionState(@Param("txId") Long transactionId,
+ @Param("txState") String transactionState);
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/repository/connector/TransactionRepositoryConnector.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/repository/connector/TransactionRepositoryConnector.java
new file mode 100644
index 0000000..4dca4c3
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/repository/connector/TransactionRepositoryConnector.java
@@ -0,0 +1,16 @@
+package com.yape.transactionmanagement.repository.connector;
+
+import com.yape.transactionmanagement.model.entity.TransactionEntity;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+public interface TransactionRepositoryConnector {
+
+ Mono> findAllTransactions();
+
+ Mono findTransactionByCode(String transactionCode);
+
+ Mono saveTransaction(TransactionEntity entity);
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/repository/connector/impl/TransactionRepositoryConnectorImpl.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/repository/connector/impl/TransactionRepositoryConnectorImpl.java
new file mode 100644
index 0000000..bde4e40
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/repository/connector/impl/TransactionRepositoryConnectorImpl.java
@@ -0,0 +1,45 @@
+package com.yape.transactionmanagement.repository.connector.impl;
+
+import com.yape.transactionmanagement.model.entity.TransactionEntity;
+import com.yape.transactionmanagement.repository.TransactionRepository;
+import com.yape.transactionmanagement.repository.connector.TransactionRepositoryConnector;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class TransactionRepositoryConnectorImpl implements TransactionRepositoryConnector {
+
+ private final TransactionRepository transactionRepository;
+
+ @Override
+ public Mono> findAllTransactions() {
+ return Mono.fromFuture(CompletableFuture.supplyAsync(transactionRepository::callGetTransactionsSP))
+ .doOnSubscribe(sub -> log.info("Buscando transacciones"))
+ .doOnSuccess(success -> log.info("Busqueda exitosa"))
+ .doOnError(err -> log.info("Ocurrio un error en la busqueda de todas las transacciones"));
+ }
+
+ @Override
+ public Mono findTransactionByCode(String transactionCode) {
+ return Mono.fromFuture(CompletableFuture.supplyAsync(() -> transactionRepository.callGetTransactionByCode(transactionCode)))
+ .doOnSubscribe(sub -> log.info("Buscando transaccion {}", transactionCode))
+ .doOnSuccess(success -> log.info("Busqueda exitosa"))
+ .doOnError(err -> log.info("Ocurrio un error en la busqueda de la transaccion"));
+ }
+
+ @Override
+ public Mono saveTransaction(TransactionEntity entity) {
+ return Mono.fromFuture(CompletableFuture.supplyAsync(() -> transactionRepository.save(entity)))
+ .doOnSubscribe(sub -> log.info("Registrando nueva transaccion"))
+ .doOnSuccess(success -> log.info("Transaccion se registro correctamente"))
+ .doOnError(err -> log.info("Ocurrio un error al intentar registrar la transaccion"));
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/service/TransactionManagementService.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/service/TransactionManagementService.java
new file mode 100644
index 0000000..7476fed
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/service/TransactionManagementService.java
@@ -0,0 +1,16 @@
+package com.yape.transactionmanagement.service;
+
+import com.yape.transactionmanagement.model.request.RegisterTransactionRequest;
+import com.yape.transactionmanagement.model.response.TransactionResponse;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public interface TransactionManagementService {
+
+ Flux getAllTransactions();
+
+ Mono getTransactionByCode(String transactionCode);
+
+ Mono registerTransaction(RegisterTransactionRequest request);
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/service/impl/TransactionManagementServiceImpl.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/service/impl/TransactionManagementServiceImpl.java
new file mode 100644
index 0000000..3382159
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/service/impl/TransactionManagementServiceImpl.java
@@ -0,0 +1,59 @@
+package com.yape.transactionmanagement.service.impl;
+
+import com.yape.transactionmanagement.builder.TransactionBuilder;
+import com.yape.transactionmanagement.kafka.producer.TransactionMessageProducer;
+import com.yape.transactionmanagement.model.constant.TransactionStatusEnum;
+import com.yape.transactionmanagement.model.request.RegisterTransactionRequest;
+import com.yape.transactionmanagement.model.response.TransactionResponse;
+import com.yape.transactionmanagement.repository.connector.TransactionRepositoryConnector;
+import com.yape.transactionmanagement.service.TransactionManagementService;
+import jakarta.transaction.Transactional;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class TransactionManagementServiceImpl implements TransactionManagementService {
+
+ private final TransactionRepositoryConnector transactionRepositoryConnector;
+ private final TransactionMessageProducer transactionMessageProducer;
+
+ @Cacheable(cacheNames = "transactions")
+ @Override
+ public Flux getAllTransactions() {
+ return transactionRepositoryConnector.findAllTransactions()
+ .flatMapMany(Flux::fromIterable)
+ .onBackpressureBuffer()
+ .map(TransactionBuilder::buildTransactionResponse);
+ }
+
+ @Cacheable(cacheNames = "transaction", key = "#txCode", unless = "#result == null")
+ @Override
+ public Mono getTransactionByCode(String txCode) {
+ return transactionRepositoryConnector.findTransactionByCode(txCode)
+ .map(TransactionBuilder::buildTransactionResponse);
+ }
+
+ @CacheEvict(cacheNames = "transactions", allEntries = true)
+ @Transactional
+ @Override
+ public Mono registerTransaction(RegisterTransactionRequest request) {
+ return Mono.fromCallable(() -> TransactionBuilder.buildTransactionEntity(request))
+ .flatMap(entity -> transactionRepositoryConnector.saveTransaction(entity)
+ .flatMap(sEntity -> Mono.fromCallable(() -> transactionMessageProducer.sendMessage(TransactionBuilder.buildTransactionDTO(sEntity)))
+ .onErrorResume(err -> {
+ log.error("Updating state to REJECTED because can't evaluate transaction value. TxCode {}", sEntity.getTransactionCode());
+ sEntity.setTransactionStatus(TransactionStatusEnum.REJECTED.getDescription());
+ return transactionRepositoryConnector.saveTransaction(entity)
+ .thenReturn(Boolean.FALSE);
+ })))
+ .then();
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/util/Constants.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/util/Constants.java
new file mode 100644
index 0000000..43c39c7
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/util/Constants.java
@@ -0,0 +1,7 @@
+package com.yape.transactionmanagement.util;
+
+public class Constants {
+
+ public static String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/util/Utils.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/util/Utils.java
new file mode 100644
index 0000000..4b88c8c
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/util/Utils.java
@@ -0,0 +1,12 @@
+package com.yape.transactionmanagement.util;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class Utils {
+
+ public static String formatDateTime(LocalDateTime dateTime) {
+ return dateTime.format(DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT));
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/web/TransactionManagementController.java b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/web/TransactionManagementController.java
new file mode 100644
index 0000000..1a4d063
--- /dev/null
+++ b/microservices/transaction-management/src/main/java/com/yape/transactionmanagement/web/TransactionManagementController.java
@@ -0,0 +1,43 @@
+package com.yape.transactionmanagement.web;
+
+import com.yape.transactionmanagement.model.request.RegisterTransactionRequest;
+import com.yape.transactionmanagement.model.response.TransactionResponse;
+import com.yape.transactionmanagement.service.TransactionManagementService;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@RestController
+@RequestMapping("${application.core.api.path}/transactions")
+@RequiredArgsConstructor
+public class TransactionManagementController {
+
+ private final TransactionManagementService transactionManagementService;
+
+ @GetMapping(value = "", produces = MediaType.APPLICATION_NDJSON_VALUE)
+ public Flux getAllTransactions() {
+ return transactionManagementService.getAllTransactions()
+ .doOnSubscribe(sub -> log.info("Iniciando busqueda todas las transacciones"))
+ .doAfterTerminate(() -> log.info("Exito en la busqueda de todas las transacciones."));
+ }
+
+ @GetMapping(value = "/{transactionCode}")
+ public Mono getTransactionByCode(@PathVariable("transactionCode") String transactionCode) {
+ return transactionManagementService.getTransactionByCode(transactionCode)
+ .doOnSubscribe(sub -> log.info("Iniciando busqueda de transaccion por codigo {}", transactionCode))
+ .doOnSuccess(response -> log.info("Exito en la busqueda de la transaccion."));
+ }
+
+ @PostMapping(value = "", consumes = MediaType.APPLICATION_JSON_VALUE)
+ public Mono registerTransaction(@Valid @RequestBody RegisterTransactionRequest request) {
+ return transactionManagementService.registerTransaction(request)
+ .doOnSubscribe(sub -> log.info("Iniciando registro de nueva transaccion"))
+ .doOnSuccess(response -> log.info("Exito en el registro de la transaccion."));
+ }
+
+}
diff --git a/microservices/transaction-management/src/main/resources/application.yml b/microservices/transaction-management/src/main/resources/application.yml
new file mode 100644
index 0000000..9fe8130
--- /dev/null
+++ b/microservices/transaction-management/src/main/resources/application.yml
@@ -0,0 +1,46 @@
+application:
+ core:
+ api:
+ path: /api/v1.0/transaction-management
+
+server:
+ port: 8093
+
+spring:
+ datasource:
+ url: jdbc:postgresql://localhost:5432/bd_challenge
+ username: postgres
+ password: postgres
+ driver-class-name: org.postgresql.Driver
+ jpa:
+ hibernate:
+ ddl-auto: none
+ kafka:
+ bootstrap-servers: 'localhost:9092'
+ consumer:
+ group-id: yape-group
+ offset-mode: 'earliest'
+ properties:
+ spring:
+ json:
+ trusted:
+ packages: com.yape.fraudevaluation.model.dto
+ producer:
+ properties:
+ spring:
+ json:
+ add:
+ type:
+ headers: false
+ key-serializer: org.springframework.kafka.support.serializer.JsonSerializer
+ value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
+
+redis:
+ url: 'http://localhost:6379'
+ ttl-secconds: 60
+
+transaction-management:
+ kafka-topics:
+ fraud-evaluation: 'topic-fraud'
+ transaction: 'topic-transaction'
+
diff --git a/script/01_init_query.sql b/script/01_init_query.sql
new file mode 100644
index 0000000..06aa3e4
--- /dev/null
+++ b/script/01_init_query.sql
@@ -0,0 +1,30 @@
+CREATE TABLE public.transaction (
+ "id" BIGSERIAL PRIMARY KEY,
+ "code" VARCHAR(100) NOT NULL,
+ "account_external_id_debit" VARCHAR(100) NOT NULL,
+ "account_external_id_credit" VARCHAR(100) NOT NULL,
+ "transfer_type" VARCHAR(50) NOT NULL,
+ "value" DECIMAL NOT NULL,
+ "status" VARCHAR(50) NOT NULL,
+ "created_at" TIMESTAMP,
+ "updated_at" TIMESTAMP,
+ CONSTRAINT "uq_code" UNIQUE ("code")
+);
+
+
+
+CREATE OR REPLACE FUNCTION public.find_all_transactions_sp() RETURNS SETOF transaction AS $$
+BEGIN
+RETURN QUERY SELECT * FROM public.transaction;
+END;
+$$ LANGUAGE plpgsql;
+
+
+CREATE OR REPLACE FUNCTION public.find_transaction_by_code_sp(transaction_code VARCHAR) RETURNS transaction AS $$
+DECLARE
+tx_result transaction%ROWTYPE;
+BEGIN
+SELECT * INTO tx_result FROM public.transaction WHERE "code" = transaction_code LIMIT 1;
+RETURN tx_result;
+END;
+$$ LANGUAGE plpgsql;
\ No newline at end of file