Production-ready payment system with Craftgate integration, built with DDD Architecture (Backend) and Modern React (Frontend).
π¦ Project Root
βββ π backend/ # Spring Boot + DDD
β βββ domain/ # Pure business logic
β βββ application/ # Use cases
β βββ infrastructure/ # Craftgate, JPA
β βββ interface/ # REST API
β
βββ π¨ frontend/ # React + TypeScript
βββ components/ # UI Components
βββ hooks/ # Custom hooks
βββ lib/ # Utilities
βββ types/ # TypeScript types
- β DDD Architecture - Clean separation of concerns
- β Multi-Gateway Support - Akbank POS & Craftgate integration
- β User Points System - Loyalty points management
- β Idempotency - Duplicate payment prevention
- β PCI-DSS Compliant - Secure card handling
- β BigDecimal - No floating-point errors
- β PostgreSQL - Production-ready database
- β Flyway Migration - Database versioning
- β Global Error Handling - RFC 7807 Problem Details
- β TypeScript Strict Mode - Full type safety
- β Zod Validation - Luhn algorithm for cards
- β Auto Card Formatting - Spaces every 4 digits
- β TanStack Query - Server state management
- β React Hook Form - Performant forms
- β TailwindCSS - Modern, responsive UI
- β Currency Formatting - Intl.NumberFormat
- Java 17+
- Node.js 18+
- PostgreSQL 14+
- Maven 3.8+
- Craftgate Account (Sandbox)
# Create database
psql -U postgres
CREATE DATABASE payment_db_dev;
\qcd backend
# Configure environment
cp .env.example .env
# Edit .env with your Craftgate credentials
# Run application
mvn clean install
mvn spring-boot:run -Dspring-boot.run.profiles=devBackend will start on http://localhost:8080
cd frontend
# Install dependencies
npm install
# Configure environment
cp .env.example .env
# Start development server
npm run devFrontend will start on http://localhost:3000
β
Success: 5400010000000004
β Decline: 5400010000000012
π 3D Secure: 5400010000000020
- Open
http://localhost:3000 - Enter amount and select currency
- Use test card:
5400010000000004 - Expire:
12/2030, CVV:123 - Click "Pay Now"
POST /api/v1/payments
Content-Type: application/json
Idempotency-Key: unique-key-123
{
"conversationId": "ORDER-12345",
"amount": 100.50,
"currency": "TRY",
"buyerId": "buyer-123",
"cardInfo": {
"cardHolderName": "JOHN DOE",
"cardNumber": "5400010000000004",
"expireMonth": "12",
"expireYear": "2030",
"cvv": "123"
}
}{
"id": "uuid",
"conversationId": "ORDER-12345",
"amount": 100.50,
"currency": "TRY",
"status": "SUCCESS",
"buyerId": "buyer-123",
"createdAt": "2024-01-15T10:30:00",
"externalPaymentId": "12345678"
}{
"id": "uuid",
"conversationId": "ORDER-12345",
"amount": 100.50,
"currency": "TRY",
"status": "FAILED",
"buyerId": "buyer-123",
"createdAt": "2024-01-15T10:30:00",
"errorCode": "INSUFFICIENT_FUNDS",
"errorMessage": "Insufficient funds"
}GET /api/v1/user-points/{userId}Response:
{
"userId": "user123",
"totalPoints": 150.00,
"availablePoints": 120.00,
"lockedPoints": 30.00,
"createdAt": "2024-01-15T10:30:00",
"lastUpdated": "2024-01-20T14:45:00"
}POST /api/v1/user-points/earn
Content-Type: application/json
{
"userId": "user123",
"points": 50.00,
"reason": "Payment completed successfully"
}POST /api/v1/user-points/spend
Content-Type: application/json
{
"userId": "user123",
"points": 20.00,
"reason": "Used in payment"
}GET /api/v1/user-points/{userId}/check/{requiredPoints}Response: true or false
π Detailed Documentation: See USER_POINTS_API.md
Backend includes comprehensive test coverage:
- β Unit Tests (UT): Domain logic, business rules (95% coverage)
- β Integration Tests (IT): Repository, database with TestContainers
- β Functional Tests (FT): End-to-end API tests with real PostgreSQL
# Run all tests
mvn clean test
# Run specific test types
mvn test -Dtest="*Test" # Unit tests only
mvn test -Dtest="*IT" # Integration tests only
mvn test -Dtest="*FT" # Functional tests only
# Run with coverage report
mvn clean verify
# Report: target/site/jacoco/index.htmlDomain Layer - Unit Test:
@Test
@DisplayName("Should earn points successfully")
void shouldEarnPointsSuccessfully() {
UserPoints userPoints = new UserPoints("user123");
userPoints.earnPoints(new BigDecimal("50.00"));
assertThat(userPoints.getTotalPoints())
.isEqualByComparingTo(new BigDecimal("50.00"));
}Infrastructure Layer - Integration Test with TestContainers:
@DataJpaTest
@Testcontainers
@DisplayName("JPA UserPoints Repository Integration Tests")
class JpaUserPointsRepositoryIT {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:14-alpine");
// Tests with real database...
}Interfaces Layer - Functional Test:
@Test
@DisplayName("Should get user points successfully")
void shouldGetUserPointsSuccessfully() {
given()
.when()
.get("/api/v1/user-points/user123")
.then()
.statusCode(200)
.body("userId", equalTo("user123"));
}π Testing Rules: See .cursor/rules/06-backend-testing/RULE.mdc
// β Don't log sensitive data
logger.info("Card: " + cardNumber); // WRONG!
// β Don't use float/double for money
double amount = 100.50; // WRONG!
// β Don't trust frontend input
String amount = request.getAmount(); // WRONG! Validate!// β
Log only safe information
logger.info("Processing payment. ConversationId: {}", conversationId);
// β
Use BigDecimal for money
BigDecimal amount = new BigDecimal("100.50");
// β
Validate everything at API boundary
@Valid @RequestBody CreatePaymentRequest request// β
CORRECT
Money money = new Money(new BigDecimal("100.50"), Currency.TRY);
// β WRONG
double amount = 100.50; // Causes rounding errors!// β
CORRECT
formatCurrency(100.50, 'TRY') // "βΊ100,50"
// β WRONG
`${amount} TL` // Don't manipulate strings!Backend:
- 30+ Java classes
- 4 DDD layers
- 100% type-safe
- Zero tolerance for sensitive data logging
Frontend:
- 15+ React components
- Full TypeScript strict mode
- Luhn validation
- Auto card formatting
- Currency formatting with Intl API
- β Craftgate - Turkish payment gateway
- β Akbank Sanal POS - Real bank integration with 3D Secure
- β Easy to add more!
curl -X POST http://localhost:8080/api/v1/payments \
-H "Content-Type: application/json" \
-d '{
"provider": "AKBANK",
"amount": 100.50,
"currency": "TRY",
...
}'# Build
mvn clean package -DskipTests
# Run with production profile
java -jar payment-interfaces/target/payment-interfaces-1.0.0-SNAPSHOT.jar --spring.profiles.active=prod# Build
npm run build
# Deploy dist/ folder to CDN or web server- Backend: See
backend/README.md - Frontend: See
frontend/README.md - API Docs: See
docs/api-specs.md(if exists) - DDD Rules: See
02-backend-ddd/RULE.md - Security Rules: See
01-global-security/RULE.md
cd backend
# Make changes
mvn clean install
mvn spring-boot:run -Dspring-boot.run.profiles=devcd frontend
# Make changes
npm run dev
# Changes hot-reload automatically- Start backend (port 8080)
- Start frontend (port 3000)
- Frontend proxies API calls to backend
- Test payment flow end-to-end
# Start PostgreSQL
docker-compose up -d postgres
# Or start everything
docker-compose up -d- β Code & commits in English
- β Turkish comments only for complex business logic
- β Never log sensitive data
- β Use BigDecimal for money (backend)
- β Use Intl.NumberFormat for currency (frontend)
- β Follow DDD layers (backend)
- β Feature-based folders (frontend)
- β
No
anytype (TypeScript)
# Backend
mvn clean test
mvn clean verify
# Frontend
npm run type-check
npm run lintProprietary - All rights reserved
Issue: Backend won't start
- Solution: Check if PostgreSQL is running and database exists
Issue: Frontend can't connect to backend
- Solution: Ensure backend is running on port 8080
Issue: Payment fails with "CRAFTGATE_ERROR"
- Solution: Verify Craftgate API keys in
.env
Issue: CORS errors
- Solution: Frontend dev server has proxy configured, use it
For issues related to:
- Architecture: Review DDD rules in
/rules - Security: Review security rules in
/rules - Craftgate API: See Craftgate Docs
Built with β€οΈ using DDD, Spring Boot, React, and TypeScript
π PCI-DSS Compliant | β Luhn Validated | π³ Idempotent | π Production Ready