Skip to content

Repository files navigation

DifabelZone

Inclusive e-commerce & donation crowdfunding platform for disability-owned businesses (DOB).

DifabelZone is a REST API built with Spring Boot 3.4 + Java 17. It powers product discovery with accessibility-aware filters, DOB verification badges, donation wishlists, and secure JWT-based authentication with Midtrans payment integration.

Java 17 Spring Boot Tests License: MIT


Architecture

DifabelZone architecture

Request flow (numbered in the diagram):

  1. Client β†’ Filter chain β€” CORS β†’ JWT validation/blacklist β†’ in-memory per-IP rate limit β†’ exception translation
  2. Controller layer β€” Auth, Order, Payment, Payment Proof, Donation controllers
  3. Service layer β€” business logic; OrderServiceImpl.placeOrder creates the order + PENDING payment
  4. Midtrans β€” MidtransApiClient calls Snap, returns snapToken + redirectUrl; user pays off-site
  5. Webhook β€” Midtrans notifies /public/payments/midtrans/notification; SHA-512 signature verified, order/donation status applied (ORDER_ACCEPTED / CANCELLED / FUNDED)
  6. Persistence β€” PostgreSQL (orders, payments, proofs, donations) + Redis cache; safety-net reconciliation job polls Midtrans every 10 min

Payment intent flow (order & donation)

sequenceDiagram
    autonumber
    participant F as Client / Frontend
    participant C as PaymentController
    participant M as MidtransServiceImpl
    participant A as MidtransApiClient
    participant G as Midtrans Gateway
    participant P as PostgreSQL

    F->>C: POST /users/orders/{orderId}/midtrans/charge (JWT)
    C->>M: chargeOrder(email, orderId)
    M->>A: createSnapTransaction(transaction_details)
    A->>G: POST snap/v1/transactions
    G-->>A: snapToken + redirectUrl
    M->>P: save pgSnapToken/pgRedirectUrl, pgStatus=PENDING
    M-->>C: MidtransChargeResponse
    C-->>F: snapToken + redirectUrl
    F->>G: open Midtrans Snap, user pays off-site
Loading

Webhook / status confirmation flow

sequenceDiagram
    autonumber
    participant G as Midtrans Gateway
    participant C as PaymentController
    participant M as MidtransServiceImpl
    participant A as MidtransApiClient
    participant P as PostgreSQL

    G->>C: POST /public/payments/midtrans/notification
    C->>M: handleNotification(payload)
    M->>A: verifySignature() SHA-512(serverKey+orderId+statusCode+grossAmount)
    A-->>M: valid (constant-time compare)
    alt order_id starts with "DON-"
        M->>P: applyDonationStatus β†’ collectedAmount += amount, FUNDED if target met
    else product order
        M->>P: capture/settlement β†’ ORDER_ACCEPTED, deny/cancel/expire/failure β†’ CANCELLED
    end
    M->>P: OrderStatusHistory audit trail
    M-->>C: 200 OK
Loading

Notes (matching the current code):

  • Midtrans only β€” no payment strategy pattern; all flows go through MidtransServiceImpl + MidtransApiClient.
  • Idempotency key & payment strategy are planned (dashed in the diagram) β€” not yet in the code. Order concurrency is guarded by @Version optimistic locking.
  • Rate limiting is in-memory (ConcurrentHashMap per IP), not Redis.
  • Without MIDTRANS_SERVER_KEY everything runs in dev-mock mode.

Entity Relationship

DifabelZone ERD

  • orders has no FK to users β€” linked to its owner via the email column (dashed line).
  • orders.coupon_code references coupons.code by value, not by FK (dashed line).
  • product_accessibility_attributes, wishlist_items, and user_role are many-to-many join tables.
  • payments relates 1 : 1 with orders (FK orders.payment_id).
  • orders links to payments and payment_proofs each 1 : 1.

Features

E-commerce

  • Product catalog with categories, search, keyword filter, and pagination
  • Cart, wishlist, coupons, flash sales, product reviews & ratings
  • Order placement with full status history
  • Custom order (personalized batik) with quote & accept/reject flow
  • Payment: Midtrans Snap (bank transfer proof upload also supported)

Disability-Owned Business (DOB)

  • Sellers request a DOB badge; admins approve/reject
  • Badge + verification date shown on products, with DOB filter
  • Dedicated artisan storefronts

Donation crowdfunding

  • Beneficiaries create wishlists with target amounts
  • Public users donate via Midtrans; wishlist auto-marks FUNDED at target
  • Email confirmation via SendGrid

Security

  • JWT access (30 min) + refresh token (7 days), HttpOnly cookie & Bearer header
  • Role-based access control (USER, SELLER, ADMIN)
  • Token blacklist on logout, brute-force protection, rate limiting
  • reCAPTCHA, CORS, security headers (CSP/XSS/HSTS), SHA-512 webhook signature verification

Observability

  • Swagger UI / OpenAPI export, Prometheus metrics, global exception handler

Tech Stack

Layer Technology
Language Java 17 (Temurin)
Framework Spring Boot 3.4.1, Spring Security 6, JJWT 0.12.6
Database PostgreSQL 16 (prod) / H2 (dev & tests)
ORM & Migrations Hibernate 6 + JPA, Flyway 17 migrations
Cache Redis 7 (Spring Data Redis, optional)
Payments Midtrans Snap (REST API, SHA-512 signature)
Email SendGrid
API Docs SpringDoc OpenAPI 2.8.0 (Swagger UI)
Build / CI Maven 3.9+, GitHub Actions
Infra Docker + Docker Compose, Prometheus/Actuator

Quick Start

Requires Java 17 and Maven 3.9+ (or the bundled ./mvnw). Runs on H2 in-memory β€” no database, Redis, or Docker needed.

git clone https://github.com/hendwunga/DifabelZone.git
cd DifabelZone/backend

./mvnw clean test      # build + run all 237 tests
./mvnw spring-boot:run # start the app

The app is then available at http://localhost:8088/api/v1.

Or build a JAR: ./mvnw clean package -DskipTests && java -jar target/backend-0.0.1-SNAPSHOT.jar

Seeded Accounts

Username Password Role
admin adminPass ADMIN
seller1 sellerPass SELLER (DOB badge)
user1 userPass USER

H2 Console

http://localhost:8088/api/v1/h2-console β€” JDBC URL jdbc:h2:mem:difabelzone, user sa, empty password.


Docker Setup

Option 1 β€” Dev with hot-reload

PostgreSQL 16 + Redis 7 + backend, with live reload on source changes:

cp .env.example .env   # adjust values if needed
make dev-up            # or: docker compose -f docker-compose.dev.yml up -d --build

Option 2 β€” Production-like (pre-built image)

cp .env.example .env
# Required: set DB_PASSWORD and JWT_SECRET (min 32 chars, e.g. `openssl rand -base64 64`)

cd backend && ./mvnw clean package -DskipTests && cd ..
docker build -f Dockerfile.local -t difabelzone-backend:local backend/
docker compose -f docker-compose.local.yml up -d

docker compose -f docker-compose.local.yml logs -f backend

Configuration

Profiles

Profile Database Cache Use case
h2 (default) H2 in-memory In-memory Local dev & tests
dev PostgreSQL (local) In-memory Local with real DB
docker PostgreSQL (container) Redis Production-like

Redis is only active in the docker profile (OTP store, JWT blacklist, Spring Cache). Without a MIDTRANS_SERVER_KEY, the app runs in dev-mock mode (fake snap tokens, still records PENDING state).

Environment Variables

Variable Required Default Description
DB_PASSWORD docker β€” PostgreSQL password
JWT_SECRET yes β€” Base64 JWT signing key, min 256-bit
DB_URL no jdbc:postgresql://db:5432/difabelzone PostgreSQL JDBC URL
DB_USERNAME no difabelzone PostgreSQL user
REDIS_HOST / REDIS_PORT no redis / 6379 Redis connection
RECAPTCHA_SECRET_KEY no (empty) reCAPTCHA (disabled if empty)
SENDGRID_API_KEY no (empty) SendGrid (email disabled if empty)
MIDTRANS_SERVER_KEY / CLIENT_KEY no (empty) Midtrans (dev-mock if empty)
IMAGE_BASE_URL no http://localhost:8088/images Product image base URL
FRONTEND_URL no http://localhost:5173/ CORS allowed origin

API Documentation

Once running:

Resource URL
Swagger UI http://localhost:8088/api/v1/swagger-ui/index.html
OpenAPI spec http://localhost:8088/api/v1/api-docs
OpenAPI export (pretty JSON) http://localhost:8088/api/v1/public/api-docs/export.json
# Download the OpenAPI spec for frontend/Postman work
curl -o difabelzone-openapi.json http://localhost:8088/api/v1/public/api-docs/export.json

An Insomnia collection with pre-configured requests is available at insomnia-collection.json.


Project Structure

DifabelZone/
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ src/main/java/difabelzone/backend/
β”‚   β”‚   β”œβ”€β”€ config/            # Security, CORS, cache, rate limit, OpenAPI
β”‚   β”‚   β”œβ”€β”€ controller/        # 27 REST controllers
β”‚   β”‚   β”œβ”€β”€ dto/               # request/response DTOs with @Schema
β”‚   β”‚   β”œβ”€β”€ entity/            # 33 JPA entities
β”‚   β”‚   β”œβ”€β”€ exception/         # global exception handler
β”‚   β”‚   β”œβ”€β”€ repository/        # 28 Spring Data repositories
β”‚   β”‚   └── service/           # service interfaces + impl
β”‚   β”œβ”€β”€ src/main/resources/
β”‚   β”‚   β”œβ”€β”€ application.yml          # default (h2 profile)
β”‚   β”‚   β”œβ”€β”€ application-{h2,dev,docker}.yml
β”‚   β”‚   └── db/migration/            # 17 Flyway migrations
β”‚   β”œβ”€β”€ src/test/              # 237 unit & integration tests
β”‚   └── Dockerfile             # multi-stage production image
β”œβ”€β”€ docker-compose.{dev,local}.yml
β”œβ”€β”€ .github/workflows/ci.yml   # compile + test on push/PR
β”œβ”€β”€ Makefile                   # dev-up, test, build, run-h2, ...
β”œβ”€β”€ docs/diagrams/             # architecture, auth-flow & ERD (SVG/PNG/excalidraw)
└── insomnia-collection.json   # API request collection

Authentication

DifabelZone authentication flow

Token Lifetime Storage Usage
Access (JWT) 30 min HttpOnly cookie + body Authorization: Bearer <token>
Refresh (UUID) 7 days HttpOnly cookie + body POST /auth/refresh

Phases in the diagram above:

  • A Β· Sign-in β€” POST /auth/signin authenticates via AuthenticationManager, optionally verifies reCAPTCHA (after failed attempts), issues the access JWT + refresh token, and returns them as HttpOnly cookies alongside UserInfoResponse.
  • B Β· Authenticated requests β€” AuthTokenFilter (a OncePerRequestFilter) validates the Bearer token (including blacklist lookup) and sets the SecurityContext; controllers enforce roles with @PreAuthorize.
  • C Β· Refresh & sign-out β€” expired access tokens are rotated via POST /auth/refresh (refresh token verified + rotated). POST /auth/signout blacklists the JWT, revokes all of the user's refresh tokens, and clears both cookies.

Token blacklist & refresh storage: Redis on the docker profile, in-memory otherwise.


Testing

cd backend

./mvnw test                       # run all 237 tests
./mvnw test -Dtest=OrderServiceTest   # run a single test class
./mvnw test jacoco:report          # with coverage (target/site/jacoco/index.html)

Coverage: unit tests for services/utilities + integration tests for the auth, category, and product controllers (JUnit 5, Mockito, JaCoCo).


Makefile

Command Description
make dev-up / make dev-down / make dev-logs Dev Docker (PostgreSQL + Redis + hot-reload)
make build Build JAR locally
make test Run all tests
make run-h2 Run with H2 profile
make test-coverage Tests with JaCoCo report
make clean Clean build artifacts

Contributing

  1. Fork the repository
  2. Create a branch: git checkout -b feat/my-feature
  3. Make changes and keep tests green: ./mvnw test
  4. Commit with a conventional message: git commit -m "feat: ..."
  5. Push and open a Pull Request to dev

License

MIT β€” see LICENSE.

About

DifabelZone e-Commerce Batik RestAPI is a backend system built with Spring Boot and PostgreSQL to enable inclusive batik sales, featuring secure authentication πŸ”’, product management πŸ›οΈ, and payment integration πŸ’³.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages