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
TED OUT
CREATED — transfer confirmed by the user, pending submission to JD
PENDING — submitted to JD, awaiting acknowledgment
PROCESSING — JD accepted the transfer and is processing it
COMPLETED — transfer successfully settled
REJECTED — JD returned a 4xx error (e.g., invalid data)
FAILED — JD returned a 5xx error or the request timed out
CANCELLED — user cancelled before JD processing began
TED IN
RECEIVED — message from JD persisted, awaiting internal processing
PROCESSING — system is crediting the recipient account
COMPLETED — recipient account successfully credited
REJECTED — recipient account not found
P2P
CREATED — transfer initiated between internal accounts
PROCESSING — Midaz transaction in progress
COMPLETED — both accounts updated successfully
FAILED — Midaz returned an error during processing
CANCELLED — transfer cancelled before processing began
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:
PENDING_CONFIRMATION — initiation created, user is reviewing fees before confirming
PROCESSED — user confirmed, transfer successfully created
EXPIRED — 24 hours elapsed without user confirmation
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 owns many Transfer records (one per tenant)
- Each
Transfer has many TransferStatusHistory records (one per state transition)
- Each
Transfer may originate from one PaymentInitiation (TED OUT two-step flow only)
- Each
JDIncomingMessage creates at most one 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.