diff --git a/.gitignore b/.gitignore
index 67045665db..bed58806b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -102,3 +102,15 @@ dist
# TernJS port file
.tern-port
+
+
+*.class
+*.jar
+
+#Maven
+target/
+dist/
+
+# JetBrains IDE
+.idea/
+.iml/
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000000..ab1f4164ed
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000000..40c03a4e0e
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000000..63e9001932
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000000..712ab9d985
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000000..9dc782bb2d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000000..966e525331
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ 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/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000000..8dea6c227c
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,3 @@
+wrapperVersion=3.3.4
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
diff --git a/app/app.iml b/app/app.iml
new file mode 100644
index 0000000000..a80f8afc3c
--- /dev/null
+++ b/app/app.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/pom.xml b/app/pom.xml
new file mode 100644
index 0000000000..67a3009db5
--- /dev/null
+++ b/app/pom.xml
@@ -0,0 +1,67 @@
+
+ 4.0.0
+
+
+ com.yape.challenge
+ transaction-system
+ 1.0.0
+
+
+ app
+
+
+
+ com.yape.challenge
+ core
+ 1.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.postgresql
+ postgresql
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.5.0
+
+
+ org.mapstruct
+ mapstruct
+ ${mapstruct.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+ provided
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/app/src/main/java/com/challenge/transaction/app/TransactionApplication.java b/app/src/main/java/com/challenge/transaction/app/TransactionApplication.java
new file mode 100644
index 0000000000..2d6ac21920
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/TransactionApplication.java
@@ -0,0 +1,12 @@
+package com.challenge.transaction.app;
+
+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/app/src/main/java/com/challenge/transaction/app/config/BeanConfiguration.java b/app/src/main/java/com/challenge/transaction/app/config/BeanConfiguration.java
new file mode 100644
index 0000000000..1f4fffac94
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/config/BeanConfiguration.java
@@ -0,0 +1,34 @@
+package com.challenge.transaction.app.config;
+
+import com.challenge.transaction.core.ports.input.CreateTransactionUseCase;
+import com.challenge.transaction.core.ports.input.GetTransactionUseCase;
+import com.challenge.transaction.core.ports.output.TransactionEventPublisher;
+import com.challenge.transaction.core.ports.output.TransactionRepository;
+import com.challenge.transaction.core.usecase.CreateTransactionService;
+import com.challenge.transaction.core.usecase.GetTransactionService;
+import com.challenge.transaction.core.usecase.UpdateTransactionStatusService;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class BeanConfiguration {
+
+ @Bean
+ public CreateTransactionUseCase createTransactionUseCase(
+ TransactionRepository repository,
+ TransactionEventPublisher publisher) {
+ return new CreateTransactionService(repository, publisher);
+ }
+
+ @Bean
+ public GetTransactionUseCase getTransactionUseCase(
+ TransactionRepository repository) {
+ return new GetTransactionService(repository);
+ }
+
+ @Bean
+ public UpdateTransactionStatusService updateTransactionStatusService(
+ TransactionRepository repository) {
+ return new UpdateTransactionStatusService(repository);
+ }
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/config/KafkaConfig.java b/app/src/main/java/com/challenge/transaction/app/config/KafkaConfig.java
new file mode 100644
index 0000000000..e8e5416cd5
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/config/KafkaConfig.java
@@ -0,0 +1,19 @@
+package com.challenge.transaction.app.config;
+
+import org.apache.kafka.clients.admin.NewTopic;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class KafkaConfig {
+
+ @Bean
+ public NewTopic transactionCreatedTopic() {
+ return new NewTopic("transaction-created", 3, (short) 1);
+ }
+
+ @Bean
+ public NewTopic transactionValidatedTopic() {
+ return new NewTopic("transaction-validated", 3, (short) 1);
+ }
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/config/SwaggerConfig.java b/app/src/main/java/com/challenge/transaction/app/config/SwaggerConfig.java
new file mode 100644
index 0000000000..ed7f13584f
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/config/SwaggerConfig.java
@@ -0,0 +1,22 @@
+package com.challenge.transaction.app.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SwaggerConfig {
+
+ @Bean
+ public OpenAPI transactionApi() {
+
+ return new OpenAPI()
+ .info(new Info()
+ .title("Transaction API")
+ .description("Reto de Yape")
+ .version("1.0"));
+
+ }
+
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/controller/TransactionController.java b/app/src/main/java/com/challenge/transaction/app/controller/TransactionController.java
new file mode 100644
index 0000000000..7dc457c9b0
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/controller/TransactionController.java
@@ -0,0 +1,38 @@
+package com.challenge.transaction.app.controller;
+
+import com.challenge.transaction.app.dto.CreateTransactionRequest;
+import com.challenge.transaction.app.dto.TransactionResponse;
+import com.challenge.transaction.app.mapper.TransactionMapper;
+import com.challenge.transaction.core.domain.model.Transaction;
+import com.challenge.transaction.core.ports.input.CreateTransactionUseCase;
+import com.challenge.transaction.core.ports.input.GetTransactionUseCase;
+import lombok.AllArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/transactions")
+@AllArgsConstructor
+public class TransactionController {
+
+ private final CreateTransactionUseCase createUseCase;
+ private final GetTransactionUseCase getUseCase;
+
+ @PostMapping
+ public ResponseEntity create(
+ @RequestBody CreateTransactionRequest request) {
+ Transaction transaction = TransactionMapper.MAPPER.toDomain(request);
+ Transaction saved = createUseCase.create(transaction);
+
+ return ResponseEntity.ok(TransactionMapper.MAPPER.toResponse(saved));
+ }
+
+ @GetMapping("/{id}")
+ public ResponseEntity get(@PathVariable UUID id) {
+ Transaction transaction = getUseCase.get(id);
+
+ return ResponseEntity.ok(TransactionMapper.MAPPER.toResponse(transaction));
+ }
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/dto/CreateTransactionRequest.java b/app/src/main/java/com/challenge/transaction/app/dto/CreateTransactionRequest.java
new file mode 100644
index 0000000000..33f38feb08
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/dto/CreateTransactionRequest.java
@@ -0,0 +1,21 @@
+package com.challenge.transaction.app.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.UUID;
+
+@Getter
+@Setter
+public class CreateTransactionRequest implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 3741074636662158145L;
+
+ private UUID accountExternalIdDebit;
+ private UUID accountExternalIdCredit;
+ private Integer transferTypeId;
+ private Double value;
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/dto/TransactionResponse.java b/app/src/main/java/com/challenge/transaction/app/dto/TransactionResponse.java
new file mode 100644
index 0000000000..52ffd1a52a
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/dto/TransactionResponse.java
@@ -0,0 +1,41 @@
+package com.challenge.transaction.app.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@Setter
+public class TransactionResponse implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = -5230394309771679808L;
+
+ private UUID transactionExternalId;
+ private TypeDto transactionType;
+ private StatusDto transactionStatus;
+ private Double value;
+ private LocalDateTime createdAt;
+
+ @Getter
+ @Setter
+ public static class StatusDto implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 3144852181997944515L;
+
+ private String name;
+ }
+
+ @Getter
+ @Setter
+ public static class TypeDto implements Serializable {
+ @Serial
+ private static final long serialVersionUID = -5686189229013063853L;
+
+ private String name;
+ }
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/kafka/FraudResultConsumer.java b/app/src/main/java/com/challenge/transaction/app/kafka/FraudResultConsumer.java
new file mode 100644
index 0000000000..ed95ac3967
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/kafka/FraudResultConsumer.java
@@ -0,0 +1,47 @@
+package com.challenge.transaction.app.kafka;
+
+import com.challenge.transaction.core.domain.enums.TransactionStatus;
+import com.challenge.transaction.core.domain.event.FraudResultEvent;
+import com.challenge.transaction.core.domain.model.Transaction;
+import com.challenge.transaction.core.usecase.UpdateTransactionStatusService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+@Slf4j
+@Component
+@AllArgsConstructor
+public class FraudResultConsumer {
+ private final UpdateTransactionStatusService service;
+ private final FraudResultProducer producer;
+
+ @KafkaListener(topics = "transaction-created")
+ public void consume(Transaction transaction) {
+ String status;
+
+ if (transaction.getValue() > 1000) {
+ status = TransactionStatus.REJECTED.name();
+ } else {
+ status = TransactionStatus.APPROVED.name();
+ }
+
+ producer.publish(transaction.getTransactionExternalId().toString(), status);
+ }
+
+ @KafkaListener(topics = "transaction-validated")
+ public void consume(FraudResultEvent event) {
+ log.info("[TRANSACTION-VALIDATED] init...");
+ log.info("[TRANSACTION-VALIDATED] transactionId: {}",event.getTransactionId());
+
+ service.updateStatus(
+ UUID.fromString(event.getTransactionId()),
+ event.getStatus()
+ );
+
+ log.info("[TRANSACTION-VALIDATED] finished.");
+ }
+
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/kafka/FraudResultProducer.java b/app/src/main/java/com/challenge/transaction/app/kafka/FraudResultProducer.java
new file mode 100644
index 0000000000..04c46dc5d8
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/kafka/FraudResultProducer.java
@@ -0,0 +1,28 @@
+package com.challenge.transaction.app.kafka;
+
+import com.challenge.transaction.core.domain.event.FraudResultEvent;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class FraudResultProducer {
+
+ private final KafkaTemplate kafkaTemplate;
+
+ public void publish(String transactionId, String status) {
+ log.info("[FRAUD-VALIDATION] init...");
+ log.info("[FRAUD-VALIDATION] transactionId = {}", transactionId);
+
+ FraudResultEvent event = new FraudResultEvent();
+ event.setTransactionId(transactionId);
+ event.setStatus(status);
+
+ kafkaTemplate.send("transaction-validated", event);
+
+ log.info("[FRAUD-VALIDATION] valid finish.");
+ }
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/kafka/TransactionProducer.java b/app/src/main/java/com/challenge/transaction/app/kafka/TransactionProducer.java
new file mode 100644
index 0000000000..ed17ca726f
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/kafka/TransactionProducer.java
@@ -0,0 +1,26 @@
+package com.challenge.transaction.app.kafka;
+
+import com.challenge.transaction.core.domain.model.Transaction;
+import com.challenge.transaction.core.ports.output.TransactionEventPublisher;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@AllArgsConstructor
+public class TransactionProducer implements TransactionEventPublisher {
+
+ private final KafkaTemplate kafkaTemplate;
+
+ @Override
+ public void publishTransactionCreated(Transaction transaction) {
+ log.info("[TRANSACTION-SEND] init...");
+ log.info("[TRANSACTION-SEND] transactionId: {}", transaction.getTransactionExternalId());
+
+ kafkaTemplate.send("transaction-created", transaction);
+
+ log.info("[TRANSACTION-SEND] init...");
+ }
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/mapper/TransactionMapper.java b/app/src/main/java/com/challenge/transaction/app/mapper/TransactionMapper.java
new file mode 100644
index 0000000000..1a4802b0cd
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/mapper/TransactionMapper.java
@@ -0,0 +1,44 @@
+package com.challenge.transaction.app.mapper;
+
+import com.challenge.transaction.app.dto.CreateTransactionRequest;
+import com.challenge.transaction.app.dto.TransactionResponse;
+import com.challenge.transaction.core.domain.enums.TransactionStatus;
+import com.challenge.transaction.core.domain.model.Transaction;
+import com.challenge.transaction.core.domain.model.TransactionType;
+import org.mapstruct.Mapping;
+import org.mapstruct.Named;
+import org.mapstruct.factory.Mappers;
+import org.springframework.stereotype.Component;
+
+@Component
+public interface TransactionMapper {
+ TransactionMapper MAPPER = Mappers.getMapper(TransactionMapper.class);
+
+ @Mapping(target = "transactionType", source = "transferTypeId", qualifiedByName = "toTransactionType")
+ Transaction toDomain(CreateTransactionRequest request);
+
+ @Mapping(target = "transactionStatus", source = "status", qualifiedByName = "toTransactionStatus")
+ @Mapping(target = "transactionType", source = "transactionType", qualifiedByName = "toTransactionTypeName")
+ TransactionResponse toResponse(Transaction transaction);
+
+ @Named("toTransactionStatus")
+ default TransactionResponse.StatusDto toTransactionStatus(TransactionStatus status) {
+ TransactionResponse.StatusDto statusDto = new TransactionResponse.StatusDto();
+ statusDto.setName(status.name());
+
+ return statusDto;
+ }
+
+ @Named("toTransactionType")
+ default TransactionType toTransactionType(Integer transferTypeId) {
+ return TransactionType.fromId(transferTypeId);
+ }
+
+ @Named("toTransactionTypeName")
+ default TransactionResponse.TypeDto toTransactionTypeName(TransactionType transactionType) {
+ TransactionResponse.TypeDto typeDto = new TransactionResponse.TypeDto();
+ typeDto.setName(transactionType.name());
+
+ return typeDto;
+ }
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/persistence/TransactionEntity.java b/app/src/main/java/com/challenge/transaction/app/persistence/TransactionEntity.java
new file mode 100644
index 0000000000..d0064a4d3c
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/persistence/TransactionEntity.java
@@ -0,0 +1,31 @@
+package com.challenge.transaction.app.persistence;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.data.annotation.Id;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Entity
+@Table(name = "transactions")
+@Getter
+@Setter
+public class TransactionEntity implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 4397230746145932537L;
+
+ @Id
+ private UUID transactionExternalId;
+ private UUID accountExternalIdDebit;
+ private UUID accountExternalIdCredit;
+ private Integer transferTypeId;
+ private Double value;
+ private String status;
+ private LocalDateTime createdAt;
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/persistence/TransactionJpaRepository.java b/app/src/main/java/com/challenge/transaction/app/persistence/TransactionJpaRepository.java
new file mode 100644
index 0000000000..47a1fec50d
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/persistence/TransactionJpaRepository.java
@@ -0,0 +1,9 @@
+package com.challenge.transaction.app.persistence;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.UUID;
+
+public interface TransactionJpaRepository
+ extends JpaRepository {
+}
diff --git a/app/src/main/java/com/challenge/transaction/app/persistence/TransactionRepositoryAdapter.java b/app/src/main/java/com/challenge/transaction/app/persistence/TransactionRepositoryAdapter.java
new file mode 100644
index 0000000000..366303ddbb
--- /dev/null
+++ b/app/src/main/java/com/challenge/transaction/app/persistence/TransactionRepositoryAdapter.java
@@ -0,0 +1,56 @@
+package com.challenge.transaction.app.persistence;
+
+import com.challenge.transaction.core.domain.enums.TransactionStatus;
+import com.challenge.transaction.core.domain.model.Transaction;
+import com.challenge.transaction.core.ports.output.TransactionRepository;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+import java.util.UUID;
+
+@Slf4j
+@Component
+@AllArgsConstructor
+public class TransactionRepositoryAdapter implements TransactionRepository {
+
+ private final TransactionJpaRepository repository;
+
+ @Override
+ public Transaction save(Transaction transaction) {
+ log.info("[TRANSACTION-SAVE] init...");
+ TransactionEntity entity = new TransactionEntity();
+
+ entity.setTransactionExternalId(transaction.getTransactionExternalId());
+ entity.setValue(transaction.getValue());
+ entity.setStatus(transaction.getStatus().name());
+ entity.setCreatedAt(transaction.getCreatedAt());
+
+ repository.save(entity);
+
+ log.info("[TRANSACTION-SAVE] transactionExternalId: {}", transaction.getTransactionExternalId());
+ log.info("[TRANSACTION-SAVE] end.");
+
+ return transaction;
+ }
+
+ @Override
+ public Optional findByExternalId(UUID id) {
+ return repository.findById(id).map(e -> {
+ Transaction t = new Transaction();
+ t.setTransactionExternalId(e.getTransactionExternalId());
+ t.setValue(e.getValue());
+ return t;
+ });
+ }
+
+ @Override
+ public void updateStatus(UUID id, String status) {
+
+ repository.findById(id).ifPresent(entity -> {
+ entity.setStatus(status);
+ repository.save(entity);
+ });
+ }
+}
diff --git a/app/src/main/resources/application.yaml b/app/src/main/resources/application.yaml
new file mode 100644
index 0000000000..3c70d423a7
--- /dev/null
+++ b/app/src/main/resources/application.yaml
@@ -0,0 +1,12 @@
+spring:
+ datasource:
+ url: jdbc:postgresql://localhost:5432/transactions
+ username: postgres
+ password: postgres
+
+ jpa:
+ hibernate:
+ ddl-auto: update
+
+ kafka:
+ bootstrap-servers: localhost:9092
diff --git a/core/core.iml b/core/core.iml
new file mode 100644
index 0000000000..a80f8afc3c
--- /dev/null
+++ b/core/core.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/pom.xml b/core/pom.xml
new file mode 100644
index 0000000000..5d6be521d6
--- /dev/null
+++ b/core/pom.xml
@@ -0,0 +1,27 @@
+
+ 4.0.0
+
+
+ com.yape.challenge
+ transaction-system
+ 1.0.0
+
+
+ core
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.32
+ provided
+
+
+ jakarta.validation
+ jakarta.validation-api
+
+
+
diff --git a/core/src/main/java/com/challenge/transaction/core/domain/enums/TransactionStatus.java b/core/src/main/java/com/challenge/transaction/core/domain/enums/TransactionStatus.java
new file mode 100644
index 0000000000..75a7c14072
--- /dev/null
+++ b/core/src/main/java/com/challenge/transaction/core/domain/enums/TransactionStatus.java
@@ -0,0 +1,7 @@
+package com.challenge.transaction.core.domain.enums;
+
+public enum TransactionStatus {
+ PENDING,
+ APPROVED,
+ REJECTED
+}
diff --git a/core/src/main/java/com/challenge/transaction/core/domain/event/FraudResultEvent.java b/core/src/main/java/com/challenge/transaction/core/domain/event/FraudResultEvent.java
new file mode 100644
index 0000000000..f90f885dc7
--- /dev/null
+++ b/core/src/main/java/com/challenge/transaction/core/domain/event/FraudResultEvent.java
@@ -0,0 +1,18 @@
+package com.challenge.transaction.core.domain.event;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Getter
+@Setter
+public class FraudResultEvent implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 7775625147330100345L;
+
+ private String transactionId;
+ private String status;
+}
diff --git a/core/src/main/java/com/challenge/transaction/core/domain/model/Transaction.java b/core/src/main/java/com/challenge/transaction/core/domain/model/Transaction.java
new file mode 100644
index 0000000000..98752fb35d
--- /dev/null
+++ b/core/src/main/java/com/challenge/transaction/core/domain/model/Transaction.java
@@ -0,0 +1,26 @@
+package com.challenge.transaction.core.domain.model;
+
+import com.challenge.transaction.core.domain.enums.TransactionStatus;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@Setter
+public class Transaction implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = -1915736314429727217L;
+
+ private UUID transactionExternalId;
+ private UUID accountExternalIdDebit;
+ private UUID accountExternalIdCredit;
+ private TransactionType transactionType;
+ private Double value;
+ private TransactionStatus status;
+ private LocalDateTime createdAt;
+}
diff --git a/core/src/main/java/com/challenge/transaction/core/domain/model/TransactionType.java b/core/src/main/java/com/challenge/transaction/core/domain/model/TransactionType.java
new file mode 100644
index 0000000000..994115fe28
--- /dev/null
+++ b/core/src/main/java/com/challenge/transaction/core/domain/model/TransactionType.java
@@ -0,0 +1,27 @@
+package com.challenge.transaction.core.domain.model;
+
+import lombok.Getter;
+
+@Getter
+public enum TransactionType {
+
+ TRANSFER(1);
+
+ private final int id;
+
+ TransactionType(int id) {
+ this.id = id;
+ }
+
+ public static TransactionType fromId(int id) {
+
+ for (TransactionType type : values()) {
+ if (type.id == id) {
+ return type;
+ }
+ }
+
+ throw new IllegalArgumentException("Tipo de transaccion no registrada");
+ }
+
+}
diff --git a/core/src/main/java/com/challenge/transaction/core/ports/input/CreateTransactionUseCase.java b/core/src/main/java/com/challenge/transaction/core/ports/input/CreateTransactionUseCase.java
new file mode 100644
index 0000000000..68e9edaf32
--- /dev/null
+++ b/core/src/main/java/com/challenge/transaction/core/ports/input/CreateTransactionUseCase.java
@@ -0,0 +1,7 @@
+package com.challenge.transaction.core.ports.input;
+
+import com.challenge.transaction.core.domain.model.Transaction;
+
+public interface CreateTransactionUseCase {
+ Transaction create(Transaction transaction);
+}
diff --git a/core/src/main/java/com/challenge/transaction/core/ports/input/GetTransactionUseCase.java b/core/src/main/java/com/challenge/transaction/core/ports/input/GetTransactionUseCase.java
new file mode 100644
index 0000000000..8d24e9db17
--- /dev/null
+++ b/core/src/main/java/com/challenge/transaction/core/ports/input/GetTransactionUseCase.java
@@ -0,0 +1,9 @@
+package com.challenge.transaction.core.ports.input;
+
+import com.challenge.transaction.core.domain.model.Transaction;
+
+import java.util.UUID;
+
+public interface GetTransactionUseCase {
+ Transaction get(UUID transactionExternalId);
+}
diff --git a/core/src/main/java/com/challenge/transaction/core/ports/output/TransactionEventPublisher.java b/core/src/main/java/com/challenge/transaction/core/ports/output/TransactionEventPublisher.java
new file mode 100644
index 0000000000..492f68e3d5
--- /dev/null
+++ b/core/src/main/java/com/challenge/transaction/core/ports/output/TransactionEventPublisher.java
@@ -0,0 +1,7 @@
+package com.challenge.transaction.core.ports.output;
+
+import com.challenge.transaction.core.domain.model.Transaction;
+
+public interface TransactionEventPublisher {
+ void publishTransactionCreated(Transaction transaction);
+}
diff --git a/core/src/main/java/com/challenge/transaction/core/ports/output/TransactionRepository.java b/core/src/main/java/com/challenge/transaction/core/ports/output/TransactionRepository.java
new file mode 100644
index 0000000000..4aae30f1c8
--- /dev/null
+++ b/core/src/main/java/com/challenge/transaction/core/ports/output/TransactionRepository.java
@@ -0,0 +1,14 @@
+package com.challenge.transaction.core.ports.output;
+
+import com.challenge.transaction.core.domain.model.Transaction;
+
+import java.util.Optional;
+import java.util.UUID;
+
+public interface TransactionRepository {
+ Transaction save(Transaction transaction);
+
+ Optional findByExternalId(UUID transactionExternalId);
+
+ void updateStatus(UUID transactionExternalId, String status);
+}
diff --git a/core/src/main/java/com/challenge/transaction/core/usecase/CreateTransactionService.java b/core/src/main/java/com/challenge/transaction/core/usecase/CreateTransactionService.java
new file mode 100644
index 0000000000..2ddfbd3cab
--- /dev/null
+++ b/core/src/main/java/com/challenge/transaction/core/usecase/CreateTransactionService.java
@@ -0,0 +1,32 @@
+package com.challenge.transaction.core.usecase;
+
+import com.challenge.transaction.core.domain.enums.TransactionStatus;
+import com.challenge.transaction.core.domain.model.Transaction;
+import com.challenge.transaction.core.ports.input.CreateTransactionUseCase;
+import com.challenge.transaction.core.ports.output.TransactionEventPublisher;
+import com.challenge.transaction.core.ports.output.TransactionRepository;
+import lombok.RequiredArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@RequiredArgsConstructor
+public class CreateTransactionService implements CreateTransactionUseCase {
+
+ private final TransactionRepository repository;
+ private final TransactionEventPublisher publisher;
+
+ @Override
+ public Transaction create(Transaction transaction) {
+ transaction.setTransactionExternalId(UUID.randomUUID());
+ transaction.setStatus(TransactionStatus.PENDING);
+ transaction.setCreatedAt(LocalDateTime.now());
+
+ Transaction saved = repository.save(transaction);
+
+ publisher.publishTransactionCreated(saved);
+
+ return saved;
+ }
+
+}
diff --git a/core/src/main/java/com/challenge/transaction/core/usecase/GetTransactionService.java b/core/src/main/java/com/challenge/transaction/core/usecase/GetTransactionService.java
new file mode 100644
index 0000000000..83d39cd737
--- /dev/null
+++ b/core/src/main/java/com/challenge/transaction/core/usecase/GetTransactionService.java
@@ -0,0 +1,20 @@
+package com.challenge.transaction.core.usecase;
+
+import com.challenge.transaction.core.domain.model.Transaction;
+import com.challenge.transaction.core.ports.input.GetTransactionUseCase;
+import com.challenge.transaction.core.ports.output.TransactionRepository;
+import lombok.AllArgsConstructor;
+
+import java.util.UUID;
+
+@AllArgsConstructor
+public class GetTransactionService implements GetTransactionUseCase {
+ private final TransactionRepository repository;
+
+ @Override
+ public Transaction get(UUID transactionExternalId) {
+ return repository.findByExternalId(transactionExternalId)
+ .orElseThrow(() -> new RuntimeException("Transaction not found"));
+ }
+
+}
diff --git a/core/src/main/java/com/challenge/transaction/core/usecase/UpdateTransactionStatusService.java b/core/src/main/java/com/challenge/transaction/core/usecase/UpdateTransactionStatusService.java
new file mode 100644
index 0000000000..4258fcf3ff
--- /dev/null
+++ b/core/src/main/java/com/challenge/transaction/core/usecase/UpdateTransactionStatusService.java
@@ -0,0 +1,16 @@
+package com.challenge.transaction.core.usecase;
+
+import com.challenge.transaction.core.ports.output.TransactionRepository;
+import lombok.AllArgsConstructor;
+
+import java.util.UUID;
+
+@AllArgsConstructor
+public class UpdateTransactionStatusService {
+
+ private final TransactionRepository repository;
+
+ public void updateStatus(UUID transactionId, String status) {
+ repository.updateStatus(transactionId, status);
+ }
+}
diff --git a/core/src/test/java/com/challenge/transaction/core/usercase/CreateTransactionServiceTest.java b/core/src/test/java/com/challenge/transaction/core/usercase/CreateTransactionServiceTest.java
new file mode 100644
index 0000000000..14f88695ea
--- /dev/null
+++ b/core/src/test/java/com/challenge/transaction/core/usercase/CreateTransactionServiceTest.java
@@ -0,0 +1,45 @@
+package com.challenge.transaction.core.usercase;
+
+import com.challenge.transaction.core.domain.model.Transaction;
+import com.challenge.transaction.core.ports.output.TransactionEventPublisher;
+import com.challenge.transaction.core.ports.output.TransactionRepository;
+import com.challenge.transaction.core.usecase.CreateTransactionService;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class CreateTransactionServiceTest {
+
+ @Mock
+ private TransactionRepository repository;
+
+ @Mock
+ private TransactionEventPublisher publisher;
+
+ @InjectMocks
+ private CreateTransactionService service;
+
+ @Test
+ void shouldCreateTransactionAndPublishEvent() {
+
+ Transaction transaction = new Transaction();
+ transaction.setValue(100.0);
+
+ when(repository.save(any())).thenReturn(transaction);
+
+ Transaction result = service.create(transaction);
+
+ verify(repository).save(any());
+ verify(publisher).publishTransactionCreated(any());
+
+ assertNotNull(result);
+ }
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000..3b32b09987
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,65 @@
+
+ 4.0.0
+
+ com.yape.challenge
+ transaction-system
+ 1.0.0
+ pom
+
+
+ core
+ app
+
+
+
+ 21
+ 3.3.2
+ 1.5.5.Final
+ 1.18.32
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.version}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.mockito
+ mockito-junit-jupiter
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ ${java.version}
+ ${java.version}
+
+
+
+
+
+