From 1b93a59a218110fad80b847124c24ea77cc7a45e Mon Sep 17 00:00:00 2001 From: Elmer Eber Rosario Soriano Date: Thu, 29 Jan 2026 23:23:57 -0500 Subject: [PATCH] =?UTF-8?q?feat(transactions):=20implementar=20m=C3=B3dulo?= =?UTF-8?q?=20de=20transacciones=20con=20antifraude,=20persistencia=20y=20?= =?UTF-8?q?mensajer=C3=ADa=20Kafka?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 5 + .idea/app-nodejs-codechallenge.iml | 9 ++ .idea/compiler.xml | 15 +++ .idea/jarRepositories.xml | 30 ++++++ .idea/misc.xml | 15 +++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 ++ anti-fraud-service/pom.xml | 83 +++++++++++++++ .../yape/antifraud/AntiFraudApplication.java | 11 ++ .../aplication/FraudDecisionService.java | 8 ++ .../antifraud/aplication/ProducerService.java | 8 ++ .../impl/FraudDetectionService.java | 35 +++++++ .../aplication/impl/FraudPolicy.java | 12 +++ .../antifraud/kafka/config/KafkaConfig.java | 65 ++++++++++++ .../kafka/event/TransactionConsumer.java | 31 ++++++ .../kafka/event/TransactionProducer.java | 29 ++++++ .../kafka/models/TransactionReqEvent.java | 42 ++++++++ .../kafka/models/TransactionRespEvent.java | 10 ++ .../antifraud/model/TransactionStatus.java | 7 ++ .../com/yape/antifraud/utils/Constants.java | 12 +++ .../src/main/resources/application.yaml | 18 ++++ .../antifraud/FraudDetectionServiceTest.java | 63 ++++++++++++ .../target/classes/application.yaml | 18 ++++ .../yape/antifraud/AntiFraudApplication.class | Bin 0 -> 722 bytes .../aplication/FraudDecisionService.class | Bin 0 -> 228 bytes .../aplication/ProducerService.class | Bin 0 -> 223 bytes .../impl/FraudDetectionService.class | Bin 0 -> 2207 bytes .../aplication/impl/FraudPolicy.class | Bin 0 -> 599 bytes .../antifraud/kafka/config/KafkaConfig.class | Bin 0 -> 4268 bytes .../kafka/event/TransactionConsumer.class | Bin 0 -> 1990 bytes .../kafka/event/TransactionProducer.class | Bin 0 -> 1856 bytes .../kafka/models/TransactionReqEvent.class | Bin 0 -> 1313 bytes .../kafka/models/TransactionRespEvent.class | Bin 0 -> 1794 bytes .../antifraud/model/TransactionStatus.class | Bin 0 -> 1241 bytes .../com/yape/antifraud/utils/Constants.class | Bin 0 -> 570 bytes .../antifraud/FraudDetectionServiceTest.class | Bin 0 -> 3161 bytes transaction-service/pom.xml | 88 ++++++++++++++++ .../transaction/TransactionApplication.java | 12 +++ .../port/in/TransactionUseCase.java | 12 +++ .../port/in/UpdateTransactionUseCase.java | 8 ++ .../port/out/TransactionEventPublisher.java | 7 ++ .../service/TransactionService.java | 46 +++++++++ .../service/UpdateTransactionService.java | 26 +++++ .../application/utils/Constants.java | 10 ++ .../transaction/domain/model/Transaction.java | 32 ++++++ .../domain/model/TransactionStatus.java | 6 ++ .../port/out/TransactionRepository.java | 12 +++ .../infrastructure/config/KafkaConfig.java | 64 ++++++++++++ .../infrastructure/db/JpaRepository.java | 8 ++ .../db/TransactionRepositoryAdapter.java | 39 +++++++ .../db/entity/TransactionEntity.java | 59 +++++++++++ .../infrastructure/kafka/KafkaConsumer.java | 30 ++++++ .../infrastructure/kafka/KafkaProducer.java | 28 ++++++ .../kafka/events/TransactionUpdatedEvent.java | 9 ++ .../rest/TransactionController.java | 49 +++++++++ .../rest/dto/TransactionRequest.java | 22 ++++ .../exception/GlobalExceptionHandler.java | 42 ++++++++ .../src/main/resources/application.yaml | 15 +++ .../aplication/TransactionServiceTest.java | 75 ++++++++++++++ .../UpdateTransactionServiceTest.java | 54 ++++++++++ .../db/TransactionRepositoryAdapterTest.java | 95 ++++++++++++++++++ .../kafka/KafkaConsumerTest.java | 34 +++++++ .../rest/TransactionControllerTest.java | 71 +++++++++++++ .../target/classes/application.yaml | 15 +++ .../transaction/TransactionApplication.class | Bin 0 -> 732 bytes .../port/in/TransactionUseCase.class | Bin 0 -> 518 bytes .../port/in/UpdateTransactionUseCase.class | Bin 0 -> 268 bytes .../port/out/TransactionEventPublisher.class | Bin 0 -> 244 bytes .../service/TransactionService.class | Bin 0 -> 3543 bytes .../service/UpdateTransactionService.class | Bin 0 -> 2338 bytes .../application/utils/Constants.class | Bin 0 -> 549 bytes .../domain/model/Transaction.class | Bin 0 -> 1790 bytes .../domain/model/TransactionStatus.class | Bin 0 -> 1287 bytes .../port/out/TransactionRepository.class | Bin 0 -> 561 bytes .../infrastructure/config/KafkaConfig.class | Bin 0 -> 4467 bytes .../infrastructure/db/JpaRepository.class | Bin 0 -> 386 bytes .../db/TransactionRepositoryAdapter.class | Bin 0 -> 2787 bytes .../db/entity/TransactionEntity.class | Bin 0 -> 2397 bytes .../infrastructure/kafka/KafkaConsumer.class | Bin 0 -> 2133 bytes .../infrastructure/kafka/KafkaProducer.class | Bin 0 -> 1943 bytes .../events/TransactionUpdatedEvent.class | Bin 0 -> 1924 bytes .../rest/TransactionController.class | Bin 0 -> 2612 bytes .../rest/dto/TransactionRequest.class | Bin 0 -> 1279 bytes .../exception/GlobalExceptionHandler.class | Bin 0 -> 4487 bytes 84 files changed, 1508 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/app-nodejs-codechallenge.iml create mode 100644 .idea/compiler.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 anti-fraud-service/pom.xml create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/AntiFraudApplication.java create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/aplication/FraudDecisionService.java create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/aplication/ProducerService.java create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/aplication/impl/FraudDetectionService.java create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/aplication/impl/FraudPolicy.java create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/kafka/config/KafkaConfig.java create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/kafka/event/TransactionConsumer.java create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/kafka/event/TransactionProducer.java create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/kafka/models/TransactionReqEvent.java create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/kafka/models/TransactionRespEvent.java create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/model/TransactionStatus.java create mode 100644 anti-fraud-service/src/main/java/com/yape/antifraud/utils/Constants.java create mode 100644 anti-fraud-service/src/main/resources/application.yaml create mode 100644 anti-fraud-service/src/test/java/com/yape/antifraud/FraudDetectionServiceTest.java create mode 100644 anti-fraud-service/target/classes/application.yaml create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/AntiFraudApplication.class create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/aplication/FraudDecisionService.class create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/aplication/ProducerService.class create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/aplication/impl/FraudDetectionService.class create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/aplication/impl/FraudPolicy.class create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/kafka/config/KafkaConfig.class create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/kafka/event/TransactionConsumer.class create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/kafka/event/TransactionProducer.class create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/kafka/models/TransactionReqEvent.class create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/kafka/models/TransactionRespEvent.class create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/model/TransactionStatus.class create mode 100644 anti-fraud-service/target/classes/com/yape/antifraud/utils/Constants.class create mode 100644 anti-fraud-service/target/test-classes/com/yape/antifraud/FraudDetectionServiceTest.class create mode 100644 transaction-service/pom.xml create mode 100644 transaction-service/src/main/java/com/yape/transaction/TransactionApplication.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/application/port/in/TransactionUseCase.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/application/port/in/UpdateTransactionUseCase.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/application/port/out/TransactionEventPublisher.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/application/service/TransactionService.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/application/service/UpdateTransactionService.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/application/utils/Constants.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/domain/model/Transaction.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/domain/model/TransactionStatus.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/domain/port/out/TransactionRepository.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/infrastructure/config/KafkaConfig.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/infrastructure/db/JpaRepository.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/infrastructure/db/TransactionRepositoryAdapter.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/infrastructure/db/entity/TransactionEntity.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/KafkaConsumer.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/KafkaProducer.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/infrastructure/kafka/events/TransactionUpdatedEvent.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/TransactionController.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/dto/TransactionRequest.java create mode 100644 transaction-service/src/main/java/com/yape/transaction/infrastructure/rest/exception/GlobalExceptionHandler.java create mode 100644 transaction-service/src/main/resources/application.yaml create mode 100644 transaction-service/src/test/java/com/yape/antifraud/aplication/TransactionServiceTest.java create mode 100644 transaction-service/src/test/java/com/yape/antifraud/aplication/UpdateTransactionServiceTest.java create mode 100644 transaction-service/src/test/java/com/yape/antifraud/infraestructure/db/TransactionRepositoryAdapterTest.java create mode 100644 transaction-service/src/test/java/com/yape/antifraud/infraestructure/kafka/KafkaConsumerTest.java create mode 100644 transaction-service/src/test/java/com/yape/antifraud/infraestructure/rest/TransactionControllerTest.java create mode 100644 transaction-service/target/classes/application.yaml create mode 100644 transaction-service/target/classes/com/yape/transaction/TransactionApplication.class create mode 100644 transaction-service/target/classes/com/yape/transaction/application/port/in/TransactionUseCase.class create mode 100644 transaction-service/target/classes/com/yape/transaction/application/port/in/UpdateTransactionUseCase.class create mode 100644 transaction-service/target/classes/com/yape/transaction/application/port/out/TransactionEventPublisher.class create mode 100644 transaction-service/target/classes/com/yape/transaction/application/service/TransactionService.class create mode 100644 transaction-service/target/classes/com/yape/transaction/application/service/UpdateTransactionService.class create mode 100644 transaction-service/target/classes/com/yape/transaction/application/utils/Constants.class create mode 100644 transaction-service/target/classes/com/yape/transaction/domain/model/Transaction.class create mode 100644 transaction-service/target/classes/com/yape/transaction/domain/model/TransactionStatus.class create mode 100644 transaction-service/target/classes/com/yape/transaction/domain/port/out/TransactionRepository.class create mode 100644 transaction-service/target/classes/com/yape/transaction/infrastructure/config/KafkaConfig.class create mode 100644 transaction-service/target/classes/com/yape/transaction/infrastructure/db/JpaRepository.class create mode 100644 transaction-service/target/classes/com/yape/transaction/infrastructure/db/TransactionRepositoryAdapter.class create mode 100644 transaction-service/target/classes/com/yape/transaction/infrastructure/db/entity/TransactionEntity.class create mode 100644 transaction-service/target/classes/com/yape/transaction/infrastructure/kafka/KafkaConsumer.class create mode 100644 transaction-service/target/classes/com/yape/transaction/infrastructure/kafka/KafkaProducer.class create mode 100644 transaction-service/target/classes/com/yape/transaction/infrastructure/kafka/events/TransactionUpdatedEvent.class create mode 100644 transaction-service/target/classes/com/yape/transaction/infrastructure/rest/TransactionController.class create mode 100644 transaction-service/target/classes/com/yape/transaction/infrastructure/rest/dto/TransactionRequest.class create mode 100644 transaction-service/target/classes/com/yape/transaction/infrastructure/rest/exception/GlobalExceptionHandler.class 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 0000000000000000000000000000000000000000..b62861fbbe23adc28a80b9fca566469c17a4f10b GIT binary patch literal 722 zcma)4OHUgy5dItzHiYnKpy7GpM3O^&;}V1vBIUGdr3R&kp1g}$47+yZ^_KFpIDt6y z2lPi(9d9EghdtDXvFG*8_?!9t=lvIe5l&mEVJ$$tjRw{kb|1tK!Be3V{v~>lv13@j zR9d+!hP7UQ7NE&+9-E9mi@D@NJ9TG8amz>an)ugfK2KFFoHDwF7B&O4+t|W3!@$^t z=X0xcLOGdyGWL;2#yCDLjVqZ{%ods<>V?aO#;M5j;rA8G)OoOBKdeHIjdt><<71=m zR8m+GrE;YNnZ0|+P#>FH$*>bDEk74oB<+olW$1(^7U@h_<@JTJ?(S92&=3EU1YaN% ziayirRrNk2zpx2G?p3%MT2oWlSWcAh=%|Y3Po5(3eJyAPnVhLy`LAkiTsa1UzN%!X z_V%Y46-J~p5QT4FTO#R1uCI4tE6j_LNbPJqABe7(Q` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..71cede18cf66b0298ab04d4675dbfa60d7009329 GIT binary patch literal 228 zcmZvXJqp4=5QX3R7qznW2HLn_C0Ge+B?w0B$IV1GSvR^_1Mz4U9>7D1o8IOzGsXAK z`+Q$-04oe4v;^j=xXPMtEXjJEI(j6MZbmEey0CKF9M-IK8Mc^Rr4>i$2#hkSNE)&i zxj$#Dyg)>jO#Nhm#boyfn^T&TycP+YQXU=Il2ju(@O@LU^~>o=U>Fw|FqSs7_LblNV0hIzniU0rr literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e68d60bf32b2e773f3923280aa0a733eb8e738b9 GIT binary patch literal 223 zcmZvXIS#@w6hy~tVQDBh1Qg&JQBgnz0ue<2aX!dd?8r_caWx7Kz@ZRHi)cnuy*Kmz zJYN9T7zAhu%$3dLgL0N+3Vm}_Mw0SWD=M@#^5|?-Ds~~eN-GY~5g5l*kxa?l<>4B$ zDg;KJO|)n4>COVn>Fy6Tp<6;Svk|9Wo*fxas&Smy=bMU6v6@{3hM_H;;;l|=(BwzI XXv{qUfp&cX5cQfadQC%6XAAu=2u4A= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3e9ff8b2e75cd807588e6fdb8c489ebee5bd35e0 GIT binary patch literal 2207 zcmb7GYg5}s6g_JUvW<8-Z5jx9G;OJE9!dyF3C^Pgo0imoYm8@-Z;QR53by1*GA;cl z{RQpEHq(%4JJTP~-_+^!NS1?#5knaUNqhJ1J!j9}z1M&L`^R4ZR#8hKh5-$69fKGW z7=I-XrO}kuzOlXcO4U7qp@*hrdXEJLGTB-Z2_!Y7bPPimSgzYG<4CrZAuZ2*>BvsQ zknN^fm!4@`hS_R2jdkBzP#%NPwxgUwv#tb&1Mjj;=OcmJrT-hLyCUxs8Pzd{ae+kJ zu^XMbas-TX;*>+rkVZa*G%jg)PsjVXEO0l1#esZzAdQyYP)*mUI?{4wu#<}Vws@#4 zDwNz;-fGM4_}>(%kir#AYM9b-71II>5kVtG>WQJ5ZXlXKJX6TNNa8wfXt=54L);R` zMT863^^@E2q}OpNO{Ms(xK=F|1m>coIb$K^y^fD{eBuKpR?FqecCE;LrSYkb46@v_ ztGphFk-13dEORb_Ssinj7Z{~>yX}VbR48UEGT3>Ckk3X@ID3`b4&d^`FmPML9UY6f zD=;6SZQoIy${r@MtE|SRa$UL4lzwnQ>OB(KnudD zCA%)0HR+iCI5ft+Z%kgdm2-ejh@CEoc>zsYvU&6aA(kUIcy=yy3sFFO*gL6E0{5c$ zoEt|#yzp;$P(!ND_W~20;EMKDKY{xfi``3hfe|KO{a{nJL+w&KcE_o!b<JnUoaglWH%@I65LAlCRSaP-`7j(m^h-a_PN-$2XFy}`(@G{sQlGmSW( z<9tpa$|iw1J6R3pXoiwxy9UX&}NS@af}ZhP5+4G zYh3#YL$C4C*6jQ-W^$a%t;FdyG8z9J3o-ai1_CBi7~=O8Ze|iw{JVi^%-|aCu#o$V zmbwV<7%k!}l!&j>>nb+MXo{=1aEtL~Xx&DcyZV|d`3vD6_Qt0gBgOop9kMQPKF0a+ uKe6Fy4e~7nO;I^yN z06k)kagmi|7R=4Pdjxm&^AcmL1|ux<-uc&nE?%(WzOc$zeLf>)z`V4*hWYP55QRSx r2Dd~4JhHTFmURlVbH-BL*6@-%>qMJqbb5jfuUIs&flcPh!fW6!*)oXM literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..fbfc6cd76962927ae0d0b64e5d19f72412e888d0 GIT binary patch literal 4268 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..81e45698e9e496eddde4a0350c96e72cdf8f4664 GIT binary patch literal 1856 zcmbVN?Q+vb6g}&ND7FQp4ox6HYG?t+!4c3jEmaztIyPi#0%JRdf$5Kpyb(%dt!E{L z$#nWI{nh_1{homb=)3R=o!+$)&@iSY^^Er8dUfu{IeYcj-+#UUu!=hdG7J|T*|YX} zUsybdylutft`$d~A6iuzgb^9T97b`>z?caG;|vo!e2-f`4_xcs){bx@hOuQY@S+ul zkOcTd(g5kyx2fKWGms?^_1d-KNJP5fHc`~@KWH)w%VmLlH(H6bF&m+OG z(2&Zt!oKnXmtyRRr&8@&35+9^u)fxahv)5aOfXDFuaPTpze_;f9155;aLUB{m}0nk z6jX-MPy}6ujZ))v30jek$xd04qS$kZN!QYHWZ=X~>2#}-auN~W@6Xs~he0O1-(dxmIb_nw|A(t$4qA zZ*8+$YkpJw_Pb(bg~l9a@qvM&i3_;Mu=rLUda^5s?-UVD5%z1E9fpiY85c_FY&JL7 z*KCdck%>#VO!SjZqBta)=-d!D0|#mR*5JIIM+tKV$|mMC)GYf))i4wYlcf{cU7NPqGU+AyzhGs z*MhbV72OhQ&vQt~*_P)9R4hg7_K`%roJ#MlqFb>Uu9uF4YtMD4&Q;O?!^wshh>f_n zB~)80bRb2C`yH-4y+1IHMo&CqdvO@o|8cb`zNv;xL+x?OPnY|2>JlDmXF)d`hBM8W z8gx%|ywD>mR)av&Eh3Q-6Xl_dhmlZ1MpWQI*O38TXq%dW?&l02BZC>;b?9SI(5;$m zpU^skC5+J8z^AnSj4Tgn&d{7I|AvwB{4dD_5^`c$dgX70R=4majoRBI!qVPm%ReJqI640--h1#9rVD3jb)KFN|GFx);W)n#M*nX+kuyE%yi5)@@-?Sep2&)TG43 z3x9w=%J|N*d<2)6-kguwnR%a?GyD6`&tCuzQAs0$Xc934aU>MhF7%bQI@&$A-ZwAo zmZy+->9~$}pb#}HjTBapN+NCG77T^wtzOsqqCeS|c0K2;uZL~xQlDLFtJ`baoq=`I z*X}^KJg4V=us^@KvR!73IBhD`eW{`6bga|UquRct&Kg+78Z~vdHxyS@6La3cItnxo zJnan!3eV~x!oK9Z;<4|&uMm4p8im!m`q5s4Z zGiwv4adQ;*sBxoaO1??3W!@xKq|?W}VZUX+b)@|pA>*+pV}UYXJ;Di%a^!lxmmK3@039dS)@plB~Jl4KY5i_CJ2(tNvK7<=yOXgMHyoHDcr?YAU)&0 zM)Dh-BA9U(Lb>nZeh_>>Z%Lf+nP;H#PlG6NRw|X3#$HpnL;$Do0NYcL2v5?$Yh=Fi zFb}d73R1?yDM*wp6p&r^Z4qRfAmt?>@}}}~kFXQun(s(%u_HU7Adm563NqJ`xu+X< S#3V>%35Yz6fb9CEn|}ednCb-p literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a31a2f12ba184dcd4b9f238529b2df7fc24b7672 GIT binary patch literal 1794 zcmbVM+foxj5Iqw@Hi-)f5l}=06*LLRiXw_ZQ9;XSP>b7xK24H=EZOYFy->wJ`5;wN zR`K8m_)(U<7hsj;A`e?L)7yQ{obI0f^?UCpfERe4MhqPi;xamsV90LrEv{%>-%yrB z%`oc>iCI-w?Pm-f`NCQXNu(sCWn6&FFjX^J$~JEch3mGuZgQutH2HdyD=nigG)q}A zxo+{Ats450u-dP-gl;p$RKm?xJTAvpHDz_RyijtnyJcjMB~r`gwqr4jS0X4p@G$nW z=UvK?rksq6=w(RS#d=$v3_be6s8+zIBu{?%;8JBRG7#iGa%#%)rne#G~ z-78;_F^H=qT6}T1Mxx)H6E1Y$57kXkvrC0f40HK^GaZ$V<8oS9V~G1IGZj@A@10gv zm@B-hk?vf@sBvwLo2q*bjB&f6l8EUjmeZ3?jj<@~hEe~>P2Li=FbS(SA5jahBg+hZ zfj+NJA>GVyFL>C{!7noIqaF4h!vq=sqtzJF%Z6jt#B0?}#6VP|Pq^Gv*8P{1eQ6lB zWt+V1E3}fh$BY>OMHb zPBxEuq4bfj2>z}L4yPuP1jF$r5gBxF-bEfMo%t=oIAnXdEpKm zS`%P20vURl$0Dtn;t#Om*e;~v_%6D3$P~j+;0{O_rv0$*iW|5|CkpbQnYVMn zW4J~8RE*5-PoRfJ)ClppOp~;_u2ON2?D$68qYSd|5-{}8{27f1?*qP`DSQyOgYa1* zbMuibj_qOKxcGkG=RjZ*caSFpMv4jnlH#3#&f<4SC#mQRU>K#M7!llcmf$$XJf-8f jOKUgX&#$Im%L&iM@BneDw?a9-k0)eH6I}^UF^7Rav5Bpn literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..27a44d31e14e605683e5082bd038a63b06efc155 GIT binary patch literal 1241 zcmb7DZBNrs6n^g7u3IfKCxT4y1yMHMDhT>vgg~Gg%p42FCB#pcQlO-3P1lk5&7Y(r z5u(xX*&k&*_cG%Mnz$zIx##qGp7YYvA3wi*2e69gI#LXiZq%^e^LL)b!^Gc@d27#V zM0;LfZO1%payRj#&`EgGYG%Qpn$VDD$gI|CTbp%zongB2{}N@Pkui}K8hXvHu5VOd zir6(1rqtcCU)gKh_PPjOH_?wA0mtjBl{dD-F!Q#TWCrrMsUv4%0Jj)4H;6)yVW3#5 z9Pwjr1w1^kHg}IaHz^w!!fhSHCJMMiss8}%u+^X%j(O1XHuo78i(TxsfH#}vu7H!o zet1ys&L9M&{-}w&xW|xQ@M%D-^$awF+8@mzn}I2gk4!wqgqVaZqsd?u zOFhkkvi_#3YxI2?`YV3uRa=c+FW%<60Yz5+QckItfivVQk;{WRk9{H8(N#mpd^pfkDUE-f+NrC!QhE;ATT z)QVm2g)bT(yt3=F;`^`6(ddWhXHi#%#o7a8X?3Ngbp>(-v7v;}C|<+_;Y;L=krkKG zM&D=LI7j{?dGxc$w=_sXW$%0BxU?}|! zY)5HBady-;bZ19v(=bK7m;1}$Dds!mtqytiD{=Gpl~X)8fg+tTRXVzK9%|At&M literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d4a515921c30fa39c122b5ea8b251ea5f0485760 GIT binary patch literal 570 zcmaix+fLg+5QhH=AxT3xC4~}Nidu02NZ%&Jq?GJ6f-icnY4S zSCzQn0s2rCW5OZgrZ+SCX8-+ncINND`v(9vHp|Firigq6vnUXjuEdS7LJ^Iu=HN;O zhEO(uG@S`E9)+N$@~O2=pwjSfm!Ce(};Q6d7P z;%F}&4~3CKcAZ>y+Ad#il<@JLFwk-$tWAU+x9y)gZvUj+?6pexM5s^uCQ{$1K<3~q~#HkMC50&{X zybLGV&wh_@DDr-QJnHy>GEZraDTfO40v2C+iMcw(HRhEmUS(x2+c4)fcCa`T=h@vq cpx;lHb&lC__Sibd%0z(A_`*ck;F!bLNBZP|g8%>k literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5ab2a71a48a1d2db04d9299b2cf46577647b8959 GIT binary patch literal 3161 zcmb_eds7ov96dKY0&xq97Q`o|qY^;YR;@~qf+4;dltPH5_O&DzSV(rmKB(BoN2s5r zofc@Fah(3yPt#A(>AAa!B#;!D=`dt>@BO{b`91Fa>z_Yf0hq*(QA7m#b9PbRG)hu8 zEYDnW3_q{?o>_49q;0t#7u^_|(Go?ghA7$uj;=43lM zaWsl!8oF>?V7QK*z~NrKjC6^hv*g%$KPR2CH32w~56m<(M4jiDo z7k}aa`Mu8KSq1l`hF+W!h`J#K0;31m+4Z}ZhC~!+1g_S>yk;z|8G6yqOBO}OF)Y^z zc%PN)sSRm)F`UEsDEc(Khkk*MpgdFw>GSgsrUYVC$=k*7qiZ0Zt`$hMPW<#Y3lfOo zG%hLzF9}Qx>=m1;2Q7%II;c6%@O(EB&#H99H4I{i{1}!|*nBQoU3C+45JH-Z;HNu%W1jzHUz?Ia6^>$3dH1O$B+ ztR>><#=0!O>-Z>&8yarnmca39(dp0~`*cG(=F+CX#cJze7t{RS#%4_<1TNP>-M9~8 z&~aPCI6fxtIpXpi@_wR{_NuXwID%13YPf@>z!6tk`Dy98#xlik9WqU2s)MwjMoPn7 z+!JVDmR?v7oJS0x4vt3k6T<^MjN%gwX-o^8S8OsZ((zXHob7N}mP~yzSl|R=x?1GVbqaUJL>3kme`uy1CeNBGCv4Fn{=TKQZGR(T8HQ%UfK z#?@uM>3j-4TL!n$^fO-~SmCcy0=i-HR|^HOiZy-#1u91Z<$1L54d{J|_JwU6Ne{k+ zwlFmKD>}Au;y3i{8tgz51oh<+9;6977QnomHZ?DFc+=xO>I0l$GpD zoL<<$*%NqQY~wFF{?rcc|AI$b$UNcB z=iJ%Cm%D2!)}{dbIr0NsAe)!agE-D&2v>2Lm*El2lQ9*0Ul>U__D-in?%RgDVwW-*N|d_6FCV`*3}8cwCEha0P3-xccc4MAFGiKo4&4R(O-qjdEp- f*lrWs`2T_}f=&8~;CXnd + 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> buildResponse(HttpStatus status, String message) { + Map body = new HashMap<>(); + body.put("status", status.value()); + body.put("error", message); + return ResponseEntity.status(status).body(body); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationException(MethodArgumentNotValidException ex) { + String errorMessage = ex.getBindingResult() + .getFieldErrors() + .stream() + .findFirst() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .orElse("Request inválido"); + return buildResponse(HttpStatus.BAD_REQUEST, errorMessage); + } + + @ExceptionHandler(IllegalStateException.class) + public ResponseEntity> handleIllegalStateException(IllegalStateException ex) { + return buildResponse(HttpStatus.BAD_REQUEST, ex.getMessage()); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleGeneralException(Exception ex) { + return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage()); + } +} diff --git a/transaction-service/src/main/resources/application.yaml b/transaction-service/src/main/resources/application.yaml new file mode 100644 index 0000000000..40ed7bbf3e --- /dev/null +++ b/transaction-service/src/main/resources/application.yaml @@ -0,0 +1,15 @@ +spring: + application: + name: transaction-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: "*" diff --git a/transaction-service/src/test/java/com/yape/antifraud/aplication/TransactionServiceTest.java b/transaction-service/src/test/java/com/yape/antifraud/aplication/TransactionServiceTest.java new file mode 100644 index 0000000000..ae0b994cea --- /dev/null +++ b/transaction-service/src/test/java/com/yape/antifraud/aplication/TransactionServiceTest.java @@ -0,0 +1,75 @@ +package com.yape.antifraud.aplication; + +import com.yape.transaction.application.port.out.TransactionEventPublisher; +import com.yape.transaction.application.service.TransactionService; +import com.yape.transaction.domain.model.Transaction; +import com.yape.transaction.domain.port.out.TransactionRepository; +import com.yape.transaction.infrastructure.rest.dto.TransactionRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +class TransactionServiceTest { + + private TransactionRepository repository; + private TransactionEventPublisher publisher; + private TransactionService service; + + @BeforeEach + void setup() { + repository = mock(TransactionRepository.class); + publisher = mock(TransactionEventPublisher.class); + service = new TransactionService(repository, publisher); + } + + @Test + void create_PublishTransaction() { + TransactionRequest request = new TransactionRequest(); + request.setAmount(150.0); + + Transaction tx = service.create(request); + + verify(repository, times(1)).save(any(Transaction.class)); + verify(publisher, times(1)).publish(any(Transaction.class)); + + assertThat(tx.getAmount()).isEqualTo(150.0); + assertThat(tx.getId()).isNotNull(); + } + + @Test + void getById_Transaction_WhenExists() { + UUID id = UUID.randomUUID(); + Transaction tx = new Transaction(id, 200.0); + when(repository.findById(id)).thenReturn(Optional.of(tx)); + + Transaction result = service.getById(id); + + assertThat(result).isEqualTo(tx); + verify(repository, times(1)).findById(id); + } + + @Test + void getById_Exception_WhenNotFound() { + UUID id = UUID.randomUUID(); + when(repository.findById(id)).thenReturn(Optional.empty()); + + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> service.getById(id)); + assertThat(ex.getMessage()).isEqualTo("Transaction not found"); + verify(repository, times(1)).findById(id); + } + + @Test + void getAllTransactions_WhenEmpty() { + when(repository.findAll()).thenReturn(List.of()); + + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> service.getAllTransactions()); + assertThat(ex.getMessage()).isEqualTo("Transactions not found"); + verify(repository, times(1)).findAll(); + } +} diff --git a/transaction-service/src/test/java/com/yape/antifraud/aplication/UpdateTransactionServiceTest.java b/transaction-service/src/test/java/com/yape/antifraud/aplication/UpdateTransactionServiceTest.java new file mode 100644 index 0000000000..a77ad19c10 --- /dev/null +++ b/transaction-service/src/test/java/com/yape/antifraud/aplication/UpdateTransactionServiceTest.java @@ -0,0 +1,54 @@ +package com.yape.antifraud.aplication; + +import com.yape.transaction.application.service.UpdateTransactionService; +import com.yape.transaction.domain.model.Transaction; +import com.yape.transaction.domain.model.TransactionStatus; +import com.yape.transaction.domain.port.out.TransactionRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +class UpdateTransactionServiceTest { + + private TransactionRepository repository; + private UpdateTransactionService service; + + @BeforeEach + void setup() { + repository = mock(TransactionRepository.class); + service = new UpdateTransactionService(repository); + } + + @Test + void updateStatus_Transaction() { + UUID id = UUID.randomUUID(); + Transaction transaction = new Transaction(id, 100.0); + + when(repository.findById(id)).thenReturn(Optional.of(transaction)); + + service.updateStatus(id, TransactionStatus.APPROVED); + + assertThat(transaction.getStatus()).isEqualTo(TransactionStatus.APPROVED); + verify(repository, times(1)).save(transaction); + } + + @Test + void updateStatus_WhenTransactionNotFound() { + UUID id = UUID.randomUUID(); + when(repository.findById(id)).thenReturn(Optional.empty()); + + IllegalStateException ex = assertThrows( + IllegalStateException.class, + () -> service.updateStatus(id, TransactionStatus.REJECTED) + ); + + assertThat(ex.getMessage()).isEqualTo("Transaction not found"); + verify(repository, never()).save(any()); + } +} diff --git a/transaction-service/src/test/java/com/yape/antifraud/infraestructure/db/TransactionRepositoryAdapterTest.java b/transaction-service/src/test/java/com/yape/antifraud/infraestructure/db/TransactionRepositoryAdapterTest.java new file mode 100644 index 0000000000..11ecb4a57d --- /dev/null +++ b/transaction-service/src/test/java/com/yape/antifraud/infraestructure/db/TransactionRepositoryAdapterTest.java @@ -0,0 +1,95 @@ +package com.yape.antifraud.infraestructure.db; + +import com.yape.transaction.domain.model.Transaction; +import com.yape.transaction.domain.model.TransactionStatus; +import com.yape.transaction.infrastructure.db.JpaRepository; +import com.yape.transaction.infrastructure.db.TransactionRepositoryAdapter; +import com.yape.transaction.infrastructure.db.entity.TransactionEntity; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class TransactionRepositoryAdapterTest { + + @Mock + private JpaRepository jpaRepository; + + private TransactionRepositoryAdapter adapter; + + private UUID id; + private Transaction transaction; + private TransactionEntity entity; + + @BeforeEach + void setup() { + adapter = new TransactionRepositoryAdapter(jpaRepository); + + id = UUID.randomUUID(); + Instant now = Instant.now(); + + transaction = new Transaction( + id, + 100.0, + TransactionStatus.PENDING, + now + ); + + entity = TransactionEntity.from(transaction); + } + + @Test + void shouldSaveTransaction() { + adapter.save(transaction); + + verify(jpaRepository).save(any(TransactionEntity.class)); + } + + @Test + void shouldFindTransactionById() { + when(jpaRepository.findById(id)).thenReturn(Optional.of(entity)); + + Optional result = adapter.findById(id); + + assertThat(result).isPresent(); + assertThat(result.get().getId()).isEqualTo(id); + assertThat(result.get().getStatus()).isEqualTo(TransactionStatus.PENDING); + } + + @Test + void shouldTransactionNotFound() { + when(jpaRepository.findById(id)).thenReturn(Optional.empty()); + + Optional result = adapter.findById(id); + + assertThat(result).isEmpty(); + } + + @Test + void shouldFindAllTransactions() { + when(jpaRepository.findAll()).thenReturn(List.of(entity)); + + List result = adapter.findAll(); + + assertThat(result).hasSize(1); + } + + @Test + void shouldNoTransactionsExist() { + when(jpaRepository.findAll()).thenReturn(List.of()); + + List result = adapter.findAll(); + + assertThat(result).isEmpty(); + } +} diff --git a/transaction-service/src/test/java/com/yape/antifraud/infraestructure/kafka/KafkaConsumerTest.java b/transaction-service/src/test/java/com/yape/antifraud/infraestructure/kafka/KafkaConsumerTest.java new file mode 100644 index 0000000000..f47cc1a805 --- /dev/null +++ b/transaction-service/src/test/java/com/yape/antifraud/infraestructure/kafka/KafkaConsumerTest.java @@ -0,0 +1,34 @@ +package com.yape.antifraud.infraestructure.kafka; + +import java.util.UUID; +import com.yape.transaction.application.port.in.UpdateTransactionUseCase; +import com.yape.transaction.domain.model.TransactionStatus; +import com.yape.transaction.infrastructure.kafka.KafkaConsumer; +import com.yape.transaction.infrastructure.kafka.events.TransactionUpdatedEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; + +class KafkaConsumerTest { + + private UpdateTransactionUseCase useCase; + private KafkaConsumer consumer; + + @BeforeEach + void setup() { + useCase = mock(UpdateTransactionUseCase.class); + consumer = new KafkaConsumer(useCase); + } + + @Test + void consume_CallUpdateStatus() { + UUID id = UUID.randomUUID(); + TransactionUpdatedEvent event = new TransactionUpdatedEvent(id, TransactionStatus.APPROVED); + + consumer.consume(event); + + verify(useCase, times(1)) + .updateStatus(id, TransactionStatus.APPROVED); + } +} diff --git a/transaction-service/src/test/java/com/yape/antifraud/infraestructure/rest/TransactionControllerTest.java b/transaction-service/src/test/java/com/yape/antifraud/infraestructure/rest/TransactionControllerTest.java new file mode 100644 index 0000000000..46de1451ef --- /dev/null +++ b/transaction-service/src/test/java/com/yape/antifraud/infraestructure/rest/TransactionControllerTest.java @@ -0,0 +1,71 @@ +package com.yape.antifraud.infraestructure.rest; + +import java.util.UUID; +import java.util.List; +import java.util.Arrays; + +import com.yape.transaction.infrastructure.rest.TransactionController; +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.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.assertThat; + +class TransactionControllerTest { + + private TransactionUseCase useCase; + private TransactionController controller; + + @BeforeEach + void setup() { + useCase = mock(TransactionUseCase.class); + controller = new TransactionController(useCase); + } + + @Test + void create_Transaction() { + TransactionRequest request = new TransactionRequest(); + request.setAmount(100.0); + + Transaction tx = new Transaction(UUID.randomUUID(), 100.0); + when(useCase.create(request)).thenReturn(tx); + + Transaction result = controller.create(request); + + assertThat(result).isNotNull(); + assertThat(result.getAmount()).isEqualTo(100.0); + verify(useCase, times(1)).create(request); + } + + @Test + void getAll_Transactions() { + Transaction tx1 = new Transaction(UUID.randomUUID(), 50.0); + Transaction tx2 = new Transaction(UUID.randomUUID(), 75.0); + List transactions = Arrays.asList(tx1, tx2); + + when(useCase.getAllTransactions()).thenReturn(transactions); + + List result = controller.getAll(); + + assertThat(result).hasSize(2); + assertThat(result).containsExactly(tx1, tx2); + verify(useCase, times(1)).getAllTransactions(); + } + + @Test + void getById_Transaction() { + UUID id = UUID.randomUUID(); + Transaction tx = new Transaction(id, 200.0); + + when(useCase.getById(id)).thenReturn(tx); + + Transaction result = controller.getById(id); + + assertThat(result.getId()).isEqualTo(id); + assertThat(result.getAmount()).isEqualTo(200.0); + verify(useCase, times(1)).getById(id); + } +} diff --git a/transaction-service/target/classes/application.yaml b/transaction-service/target/classes/application.yaml new file mode 100644 index 0000000000..40ed7bbf3e --- /dev/null +++ b/transaction-service/target/classes/application.yaml @@ -0,0 +1,15 @@ +spring: + application: + name: transaction-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: "*" diff --git a/transaction-service/target/classes/com/yape/transaction/TransactionApplication.class b/transaction-service/target/classes/com/yape/transaction/TransactionApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..7623f44ddce79801615a0a1e277f6d285e826777 GIT binary patch literal 732 zcmb7CO;6iE5Ph2j970+`XrVway@ga|Z(M?qXe3TnD-o0)IIZ2q-C*xV>op%gixY?g zKY$;Fm^Dc8!I%0l<9YMBZ|3>s@d>~&zIM<+GeE10B`h=aX5vclROp2Nj%G4;49kZ~ zD|f`u9PCX4v>Cp~CgZnaF1fQp=OT8>XnwZ%Kc3H16$=k{(7|eeZWkZ$kzr_T!t=RR zI=QeSlh?*x^2iv+CnZ?Pq=vT84ACH53^h(go{!Ee$i(?#qrI@Ma%{AdH;#{uzEDYF zMU=`)3xd6S#Lya>zmj1sR9gNjvPjx9AIs1WO)S!>u*%!-V9WheIm3_eKZx*6GNI@} zn}fRfr{otlp|Tq_Ziddp6gHM8%1^ahPxihiiedY-&`xD?s&eHYthI6FGN|}bEl=eJ zMBxk(RuU!O_wkcfQz2}GZwTmO{MP{UH$!rbEKZt{UK-wm-IeYhN$&>QSSQ(i3qT(m xWUb&6J`>`T56WT`*AIpdSoyQa_vZ;Zp#ZCu8f|Q1i|_`%lyVz8qHyGV?9?jfBNxrCzs@8 zj01*?4ZRW9RG0kWc_W2qh=i5oC4)_}y?9l>SaROB!nam(E1l3yp}S=rS!MkRl%@GvOZkvmF9WAy88!h7HA8t2jv~ZESV3;nF=`>CTmsJ z)D>b_{yK2|#~wF9`h<1mgxMCis6re$^ZZW literal 0 HcmV?d00001 diff --git a/transaction-service/target/classes/com/yape/transaction/application/port/in/UpdateTransactionUseCase.class b/transaction-service/target/classes/com/yape/transaction/application/port/in/UpdateTransactionUseCase.class new file mode 100644 index 0000000000000000000000000000000000000000..4e65061d8db61f697033ff362b38fd9b83b35b0e GIT binary patch literal 268 zcmZ{fv2MaZ5JYDU*nlYV1N2G5et;_kLLecf0H@!aPw1?(?_{5m_%(`rKt2j#Opz2x zGn!&{-^};z`UPN#NrH&5RIcQUc+H`fsu2~8t603ZMoI6zs{@;gGoMS)$DdTz=#?}& z!I1D$$XVD*b>jEWLaRWSwf(^%NN5|v_I2N9+F&ea*>0Wg|3v4ml#W)qTwDG{d;Uq^ lJ_yr;Yip%9#%jX+zg~+6E+!BL-ClsYnj^#yhosX8<2&RhQyu^S literal 0 HcmV?d00001 diff --git a/transaction-service/target/classes/com/yape/transaction/application/port/out/TransactionEventPublisher.class b/transaction-service/target/classes/com/yape/transaction/application/port/out/TransactionEventPublisher.class new file mode 100644 index 0000000000000000000000000000000000000000..a85f51cfcc8825d54116c536254890d66fd8a827 GIT binary patch literal 244 zcmZ{fu?_)27=-6P+@kUXY8snpRU%Xp5~Ba@ZCKgc-JCs%S5tTZ4BnLl_qFD0Ik?czW@LL literal 0 HcmV?d00001 diff --git a/transaction-service/target/classes/com/yape/transaction/application/service/TransactionService.class b/transaction-service/target/classes/com/yape/transaction/application/service/TransactionService.class new file mode 100644 index 0000000000000000000000000000000000000000..d969b33a0275243f32486e19c87bd18f43fcfdaa GIT binary patch literal 3543 zcmbVO`&ScJ5dLmN7M2Azb6{{%B+4}cH4eL68EI=8BXS!sw}x>ZaJps`qFeO zGOljsiIR-6uFF6-k__!$W!|=eC8cz-xzf0u%`{K02uEJ3smW+aF&tjcys}#ocHA}{ z+p$AKuYsL-fuSpmZbe$QIX5>o!Jt!A$hGPsHJr{hZpi3(5xX?(Hn0bK8O}5Xx10r^ z2hy(;WyKe!F9KMWVY;99D3hg!wau_!X-xqAr4@lhCu`SG1QO` z)eQ_CrEq6~+Z7Qe#e|L?98?K(NGW~VhlzBd18IzC7&S14!we^yy0M`vW@ric3gKQG zZhohcmNhq#NpEt02TUB*aLmAQyu>ix)aK1)5Q7#aCByMe+aE^p6$7v0HHNkY%PE~* zohmULTodtl8eQ~M8FD+5qI!SBzzJmN(kKfU`fK+eCgeiJsa9{kqRLnJlnSSCTEiIw zZz9W(s^?}+DJ6sJPuhW)UGm+V49DW8tW_A<6=Ro-%8YRX6PTpnD4(*nO;8P%<*Y!` z#1|Kdhm!5o!!6he$1^V=fTm zJfQTxyMB5!W2+bsHc$1i^-N-4!T1^?F;N?o;#yT^fV`jEHQU{WeVjlvRF>IJ3>2+5|d7Lf&F9n=5)I zZ;=sHt<_S`#_LNpjpuNwQ`S4NqL1X$d`*|DnuKk~@{(?~o0_SNi5gFaHA>agm=>#E zEj6++6t#E#Houm=Pg7oWQu!8_r@0rUTYKJGbhzqyM9*s&<9QI0HZRYY_)rw^p`#4@ zYl_3_Z^|PoW%F*uFN$-Ps*RnoC`Gf*5k*A|eKXW}t+FUsfkkDLb%<}mF^S?koOAs} zGw>9>P(Lq=o36iX1~e22S5hF&NEM+a&$_OphR?leA(z}zkOU=GV>_=-Z<+2^e4^7- z^r-<4Hc}ZItkQEU$+zM%{l7=6 zD{3gGXZOehu#wSw&_>4Yq4O6KCFr9%))M`1Lp#lwUD$!EBpIk4!~6JvR-^zQ5)2F9 z6p(`d2p>l<0YNI5^kei~`3>7f@1yTN`XAuH^vJK!#vWogfy?yTNv}r|82gzv#|`Ks zLw3<ut<6tmM}@*R2*;9Ii3@i`3^_tM$M6$6A0u}q zb>RUn{V}S5T4HfcL&pfVVU(O2i-Gk=U^*FL;W|Mr(@7QIs>G(ZGD+IN{=puNCKiWQ z3AjmJRUds-Mh%9}0D5Rao-9!KEW~f6C&U8;r%k~Tt1Uaa4GNp8Z*X7A3vVENR) z)Xuaz+L=y2JN-?ap4}{FounvEX0vAzzc2p+P{d;uDU2vcYdC{ZhRhRw z$aRa`9ero-iD>!^qid#Z`ga&ca{0zM#xSlx)i42#q1cpN{fIk4_g!v#yy=_L*16+Y zW|K$Xo^TJ%rqJt7i~FM1*Q)k5$fPSA>6yNCkBScIxD0ERp|~yC<)*DW()D#2_<90u z_cewwfwP!WkkRk~vJAK1ABthDZQ8B%qjHO3DOZWO34GJi>-F+|BcBlLIH9-PGO|SL zIStd8A;LO>_`8%#Dz*c=M^ZHiSrV?H;sRzBe5B!He8Q0JTR8@0n2_#<<%!z9E1xpl zNM2Cd-i zV7F8(<7!wMtE3UL9`RcBUDNK6R|@oZDT63yDR&3Tpzeth_lS$TCv)+Zq|Y~q)Dp#z zVX9)qs;Cl@}V3%Q`Uv8#-C=Y~Qi5MYp-lhpbeV$nECRhI?ODjIq zyUP~0TbA$^D^eZ=j?s&EvH}y5HxAaw$E5nZZ}v={d^Xu)W8@OItg7h?B2+8ews1=p z_dMY#s54v{;AEJ43LY_BJXw`8yvGG~<}sSx4A_%SBveMfuW&2I{DpMVw6|RN z9m>iJOs4P>9|F&C{%_1}uU>tIOX1e)3tUd&QQzPUU5rK}nWEW<^EB)(U%SyvQ6o&ulHP+s2y;WE4RHzZRicr!jg+cHguIweg)GAa02t^^l8fDa{} zG!}z9H|NZE=AZv?&U}8oe*kFVw15=Sd1Q*nBF9i&@-^3O?kx1)Y$+_okh_zPRQC+& z^NUFy8pE~auJjk~30?Wz3Am-C>*(C`Y-#b$IaJaPbi;K5#T^we6i2;&*O-p`?bfKn zP&0q~43#gP#Vtc=tDkT?6dFnl^+bba=*_tja}vg8#-KxHZ4K2wLdzGiupSGCox!AQ zbf%Ak-ng%!&d^Bu2Et!UOEee08+r^G!<`F;iYXoO6s~5%AMu$@m73{V+@5e>M*LIE zsAoy@-I)K~2@PBrx}k502N}7Sx6=n#(Qa^tJbew2Mgu!2fMJ&?g(7h|!4=}Y1lLHJ piawL_KE2Q>Gs@Y^H?Y@@#R0|W8pz;~Vlfur2**UU9f~QOd;%LIM~jZc8c-QFY*!_ zjSqf+A7xzUOlfOrLf~c2oH_ffwb$Bv&!4})egoLXqbPjvhY`>aL`WcZVw@U!)v%BC z7o`)k>zs`}w!A)k|=qZ-B#Bb!lknl@=gK7u$V!kE;Mz?8t+rMUK8 z!)-PM!Y`lgKn64Kgu$8_NvV=*! zV#m<`f_-W=EXv-tZHJ{|FH&{9SaoV8=bgT5+Gbt4O<}=f(=D(U<7Xx6b|cf`;dZ+X z+4Onu%UPnEymFOJ63~dd(=GV&0CUgvQcent?mNwT*?exv3dedTYE{N#iSy&*I0rZ| z9812%P@EOQ{>q;`6ot8yi_0g zOh9ta1P4&hk~&4|Ia1FviEB)0K~b+$6`?xRsSb5YIU(IiDKDhnlpH?Z5Lm$-rX{_2 z%vDB(qz}oi=#UZ+Q7ltlM297$5cwCB_ZYXzl~aa$ZA_gzDL(g!t)FDwK4s={gd>`IB@c zLNppa`=gBKUS=2pzpTmax#!&HJm=+}`}@!LUjUZzN=JfW#tG}zC;rj3;)n+g?!;ag zSo>j}dx2FC_g&xGxje9A9yc3lFsLRpBpLK-d2@ArbB$rH(gjsXaHUM71y^dhTHW5N zl~;xFmI+fDx62#lm7NP?uZccnDC$^kx$>@TGt9s5Dy)Gl?&!#v=*L|K&GEy)W$4cr zDo6a7TRsmCtgXEx*NIC825?Wupot;eXXw2Il!IoSk~!vn)7|<&Ey#CxulT&tD0LX@ zIP!vnQfCA~Ao)j3JitSSBI?Lytt-_6`D?&4^r;e4$3ewBY#dUaoa;iH1VjO56W2>3AuskWA?Wl|y@4Q_^KRV~BaAkfJ0DD*b_h z9{m9lFYeunP!*vNC|HYE$7C&AHoGC&6!OTZVODZ`YbX-f$mh#E%`EMi_d==y57^*4Q~l*XCq?-PQzUzkuE&bci+JO zEa^>MnaAou8@}c2$BZuPKWGaGG&yvj$&nqPjXJsj9mcziHqhh#5J#K-n5&aD)Bgg? C1FdEN literal 0 HcmV?d00001 diff --git a/transaction-service/target/classes/com/yape/transaction/infrastructure/config/KafkaConfig.class b/transaction-service/target/classes/com/yape/transaction/infrastructure/config/KafkaConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..7397a88562a7c09606a7160199db22ab49f77e02 GIT binary patch literal 4467 zcmcInS$7mg6#gm;GYrEvBAXzqnqyMYN{SR)LJBm@I0RAzdTPG-8tUIGDe-+A%f zKj866&+#N`j{X22{7D|as_scTd!q0#)78~=Z+-Xs?!DFh*FV4i37`v46KD`PSahn! zn5>zG?@HT~Mc;C4L)K~)t0?t2@U4nxoO5i?m$vVv(1hj$R;7_ZQeeZd9F;~z+JnaR zfnl@g3#{t2Y|B3_(Ad)2mq1Ejqh6#O8Qszwx+-fatX5vu5`g0laz9xdGK~>g9+5_| zVwv2?sJTulD4MQuBN`Ax*&0k@Jv|OMj_(n2E$5l;sOfsEu@RXBUPxmTUKBV!Z(NpL zb|uY%q93@XQPd8ND{9-&IRl#V+!Hz$$P}i7?D1X89?Y-77HmymTN>N(lEB*O02v%3 zuLUHcqa|h-vXh@X(>ix5iJb!LM$9o4oFyyPBhyV_7o(a>mQ$@#u==`CvJfQkN481q zVPK=O5|}eQ?i0u>k6uj$MQW<%1IHbSbnt>&&2jxH-&EqV=h$(#uL$f2jdR0%l#``W z&L69pxgk@QC`t;ip(TOVG}@4@=YdX^vyM|Sr7e)qu&$Q{Hn-e~a}lk`CvZUESfl2sF45lUo^h*IA~&TA>Ol%8aYhC7hQOwH5uB??&(oxzozXhNgiYeSfM`?J z7t^?eZVJYX>U9H;o^QzFh#WLMl_+;(H4{~2YoD^aoW>OtxT|OSH_fUuYW6Z6yTfc0 zXkvhvw1Gu58`^K(2wR6ZsT1g z(|ZDsW>18ka}Q4U+1A*T$CKGogX2by(171*=?anzZ0#pb4tHJz#K%}-bk z=bVyas6hU%1=Ru5?UjU~PCTb5D}9m&2=AlGCV$8xu8zWT+5|~gqG_oZImZecyJd|o z)`#Ug)rDEoGdE<-Mw=d+xwLa`;m2m8GxjvMTHRv}+EShJ0>O$~@_gIO8+FO4GWm=u zd8`<*ZkSgX4$P|{neokseq6h=6qW1OYNhJO413hlxHZ3Z3%c-DX!2|VO_yRviNKk8 ztU&8UkAUB-1*6w=}7V^?GMk)*>flS$7e|Ba|fI1aRRq!UbIv%-*9F6^CqXhQ}~X$ z2=B2Y<$|rczc!~>$LBSgr<+lKw)?=<2U#9_cosMDi&LOMeVXw)%)2$bj__CUP(?Eb z{F>yy%|`~Wg4gD@2{iu9K?5BAuhNrSQR6rrF2MV6c_-AnI2yI__q;Z6u4@vj`zNrb zkj<=X`vn_*!{$cZ&SrLGUak*!XZF^I`x}47tKrQg+WWH;$o)hoje6tl+#1+Le7gyM z5C8Wfi+$+8ehp@GxM2h)i4*YP6I4Je1vH!)<>&!t8t_mPpu!pBJzu@>I0CPNP?4u6 zaj3s-97iVby224BpfyH7I@&Yz`@t-~eEW&~{!NA(>F4c9ob1nZPT+JQ+w=#z`WrLn zdd6`f+cb_hBf_q>#|dlCT+h4}64RdP$=s@Qbi2QO68$9jF1dPVnybBx7RVt*6}R9Z zcHl5sI>HZ}qv)hemnf7HaCe9&!k@x5Ji&*IeLvSJ_8K&MirMhlM^n#6ITyzJ@vL}1 z!KYEY_qj$TL1q&7h*1`@By(UKo%Le8HIz Y!Iv`-G~g@l*?_P0Y>K0Ac>gW%FOD@`{{R30 literal 0 HcmV?d00001 diff --git a/transaction-service/target/classes/com/yape/transaction/infrastructure/db/JpaRepository.class b/transaction-service/target/classes/com/yape/transaction/infrastructure/db/JpaRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..120e80f402e9af6581fc867ffc59a8f32404d6e0 GIT binary patch literal 386 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 E4{u>mVgLXD literal 0 HcmV?d00001 diff --git a/transaction-service/target/classes/com/yape/transaction/infrastructure/kafka/KafkaProducer.class b/transaction-service/target/classes/com/yape/transaction/infrastructure/kafka/KafkaProducer.class new file mode 100644 index 0000000000000000000000000000000000000000..93db8bafc65171cf79b0d7b5a0874b0014dab6ea GIT binary patch literal 1943 zcmb_d>uwuG6#m9>v$3;nic=EO(%?c%outmTrL=?%f#5h0)k-4UaS@0gll8>iWW8f% zXM-ZdyYP$uQ0@{u0PoUQsCvfhv`gg*EMaNqay;kuoo~+k{p63o0IcIv3mJx+p6og& zyeAy3co=a{`!aNRuNU|p@2_L+2a!{eVWfGeqdc-WV`1Eeg$ai9hy0j30S`OQ&i#WTzt(W%N3s@c6qwA3yrO_(ik6zTH@_G`DuP%Z+Ni zwNl z7NuJ#rBm74+uCrA<*PQX;cbQ-9cyT=5|im0qFYPuHgFjR)- zoxWNB4h`vL1$Wn6hFhhP zoVp7wDousB!!T9zL$Muq_l0U2cK2!Q@u0<(Z}tOWRzLKKDJ#Qy^6$NYf}E+zwB3{5 zsck)4^#2EB1^}y<6p-O|X?R*M#oVY*rur0vGol5CiB&Hc^x%9$#>x|$ejomS^wee3 zvKZ#-aj5;SX!(&(MOhC+Nq3iC0jkMx^dc>kkeX0-8bTfWo^Yw3n1_Mk9b|CPbS`>h z3a00i>|I)Ca2I2=ws4Qu?~&v)nlm)#7k|ar;?mE^{Xn}htk8IlW}~0QG;+9r_h~ni zoQ8{4+L7MC`UkYO$#NpO`^?`Otvvvqw!bFfsMKFg+%H%RGr6QPr7QsBdq92p}2~_Ay{9K{g{Bni-Zzw;)tT9RCrg6Og S*AzL}pj`%4Y!0NK08atBWG*-W literal 0 HcmV?d00001 diff --git a/transaction-service/target/classes/com/yape/transaction/infrastructure/kafka/events/TransactionUpdatedEvent.class b/transaction-service/target/classes/com/yape/transaction/infrastructure/kafka/events/TransactionUpdatedEvent.class new file mode 100644 index 0000000000000000000000000000000000000000..1074c9aa6de3733103e3ff3f5acc3c803182299b GIT binary patch literal 1924 zcmb_cT~8B16g^W4+t!5^1QbC*1#Jtmq9SVX6Et9o8u@t8L^Ev%SZH_a?koxa$p=X! zCOr59{87d`y9-^VAR#<#X6Mem=ghfx?)~+9?jirX)a=pT(sJv7M3^9|qGDV-?lV;UeUY?&VD83FIX=LakuH@2l z877NOX)AV(o0d_tE20|ZS@gpST_m_m$9Z%!B&5A49n)H4_>^sqb_x-NTyT9qeF47D z+NGK& zc`E-?nA{3OJV;BrXj78+Oju=5jf6qt5Oy2aM89my?P;%sjy=rmN0N> zyDnyhTXxL4w4G?kOqIKC)DU8PJPZ7-?ueMuP?#j#rL% zr5Xf+F+me8M*SK?mztq!D5>#AYa191;mdS#g?0;SFw>gO{{YL6?n29t?V^2$Ofd|G z?kZ@A{)YlrT*WooQIHSK{67UA!*%*k#>lJ=fliu(!^EeUCTLZzT7Hl0_(s2n8T7y< zWay-UHqs=#2l#rT@P6C~!>5T%%Raj;ZX)DbfwXqMGtxfhW5>8 zrs0@w&Df~yODhoQowr>(SP4$>7#iRXRxs8MY4vCN?U8bZr?$PE}1m?{FN) zP`T3A2u^DFrUWju0V#%LCX0`7ll;&DahwD%eZD-3Oc>aXQL_Dj+6{sHa_C!USz{?T zzqX?JqQU+oi%;Red++@!S8ZRVw2mu1_xY3-8hsvs&kH1<}E zTtnv>%UWGNSKd?OxvUr!+pQYr@mXV2`N2bCrxQ}3kL2*fx}=DqB%u4ga=F{^R5i5p z?v<0ucMlpT4H#o`KQ+C;G@hA`U5!g&SwB+C#L3H>EMs*UNbi5T&@5NWs(L`-y>W_= zTswBnHFYr;D4t1?`SuZbV?HlX^jycNK9C7e2b&46+ksB7p}6q1@=KtHD224M$9Q2^ zUkcprSktB%JUJpPO-$KT&^K>6&GwhsQK4taRa?`0tSvETbyXL*)3M5J_J$L@ew(XN zXK4{`Fxi8BjI$%A*yB^IifoH~8rD6Va}S6P?()CD)hbWubD8NsAf{)2L;7c~Qkdue zIdt*=GP+q*`*9WbxXQ(+v4Hzr@m?fVP#W6|PimQ4yr*T3{(`~bcV>RayN4Lh!g*z`Ur4|BFmgp_%>2A%%|_T{+_$B HsvrIXd5F>d literal 0 HcmV?d00001 diff --git a/transaction-service/target/classes/com/yape/transaction/infrastructure/rest/dto/TransactionRequest.class b/transaction-service/target/classes/com/yape/transaction/infrastructure/rest/dto/TransactionRequest.class new file mode 100644 index 0000000000000000000000000000000000000000..b6e3ba0dd071c0c5bcbf9c6cbb8e901928abb999 GIT binary patch literal 1279 zcmbu9-%k@k5XWcRD+R2A<+q3)D$*9h6@5dasS*uI#RN&;w|8q@xZACFdj$R*{{u}l zCO-J$n}3w?yS-vI++jk;r(B>rNC|@L}La-n-M~s#Z@JvRcB@+ovLn&>zjgc^_ zz~Pw3hI=PGltEAJ`FMm>q%w)Oug!iMhQJgfktDn$7%et>;asKNZYl!dCL(qftQc)v z#@iLXjClA;Dj*98ngZz5x9ZzSw9gGhJ7Y%tOvf&F>;C}er#9E^dhf?*ZB(x3-0e+R z**~N8OB0tJX%fpuT|`I3dGsF59!Hyc9kX(dQSI+b7@<%a^jEvlj}NC3`vW(~N+3}F zmEqPLo^^-Y<2=^^>NC}pO6;dmTf~PPEwqi+J`YGe52FY7cZo} z=EB8k-?F*TDqf)+3eK?NCBk|Y(hfOv4Z5j>rs+DA<%DLeG)r@tG;gH^TBJNJ?a>Wd z-lLmVwL-Vx|BRd&`a{5Rhyi(s0(k@{kNEfsV7(3EL+lKDq4t&7CmeF*VznO(LRJol zt2mW1Bf3L(Au+l~Yp}3@9qiV2thVumiUWcTU^&)%KC|e1!?srvM)$2B?AEd4Ri|p7 c=|QsIh1zE-e#9Y5Dnph>Bs2`F^*lCy1E=sdW&i*H literal 0 HcmV?d00001 diff --git a/transaction-service/target/classes/com/yape/transaction/infrastructure/rest/exception/GlobalExceptionHandler.class b/transaction-service/target/classes/com/yape/transaction/infrastructure/rest/exception/GlobalExceptionHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..79b359f52b3f833113bda2e176587c66ddf5bf99 GIT binary patch literal 4487 zcmcgv`BM{D9RED7g|I3_#e>!p6%broYio^F0fSHj)S!5^#ZB@EE1TW82S@ML+WWSb zJ+*&9+aFNuOs6ybt<%4$)9>32WHW3WP+KzW@%DRszV~~7{d4Dc0A2Vgf(lehsFG2Q z8is~T>ZGdZs*zQOQkQtzVW>Hz8JcsLp|Z7YL_#e?qetXAny&Pz_V|FBk3d3YE$WHd zc2vi;BUpiz5?0A*KqJF;)5ZDJ7*D8D)Sw<7TGI%DOpjID@7$qUu^EKAq-yM>7q|(=2zo ziINhEkh?ksa&K!m7|IeYqY5^qgoAwZDE3KsOvZjZ&d?Ets2nrHNxx23 z=osq8$RWL&Ws@_SN(2shUQ_j0l#2eCj5vBJb=7=cr?74=Asus#wCAhd!UF}a5B(CJ zmT>|JGP9_EUM!VTP88I>LV|dmVX>f~d*)eVwHT!8PV&nxx1BATG5P0B3YRIAJ|p8K zlH?`Rit9G@g`J@Z=-~-;teAvTC5?>yNNG|>9ce_y8JuOP@9sJ_nv9=39Umsr9px*l zScBw-^SB`4SsBmad4~S9nNw!eJXah`HEh*)nr2XYqp-MX$F;a(kxdGp&sycUZl+Xy zA&dJ|BcqE(wjw2}b-n@_QcLOz!7k;Sxbz-D)?9=tAtfV?4CO%4iT%3Hv#Kt7GGBno z5TSG*;I^%1DG+PsQ^s?9LAh}7n2fA&@Y?>tQ}N_rS7LNHo*aoMN8`!lP%;8dD80n6 zx~MOpqe?V7hE)m8;Dc^1#jR6nN~e`*!c43Bh-zu#y|7s2jB8YOClU`V5;2-7xp^C- zfgw<^P(UkYKg|$gXexy^lb?sTzm)XQVsU|$!bmwx0_%pgtU)8xBD=5u=ZR2&3wQ$@^ZSTv-V}=So&2TuhgqN(3LH6eOLW1A;zYMq!|0iAA zMCAC0W@}UwU4~&&$cq><6|?A4mjya45~PqPI0r1mV&c zt~@}M_wMf|-z~?v!L2|OXE+*!x8ToZd0}n93q=VGQ}-$Q-mQ%co354Sy_#sh&EbP< zkLZQVNwyx-a4eJNi`A8x)Y6=it)bgA9U7Qw-rw@s4M?u4#)lDngpVbBBI8prpSr{L zW@!?~rya$1^Lf*9lw*8MrJZ1*hCsi&=G2ht z17{KmKhU0Qk%z*Jm2iV$)7?pFf9QLL&S2;1Ff0D5LEBr@&~<{|6uL99+I|b{7ka3G zPFL|6v}eiDRrU#B!1TV;7LTX|^EFhWite4iK{|H}b%UMJ$nIIlw-KGg`bwPXjBe`i zpLXBImd-issKC#}Q0Xa&ddv-|MI+WDifweP6$;yZp#_B`o=%QuJrJG9<1#6)#SS{N zStQ3MdI8X1acIS*l?qJK+7zbgT>!m8_e#2V61v@Uc!XgNPl(G?40q52%;I=-U=~Av zV0fVYSDfyc#W?}g_r@kV!Gj=H(V4jp8?h42^hW1q`n6z_2YQRIAOLy>FA#_p;&{;m z?E`xWFO#g5^yL-$Z@2)K_UK;4YXz`J=`KPg3m^n|v;>PEneCp=CZBFWr;HiAPEvhM zZ-i(PU$5ZJ5KWvkjfZGzEvxD3f~L1ZG+iZcz3qAM9q+5a{Vu(~CoH4q&k4i(_yC{b WTUv>dB|9lezpJUk_xKSvvEg6Ecuw#D literal 0 HcmV?d00001