Skip to content

Getting Started

danielmori-hcp edited this page May 13, 2026 · 2 revisions

Getting Started

Prerequisites

  • Java 17+
  • Maven 3.8+
  • Docker (for Redis and RabbitMQ)

1. Clone the repository

git clone https://github.com/danielsmori/open-fednow.git
cd open-fednow

2. Start infrastructure services

docker-compose up -d

This starts:

  • Redis on port 6379 — Shadow Ledger real-time balance cache
  • RabbitMQ on port 5672 — maintenance-window transaction queue
  • Kafka on port 9092 — optional event streaming bus (KRaft mode, no Zookeeper)
    • Only needed if openfednow.kafka.enabled=true. Start just Redis + RabbitMQ with: docker-compose up -d redis rabbitmq

The dev profile uses an in-memory H2 database, so no PostgreSQL container is needed locally.

3. Run the application

mvn spring-boot:run

The application starts on http://localhost:8080. Wait for Started OpenFedNowApplication in the console output.

4. Verify it's up

curl http://localhost:8080/fednow/health
# → OpenFedNow Gateway — operational

5. Run the demo

./demo/run-demo.sh

The script runs all four scenarios automatically and prints pass/fail for each:

Scenario What it tests
ACSC Normal payment — core online, accepted and settled
RJCT / AM04 Rejection — insufficient funds
ACSP Provisional acceptance — core timeout, SyncAsyncBridge returns immediately
Reconcile POST /admin/reconcile — finds unconfirmed Shadow Ledger entries

Endpoints

Method Path Description
POST /fednow/receive Receive an inbound pacs.008 credit transfer from FedNow
POST /fednow/send Submit an outbound pacs.008 credit transfer to FedNow
GET /fednow/health Gateway health check
POST /admin/reconcile Trigger a reconciliation run — requires HTTP Basic auth (admin / changeme)
GET /actuator/health Detailed health (Redis, RabbitMQ, PostgreSQL, core adapter)

Full interactive API spec: http://localhost:8080/swagger-ui.html


Manual curl examples

Send a payment (ACSC)

curl -s -X POST http://localhost:8080/fednow/receive \
  -H "Content-Type: application/json" \
  -d '{
    "messageId":                  "MSG-001",
    "endToEndId":                 "E2E-001",
    "transactionId":              "TXN-001",
    "interbankSettlementAmount":  250.00,
    "interbankSettlementCurrency":"USD",
    "creditorAccountNumber":      "ACC-12345"
  }'
{"transactionStatus":"ACSC","originalEndToEndId":"E2E-001","originalTransactionId":"TXN-001"}

Trigger a rejection

Control the sandbox response with the creditorAccountNumber prefix:

Prefix Response Reason code
RJCT_FUNDS_ RJCT AM04 — insufficient funds
RJCT_ACCT_ RJCT AC01 — invalid account
RJCT_CLOSED_ RJCT AC04 — closed account
TOUT_ ACSP Core timeout — provisional acceptance
PEND_ ACSP Core pending — provisional acceptance
(any other) ACSC Accepted
curl -s -X POST http://localhost:8080/fednow/receive \
  -H "Content-Type: application/json" \
  -d '{
    "messageId":                  "MSG-002",
    "endToEndId":                 "E2E-002",
    "transactionId":              "TXN-002",
    "interbankSettlementAmount":  250.00,
    "interbankSettlementCurrency":"USD",
    "creditorAccountNumber":      "RJCT_FUNDS_ACC-67890"
  }'
{"transactionStatus":"RJCT","originalEndToEndId":"E2E-002","rejectReasonCode":"AM04"}

Trigger reconciliation

/admin/* requires HTTP Basic authentication (default: admin / changeme):

curl -s -u admin:changeme -X POST http://localhost:8080/admin/reconcile
{
  "transactionsReplayed": 0,
  "discrepanciesDetected": 0,
  "reconciliationSuccessful": true,
  "summary": "Clean reconciliation: 0 entries confirmed across all accounts"
}

transactionsReplayed: 0 is expected in local dev — H2 resets on restart so no bridge-mode entries accumulate. In a PostgreSQL deployment this reflects the actual backlog from the maintenance window.


Running Tests

# Unit tests only (fast, no Docker required)
mvn test -Dgroups=\!integration

# All tests including Testcontainers integration tests
mvn verify

Integration tests use Testcontainers to spin up PostgreSQL, Redis, RabbitMQ, and (for the Kafka suite) Kafka automatically. All integration tests are annotated with @Testcontainers(disabledWithoutDocker = true) — they are skipped rather than failed when Docker is not available.


Related

Clone this wiki locally