Twisp 101
Step 3: Model Intra-Bank Transfers
Zuzu also needs to support bank transfers between customer accounts so that customers can send money to one another.
This type of transaction we'll model with a tran code called BANK_TRANSFER
, but it's going to be a little more complex than the previous one.
Zuzu isn't just letting customers transfer money for free, all day long. Instead, they'll charge a small percentage fee of 1% with a $10 maximum, paid by the sender.
Create a revenue account
The fee charged for a bank transfer will be represented as a DEBIT
against the sender's bank account. The balancing CREDIT
entry will be posted to Zuzu's revenue account, which doesn't exist yet.
To support bank transfers, then, we'll need to first create the revenue account for Zuzu.
008_CreateRevenueAccount
mutation CreateRevenueAccount($revenueId: UUID!) {
createAccount(
input: {
accountId: $revenueId
name: "Revenues"
code: "REV"
description: "Company revenues (e.g. fees)"
normalBalanceType: CREDIT
}
) {
accountId
name
}
}
Now that we have the revenue account, we can design the tran code for internal transfers.
Define the TranCode for transfers
The tran code for this transaction type needs to do a few things:
- Write entries to move the defined amount from the sender's checking account to the receiver's checking account
- Write an additional two entries to move the fee from the sender's checking account to the Revenue account, using a runtime expression to calculate the fee amount
When posting a transaction, we want to enable the poster to provide the sender's account ID, the receiver's account ID, the amount to transfer, the 1% fee, and the effective date of the transfer. We'll define each of these as params
on the TranCode.
009_CreateBankTransferTranCode
mutation CreateBankTransferTranCode($transferId: UUID!) {
createTranCode(
input: {
tranCodeId: $transferId
code: "BANK_TRANSFER"
description: "Transfer $ internally from one checking account to another. The sender is charged a 1% fee or $10, whichever is smaller."
params: [
{ name: "fromAccount", type: UUID, description: "Sender's account ID." }
{ name: "toAccount", type: UUID, description: "Receiver's account ID." }
{
name: "amount"
type: DECIMAL
description: "Amount with decimal, e.g. `1.23`."
}
{
name: "fee"
type: DECIMAL
description: "Transfer fee as decimal percentage, e.g. `0.01` for 1%"
}
{
name: "effective"
type: DATE
description: "Effective date for transaction."
}
]
transaction: {
journalId: "uuid('822cb59f-ce51-4837-8391-2af3b7a5fc51')"
effective: "params.effective"
}
entries: [
{
accountId: "uuid(params.fromAccount)"
units: "params.amount"
currency: "'USD'"
entryType: "'TRANSFER_DR'"
direction: "DEBIT"
layer: "SETTLED"
}
{
accountId: "uuid(params.toAccount)"
units: "params.amount"
currency: "'USD'"
entryType: "'TRANSFER_CR'"
direction: "CREDIT"
layer: "SETTLED"
}
{
accountId: "uuid(params.fromAccount)"
units: "decimal.Round(decimal.Mul(params.amount, params.fee), 'half_up', 2)"
currency: "'USD'"
entryType: "'TRANSFER_FEE_DR'"
direction: "DEBIT"
layer: "SETTLED"
}
{
accountId: "uuid('ece5e752-5445-4f4e-8861-d09c5c417061')" # This is the account ID for the Revenues account
units: "decimal.Round(decimal.Mul(params.amount, params.fee), 'half_up', 2)"
currency: "'USD'"
entryType: "'TRANSFER_FEE_CR'"
direction: "CREDIT"
layer: "SETTLED"
}
]
}
) {
tranCodeId
}
}
Writing clear descriptions for tran codes and their parameters is a great way to help API users can understand what the tran code is for and how to invoke it.
Post a test transaction
Let's test this transaction out by sending $2.25 from Ernie to Bert. To see the results of the transaction as encoded by the tran code, we'll return the entries posted, digging all the way down into the account for each entry.
012_PostBankTransfer
mutation PostBankTransfer {
postTransaction(
input: {
transactionId: "9c328550-bba3-423b-a58a-b3f9786a80ae"
tranCode: "BANK_TRANSFER"
params: {
fromAccount: "1fd1dd3e-33fe-4ef5-9d58-676ef8d306b5"
toAccount: "6c6affb0-5cf5-402b-8d84-01bfc1624a2c"
amount: "2.25"
fee: "0.02"
effective: "2022-09-10"
}
}
) {
transactionId
entries(first: 10) {
nodes {
units
direction
entryType
account {
name
}
}
}
}
}
Success! From our response, we can see that each entry was posted to the correct account and for the correct amount.