The data model was designed to ensure transactional integrity, audit traceability, and flexibility to support the different transfer flows. Entities are stored in PostgreSQL.
Main entities
Transfer
The Transfer entity is the central record for any type of transfer (TED OUT, TED IN, P2P). It stores sender and recipient details, amounts, and the current transaction state.
Main fields:
| Field | Type | Description |
|---|
transferId | UUID (PK) | Unique transfer identifier |
organizationId | UUID (FK) | Multi-tenant isolation |
ledgerId | UUID | From CRM (Midaz multi-tenant) |
senderAccountId | UUID | Sender’s Midaz account |
recipientAccountId | UUID (nullable) | Only for P2P |
recipientDetails | JSONB | ISPB, branch, account, holder |
amount | Decimal | Amount without fee |
feeAmount | Decimal | Fee charged |
totalAmount | Decimal | amount + fee (calculated) |
type | Enum | TED_OUT, TED_IN, P2P |
status | Enum | Current transfer state |
confirmationNumber | String | Human-readable number, unique per organization |
controlNumber | String | JD SPB NumCtrlIF |
midazTransactionId | UUID | Midaz transaction reference |
State machines
CREATED → PENDING → PROCESSING → COMPLETED
→ REJECTED (4xx from JD)
→ FAILED (5xx/timeout)
→ CANCELLED (user cancels before processing)
RECEIVED → PROCESSING → COMPLETED
→ REJECTED (recipient not found)
CREATED → PROCESSING → COMPLETED
→ FAILED (Midaz error)
→ CANCELLED
PaymentInitiation
Used in the TED OUT two-step flow, stores the transfer details so the client can review fees before confirming. An initiation expires in 24 hours if not processed.
Main fields:
| Field | Type | Description |
|---|
initiationId | UUID (PK) | Unique identifier |
organizationId | UUID (FK) | Multi-tenant isolation |
senderAccountId | UUID | Sender account |
recipientDetails | JSONB | Recipient data |
amount | Decimal | Transfer amount |
feeAmount | Decimal | Calculated by plugin-fees |
totalAmount | Decimal | amount + fee |
status | Enum | PENDING_CONFIRMATION, PROCESSED, EXPIRED |
transferId | UUID (FK) | Populated when PROCESSED |
expiresAt | Timestamp | 24h from creation |
Lifecycle:
Created → User reviews fee → ProcessTransfer → PROCESSED
→ EXPIRED (after 24h)
TransferStatusHistory
Immutable audit log that records each state transition of the Transfer entity. Essential for traceability and compliance with BACEN’s 5-year data retention requirement.
Main fields:
| Field | Type | Description |
|---|
historyId | UUID (PK) | Unique identifier |
transferId | UUID (FK) | Reference to Transfer |
oldStatus | Enum (nullable) | Previous state |
newStatus | Enum | New state |
reason | String (nullable) | Reason (for error cases) |
changedAt | Timestamp | Date/time of change |
changedBy | Enum | system, user, admin |
Retention: 5 years per BACEN regulatory requirement (Resolution 4.753/2019).
JDIncomingMessage
Persists raw messages received from the JD Consultores system before any processing. Ensures no incoming transfer (TED IN) is lost in case of failure, implementing at-least-once delivery guarantee. Deduplication is handled via the unique sequenceNumber constraint.
Main fields:
| Field | Type | Description |
|---|
organizationId | UUID (FK) | Multi-tenant isolation |
sequenceNumber | String (unique) | JD NumCabSeq (used for deduplication) |
controlNumber | String (nullable) | NumCtrlIF |
messageCode | Enum | STR0008R2, STR0010R2 |
returnType | Enum | P=Processed, E=Error, R=Receipt, C=Cancelled |
rawXml | Text | Complete JD message (audit) |
processed | Boolean | false initially |
transferId | UUID (FK) | Populated after processing |
Critical guarantee: The message is persisted BEFORE processing, ensuring no transfer is lost in case of failure. Duplicate messages are handled via the unique sequenceNumber constraint.
OrganizationConfig
Stores each client’s (tenant) specific configurations, such as JD credentials (encrypted), webhook URL, and fee settings.
Main fields:
| Field | Type | Description |
|---|
organizationId | UUID (PK) | Same as JWT tenantId |
jdCredentials | JSONB (encrypted) | legacyCode, userCode, password, privateKey |
jdIspb | String | 8 digits (institution’s ISPB) |
webhookUrl | String | HTTPS URL for notifications |
webhookSecret | String | HMAC secret for validation |
feeEnabled | Boolean | Enables fee charging |
duplicateWindowSec | Integer | Deduplication window (10-300, default 60) |
tenantMode | Enum | DATABASE, SCHEMA, SINGLE |
Entity relationships
OrganizationConfig (1) ──< has many >── (*) Transfer
├─< has many >─ (*) TransferStatusHistory
└─< originates from >─ (0..1) PaymentInitiation
JDIncomingMessage (1) ──< creates >── (0..1) Transfer (of type TED_IN)
Data ownership: Each entity is owned by ONE component, following Hexagonal Architecture principles.
Main indexes
| Table | Index | Purpose |
|---|
transfers | (organization_id, created_at) | Paginated listing by date |
transfers | (organization_id, status) | Filter by status |
transfers | (control_number) | Lookup by JD number |
jd_incoming_message | (sequence_number) | At-least-once deduplication |
transfer_status_history | (transfer_id, changed_at) | Chronological audit |
Partitioning
Audit tables are partitioned by month for better performance in historical queries and to facilitate data retention management.
Reconciliation fields
| Field | Usage |
|---|
controlNumber | JD SPB control number (unique per transfer) |
transferId | Lerian internal identifier |
confirmationNumber | User-readable number |
createdAt | Creation timestamp |
completedAt | Completion timestamp |
Transfer history is kept for 5 years, per BACEN regulatory requirements.