humanblnk tutorial

Chapter 1 · Foundations

Setting up

Blnk groups accounts into ledgers. You need only a few, made once:

LedgerHolds
Customer Walletsevery customer's spending accounts, in every currency
Savings (optional)customers' savings accounts
Operations (optional)your company's own accounts that aren't system accounts, such as an escrow pool

Add a ledger only for a different kind of money. Never make one per customer or per currency. You never create the General Ledger or the system accounts (@World, @FeeIncome, clearing accounts): Blnk makes them the first time a transaction uses them.

Step 1: create the ledgers, once

Run this from a setup script or migration, not when your service starts. Ledgers.Create isn't safe to repeat: each call makes another ledger. If you're not sure the setup already ran, look first with Ledgers.List(ctx, 10, 0).

01-setup/main.go
setup := humanblnk.New(client)
suffix := time.Now().Format("150405") // only so this example can run twice
wallets, err := setup.Ledgers.Create(ctx, "Customer Wallets "+suffix, nil)
if err != nil {
    log.Fatal(err)
}
savings, err := setup.Ledgers.Create(ctx, "Savings "+suffix, nil)
if err != nil {
    log.Fatal(err)
}
ops, err := setup.Ledgers.Create(ctx, "Operations "+suffix, nil)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Put these in your config:")
fmt.Println("  WALLETS_LEDGER =", wallets.ID)
fmt.Println("  SAVINGS_LEDGER =", savings.ID)
fmt.Println("  OPS_LEDGER     =", ops.ID)

Step 2: build the Client at start-up

Every time your service starts, build one *humanblnk.Client from the ids in your config, and share it. Everything hangs off it:

01-setup/main.go
hb := humanblnk.New(client,
    humanblnk.WithAccountLedger(wallets.ID),   // where customer wallets open
    humanblnk.WithCustomerLedgers(savings.ID), // other customer-only ledgers
)

// The services hang off it.
_ = hb.Identities     // owners of customer accounts
_ = hb.Accounts       // open, create and read accounts
_ = hb.Transactions   // record, preview, reverse and read money movements
_ = hb.Holds          // reserve money, then capture or cancel
_ = hb.Watches        // balance thresholds that fire a webhook
_ = hb.Reconciliation // match provider statements against entries
_ = hb.Ledgers        // the ledgers themselves
OptionWhy
WithAccountLedger(id)The ledger Accounts.OpenForCustomer uses when you don't name one. Don't change it once customers have accounts: their wallets live in the old one.
WithCustomerLedgers(ids...)Other ledgers that hold only customer accounts. CreateInternal refuses them, so a customer account can never be made without an owner.

Connect with blnk.New(url), adding blnk.WithAPIKey(key) when Blnk runs in secure mode.

The whole example

go run ./docs/examples/01-setup
01-setup/main.go
// Chapter 1: create the ledgers once, then build the Client your service
// uses from their ids.
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/otyang/humanblnk"
    "github.com/otyang/humanblnk/blnk"
)

func main() {
    ctx := context.Background()
    url := os.Getenv("BLNK_URL")
    if url == "" {
        url = "http://localhost:5001"
    }
    client := blnk.New(url) // add blnk.WithAPIKey(key) when Blnk runs in secure mode

    // --- Once, at setup (a script or migration, never at start-up) ---
    setup := humanblnk.New(client)
    suffix := time.Now().Format("150405") // only so this example can run twice
    wallets, err := setup.Ledgers.Create(ctx, "Customer Wallets "+suffix, nil)
    if err != nil {
        log.Fatal(err)
    }
    savings, err := setup.Ledgers.Create(ctx, "Savings "+suffix, nil)
    if err != nil {
        log.Fatal(err)
    }
    ops, err := setup.Ledgers.Create(ctx, "Operations "+suffix, nil)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Put these in your config:")
    fmt.Println("  WALLETS_LEDGER =", wallets.ID)
    fmt.Println("  SAVINGS_LEDGER =", savings.ID)
    fmt.Println("  OPS_LEDGER     =", ops.ID)

    // --- At start-up, every time: one Client for the whole service ---
    hb := humanblnk.New(client,
        humanblnk.WithAccountLedger(wallets.ID),   // where customer wallets open
        humanblnk.WithCustomerLedgers(savings.ID), // other customer-only ledgers
    )

    // The services hang off it.
    _ = hb.Identities     // owners of customer accounts
    _ = hb.Accounts       // open, create and read accounts
    _ = hb.Transactions   // record, preview, reverse and read money movements
    _ = hb.Holds          // reserve money, then capture or cancel
    _ = hb.Watches        // balance thresholds that fire a webhook
    _ = hb.Reconciliation // match provider statements against entries
    _ = hb.Ledgers        // the ledgers themselves

    all, err := hb.Ledgers.List(ctx, 3, 0)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Newest ledgers:")
    for _, b := range all {
        fmt.Printf("  %s  %s\n", b.ID, b.Name)
    }
}

The other examples skip these steps with demo.Setup() (docs/examples/demo), which makes a fresh wallets ledger per run, and demo.Customer, which onboards a customer as chapter 2 shows. A real service doesn't need them.