Skip to content

About

Spring Boot 3 Java 17 payments APIs with OAuth2, Kafka, ISO 20022-inspired flows. Original demo, not bank IP.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

banking-payments-spring

Original credit-transfer payments microservice by Md Tanvir Alam (tanvir-ux).

demo of how a high-concurrency bank-style initiation API is usually shaped: JWT resource server, idempotent POST, JPA persistence, a sanctions stub, and Kafka domain events. It is not employer IP and is not affiliated with any core-banking vendor or commercial bank. Message field names are ISO 20022 / SWIFT-inspired (public standards: debtor/creditor parties, instructed amount + currency).

Stack: Spring Boot 3.3 / Java 17, PostgreSQL, Kafka (KRaft), OpenAPI, Actuator.

Architecture

flowchart LR
  Client[Client / recruiter curl] -->|Bearer JWT + Idempotency-Key| API[PaymentController]
  API --> SVC[PaymentService]
  SVC --> AML[AmlScreeningService stub]
  SVC --> DB[(PostgreSQL payments)]
  SVC -->|payment.initiated / payment.posted| K[Kafka]
  API -.->|OpenAPI| Swagger[swagger-ui]
  API -.->|health / info| Actuator
Loading

Happy path inside one transaction (demo simplification of a real posting engine):

sequenceDiagram
  participant C as Client
  participant API as REST
  participant S as PaymentService
  participant AML as AML stub
  participant DB as Postgres
  participant K as Kafka
  C->>API: POST /api/v1/payments
  API->>S: create(request, Idempotency-Key)
  S->>DB: persist INITIATED
  S->>K: payment.initiated
  S->>AML: screen creditor name
  alt name contains SANCTIONED_DEMO
    S->>DB: REJECTED
    API-->>C: 201 Payment status REJECTED
  else clear
    S->>DB: VALIDATED then POSTED
    S->>K: payment.posted
    API-->>C: 201 Payment status POSTED
  end
Loading

How this maps to high-concurrency banking APIs

Demo choice What production initiation APIs typically do
Idempotency-Key + SHA-256 of canonical payload Exactly-once acceptance under client retries / at-least-once HTTP. Unique index + hash compare (409 on mismatch).
Unique constraint race handling Two concurrent POSTs with the same key: one insert wins, the other maps DataIntegrityViolation back to the stored row.
JWT resource server (HS256 demo secret) OAuth2/OIDC from an IdP; scopes per product (payments:write). HS256 is demo only.
Synchronous INITIATED → POSTED Real systems accept quickly (INITIATED/ACCEPTED), then post asynchronously on a ledger/clearing worker; clients poll or consume status events.
Kafka payment.initiated / payment.posted Downstream AML, notifications, liquidity, and general-ledger consumers. Topics stay schema-stable (ISO 20022-inspired).
Pagination on GET /payments Ops/investigation screens; never dump the ledger.
Actuator health K8s probes; readiness should eventually include Postgres + Kafka.

Run with Docker Compose

docker compose up --build

Services:

curl examples

Mint a demo JWT (the token endpoint is unauthenticated on purpose for local recruiter runs):

TOKEN=$(curl -s -X POST http://localhost:8080/api/v1/demo/token | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

Create a payment:

curl -s -X POST http://localhost:8080/api/v1/payments \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen || cat /proc/sys/kernel/random/uuid)" \
  -H "Content-Type: application/json" \
  -d '{
    "debtor":   { "name": "Ada Lovelace", "account": "NL91ABNA0417164300" },
    "creditor": { "name": "Grace Hopper", "account": "DE89370400440532013000" },
    "amount": 100.00,
    "currency": "EUR"
  }'

Replay the same key + body: HTTP 200, same id. Change the amount, keep the key: HTTP 409.

Sanctions stub (beneficiary name contains SANCTIONED_DEMO):

curl -s -X POST http://localhost:8080/api/v1/payments \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen || cat /proc/sys/kernel/random/uuid)" \
  -H "Content-Type: application/json" \
  -d '{
    "debtor":   { "name": "Ada Lovelace", "account": "NL91ABNA0417164300" },
    "creditor": { "name": "SANCTIONED_DEMO Holdings", "account": "DE89370400440532013000" },
    "amount": 1.00,
    "currency": "EUR"
  }'

Fetch / list:

curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/payments/{id}
curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8080/api/v1/payments?page=0&size=20&sort=createdAt,desc"

Offline token (same demo secret as application.yml):

python3 scripts/mint-demo-jwt.py

JWT (demo)

application.yml documents:

app.security.jwt.secret: demo-hs256-secret-do-not-use-in-production-min-32b!!
app.security.jwt.issuer: payments-demo

Never ship this secret to a real environment. Replace with an RSA/JWKS resource server bound to your IdP.

Tests

mvn -q test
  • Unit: PaymentServiceTest, AmlScreeningServiceTest (Mockito, no Spring)
  • Slice/API: PaymentControllerTest (MockMvc + H2 + real JWT encoder)

Tests disable Kafka (app.kafka.enabled=false) and use H2.

Package layout

com.tanviralam.payments
  api            REST, DTOs, errors, demo token
  application    use-cases, AML stub, hashing
  domain         Payment aggregate + status
  infrastructure JPA, OpenAPI
  kafka          producer + no-op for tests
  security       OAuth2 resource server (HS256)

License

MIT © 2026 Md Tanvir Alam

About

Spring Boot 3 Java 17 payments APIs with OAuth2, Kafka, ISO 20022-inspired flows. Original demo, not bank IP.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages