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.
Request flow (numbered in the diagram):
- Client β Filter chain β CORS β JWT validation/blacklist β in-memory per-IP rate limit β exception translation
- Controller layer β Auth, Order, Payment, Payment Proof, Donation controllers
- Service layer β business logic;
OrderServiceImpl.placeOrdercreates the order +PENDINGpayment - Midtrans β
MidtransApiClientcalls Snap, returnssnapToken+redirectUrl; user pays off-site - Webhook β Midtrans notifies
/public/payments/midtrans/notification; SHA-512 signature verified, order/donation status applied (ORDER_ACCEPTED/CANCELLED/FUNDED) - Persistence β PostgreSQL (orders, payments, proofs, donations) + Redis cache; safety-net reconciliation job polls Midtrans every 10 min
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
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
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
@Versionoptimistic locking. - Rate limiting is in-memory (
ConcurrentHashMapper IP), not Redis. - Without
MIDTRANS_SERVER_KEYeverything runs in dev-mock mode.
ordershas no FK tousersβ linked to its owner via theemailcolumn (dashed line).orders.coupon_codereferencescoupons.codeby value, not by FK (dashed line).product_accessibility_attributes,wishlist_items, anduser_roleare many-to-many join tables.paymentsrelates1 : 1withorders(FKorders.payment_id).orderslinks topaymentsandpayment_proofseach1 : 1.
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
FUNDEDat 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
| 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) |
| SendGrid | |
| API Docs | SpringDoc OpenAPI 2.8.0 (Swagger UI) |
| Build / CI | Maven 3.9+, GitHub Actions |
| Infra | Docker + Docker Compose, Prometheus/Actuator |
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 appThe 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
| Username | Password | Role |
|---|---|---|
admin |
adminPass |
ADMIN |
seller1 |
sellerPass |
SELLER (DOB badge) |
user1 |
userPass |
USER |
http://localhost:8088/api/v1/h2-console β JDBC URL jdbc:h2:mem:difabelzone, user sa, empty password.
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 --buildcp .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| 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).
| 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 |
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.jsonAn Insomnia collection with pre-configured requests is available at insomnia-collection.json.
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
| 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/signinauthenticates viaAuthenticationManager, optionally verifies reCAPTCHA (after failed attempts), issues the access JWT + refresh token, and returns them as HttpOnly cookies alongsideUserInfoResponse. - B Β· Authenticated requests β
AuthTokenFilter(aOncePerRequestFilter) validates the Bearer token (including blacklist lookup) and sets theSecurityContext; controllers enforce roles with@PreAuthorize. - C Β· Refresh & sign-out β expired access tokens are rotated via
POST /auth/refresh(refresh token verified + rotated).POST /auth/signoutblacklists 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.
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).
| 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 |
- Fork the repository
- Create a branch:
git checkout -b feat/my-feature - Make changes and keep tests green:
./mvnw test - Commit with a conventional message:
git commit -m "feat: ..." - Push and open a Pull Request to
dev
MIT β see LICENSE.