diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000000..a0ccf77bc5
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Environment-dependent path to Maven home directory
+/mavenHomeManager.xml
diff --git a/.idea/app-nodejs-codechallenge.iml b/.idea/app-nodejs-codechallenge.iml
new file mode 100644
index 0000000000..d6ebd48059
--- /dev/null
+++ b/.idea/app-nodejs-codechallenge.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000000..afd4c0645f
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000000..ec0bcf22a0
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000000..b75ee77dfd
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000000..65867f62de
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000000..35eb1ddfbb
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/anti-fraud-service/pom.xml b/anti-fraud-service/pom.xml
new file mode 100644
index 0000000000..c540ad8f7e
--- /dev/null
+++ b/anti-fraud-service/pom.xml
@@ -0,0 +1,83 @@
+
+ 4.0.0
+ com.yape
+ anti-fraud-service
+ 1.0.0
+
+
+ 21
+ 3.2.1
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.version}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ test
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 21
+ 21
+
+
+
+
+
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/AntiFraudApplication.java b/anti-fraud-service/src/main/java/com/yape/antifraud/AntiFraudApplication.java
new file mode 100644
index 0000000000..b79fb35c2f
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/AntiFraudApplication.java
@@ -0,0 +1,11 @@
+package com.yape.antifraud;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class AntiFraudApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(AntiFraudApplication.class, args);
+ }
+}
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/FraudDecisionService.java b/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/FraudDecisionService.java
new file mode 100644
index 0000000000..9873cd95ce
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/FraudDecisionService.java
@@ -0,0 +1,8 @@
+package com.yape.antifraud.aplication;
+
+import com.yape.antifraud.kafka.models.TransactionReqEvent;
+
+public interface FraudDecisionService {
+
+ void analyze(TransactionReqEvent transaction);
+}
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/ProducerService.java b/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/ProducerService.java
new file mode 100644
index 0000000000..92d2a65c2d
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/ProducerService.java
@@ -0,0 +1,8 @@
+package com.yape.antifraud.aplication;
+
+import com.yape.antifraud.kafka.models.TransactionRespEvent;
+
+public interface ProducerService {
+
+ void sendMessage(TransactionRespEvent transaction);
+}
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/impl/FraudDetectionService.java b/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/impl/FraudDetectionService.java
new file mode 100644
index 0000000000..df00fbe395
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/impl/FraudDetectionService.java
@@ -0,0 +1,35 @@
+package com.yape.antifraud.aplication.impl;
+
+import com.yape.antifraud.kafka.models.TransactionRespEvent;
+import com.yape.antifraud.model.TransactionStatus;
+import com.yape.antifraud.kafka.models.TransactionReqEvent;
+import com.yape.antifraud.aplication.FraudDecisionService;
+import com.yape.antifraud.aplication.ProducerService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FraudDetectionService implements FraudDecisionService {
+
+ private final FraudPolicy fraudPolicy;
+ private final ProducerService producer;
+
+ public FraudDetectionService(FraudPolicy fraudPolicy, ProducerService producer){
+ this.fraudPolicy = fraudPolicy;
+ this.producer = producer;
+ }
+
+ @Override
+ public void analyze(TransactionReqEvent transaction) {
+
+ var isFraud = fraudPolicy.isFraud(transaction.getAmount());
+ var statusTrx = isFraud? TransactionStatus.REJECTED : TransactionStatus.APPROVED;
+ transaction.setStatus(statusTrx);
+
+ var updateTransaction = getUpdateEvent(transaction);
+ producer.sendMessage(updateTransaction);
+ }
+
+ private TransactionRespEvent getUpdateEvent(TransactionReqEvent transaction){
+ return new TransactionRespEvent(transaction.getId(), transaction.getStatus());
+ }
+}
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/impl/FraudPolicy.java b/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/impl/FraudPolicy.java
new file mode 100644
index 0000000000..19831862f3
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/aplication/impl/FraudPolicy.java
@@ -0,0 +1,12 @@
+package com.yape.antifraud.aplication.impl;
+
+import org.springframework.stereotype.Component;
+import static com.yape.antifraud.utils.Constants.MAX_ALLOWED_VALUE;
+
+@Component
+public class FraudPolicy {
+
+ public boolean isFraud(double amount) {
+ return amount > MAX_ALLOWED_VALUE;
+ }
+}
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/config/KafkaConfig.java b/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/config/KafkaConfig.java
new file mode 100644
index 0000000000..1b0e188861
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/config/KafkaConfig.java
@@ -0,0 +1,65 @@
+package com.yape.antifraud.kafka.config;
+
+import java.util.HashMap;
+import java.util.Map;
+import com.yape.antifraud.kafka.models.TransactionReqEvent;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.clients.producer.ProducerConfig;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.annotation.EnableKafka;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.core.*;
+import org.springframework.kafka.support.serializer.JsonDeserializer;
+import org.springframework.kafka.support.serializer.JsonSerializer;
+
+import static com.yape.antifraud.utils.Constants.TOPIC_SERVICE_GROUP;
+
+
+@EnableKafka
+@Configuration
+public class KafkaConfig {
+
+ @Value("${spring.kafka.bootstrap-servers}")
+ private String bootstrapServers;
+
+ @Bean
+ public ProducerFactory producerFactory() {
+ Map props = new HashMap<>();
+ props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
+ props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
+ props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
+ props.put(JsonSerializer.ADD_TYPE_INFO_HEADERS, false);
+ return new DefaultKafkaProducerFactory<>(props);
+ }
+
+ @Bean
+ public KafkaTemplate kafkaTemplate() {
+ return new KafkaTemplate<>(producerFactory());
+ }
+
+ @Bean
+ public ConsumerFactory consumerFactory() {
+
+ JsonDeserializer deserializer = new JsonDeserializer<>(TransactionReqEvent.class);
+ deserializer.addTrustedPackages("*");
+ deserializer.setRemoveTypeHeaders(true);
+
+ Map props = new HashMap<>();
+ props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
+ props.put(ConsumerConfig.GROUP_ID_CONFIG, TOPIC_SERVICE_GROUP);//"anti-fraud-group"
+ props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
+ return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), deserializer);
+ }
+
+ @Bean
+ public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() {
+
+ ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>();
+ factory.setConsumerFactory(consumerFactory());
+ return factory;
+ }
+}
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/event/TransactionConsumer.java b/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/event/TransactionConsumer.java
new file mode 100644
index 0000000000..f0894b196e
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/event/TransactionConsumer.java
@@ -0,0 +1,31 @@
+package com.yape.antifraud.kafka.event;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.yape.antifraud.model.TransactionStatus;
+import com.yape.antifraud.aplication.impl.FraudDetectionService;
+import com.yape.antifraud.aplication.FraudDecisionService;
+import com.yape.antifraud.kafka.models.TransactionReqEvent;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.stereotype.Component;
+
+import static com.yape.antifraud.utils.Constants.TOPIC_CREATED;
+import static com.yape.antifraud.utils.Constants.TOPIC_SERVICE_GROUP;
+
+@Component
+public class TransactionConsumer {
+
+ private static final Logger log = LoggerFactory.getLogger(TransactionConsumer.class);
+ private final FraudDecisionService fraudService;
+
+ public TransactionConsumer(FraudDetectionService fraudService) {
+ this.fraudService = fraudService;
+ }
+
+ @KafkaListener(topics = TOPIC_CREATED, groupId = TOPIC_SERVICE_GROUP)
+ public void consume(TransactionReqEvent transaction) {
+
+ fraudService.analyze(transaction);
+ log.info("📦 [ANTI-FRAUD-SERVICE LISTENER] {} -> {} {}", transaction.getId(), transaction.getStatus(), transaction.getStatus() == TransactionStatus.APPROVED ? "✅" : transaction.getStatus() == TransactionStatus.REJECTED ? "❌" : "⏳");
+ }
+}
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/event/TransactionProducer.java b/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/event/TransactionProducer.java
new file mode 100644
index 0000000000..7c4d72a7c7
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/event/TransactionProducer.java
@@ -0,0 +1,29 @@
+package com.yape.antifraud.kafka.event;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.yape.antifraud.aplication.ProducerService;
+import com.yape.antifraud.kafka.models.TransactionRespEvent;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.stereotype.Component;
+
+import static com.yape.antifraud.utils.Constants.TOPIC_UPDATE;
+
+
+@Component
+public class TransactionProducer implements ProducerService {
+
+ private static final Logger log = LoggerFactory.getLogger(TransactionProducer.class);
+ private final KafkaTemplate kafkaTemplate;
+
+ public TransactionProducer(KafkaTemplate kafkaTemplate) {
+ this.kafkaTemplate = kafkaTemplate;
+ }
+
+ @Override
+ public void sendMessage(TransactionRespEvent transaction) {
+
+ kafkaTemplate.send(TOPIC_UPDATE, transaction);
+ log.info("📦 [ANTI-FRAUD-SERVICE PRODUCER] {} -> {}", transaction.id(), transaction.status());
+ }
+}
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/models/TransactionReqEvent.java b/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/models/TransactionReqEvent.java
new file mode 100644
index 0000000000..c92b4d6e2b
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/models/TransactionReqEvent.java
@@ -0,0 +1,42 @@
+package com.yape.antifraud.kafka.models;
+
+import java.util.UUID;
+import com.yape.antifraud.model.TransactionStatus;
+
+public class TransactionReqEvent {
+ private UUID id;
+ private double amount;
+ private TransactionStatus status;
+
+ public TransactionReqEvent() {}
+
+ public TransactionReqEvent(UUID id, double amount, TransactionStatus status) {
+ this.id = id;
+ this.amount = amount;
+ this.status = status;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+
+ public void setId(UUID id) {
+ this.id = id;
+ }
+
+ public double getAmount() {
+ return amount;
+ }
+
+ public void setAmount(double amount) {
+ this.amount = amount;
+ }
+
+ public TransactionStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(TransactionStatus status) {
+ this.status = status;
+ }
+}
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/models/TransactionRespEvent.java b/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/models/TransactionRespEvent.java
new file mode 100644
index 0000000000..fe9a78fbce
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/kafka/models/TransactionRespEvent.java
@@ -0,0 +1,10 @@
+package com.yape.antifraud.kafka.models;
+
+import com.yape.antifraud.model.TransactionStatus;
+
+import java.util.UUID;
+
+public record TransactionRespEvent(
+ UUID id,
+ TransactionStatus status
+) {}
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/model/TransactionStatus.java b/anti-fraud-service/src/main/java/com/yape/antifraud/model/TransactionStatus.java
new file mode 100644
index 0000000000..38617024f3
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/model/TransactionStatus.java
@@ -0,0 +1,7 @@
+package com.yape.antifraud.model;
+
+public enum TransactionStatus {
+ APPROVED,
+ PENDING,
+ REJECTED
+}
diff --git a/anti-fraud-service/src/main/java/com/yape/antifraud/utils/Constants.java b/anti-fraud-service/src/main/java/com/yape/antifraud/utils/Constants.java
new file mode 100644
index 0000000000..8e2c6c7689
--- /dev/null
+++ b/anti-fraud-service/src/main/java/com/yape/antifraud/utils/Constants.java
@@ -0,0 +1,12 @@
+package com.yape.antifraud.utils;
+
+public class Constants {
+ private Constants() {}
+
+ public static final double MAX_ALLOWED_VALUE = 1000;
+
+ public static final String TOPIC_UPDATE = "transaction-updated";
+ public static final String TOPIC_CREATED = "transaction-created";
+
+ public static final String TOPIC_SERVICE_GROUP = "transaction-service-group";
+}
diff --git a/anti-fraud-service/src/main/resources/application.yaml b/anti-fraud-service/src/main/resources/application.yaml
new file mode 100644
index 0000000000..18c9ae968f
--- /dev/null
+++ b/anti-fraud-service/src/main/resources/application.yaml
@@ -0,0 +1,18 @@
+spring:
+ application:
+ name: anti-fraud-service
+ kafka:
+ bootstrap-servers: ${KAFKA_SERVER:localhost:9092}
+ producer:
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
+ value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
+ acks: all
+ consumer:
+ group-id: transaction-service-group
+ key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
+ value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
+ properties:
+ spring.json.trusted.packages: "*"
+
+server:
+ port: 8085
\ No newline at end of file
diff --git a/anti-fraud-service/src/test/java/com/yape/antifraud/FraudDetectionServiceTest.java b/anti-fraud-service/src/test/java/com/yape/antifraud/FraudDetectionServiceTest.java
new file mode 100644
index 0000000000..c9063afcfe
--- /dev/null
+++ b/anti-fraud-service/src/test/java/com/yape/antifraud/FraudDetectionServiceTest.java
@@ -0,0 +1,63 @@
+package com.yape.antifraud;
+
+import java.util.UUID;
+import com.yape.antifraud.aplication.ProducerService;
+import com.yape.antifraud.aplication.impl.FraudDetectionService;
+import com.yape.antifraud.aplication.impl.FraudPolicy;
+import com.yape.antifraud.kafka.models.TransactionReqEvent;
+import com.yape.antifraud.kafka.models.TransactionRespEvent;
+import com.yape.antifraud.model.TransactionStatus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static com.yape.antifraud.utils.Constants.MAX_ALLOWED_VALUE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class FraudDetectionServiceTest {
+
+ @Mock
+ private ProducerService producerService;
+
+ private FraudDetectionService service;
+
+ @BeforeEach
+ void setup() {
+ FraudPolicy fraudPolicy = new FraudPolicy(); // REAL
+ service = new FraudDetectionService(fraudPolicy, producerService);
+ }
+
+ @Test
+ void shouldTransactionNotFraud() {
+ TransactionReqEvent request = new TransactionReqEvent(UUID.randomUUID(), MAX_ALLOWED_VALUE - 10, null);
+
+ service.analyze(request);
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(TransactionRespEvent.class);
+
+ verify(producerService).sendMessage(captor.capture());
+
+ assertThat(request.getStatus()).isEqualTo(TransactionStatus.APPROVED);
+ assertThat(captor.getValue().status()).isEqualTo(TransactionStatus.APPROVED);
+ }
+
+ @Test
+ void shouldTransactionIsFraud() {
+ TransactionReqEvent request = new TransactionReqEvent(UUID.randomUUID(), MAX_ALLOWED_VALUE + 1, null);
+
+ service.analyze(request);
+
+ ArgumentCaptor captor =
+ ArgumentCaptor.forClass(TransactionRespEvent.class);
+
+ verify(producerService).sendMessage(captor.capture());
+
+ assertThat(request.getStatus()).isEqualTo(TransactionStatus.REJECTED);
+ assertThat(captor.getValue().status()).isEqualTo(TransactionStatus.REJECTED);
+ }
+}
diff --git a/anti-fraud-service/target/classes/application.yaml b/anti-fraud-service/target/classes/application.yaml
new file mode 100644
index 0000000000..18c9ae968f
--- /dev/null
+++ b/anti-fraud-service/target/classes/application.yaml
@@ -0,0 +1,18 @@
+spring:
+ application:
+ name: anti-fraud-service
+ kafka:
+ bootstrap-servers: ${KAFKA_SERVER:localhost:9092}
+ producer:
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
+ value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
+ acks: all
+ consumer:
+ group-id: transaction-service-group
+ key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
+ value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
+ properties:
+ spring.json.trusted.packages: "*"
+
+server:
+ port: 8085
\ No newline at end of file
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/AntiFraudApplication.class b/anti-fraud-service/target/classes/com/yape/antifraud/AntiFraudApplication.class
new file mode 100644
index 0000000000..b62861fbbe
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/AntiFraudApplication.class differ
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/aplication/FraudDecisionService.class b/anti-fraud-service/target/classes/com/yape/antifraud/aplication/FraudDecisionService.class
new file mode 100644
index 0000000000..71cede18cf
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/aplication/FraudDecisionService.class differ
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/aplication/ProducerService.class b/anti-fraud-service/target/classes/com/yape/antifraud/aplication/ProducerService.class
new file mode 100644
index 0000000000..e68d60bf32
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/aplication/ProducerService.class differ
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/aplication/impl/FraudDetectionService.class b/anti-fraud-service/target/classes/com/yape/antifraud/aplication/impl/FraudDetectionService.class
new file mode 100644
index 0000000000..3e9ff8b2e7
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/aplication/impl/FraudDetectionService.class differ
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/aplication/impl/FraudPolicy.class b/anti-fraud-service/target/classes/com/yape/antifraud/aplication/impl/FraudPolicy.class
new file mode 100644
index 0000000000..c00e0ff5ed
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/aplication/impl/FraudPolicy.class differ
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/kafka/config/KafkaConfig.class b/anti-fraud-service/target/classes/com/yape/antifraud/kafka/config/KafkaConfig.class
new file mode 100644
index 0000000000..fbfc6cd769
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/kafka/config/KafkaConfig.class differ
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/kafka/event/TransactionConsumer.class b/anti-fraud-service/target/classes/com/yape/antifraud/kafka/event/TransactionConsumer.class
new file mode 100644
index 0000000000..eff730575f
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/kafka/event/TransactionConsumer.class differ
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/kafka/event/TransactionProducer.class b/anti-fraud-service/target/classes/com/yape/antifraud/kafka/event/TransactionProducer.class
new file mode 100644
index 0000000000..81e45698e9
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/kafka/event/TransactionProducer.class differ
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/kafka/models/TransactionReqEvent.class b/anti-fraud-service/target/classes/com/yape/antifraud/kafka/models/TransactionReqEvent.class
new file mode 100644
index 0000000000..708af422bd
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/kafka/models/TransactionReqEvent.class differ
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/kafka/models/TransactionRespEvent.class b/anti-fraud-service/target/classes/com/yape/antifraud/kafka/models/TransactionRespEvent.class
new file mode 100644
index 0000000000..a31a2f12ba
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/kafka/models/TransactionRespEvent.class differ
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/model/TransactionStatus.class b/anti-fraud-service/target/classes/com/yape/antifraud/model/TransactionStatus.class
new file mode 100644
index 0000000000..27a44d31e1
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/model/TransactionStatus.class differ
diff --git a/anti-fraud-service/target/classes/com/yape/antifraud/utils/Constants.class b/anti-fraud-service/target/classes/com/yape/antifraud/utils/Constants.class
new file mode 100644
index 0000000000..d4a515921c
Binary files /dev/null and b/anti-fraud-service/target/classes/com/yape/antifraud/utils/Constants.class differ
diff --git a/anti-fraud-service/target/test-classes/com/yape/antifraud/FraudDetectionServiceTest.class b/anti-fraud-service/target/test-classes/com/yape/antifraud/FraudDetectionServiceTest.class
new file mode 100644
index 0000000000..5ab2a71a48
Binary files /dev/null and b/anti-fraud-service/target/test-classes/com/yape/antifraud/FraudDetectionServiceTest.class differ
diff --git a/transaction-service/pom.xml b/transaction-service/pom.xml
new file mode 100644
index 0000000000..413fc8f2a4
--- /dev/null
+++ b/transaction-service/pom.xml
@@ -0,0 +1,88 @@
+
+ 4.0.0
+ com.yape
+ transaction-service
+ 1.0.0
+
+
+ 21
+ 3.2.1
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.version}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.projectlombok
+ lombok
+ 1.18.36
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 21
+ 21
+
+
+
+
+
diff --git a/transaction-service/src/main/java/com/yape/transaction/TransactionApplication.java b/transaction-service/src/main/java/com/yape/transaction/TransactionApplication.java
new file mode 100644
index 0000000000..2fda944151
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/TransactionApplication.java
@@ -0,0 +1,12 @@
+
+package com.yape.transaction;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class TransactionApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(TransactionApplication.class, args);
+ }
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/application/port/in/TransactionUseCase.java b/transaction-service/src/main/java/com/yape/transaction/application/port/in/TransactionUseCase.java
new file mode 100644
index 0000000000..988beefc89
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/application/port/in/TransactionUseCase.java
@@ -0,0 +1,12 @@
+package com.yape.transaction.application.port.in;
+
+import java.util.List;
+import java.util.UUID;
+import com.yape.transaction.domain.model.Transaction;
+import com.yape.transaction.infrastructure.rest.dto.TransactionRequest;
+
+public interface TransactionUseCase {
+ Transaction create(TransactionRequest request);
+ Transaction getById(UUID transactionId);
+ List getAllTransactions();
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/application/port/in/UpdateTransactionUseCase.java b/transaction-service/src/main/java/com/yape/transaction/application/port/in/UpdateTransactionUseCase.java
new file mode 100644
index 0000000000..f9f5eed660
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/application/port/in/UpdateTransactionUseCase.java
@@ -0,0 +1,8 @@
+package com.yape.transaction.application.port.in;
+
+import java.util.UUID;
+import com.yape.transaction.domain.model.TransactionStatus;
+
+public interface UpdateTransactionUseCase {
+ void updateStatus(UUID transactionId, TransactionStatus status);
+}
\ No newline at end of file
diff --git a/transaction-service/src/main/java/com/yape/transaction/application/port/out/TransactionEventPublisher.java b/transaction-service/src/main/java/com/yape/transaction/application/port/out/TransactionEventPublisher.java
new file mode 100644
index 0000000000..574ecbb1d8
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/application/port/out/TransactionEventPublisher.java
@@ -0,0 +1,7 @@
+package com.yape.transaction.application.port.out;
+
+import com.yape.transaction.domain.model.Transaction;
+
+public interface TransactionEventPublisher {
+ void publish(Transaction transaction);
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/application/service/TransactionService.java b/transaction-service/src/main/java/com/yape/transaction/application/service/TransactionService.java
new file mode 100644
index 0000000000..0c779fc3f0
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/application/service/TransactionService.java
@@ -0,0 +1,46 @@
+package com.yape.transaction.application.service;
+
+import java.util.List;
+import java.util.UUID;
+import com.yape.transaction.domain.model.Transaction;
+import com.yape.transaction.application.port.in.TransactionUseCase;
+import com.yape.transaction.application.port.out.TransactionEventPublisher;
+import com.yape.transaction.domain.port.out.TransactionRepository;
+import com.yape.transaction.infrastructure.rest.dto.TransactionRequest;
+import org.springframework.stereotype.Service;
+
+@Service
+public class TransactionService implements TransactionUseCase {
+
+ private final TransactionRepository repositoryAdapter;
+ private final TransactionEventPublisher publisher;
+
+ public TransactionService(TransactionRepository repository, TransactionEventPublisher publisher) {
+ this.repositoryAdapter = repository;
+ this.publisher = publisher;
+ }
+
+ @Override
+ public Transaction create(TransactionRequest request) {
+ Transaction tx = new Transaction(UUID.randomUUID(), request.getAmount());
+
+ repositoryAdapter.save(tx);
+ publisher.publish(tx);
+ return tx;
+ }
+
+ @Override
+ public Transaction getById(UUID transactionId) {
+ return repositoryAdapter.findById(transactionId)
+ .orElseThrow(() -> new IllegalStateException("Transaction not found"));
+ }
+
+ @Override
+ public List getAllTransactions() {
+
+ var listTransaction = repositoryAdapter.findAll();
+ if(listTransaction.isEmpty())
+ throw new IllegalStateException("Transactions not found");
+ return listTransaction;
+ }
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/application/service/UpdateTransactionService.java b/transaction-service/src/main/java/com/yape/transaction/application/service/UpdateTransactionService.java
new file mode 100644
index 0000000000..b4ba0ac679
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/application/service/UpdateTransactionService.java
@@ -0,0 +1,26 @@
+package com.yape.transaction.application.service;
+
+import java.util.UUID;
+import org.springframework.stereotype.Service;
+import com.yape.transaction.application.port.in.UpdateTransactionUseCase;
+import com.yape.transaction.domain.model.TransactionStatus;
+import com.yape.transaction.domain.port.out.TransactionRepository;
+
+@Service
+public class UpdateTransactionService implements UpdateTransactionUseCase {
+
+ private final TransactionRepository repositoryApater;
+ public UpdateTransactionService(TransactionRepository repository) {
+ this.repositoryApater = repository;
+ }
+
+ @Override
+ public void updateStatus(UUID transactionId, TransactionStatus status) {
+
+ var transaction = repositoryApater.findById(transactionId)
+ .orElseThrow(() -> new IllegalStateException("Transaction not found"));
+
+ transaction.setStatus(status);
+ repositoryApater.save(transaction);
+ }
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/application/utils/Constants.java b/transaction-service/src/main/java/com/yape/transaction/application/utils/Constants.java
new file mode 100644
index 0000000000..cff9b0ea1d
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/application/utils/Constants.java
@@ -0,0 +1,10 @@
+package com.yape.transaction.application.utils;
+
+public class Constants {
+ private Constants() {}
+
+ public static final String TOPIC_UPDATE = "transaction-updated";
+ public static final String TOPIC_CREATED = "transaction-created";
+
+ public static final String TOPIC_SERVICE_GROUP = "transaction-service-group";
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/domain/model/Transaction.java b/transaction-service/src/main/java/com/yape/transaction/domain/model/Transaction.java
new file mode 100644
index 0000000000..1c5a278bcd
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/domain/model/Transaction.java
@@ -0,0 +1,32 @@
+package com.yape.transaction.domain.model;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import java.time.Instant;
+import java.util.UUID;
+
+@Setter
+@Getter
+@RequiredArgsConstructor
+public class Transaction {
+
+ private final UUID id;
+ private final double amount;
+ private TransactionStatus status;
+ private final Instant createdAt;
+
+ public Transaction(UUID id, double amount) {
+ this.id = id;
+ this.amount = amount;
+ this.status = TransactionStatus.PENDING;
+ this.createdAt = Instant.now();
+ }
+
+ public Transaction(UUID id, double amount, TransactionStatus status, Instant createdAt) {
+ this.id = id;
+ this.amount = amount;
+ this.status = status;
+ this.createdAt = createdAt;
+ }
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/domain/model/TransactionStatus.java b/transaction-service/src/main/java/com/yape/transaction/domain/model/TransactionStatus.java
new file mode 100644
index 0000000000..6b66bea37c
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/domain/model/TransactionStatus.java
@@ -0,0 +1,6 @@
+
+package com.yape.transaction.domain.model;
+
+public enum TransactionStatus {
+ PENDING, APPROVED, REJECTED
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/domain/port/out/TransactionRepository.java b/transaction-service/src/main/java/com/yape/transaction/domain/port/out/TransactionRepository.java
new file mode 100644
index 0000000000..ca39f900e3
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/domain/port/out/TransactionRepository.java
@@ -0,0 +1,12 @@
+package com.yape.transaction.domain.port.out;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import com.yape.transaction.domain.model.Transaction;
+
+public interface TransactionRepository {
+ Transaction save(Transaction transaction);
+ Optional findById(UUID id);
+ List findAll();
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/infrastructure/config/KafkaConfig.java b/transaction-service/src/main/java/com/yape/transaction/infrastructure/config/KafkaConfig.java
new file mode 100644
index 0000000000..9bcc66a30e
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/infrastructure/config/KafkaConfig.java
@@ -0,0 +1,64 @@
+package com.yape.transaction.infrastructure.config;
+
+import java.util.HashMap;
+import java.util.Map;
+import com.yape.transaction.domain.model.Transaction;
+import com.yape.transaction.infrastructure.kafka.events.TransactionUpdatedEvent;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.clients.producer.ProducerConfig;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.annotation.EnableKafka;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.core.*;
+import org.springframework.kafka.support.serializer.JsonDeserializer;
+import org.springframework.kafka.support.serializer.JsonSerializer;
+
+import static com.yape.transaction.application.utils.Constants.TOPIC_SERVICE_GROUP;
+
+@EnableKafka
+@Configuration
+public class KafkaConfig {
+
+ @Value("${spring.kafka.bootstrap-servers}")
+ private String bootstrapServers;
+
+ @Bean
+ public ProducerFactory producerFactory() {
+ Map props = new HashMap<>();
+ props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
+ props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
+ props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
+ props.put(JsonSerializer.ADD_TYPE_INFO_HEADERS, false);
+ return new DefaultKafkaProducerFactory<>(props);
+ }
+
+ @Bean
+ public KafkaTemplate kafkaTemplate() {
+ return new KafkaTemplate<>(producerFactory());
+ }
+
+ @Bean
+ public ConsumerFactory consumerFactory() {
+ JsonDeserializer deserializer = new JsonDeserializer<>(TransactionUpdatedEvent.class);
+ deserializer.addTrustedPackages("*");
+ deserializer.setRemoveTypeHeaders(true);
+
+ Map props = new HashMap<>();
+ props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
+ props.put(ConsumerConfig.GROUP_ID_CONFIG, TOPIC_SERVICE_GROUP);
+ props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
+ return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), deserializer);
+ }
+
+ @Bean
+ public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() {
+
+ ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>();
+ factory.setConsumerFactory(consumerFactory());
+ return factory;
+ }
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/infrastructure/db/JpaRepository.java b/transaction-service/src/main/java/com/yape/transaction/infrastructure/db/JpaRepository.java
new file mode 100644
index 0000000000..2e49722350
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/infrastructure/db/JpaRepository.java
@@ -0,0 +1,8 @@
+
+package com.yape.transaction.infrastructure.db;
+
+import java.util.UUID;
+import com.yape.transaction.infrastructure.db.entity.TransactionEntity;
+
+public interface JpaRepository extends org.springframework.data.jpa.repository.JpaRepository {
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/infrastructure/db/TransactionRepositoryAdapter.java b/transaction-service/src/main/java/com/yape/transaction/infrastructure/db/TransactionRepositoryAdapter.java
new file mode 100644
index 0000000000..1e11a9839d
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/infrastructure/db/TransactionRepositoryAdapter.java
@@ -0,0 +1,39 @@
+package com.yape.transaction.infrastructure.db;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import com.yape.transaction.domain.model.Transaction;
+import com.yape.transaction.domain.port.out.TransactionRepository;
+import com.yape.transaction.infrastructure.db.entity.TransactionEntity;
+import org.springframework.stereotype.Component;
+
+@Component
+public class TransactionRepositoryAdapter implements TransactionRepository {
+
+ private final JpaRepository jpaRepository;
+
+ public TransactionRepositoryAdapter(JpaRepository jpaRepository) {
+ this.jpaRepository = jpaRepository;
+ }
+
+ @Override
+ public Transaction save(Transaction tx) {
+ jpaRepository.save(TransactionEntity.from(tx));
+ return tx;
+ }
+
+ @Override
+ public Optional findById(UUID id) {
+ return jpaRepository.findById(id)
+ .map(TransactionEntity::toDomain);
+ }
+
+ @Override
+ public List findAll() {
+ return jpaRepository.findAll()
+ .stream()
+ .map(TransactionEntity::toDomain)
+ .toList();
+ }
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/infrastructure/db/entity/TransactionEntity.java b/transaction-service/src/main/java/com/yape/transaction/infrastructure/db/entity/TransactionEntity.java
new file mode 100644
index 0000000000..e9977a56fb
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/infrastructure/db/entity/TransactionEntity.java
@@ -0,0 +1,59 @@
+package com.yape.transaction.infrastructure.db.entity;
+
+import com.yape.transaction.domain.model.Transaction;
+import com.yape.transaction.domain.model.TransactionStatus;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Column;
+import lombok.Getter;
+
+import java.time.Instant;
+import java.util.UUID;
+
+@Getter
+@Entity
+@Table(name = "transactions")
+public class TransactionEntity {
+
+ @Id
+ @Column(name = "id", nullable = false, updatable = false)
+ private UUID id;
+
+ @Column(name = "amount", nullable = false)
+ private double value;
+
+ @Column(nullable = false)
+ private String status;
+
+ @Column(name = "created_at", nullable = false)
+ private Instant createdAt;
+
+ protected TransactionEntity() {
+ }
+
+ private TransactionEntity(
+ UUID id,
+ double value,
+ String status,
+ Instant createdAt) {
+
+ this.id = id;
+ this.value = value;
+ this.status = status;
+ this.createdAt = createdAt;
+ }
+
+ public static TransactionEntity from(Transaction tx) {
+ return new TransactionEntity(tx.getId(), tx.getAmount(), tx.getStatus().name(), tx.getCreatedAt());
+ }
+
+ public Transaction toDomain() {
+ return new Transaction(
+ this.id,
+ this.value,
+ TransactionStatus.valueOf(this.status),
+ this.createdAt
+ );
+ }
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/KafkaConsumer.java b/transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/KafkaConsumer.java
new file mode 100644
index 0000000000..88016b5f5c
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/KafkaConsumer.java
@@ -0,0 +1,30 @@
+package com.yape.transaction.infrastructure.kafka;
+
+import com.yape.transaction.domain.model.TransactionStatus;
+import com.yape.transaction.infrastructure.kafka.events.TransactionUpdatedEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.kafka.annotation.KafkaListener;
+import com.yape.transaction.application.port.in.UpdateTransactionUseCase;
+
+import static com.yape.transaction.application.utils.Constants.TOPIC_UPDATE;
+import static com.yape.transaction.application.utils.Constants.TOPIC_SERVICE_GROUP;
+
+@Component
+public class KafkaConsumer {
+
+ private static final Logger log = LoggerFactory.getLogger(KafkaConsumer.class);
+ private final UpdateTransactionUseCase useCase;
+
+ public KafkaConsumer(UpdateTransactionUseCase useCase) {
+ this.useCase = useCase;
+ }
+
+ @KafkaListener(topics = TOPIC_UPDATE, groupId = TOPIC_SERVICE_GROUP)
+ public void consume(TransactionUpdatedEvent event) {
+
+ useCase.updateStatus(event.id(), event.status());
+ log.info("📦 [TRANSACTION-SERVICE LISTENER] {} -> {} {}", event.id(), event.status(), event.status() == TransactionStatus.APPROVED ? "✅" : event.status() == TransactionStatus.REJECTED ? "❌" : "⏳");
+ }
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/KafkaProducer.java b/transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/KafkaProducer.java
new file mode 100644
index 0000000000..6fff0a1aec
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/KafkaProducer.java
@@ -0,0 +1,28 @@
+package com.yape.transaction.infrastructure.kafka;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.yape.transaction.application.port.out.TransactionEventPublisher;
+import com.yape.transaction.domain.model.Transaction;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.stereotype.Service;
+
+import static com.yape.transaction.application.utils.Constants.TOPIC_CREATED;
+
+@Service
+public class KafkaProducer implements TransactionEventPublisher {
+
+ private static final Logger log = LoggerFactory.getLogger(KafkaProducer.class);
+ private final KafkaTemplate kafkaTemplate;
+
+ public KafkaProducer(KafkaTemplate kafkaTemplate) {
+ this.kafkaTemplate = kafkaTemplate;
+ }
+
+ @Override
+ public void publish(Transaction transaction) {
+
+ kafkaTemplate.send(TOPIC_CREATED, transaction);
+ log.info("📦 [TRANSACTION-SERVICE PRODUCER] {} -> {}", transaction.getId(), transaction.getStatus());
+ }
+}
\ No newline at end of file
diff --git a/transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/events/TransactionUpdatedEvent.java b/transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/events/TransactionUpdatedEvent.java
new file mode 100644
index 0000000000..cd701f1e8c
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/events/TransactionUpdatedEvent.java
@@ -0,0 +1,9 @@
+package com.yape.transaction.infrastructure.kafka.events;
+
+import java.util.UUID;
+import com.yape.transaction.domain.model.TransactionStatus;
+
+public record TransactionUpdatedEvent(
+ UUID id,
+ TransactionStatus status
+) {}
diff --git a/transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/TransactionController.java b/transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/TransactionController.java
new file mode 100644
index 0000000000..6f151235e4
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/TransactionController.java
@@ -0,0 +1,49 @@
+package com.yape.transaction.infrastructure.rest;
+
+import java.util.List;
+import java.util.UUID;
+
+import jakarta.validation.Valid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.yape.transaction.application.port.in.TransactionUseCase;
+import com.yape.transaction.domain.model.Transaction;
+import com.yape.transaction.infrastructure.rest.dto.TransactionRequest;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+@RestController
+@RequestMapping("/transactions")
+public class TransactionController {
+
+ private static final Logger log = LoggerFactory.getLogger(TransactionController.class);
+
+ private final TransactionUseCase useCase;
+
+ public TransactionController(TransactionUseCase useCase) {
+ this.useCase = useCase;
+ }
+
+ @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+ public Transaction create(@RequestBody @Valid TransactionRequest request) {
+ log.info("Creando transacción para usuario: {}", request.getAmount());
+ return useCase.create(request);
+ }
+
+ @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+ public List getAll() {
+ log.info("Obteniendo todas las transacciones");
+ return useCase.getAllTransactions();
+ }
+
+ @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Transaction getById(@PathVariable("id") UUID transactionId) {
+ log.info("Buscando transacción con ID: {}", transactionId);
+ return useCase.getById(transactionId);
+ }
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/dto/TransactionRequest.java b/transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/dto/TransactionRequest.java
new file mode 100644
index 0000000000..b137ff87ae
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/dto/TransactionRequest.java
@@ -0,0 +1,22 @@
+package com.yape.transaction.infrastructure.rest.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+import jakarta.validation.constraints.DecimalMax;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.Digits;
+import jakarta.validation.constraints.NotNull;
+
+@Setter
+@Getter
+public class TransactionRequest {
+
+ @NotNull(message = "Amount no puede ser null")
+ @DecimalMin(value = "0.01", message = "Amount debe ser mayor a 0")
+ @DecimalMax(value = "1000000", message = "Amount no puede ser mayor a 1,000,000")
+ @Digits(integer = 6, fraction = 2, message = "Amount debe tener como máximo 2 decimales")
+ private Double amount;
+
+ public TransactionRequest() {
+ }
+}
diff --git a/transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/exception/GlobalExceptionHandler.java b/transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000000..7603099814
--- /dev/null
+++ b/transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/exception/GlobalExceptionHandler.java
@@ -0,0 +1,42 @@
+package com.yape.transaction.infrastructure.rest.exception;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.springframework.context.support.DefaultMessageSourceResolvable;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+ private ResponseEntity