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.

Previous
Model Deposits and Withdrawals