Advanced
Designing a Tran Code
In this tutorial, we will cover some of the factors that go into designing a transaction code.
Preliminary Questions
Tran codes should mirror a type of transaction that your system handles. They are meant to encapsulate and abstract accounting activity, and so the first step to designing a good tran code is having a clear understanding of what that activity is.
Thus, before we get into designing a tran codes, it is important to first clarify:
- What type of transaction is this? ACH transfer? Credit card purchase? Foreign exchange? Something else?
- Which accounts are involved?
- Where is the money coming from and where is it going?
- Are there any additional accounts that need to be debited/credited?
- How many entries should be written to the ledger?
- Which entries go on the debit side and which on the credit side?
- Do these entries reflect a settled amount, or are they still pending a final settlement?
Naming and Documenting
The type of transaction should be used to give the tran code a good name through its code
field. The code
field is the primary human-friendly identifier. It should provide a concise indication of what the tran code does and is used for.
We recommend using codes that just give enough information to be easily identifiable without being over-wordy. For consistency, we recommend using UPPER_SNAKE_CASE formatting for the code
.
Prefer:
- ✅
ACH_CREDIT
- ✅
CARD_HOLD_CANCEL
- ✅
INTEREST_ADJUSTMENT
Avoid:
- ❌
ACH
- ❌
CancelHold
- ❌
adjusting_interest_for_personal_loan_accounts
Depending on the size and complexity of your tran code library, you may choose to implement more formalized patterns and structures for naming your tran codes. For example, some organizations might use abbreviated versions of operations (HLD
, STL
, DEP
, CLR
) to keep tran code names extra terse.
Of course, only so much information can be communicated through a short string of characters. Because tran codes act as the API for your funds flow, they should also be well documented.
The description
field is where this documentation can live. Use it to add additional context about why the tran code exists, how it should be used, and whatever other information would benefit those interacting with your ledger. This field supports Markdown formatting.
Defining the Transaction & Ledger Entries
Posted transactions and the ledger entries written are defined within the transaction
and entries
fields, respectively. These are effectively templates used to generate the Transaction and Entries records.
Within the transaction
field, we can define values that describe important aspects of the transaction like its effective
date and which journal
it should be written to. Other Transaction fields like the correlationId
and metadata
can also be defined here, although they are optional.
The entries
field is a list of the templates for the Entries written to the ledger. Each ledger entry must define its accountId
, amount (in units
and currency
), direction
(DEBIT or CREDIT), layer
(SETTLED, PENDING, or ENCUMBRANCE), and entryType
. Optionally, a description
may be written here as well.
The entryType
for an entry is similar to the code
for a tran code: it is a short identifier for describing the type of activity that the entry represents. In many cases, the entryType
is just an extension of the code
. For example, an ACH_CREDIT_FEE
tran code might write ledger entries with types ACH_CREDIT_FEE_DR
for the debit-side and ACH_CREDIT_FEE_CR
for the credit-side entry.
How these entry definitions are written depends upon the transaction type and other information gathered as part of the pre-design process.
Example
mutation ACHCreditTC(
$achCreditId: UUID!
$journalId: Expression!
$achSettlementAcctId: Expression!
$exampleUserAcctId: Expression!
) {
achCredit: createTranCode(
input: {
tranCodeId: $achCreditId
code: "ACH_CREDIT"
description: "An ACH credit into an account."
transaction: { journalId: $journalId, effective: "date('2000-01-01')" }
entries: [
{
accountId: $achSettlementAcctId
units: "decimal('11.25')"
currency: "'USD'"
entryType: "'ACH_DR'"
direction: "DEBIT"
layer: "SETTLED"
}
{
accountId: $exampleUserAcctId
units: "decimal('11.25')"
currency: "'USD'"
entryType: "'ACH_CR'"
direction: "CREDIT"
layer: "SETTLED"
}
]
}
) {
tranCodeId
}
}
Parameterizing Inputs
In most cases, not all of the information needed to write transactions and entries is available at the time of designing a tran code, but instead needs to be passed in at runtime (i.e. when the transaction is posted). For example, most transactions are not for fixed amounts, and so these amounts need to be specified when posting the transaction.
This is where the params
field of a tran code comes in. With the params
, we can define parameters of a transaction which can then be referenced inside of the values defined for the transaction
and entries
.
Say we wanted to write entries where an amount
(in decimal units) is supplied at posting time. To do this, we need to do two things:
- Define an
amount
parameter inside of theparams
object. - Reference the
amount
value fromparams
within theunits
field of our entries.
A modification to the above tran code definition shows how this parameterization would work:
Example
mutation ACHCreditTC(
$achCreditId: UUID!
$journalId: Expression!
$achSettlementAcctId: Expression!
) {
achCredit: createTranCode(
input: {
tranCodeId: $achCreditId
code: "ACH_CREDIT"
description: "An ACH credit into an account."
params: [
{ name: "account", type: UUID, description: "Deposit account ID." }
{
name: "amount"
type: DECIMAL
description: "Amount with decimal, e.g. `1.23`."
}
{
name: "effective"
type: DATE
description: "Effective date for ACH transaction."
}
{
name: "currency"
type: STRING
description: "Currency code for entries. Defaults to 'USD'."
default: "USD"
}
]
transaction: { journalId: $journalId, effective: "params.effective" }
entries: [
{
accountId: $achSettlementAcctId
units: "params.amount"
currency: "params.currency"
entryType: "'ACH_DR'"
direction: "DEBIT"
layer: "SETTLED"
}
{
accountId: "params.account"
units: "params.amount"
currency: "params.currency"
entryType: "'ACH_CR'"
direction: "CREDIT"
layer: "SETTLED"
}
]
}
) {
tranCodeId
}
}
Note the change to params.amount
inside of the units
fields. Because these fields accept CEL expressions, we can reference fields on the runtime params
object to access the values passed in. You can see the use of params
in action: Tran Code Invocation.
In this way, the tran code can accept any number of parameterized values at runtime an inject them into the Transaction and Entries written.