Skip to main content
Settlement items are the bridge between accounting records (ledger entries) and actual money movements. While ledger entries record what should happen financially, settlement items track what actually happened—when money moved, how it moved, and to where.

What are settlement items?

A settlement item is a clearing link that applies a portion (or all) of a ledger entry to a real settlement operation. Each settlement item represents an actual money movement:
  • PIX payment to a merchant’s bank account
  • Internal transfer between accounts
  • Invoice payment for monthly costs
  • Boleto payment for settlements

Entity relationships

Core attributes

Each settlement item contains:
  • ledger_entry_id: Which ledger entry is being settled
  • settled_amount: How much of the entry is being settled (in cents)
  • settlement_date: When the money actually moved
  • method: How the money moved (PIX, internal transfer, etc.)
  • status: Current state of the settlement
  • operation_id: External reference (transaction ID, transfer ID, etc.)
  • affiliation_bank_account_id: Destination bank account (if applicable)

Settlement methods

Rinne supports multiple settlement methods:

PIX

Instant payments to merchant bank accounts (D+0)

Internal transfer

Transfers between Rinne accounts (organization fees)

Invoice

Monthly invoices for platform/provider costs

Boleto

Bank slip payments for settlements

Settlement status lifecycle

Settlement items follow a state machine with strict transition rules:

Valid transitions

  • PENDING → PROCESSING: Settlement initiated
  • PENDING → PAID: Instant settlement (PIX)
  • PENDING → FAILED: Settlement failed immediately
  • PROCESSING → PAID: Settlement completed
  • PROCESSING → FAILED: Settlement failed during processing

Terminal states

  • PAID: Settlement completed successfully (no further transitions)
  • FAILED: Settlement failed permanently (no further transitions)

PIX settlement flow

For PIX transactions, Rinne creates settlement items immediately after the transaction is approved:

1. Transaction settlement

The main transaction amount is settled instantly:
{
  "ledger_entry_id": "le_merchant_tx_credit",
  "settled_amount": 10000,
  "settlement_date": "2025-01-15",
  "method": "PIX",
  "status": "PAID",
  "operation_id": "trx_456",
  "affiliation_bank_account_id": "ba_merchant_account"
}
The provider’s corresponding debit is also settled:
{
  "ledger_entry_id": "le_provider_tx_debit",
  "settled_amount": 10000,
  "settlement_date": "2025-01-15",
  "method": "INTERNAL_TRANSFER",
  "status": "PAID",
  "operation_id": "trx_456"
}

2. Organization fee settlement

Organization fees are settled via internal transfer:
{
  "ledger_entry_id": "le_merchant_fee_debit",
  "settled_amount": 250,
  "settlement_date": "2025-01-15",
  "method": "INTERNAL_TRANSFER",
  "status": "PENDING",
  "operation_id": "internal_transfer_789"
}
When the internal transfer completes, the status updates to PAID.

3. Platform and provider costs

Platform and provider costs remain outstanding for monthly invoice settlement:
{
  "ledger_entry_id": "le_platform_cost_credit",
  "outstanding_amount": 100,
  "settled": false
}

Two-step settlement creation

To prevent race conditions, Rinne uses a two-step process for creating settlement items:

Step 1: Create with null operation_id

Settlement items are created first with operation_id: null and status: PENDING:
{
  "id": "si_merchant_fee_transfer",
  "ledger_entry_id": "le_merchant_fee_debit",
  "settled_amount": 250,
  "method": "INTERNAL_TRANSFER",
  "status": "PENDING",
  "operation_id": null
}

Step 2: Update with actual operation_id

After the operation (e.g., internal transfer) is created, settlement items are updated:
{
  "id": "si_merchant_fee_transfer",
  "operation_id": "internal_transfer_789",
  "status": "PENDING"
}

Step 3: Status updates

When the operation completes, settlement items are updated again:
{
  "id": "si_merchant_fee_transfer",
  "status": "PAID",
  "updated_at": "2025-01-15T10:35:00Z"
}

Race condition prevention

Rinne uses pair_token lookups to prevent race conditions between settlement creation and status updates:

The problem

When an internal transfer status changes, the handler needs to find associated settlement items. However, there’s a race condition where the status change event could fire before settlement items are updated with the operation_id.

The solution

Instead of looking up settlement items by operation_id, Rinne uses the pair_token from ledger entries:
  1. Settlement items are created with operation_id: null
  2. Internal transfer is created with request_id = ledger entry pair_token
  3. Status change handler finds settlement items using pair_token (via ledger entry relationship)
  4. Settlement items are updated with the actual operation_id
This ensures settlement items always exist before status changes are processed.

Ledger entry updates

When settlement items are created or updated, the corresponding ledger entries are automatically updated:

Outstanding amount calculation

outstanding_amount = amount - Σ(settled_amount from non-FAILED settlement items)

Settlement status

settled = (outstanding_amount === 0)
fully_settled_at = settled ? now() : null
last_clearing_at = most_recent_settlement_date

Example progression

Initial state (after ledger entry creation):
{
  "id": "le_merchant_fee_debit",
  "amount": 250,
  "outstanding_amount": 250,
  "settled": false,
  "fully_settled_at": null,
  "last_clearing_at": null
}
After settlement item creation (PAID):
{
  "id": "le_merchant_fee_debit",
  "amount": 250,
  "outstanding_amount": 0,
  "settled": true,
  "fully_settled_at": "2025-01-15T10:35:00Z",
  "last_clearing_at": "2025-01-15T10:35:00Z"
}

Partial settlements

Settlement items support partial settlements, where a ledger entry is settled in multiple operations:
// First settlement: R$50 of R$100
{
  "ledger_entry_id": "le_123",
  "settled_amount": 5000,
  "method": "PIX",
  "status": "PAID"
}

// Second settlement: R$30 of R$100
{
  "ledger_entry_id": "le_123",
  "settled_amount": 3000,
  "method": "PIX",
  "status": "PAID"
}

// Third settlement: R$20 of R$100
{
  "ledger_entry_id": "le_123",
  "settled_amount": 2000,
  "method": "PIX",
  "status": "PAID"
}
The ledger entry tracks the total:
{
  "id": "le_123",
  "amount": 10000,
  "outstanding_amount": 0,
  "settled": true
}

Refund settlements

Refund settlements follow the same pattern as transaction settlements:

Transaction refund

{
  "ledger_entry_id": "le_merchant_refund_debit",
  "settled_amount": 5000,
  "settlement_date": "2025-01-15",
  "method": "PIX",
  "status": "PAID",
  "operation_id": "refund_456"
}

Organization fee refund

{
  "ledger_entry_id": "le_merchant_fee_refund_credit",
  "settled_amount": 125,
  "settlement_date": "2025-01-15",
  "method": "INTERNAL_TRANSFER",
  "status": "PAID",
  "operation_id": "internal_transfer_890"
}

Idempotency

Settlement item creation is idempotent. If settlement items already exist for a ledger entry and operation ID, Rinne skips creation:
// Check for existing settlement items
const existingItems = await findSettlementItemsByLedgerEntryIdAndOperationId(
  ledgerEntryId,
  operationId
);

// Calculate total settled amount (excluding FAILED)
const totalSettled = existingItems
  .filter(item => item.status !== 'FAILED')
  .reduce((sum, item) => sum + item.settled_amount, 0);

// Skip if already fully settled
if (totalSettled >= ledgerEntry.amount) {
  return; // Already settled
}

Settlement invariants

Rinne enforces strict invariants for settlement items:

Settlement bounds

Σ(settled_amount) ≤ ledger_entry.amount
You cannot settle more than the original ledger entry amount.

Outstanding accuracy

ledger_entry.amount = Σ(settled_amount) + outstanding_amount
The sum of settled amounts plus outstanding amount must equal the original amount.

Non-negative outstanding

outstanding_amount0
Outstanding amounts can never be negative.

Settlement status consistency

settled = trueoutstanding_amount = 0
A ledger entry is settled if and only if the outstanding amount is zero.

Complete settlement example

Here’s a complete example showing how a R$100 PIX transaction is settled:

1. Ledger entries created

{
  "merchant_credit": {
    "amount": 10000,
    "outstanding_amount": 10000,
    "settled": false
  },
  "organization_fee_debit": {
    "amount": 250,
    "outstanding_amount": 250,
    "settled": false
  }
}

2. Settlement items created

{
  "merchant_settlement": {
    "ledger_entry_id": "merchant_credit",
    "settled_amount": 10000,
    "method": "PIX",
    "status": "PAID"
  },
  "fee_settlement": {
    "ledger_entry_id": "organization_fee_debit",
    "settled_amount": 250,
    "method": "INTERNAL_TRANSFER",
    "status": "PENDING"
  }
}

3. Ledger entries updated

{
  "merchant_credit": {
    "amount": 10000,
    "outstanding_amount": 0,
    "settled": true,
    "fully_settled_at": "2025-01-15T10:30:00Z"
  },
  "organization_fee_debit": {
    "amount": 250,
    "outstanding_amount": 0,
    "settled": true,
    "fully_settled_at": "2025-01-15T10:35:00Z"
  }
}

Key principles

Actual movements

Track real money movements, not just accounting

State machine

Strict status transitions prevent invalid states

Race-safe

Two-step creation prevents race conditions

Idempotent

Safe to retry settlement operations

Next steps