Skip to content

EmmanuelAdah/transactile

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

16 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ’³ Payment System β€” Spring Boot + GraphQL

A production-ready payment processing system built with Spring Boot 3.2, Spring for GraphQL, PostgreSQL, and JWT security. Designed for reliability, scalability, and observability.


πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                          Client (Web / Mobile)                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚  HTTP / WebSocket
                            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Spring Security (JWT Filter)                       β”‚
β”‚              JwtAuthenticationFilter β†’ SecurityContextHolder          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  GraphQL Layer (/graphql, /graphql-ws)               β”‚
β”‚                                                                       β”‚
β”‚  @QueryMapping     @MutationMapping     @SubscriptionMapping          β”‚
β”‚  @SchemaMapping    @Argument            @ContextValue                 β”‚
β”‚                                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ PaymentGQL     β”‚  β”‚SubscriptionCtrl  β”‚  β”‚ GraphQLExceptionRslvrβ”‚  β”‚
β”‚  β”‚ Controller     β”‚  β”‚  (Reactor Sinks) β”‚  β”‚ (Error Classificationβ”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         Service Layer                                 β”‚
β”‚                                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚PaymentServiceβ”‚ β”‚AccountServiceβ”‚ β”‚RiskService β”‚ β”‚AuditService  β”‚  β”‚
β”‚  β”‚              β”‚ β”‚              β”‚ β”‚            β”‚ β”‚  (@Async)    β”‚  β”‚
β”‚  β”‚ @CircuitBreakβ”‚ β”‚ @Cacheable   β”‚ β”‚            β”‚ β”‚              β”‚  β”‚
β”‚  β”‚ @Retry       β”‚ β”‚ @CacheEvict  β”‚ β”‚            β”‚ β”‚              β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Data Layer (JPA / Hibernate)                    β”‚
β”‚                                                                       β”‚
β”‚  Accounts  ──────── Payments ──────── Transactions                   β”‚
β”‚                        β”‚                                              β”‚
β”‚                        β”œβ”€β”€β”€β”€ Refunds                                  β”‚
β”‚                        └──── AuditLogs                                β”‚
β”‚                                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  PostgreSQL (HikariCP pool, Flyway migrations, Optimistic Lock) β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸš€ Quick Start

Prerequisites

  • Java 21+
  • Maven 3.8+
  • Docker & Docker Compose

Run with Docker

# Start PostgreSQL
docker run -d \
  --name payments-db \
  -e POSTGRES_DB=payments \
  -e POSTGRES_USER=payments \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 \
  postgres:16-alpine

# Run the application
./mvnw spring-boot:run \
  -Dspring-boot.run.jvmArguments="-DGRAPHIQL_ENABLED=true"

GraphiQL Playground

Open: http://localhost:8080/graphiql


πŸ“‘ GraphQL API

Queries

# Get account
query {
  account(id: "a0000000-0000-0000-0000-000000000001") {
    id email fullName status currency balance
    payments(page: 0, size: 5) {
      content { id referenceId amount status }
      totalElements hasNext
    }
  }
}

# List payments with filters
query {
  payments(
    filter: { status: COMPLETED, currency: USD, minAmount: "100" }
    sort: { field: "createdAt", direction: DESC }
    page: 0, size: 20
  ) {
    content {
      referenceId amount status processingFee netAmount
      sender { email }
      recipient { email }
    }
    totalElements totalPages
  }
}

# Analytics
query {
  paymentStats(currency: USD, period: "month") {
    totalVolume totalCount successRate averageAmount
  }
}

Mutations

# Create account
mutation {
  createAccount(input: {
    email: "merchant@example.com"
    fullName: "My Merchant"
    currency: USD
    externalId: "MERCHANT-001"
  }) { id email status }
}

# Initiate payment
mutation {
  initiatePayment(input: {
    senderId: "..."
    recipientId: "..."
    amount: "250.00"
    currency: USD
    method: CREDIT_CARD
    description: "Order #12345"
    idempotencyKey: "unique-client-key-001"
  }) {
    success
    message
    payment { id referenceId status processingFee netAmount }
    errors { field message }
  }
}

# Confirm payment
mutation {
  confirmPayment(paymentId: "...") {
    success payment { status completedAt }
  }
}

# Refund
mutation {
  refundPayment(input: {
    paymentId: "..."
    amount: "100.00"
    reason: "Customer requested"
    idempotencyKey: "refund-unique-key-001"
  }) {
    success
    refund { id amount status processedAt }
  }
}

Subscriptions (WebSocket)

subscription {
  paymentStatusUpdated(paymentId: "...") {
    id status updatedAt
  }
}

πŸ”‘ Key Design Decisions

Concern Solution
Idempotency Every mutation carries an idempotencyKey; duplicate requests return the original result
Concurrency Pessimistic locks (SELECT FOR UPDATE) on account rows during balance changes
Optimistic Locking @Version on Account and Payment entities to detect stale updates
Validation Domain-layer PaymentValidator + Bean Validation annotations on DTOs
Fraud Detection RiskService scoring (amount, velocity, account age, KYC) β€” extend for ML integration
Resilience Resilience4j Circuit Breaker + Retry wrapping the payment processor
Audit Async AuditService writing immutable AuditLog records with IP / user-agent
Error Handling GraphQLExceptionResolver maps domain exceptions to typed GraphQL errors
Observability Micrometer + Prometheus metrics on field fetch latency + error rates
Security JWT-based stateless auth; @PreAuthorize on all resolvers
Schema Mapping @QueryMapping, @MutationMapping, @SchemaMapping, @SubscriptionMapping

πŸ§ͺ Testing

# All tests
./mvnw test

# Unit tests only
./mvnw test -Dtest="*Test"

# Integration tests only
./mvnw test -Dtest="*IntegrationTest"

Test Coverage Areas

Layer Test File Annotations
Domain Model DomainModelTest @Nested, @DisplayName
Validation PaymentValidatorTest @ParameterizedTest, @CsvSource
Service (unit) PaymentServiceTest @ExtendWith(MockitoExtension)
Service (unit) AccountServiceTest @Mock, @InjectMocks
Repository RepositoryIntegrationTest @DataJpaTest
GraphQL layer PaymentGraphQLControllerTest @GraphQlTest, GraphQlTester
Full lifecycle PaymentSystemIntegrationTest @SpringBootTest, @Transactional

πŸ“Š Monitoring

# Health
curl http://localhost:8080/actuator/health

# Prometheus metrics
curl http://localhost:8080/actuator/prometheus

# Custom metrics
graphql_field_fetch_seconds_*   # per-field GraphQL latency
graphql_errors_total             # total GraphQL errors

πŸ“ Project Structure

src/main/java/com/payments/
β”œβ”€β”€ PaymentSystemApplication.java
β”œβ”€β”€ config/
β”‚   β”œβ”€β”€ AppConfig.java             # Thread pool, instrumentation
β”‚   β”œβ”€β”€ GraphQLConfig.java         # Scalar registrations
β”‚   └── SecurityConfig.java        # JWT security chain
β”œβ”€β”€ exception/
β”‚   β”œβ”€β”€ PaymentExceptions.java     # Domain exception hierarchy
β”‚   β”œβ”€β”€ Exceptions.java            # Factory methods
β”‚   └── GraphQLExceptionResolver.java
β”œβ”€β”€ graphql/
β”‚   β”œβ”€β”€ resolver/
β”‚   β”‚   β”œβ”€β”€ PaymentGraphQLController.java   # @QueryMapping / @MutationMapping
β”‚   β”‚   └── SubscriptionController.java     # @SubscriptionMapping
β”‚   β”œβ”€β”€ scalar/
β”‚   β”‚   └── CustomScalars.java     # Currency scalar
β”‚   └── directive/
β”‚       └── GraphQLMetricsInstrumentation.java
β”œβ”€β”€ model/
β”‚   β”œβ”€β”€ entity/                    # JPA entities
β”‚   └── dto/                       # Input/output records
β”œβ”€β”€ repository/                    # Spring Data JPA repositories
β”œβ”€β”€ security/
β”‚   β”œβ”€β”€ JwtService.java
β”‚   β”œβ”€β”€ JwtAuthenticationFilter.java
β”‚   └── AccountUserDetailsService.java
β”œβ”€β”€ service/
β”‚   β”œβ”€β”€ AccountService.java
β”‚   β”œβ”€β”€ PaymentService.java
β”‚   β”œβ”€β”€ AuditService.java
β”‚   β”œβ”€β”€ RiskService.java
β”‚   └── impl/
β”‚       β”œβ”€β”€ AccountServiceImpl.java
β”‚       └── PaymentServiceImpl.java
└── validation/
    └── PaymentValidator.java

About

An Enterprise -- spring boot based application that leverages GraphQL for user/client interaction via query for data demand without enforcing unwanted information/data.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors