Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 20 additions & 100 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,104 +1,24 @@
# Logs
logs
### IntelliJ ###
.idea/
*.iml
out/
.vscode/

### Java / Maven ###
target/
*.class
*.jar
*.war
*.ear
hs_err_pid*
replay_pid*
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
### OS ###
.DS_Store
Thumbs.db

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
### Docker ###
*.env
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port
.env.*
165 changes: 112 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,82 +1,141 @@
# Yape Code Challenge :rocket:
# Yape Code Challenge – Async Payment Transaction Platform (Java 21)

Our code challenge will let you marvel us with your Jedi coding skills :smile:.
## Overview

Don't forget that the proper way to submit your work is to fork the repo and create a PR :wink: ... have fun !!
This solution implements an event-driven microservices architecture for financial transaction processing and anti-fraud validation.

- [Problem](#problem)
- [Tech Stack](#tech_stack)
- [Send us your challenge](#send_us_your_challenge)
The platform is composed of two independent microservices communicating asynchronously through Apache Kafka:

# Problem
- **ms-payment-transaction-command**
- Exposes REST APIs to create and retrieve transactions.
- Persists transactions with initial `PENDING` status.
- Publishes `TransactionCreated` events.
- Consumes validation results and updates transaction status.

Every time a financial transaction is created it must be validated by our anti-fraud microservice and then the same service sends a message back to update the transaction status.
For now, we have only three transaction statuses:
- **ms-risk-antifraud-evaluation**
- Consumes created transaction events.
- Applies anti-fraud rules.
- Publishes validation results (`APPROVED` / `REJECTED`).

<ol>
<li>pending</li>
<li>approved</li>
<li>rejected</li>
</ol>
The solution follows **Hexagonal Architecture** and applies the **Transactional Outbox Pattern** to guarantee consistency between database state and published Kafka events.

Every transaction with a value greater than 1000 should be rejected.
---

```mermaid
flowchart LR
Transaction -- Save Transaction with pending Status --> transactionDatabase[(Database)]
Transaction --Send transaction Created event--> Anti-Fraud
Anti-Fraud -- Send transaction Status Approved event--> Transaction
Anti-Fraud -- Send transaction Status Rejected event--> Transaction
Transaction -- Update transaction Status event--> transactionDatabase[(Database)]
```
## Architecture

# Tech Stack
- Java 21 / Spring Boot 3
- Apache Kafka (event backbone)
- PostgreSQL (transactional persistence)
- Transactional Outbox Pattern
- Idempotent Kafka consumers
- Optimistic concurrency control
- Docker Compose local environment

<ol>
<li>Node. You can use any framework you want (i.e. Nestjs with an ORM like TypeOrm or Prisma) </li>
<li>Any database</li>
<li>Kafka</li>
</ol>
---

We do provide a `Dockerfile` to help you get started with a dev environment.
## Event Flow

You must have two resources:
1. Client creates transaction → status `PENDING`
2. Transaction service stores transaction and outbox event
3. Outbox publisher emits `payment.transaction.created.v1`
4. Anti-fraud service validates business rule
5. Anti-fraud publishes `payment.transaction.validated.v1`
6. Transaction service consumes result and updates transaction status

1. Resource to create a transaction that must containt:
---

## Running locally

### Start infrastructure only (Kafka + Postgres)

```bash
docker compose up -d

Kafka UI
http://localhost:8088

PostgreSQL
localhost:5432
user: postgres
password: postgres
db: yape


Start full platform (infra + microservices)

docker compose --profile apps up -d --build


REST API
Create transaction

POST http://localhost:8081/transactions
json

```json
{
"accountExternalIdDebit": "Guid",
"accountExternalIdCredit": "Guid",
"accountExternalIdDebit": "a1c1e3d4-1111-4bda-8c01-abc123",
"accountExternalIdCredit": "b2f2a3d4-2222-4bda-8c01-def456",
"tranferTypeId": 1,
"value": 120
}
```

2. Resource to retrieve a transaction

```json
Get transaction

GET http://localhost:8081/transactions/{transactionExternalId}

Response:

{
"transactionExternalId": "Guid",
"transactionType": {
"name": ""
},
"transactionStatus": {
"name": ""
},
"transactionExternalId": "uuid",
"transactionType": { "name": "TRANSFER" },
"transactionStatus": { "name": "APPROVED" },
"value": 120,
"createdAt": "Date"
"createdAt": "2026-01-06T13:10:00Z"
}
```

## Optional
Kafka Topics

payment.transaction.created.v1

payment.transaction.validated.v1

payment.transaction.created.dlq.v1

payment.transaction.validated.dlq.v1

High concurrency & reliability strategy

This solution is designed for high-write / high-read scenarios:

Transactional outbox to avoid dual-write problems

Kafka-based async processing

Idempotent consumers

Conditional updates (WHERE status = 'PENDING')

Indexed reads

Optimistic locking

Horizontal scalability

Design principles

Hexagonal architecture

Clear domain boundaries

Infrastructure isolation

Event-driven communication

You can use any approach to store transaction data but you should consider that we may deal with high volume scenarios where we have a huge amount of writes and reads for the same data at the same time. How would you tackle this requirement?
Production-oriented reliability patterns

You can use Graphql;
Notes

# Send us your challenge
This implementation prioritizes reliability, scalability and data consistency, following real-world banking-grade asynchronous processing patterns.

When you finish your challenge, after forking a repository, you **must** open a pull request to our repository. There are no limitations to the implementation, you can follow the programming paradigm, modularization, and style that you feel is the most appropriate solution.

If you have any questions, please let us know.
53 changes: 52 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,76 @@
version: "3.7"

services:
postgres:
image: postgres:14
container_name: yape-postgres
ports:
- "5432:5432"
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=yape
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d yape"]
interval: 10s
timeout: 5s
retries: 5

zookeeper:
image: confluentinc/cp-zookeeper:5.5.3
container_name: yape-zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181

kafka:
image: confluentinc/cp-enterprise-kafka:5.5.3
container_name: yape-kafka
depends_on: [zookeeper]
ports:
- "9092:9092"
environment:
KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181"
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_BROKER_ID: 1
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_JMX_PORT: 9991

kafka-ui:
image: provectuslabs/kafka-ui:latest
container_name: yape-kafka-ui
depends_on:
- kafka
- zookeeper
ports:
- "8088:8080"
environment:
- KAFKA_CLUSTERS_0_NAME=yape
- KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:29092
- KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper:2181

ms-payment-transaction-command:
build: ./ms-payment-transaction-command
container_name: ms-payment-transaction-command
depends_on:
postgres:
condition: service_healthy
kafka:
condition: service_started
ports:
- 9092:9092
- "8081:8080"
environment:
- SPRING_PROFILES_ACTIVE=local
profiles: ["apps"]

ms-risk-antifraud-evaluation:
build: ./ms-risk-antifraud-evaluation
container_name: ms-risk-antifraud-evaluation
depends_on:
kafka:
condition: service_started
ports:
- "8082:8080"
environment:
- SPRING_PROFILES_ACTIVE=local
profiles: ["apps"]
Loading