Recipes · 1 of 6
Banking — transfers, withdrawals, statements
Six procedures that together model a small bank: open account, deposit, withdraw, transfer, freeze, monthly statement.
Schema
accounts: {_id, account_id, name, balance, frozen, created_at}
ledger: {account_id, type, amount, ts}
transfers: {from, to, amount, ts}
open_account
proc open_account(account_id, name, initial) {
if count("accounts", {account_id: account_id}) > 0 {
abort "account_id taken"
}
if initial < 0 { abort "initial deposit cannot be negative" }
let id = insert("accounts", {
account_id: account_id, name: name, balance: initial, frozen: false
})
insert("ledger", {account_id: account_id, type: "open", amount: initial})
return {ok: true, id: id}
}
deposit
proc deposit(account_id, amount) {
if amount <= 0 { abort "amount must be positive" }
let acc = find_one("accounts", {account_id: account_id})
if acc == null { abort "account not found" }
if acc.frozen { abort "account frozen" }
update("accounts", {account_id: account_id}, {$inc: {balance: amount}})
insert("ledger", {account_id: account_id, type: "deposit", amount: amount})
return {ok: true, balance: acc.balance + amount}
}
withdraw
proc withdraw(account_id, amount) {
if amount <= 0 { abort "amount must be positive" }
let acc = find_one("accounts", {account_id: account_id})
if acc == null { abort "account not found" }
if acc.frozen { abort "account frozen" }
if acc.balance < amount { abort "insufficient funds" }
update("accounts", {account_id: account_id}, {$inc: {balance: -amount}})
insert("ledger", {account_id: account_id, type: "withdraw", amount: -amount})
return {ok: true, balance: acc.balance - amount}
}
transfer
proc transfer(from, to, amount) {
if from == to { abort "self-transfer" }
if amount <= 0 { abort "invalid amount" }
let s = find_one("accounts", {account_id: from})
let r = find_one("accounts", {account_id: to})
if s == null || r == null { abort "account not found" }
if s.frozen || r.frozen { abort "account frozen" }
if s.balance < amount { abort "insufficient funds" }
update("accounts", {account_id: from}, {$inc: {balance: -amount, sent_count: 1}})
update("accounts", {account_id: to}, {$inc: {balance: amount, recv_count: 1}})
insert("transfers", {from: from, to: to, amount: amount})
return {ok: true}
}
freeze / unfreeze
proc freeze(account_id, reason, actor) {
update("accounts", {account_id: account_id}, {$set: {frozen: true}})
insert("audit_log", {action: "freeze", target: account_id, reason: reason, actor: actor})
return {ok: true}
}
proc unfreeze(account_id, actor) {
update("accounts", {account_id: account_id}, {$set: {frozen: false}})
insert("audit_log", {action: "unfreeze", target: account_id, actor: actor})
return {ok: true}
}
monthly_statement
proc monthly_statement(account_id, year, month) {
let acc = find_one("accounts", {account_id: account_id})
if acc == null { abort "account not found" }
let entries = find("ledger", {account_id: account_id, year: year, month: month})
let totals = aggregate("ledger", [
{$match: {account_id: account_id, year: year, month: month}},
{$group: {_id: "$type", total: {$sum: "$amount"}, n: {$sum: 1}}}
])
return {
account: acc.name, balance: acc.balance,
period: {year: year, month: month},
entries: entries, totals: totals
}
}