This is the starter project for the Customer Transactions exercise.
The first thing you should do after cloning the repository is:
./mvnw clean testmvnw.cmd clean testThe sample test should pass before you begin implementing the exercise.
- Java 17
- Spring Boot
- Maven wrapper
- Spring Web
- Spring Data JPA
- H2 embedded database
- JUnit / Spring Boot Test
- A sample REST endpoint:
GET /api/sample - A sample test that loads the Spring context
All four operations have been implemented:
- Create transaction —
POST /api/transactions - Get transaction —
GET /api/transactions/{transactionId} - Update transaction status —
PATCH /api/transactions/{transactionId}/status - Get all transactions for a customer —
GET /api/customers/{customerId}/transactions
The surrounding design was extended with a layered structure (controller / service / repository / dto / entity / enums / exception) rather than putting everything in one package, so that request handling, business rules, and persistence stay separate and independently testable.
Every transaction contains:
- Transaction ID
- Customer ID
- Amount
- Currency
- Transaction Type
- Transaction Status
The following rules are enforced on every transaction:
| Field | Validation |
|---|---|
| Transaction ID | Required (not blank); must not already exist |
| Customer ID | Required (not blank) |
| Amount | Required; must be greater than zero (no upper bound is enforced) |
| Currency | Required; must be one of the supported values (INR, USD) |
| Transaction type | Required; must be one of the supported values (CREDIT, DEBIT) |
| Initial status | Not client-settable — every new transaction is created with status PENDING regardless of what (if anything) is sent |
Enum fields (currency, transaction type, and — on status update — status) are accepted case-insensitively: "inr", "INR", and "InR" all resolve to the same value. Any value that isn't a valid enum constant is rejected with 400 Bad Request.
Business validation beyond the annotations:
- Duplicate detection — a create request whose Transaction ID already exists in the database is rejected with
409 Conflict, not400. This is checked in the service layer (existsById), not via a Bean Validation annotation, since it depends on the current database state rather than the shape of the request. - Status transition rules — status can only move
PENDING → SUCCESSorPENDING → FAILED.SUCCESSandFAILEDare terminal: no transition is allowed out of either of them, andPENDING → PENDINGis also rejected since it isn't one of the two allowed forward transitions. An invalid transition returns400 Bad Requestwith a message naming the current and requested status. We chose two terminal end-states rather than allowing e.g.FAILED → PENDING(a retry) because the exercise doesn't define what a retry should mean for the same Transaction ID, and allowing it would need extra fields (retry count, audit trail) that are out of scope here — see Limitations.
POST /api/transactions
Request body:
{
"transactionId": "TXN1001",
"customerId": "CUST001",
"amount": 5000,
"currency": "INR",
"transactionType": "CREDIT"
}Responses:
201 Created— returns the created transaction, withtransactionStatus: "PENDING".400 Bad Request— validation failure (missing/blank field, non-positive amount, invalid enum value).409 Conflict— a transaction with this Transaction ID already exists.
Example success response:
{
"transactionId": "TXN1001",
"customerId": "CUST001",
"amount": 5000,
"currency": "INR",
"transactionType": "CREDIT",
"transactionStatus": "PENDING"
}GET /api/transactions/{transactionId}
Responses:
200 OK— returns the transaction.404 Not Found— no transaction with that ID exists.
Example: GET /api/transactions/TXN1001 → 200 OK with the same JSON shape as above.
PATCH /api/transactions/{transactionId}/status
Request body:
{
"status": "SUCCESS"
}Responses:
200 OK— returns the updated transaction.400 Bad Request— invalid status value, or a transition that isn't allowed (see Validation rules above).404 Not Found— no transaction with that ID exists.
Example: PATCH /api/transactions/TXN1001/status with {"status": "SUCCESS"} → 200 OK:
{
"transactionId": "TXN1001",
"customerId": "CUST001",
"amount": 5000,
"currency": "INR",
"transactionType": "CREDIT",
"transactionStatus": "SUCCESS"
}GET /api/customers/{customerId}/transactions
Responses:
200 OK— returns a JSON array of the customer's transactions, or[]if the customer has none. (A customer with no transactions and an unrecognized customer ID are indistinguishable — both return[]; see Limitations.)
Example: GET /api/customers/CUST001/transactions → 200 OK:
[
{
"transactionId": "TXN1001",
"customerId": "CUST001",
"amount": 5000,
"currency": "INR",
"transactionType": "CREDIT",
"transactionStatus": "SUCCESS"
},
{
"transactionId": "TXN1002",
"customerId": "CUST001",
"amount": 2000,
"currency": "INR",
"transactionType": "DEBIT",
"transactionStatus": "PENDING"
}
]15 tests in total (7 service-layer, 7 controller-layer, 1 the original sample context-load test), covering more than just startup:
- Transaction created successfully
- Transaction rejected because it fails validation
- Duplicate Transaction ID rejected
- Request for a transaction that does not exist
- Successful status transition (
PENDING → SUCCESS) - Invalid status transition rejected (
SUCCESS → FAILED) - Customer transaction lookup, including the multi-result case
- Case-insensitive enum input accepted (e.g.
"inr","credit") - Invalid enum value rejected
Known gap: controller-level (MockMvc, full HTTP layer) tests exist for create and get; the status-update and customer-lookup endpoints are currently only exercised at the service layer, not end-to-end through MockMvc. Noted honestly rather than glossed over — see Limitations and What I'd Improve below.
- Transaction IDs are supplied by the client, not generated server-side, and must be unique.
- Every new transaction starts in
PENDING; the client cannot override this. SUCCESSandFAILEDare terminal states.amountmust be strictly greater than zero; no maximum is enforced.- Only
INR/USDandCREDIT/DEBITare supported — this is our own implementation choice, not a Toucan-assigned variant, since this cohort's invitation did not specify a per-candidate variant. - A customer with zero transactions is a valid state and returns
[], not a404. - H2 in-memory storage is acceptable for the scope of this assignment; nothing needs to survive an application restart.
- Authentication, authorization, and external payment processing are out of scope.
- H2 is in-memory only — data does not survive a restart, and it isn't a production-suitable datastore.
- No authentication or authorization is implemented.
- No pagination on the customer-transactions endpoint.
- No generic/catch-all exception handler — an unexpected runtime error (e.g. a database failure) would fall through to Spring's default error response instead of this application's structured
{status, message, timestamp}shape. - Status-update and customer-lookup endpoints lack MockMvc-level (full HTTP) tests; they're covered at the service layer only.
- No audit trail — updating a status overwrites it in place, so the previous value isn't recoverable.
- No production deployment configuration is included.
- Add MockMvc tests for the PATCH status and customer-lookup endpoints.
- Add a catch-all exception handler for a consistent error shape on unexpected failures.
- Add pagination to the customer-transactions endpoint.
- Add a simple status-history/audit table.
- Tools used: ChatGPT.
- Used for: discussing Spring Boot layering, reviewing the starter project structure, generating and reviewing boilerplate (DTOs, entity, controllers, exception classes), suggesting test cases, and drafting documentation.
- What I changed/verified: all generated code was read and run locally against the starter project before being kept; the validation rules, status-transition logic, and API contract described in this README were checked against the actual source files rather than taken on faith from the AI's summary of them.
- Something the AI got wrong that I had to fix: an earlier documentation draft stated the H2 JDBC URL as
jdbc:h2:mem:testdb; the URL actually configured inapplication.ymlisjdbc:h2:mem:transactions. An earlier draft also implied full controller-level test coverage for every endpoint, which wasn't accurate — corrected above. - How I checked the final result works: ran
./mvnw clean testlocally and confirmed all tests pass; exercised each endpoint manually.
C:\Program Files\JetBrains\IntelliJ IDEA 2026.2.1\plugins\maven-plugin\lib\maven3\bin\mvn.cmd -Didea.version=2026.2.1 -Dmaven.ext.class.path=C:\Program Files\JetBrains\IntelliJ IDEA 2026.2.1\plugins\maven-plugin\lib\intellij.maven.rt\maven-event-listener.jar -Djansi.passthrough=true -Dstyle.color=always -Dmaven.repo.local=C:\Users\Anchula Sridhar\.m2\repository clean test -f pom.xml
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.example:transaction-starter >-------------------
[INFO] Building transaction-starter 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.4.1:clean (default-clean) @ transaction-starter ---
[INFO] Deleting C:\Users\Anchula Sridhar\Desktop\transaction-assignment\target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ transaction-starter ---
[INFO] Copying 1 resource from src\main\resources to target\classes
[INFO] Copying 0 resource from src\main\resources to target\classes
[INFO]
[INFO] --- compiler:3.14.0:compile (default-compile) @ transaction-starter ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 16 source files with javac [debug parameters release 17] to target\classes
[INFO] Annotation processing is enabled because one or more processors were found
on the class path. A future release of javac may disable annotation processing
unless at least one processor is specified by name (-processor), or a search
path is specified (--processor-path, --processor-module-path), or annotation
processing is enabled explicitly (-proc:only, -proc:full).
Use -Xlint:-options to suppress this message.
Use -proc:none to disable annotation processing.
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ transaction-starter ---
[INFO] skip non existing resourceDirectory C:\Users\Anchula Sridhar\Desktop\transaction-assignment\src\test\resources
[INFO]
[INFO] --- compiler:3.14.0:testCompile (default-testCompile) @ transaction-starter ---
[INFO] Recompiling the module because of changed dependency.
[INFO] Compiling 3 source files with javac [debug parameters release 17] to target\test-classes
[INFO] Annotation processing is enabled because one or more processors were found
on the class path. A future release of javac may disable annotation processing
unless at least one processor is specified by name (-processor), or a search
path is specified (--processor-path, --processor-module-path), or annotation
processing is enabled explicitly (-proc:only, -proc:full).
Use -Xlint:-options to suppress this message.
Use -proc:none to disable annotation processing.
[INFO]
[INFO] --- surefire:3.5.3:test (default-test) @ transaction-starter ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.transactionstarter.transaction.controller.TransactionControllerTest
21:40:28.850 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.example.transactionstarter.transaction.controller.TransactionControllerTest]: TransactionControllerTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
21:40:29.203 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.example.transactionstarter.TransactionStarterApplication for test class com.example.transactionstarter.transaction.controller.TransactionControllerTest
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.5.5)
2026-08-30T21:40:30.421+05:30 INFO 23296 --- [ main] c.e.t.t.c.TransactionControllerTest : Starting TransactionControllerTest using Java 21.0.3 with PID 23296 (started by Anchula Sridhar in C:\Users\Anchula Sridhar\Desktop\transaction-assignment)
2026-08-30T21:40:30.423+05:30 INFO 23296 --- [ main] c.e.t.t.c.TransactionControllerTest : No active profile set, falling back to 1 default profile: "default"
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
WARNING: A Java agent has been loaded dynamically (C:\Users\Anchula Sridhar\.m2\repository\net\bytebuddy\byte-buddy-agent\1.17.7\byte-buddy-agent-1.17.7.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2026-08-30T21:40:34.021+05:30 INFO 23296 --- [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2026-08-30T21:40:34.021+05:30 INFO 23296 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2026-08-30T21:40:34.024+05:30 INFO 23296 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 2 ms
2026-08-30T21:40:34.070+05:30 INFO 23296 --- [ main] c.e.t.t.c.TransactionControllerTest : Started TransactionControllerTest in 4.734 seconds (process running for 7.072)
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.331 s -- in com.example.transactionstarter.transaction.controller.TransactionControllerTest
[INFO] Running com.example.transactionstarter.transaction.service.TransactionServiceTest
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.358 s -- in com.example.transactionstarter.transaction.service.TransactionServiceTest
[INFO] Running com.example.transactionstarter.TransactionStarterApplicationTests
2026-08-30T21:40:35.199+05:30 INFO 23296 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.example.transactionstarter.TransactionStarterApplicationTests]: TransactionStarterApplicationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2026-08-30T21:40:35.235+05:30 INFO 23296 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.example.transactionstarter.TransactionStarterApplication for test class com.example.transactionstarter.TransactionStarterApplicationTests
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.5.5)
2026-08-30T21:40:35.298+05:30 INFO 23296 --- [ main] c.e.t.TransactionStarterApplicationTests : Starting TransactionStarterApplicationTests using Java 21.0.3 with PID 23296 (started by Anchula Sridhar in C:\Users\Anchula Sridhar\Desktop\transaction-assignment)
2026-08-30T21:40:35.299+05:30 INFO 23296 --- [ main] c.e.t.TransactionStarterApplicationTests : No active profile set, falling back to 1 default profile: "default"
2026-08-30T21:40:35.923+05:30 INFO 23296 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2026-08-30T21:40:36.036+05:30 INFO 23296 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 91 ms. Found 1 JPA repository interface.
2026-08-30T21:40:36.649+05:30 INFO 23296 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2026-08-30T21:40:36.795+05:30 INFO 23296 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.6.26.Final
2026-08-30T21:40:36.879+05:30 INFO 23296 --- [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled
2026-08-30T21:40:37.351+05:30 INFO 23296 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2026-08-30T21:40:37.467+05:30 INFO 23296 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2026-08-30T21:40:38.087+05:30 INFO 23296 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:transactions user=SA
2026-08-30T21:40:38.091+05:30 INFO 23296 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2026-08-30T21:40:38.245+05:30 INFO 23296 --- [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info:
Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)']
Database driver: undefined/unknown
Database version: 2.3.232
Autocommit mode: undefined/unknown
Isolation level: undefined/unknown
Minimum pool size: undefined/unknown
Maximum pool size: undefined/unknown
2026-08-30T21:40:40.221+05:30 INFO 23296 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2026-08-30T21:40:40.335+05:30 INFO 23296 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2026-08-30T21:40:41.198+05:30 INFO 23296 --- [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:transactions'
2026-08-30T21:40:41.293+05:30 INFO 23296 --- [ main] c.e.t.TransactionStarterApplicationTests : Started TransactionStarterApplicationTests in 6.047 seconds (process running for 14.295)
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.109 s -- in com.example.transactionstarter.TransactionStarterApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 15, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 25.396 s
[INFO] Finished at: 2026-08-30T21:40:41+05:30
[INFO] ------------------------------------------------------------------------