The balance model
Every transaction code posts to one or more balances. Read across its row: each cell tells you what happens to that balance — it adds money (+$) or it takes money (−$). That’s the whole idea.
The matrix
Rows are transaction codes. Columns are balances. Cells are what the code does to each balance.
| Code ╲ Balance | Available money | Held money | Pix used today |
|---|---|---|---|
| Cash deposit | +$ | – | – |
| Cash withdrawal | −$ | – | – |
| Card hold authorization | −$ | +$ | – |
| Pix sent | −$ | – | +$ |
| Pix received | +$ | – | – |
−$) the customer’s Available money and adds (+$) the same amount into Held — one code, moving money from one balance to another.What a cell can do
Two options. That’s the entire vocabulary for now.
+$ — the balance goes up by the amount. −$ — the balance goes down by the amount.
A code is a credit or a debit — but each balance can be set either way. That’s how a single code can add to one balance and take from another (the card hold above), instead of only ever doing one thing.
+$ / −$ rules as every other balance, it simply has a ceiling. Same machine, different balance.Level 2 — when one account type differs
Sometimes a code must touch different balances for a specific account type. The naïve idea is “one full matrix per type” — with dozens of account types that’s dozens of copies to keep in sync. It’s so impractical that there is no per-type matrix at all. Instead:
Keep one base matrix for everybody (the one above), then list only the cells that differ for specific account types. Everything not listed uses the base.
| Account type | Code | Instead of the base… |
|---|---|---|
| Savings | Pix sent | route “used today” to the Savings pix limit balance instead |
| Payroll | Pix sent | no limit balance — only Available −$ |
Every account type not in this list just uses the base. A short exceptions list, never a full copy per type.
Sending the same transaction twice is safe
Every transaction you send carries a correlationId — your own reference for it. If you send the same correlationId again (say a timeout made you retry and you’re not sure the first one landed), Digital Twin recognizes it and does not post it a second time — it’s treated as a replay, not a new transaction.
correlationId, and reuse the same one if you retry. It’s the same field whether you send over REST or Kafka, and it travels inside the message (not an HTTP header) — so retries are safe with no special replay handling on your side.