Help us shape the future of Go! Take the 2026 Go Developer Survey

humanblnk

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 14 Imported by: 0

README

humanblnk

humanblnk is a Go library for keeping a wallet's or bank's books in Blnk Core. You work in bookkeeping terms (customers, accounts, transactions, holds, reversals) and exact money, and humanblnk makes Blnk follow the rules a money ledger needs, which Blnk alone leaves to you.

It's for developers building fintech products (wallets, neobanks, marketplaces, payouts, remittance) who want Blnk's double-entry ledger without re-learning its sharp edges in production.

go get github.com/otyang/humanblnk   # Go 1.24+

Why not call Blnk directly?

Blnk is a solid double-entry engine, but it's deliberately low-level. These are the gaps humanblnk closes:

With Blnk alone With humanblnk
Amounts are integers you scale yourself; one write at the wrong precision mixes units silently Amounts are money.Money, exact decimals, sent in the currency's smallest unit at its own precision; entries read back are checked against it
A USD 10.00 transaction applied to an NGN (Nigerian naira) account lands as NGN 10.00 A currency mismatch is refused with money.ErrCurrencyMismatch
A retried request hits a "reference already used" error that doesn't say whether your write applied You build each reference from your own ids with humanblnk.Reference. A retried Record gets ErrDuplicate (it applied) or ErrReferenceUsed (something else holds that reference); a retried hold, or a reversal retried with its Ref, returns the one already made. Any reference works, / included
Writes are queued by default: insufficient funds turns up later as a REJECTED status Writes are synchronous: you get ErrInsufficientFunds from the call itself
A split (one transaction paying several accounts, such as an amount plus a fee) that fails partway has already moved its earlier parts; Blnk refunds them later, from its worker A split is held and captured at once, so one that fails partway moves nothing, not even for a moment. Its entry reads as StatusRejected and its reference can't be reused
Overdraft is a flag on each write: any write that sets it can take its source below zero, so a split from a customer and a system account can't let only one of them Only system accounts (such as @World, money outside your books, or a provider's clearing account) go below zero, each on its own terms, even in one split
Customer money and your company's own can share a ledger Customer accounts need an owner and live in the customer ledgers you name; your own accounts can't be opened there
Blnk's refund undoes a transaction once, in full; partial refunds are yours to track Reversals, full or partial, are numbered per entry and never undo more than it moved, even when they run concurrently

What it isn't

  • Not a payment provider integration. You talk to Stripe, Paystack or your bank yourself; humanblnk moves the money in your books to match.

  • Not a server. It's a library inside your Go service, with no HTTP API of its own.

  • Not a store for personal data. Customers are Blnk identities holding only your ids (such as customer_id). Names, documents and KYC stay in your own systems.

  • Not a replacement for Blnk. You run Blnk Core 0.15.6 with its worker, Postgres and Redis. To start all four locally:

    docker compose -f deploy/docker-compose.yml up -d   # Blnk on http://localhost:5001
    

A first look

import (
	"github.com/otyang/humanblnk"
	"github.com/otyang/humanblnk/blnk"
	"github.com/otyang/humanblnk/money"
	"github.com/otyang/humanblnk/money/currency"
)

usd := currency.MustGet(currency.USD)
hb := humanblnk.New(blnk.New("http://localhost:5001"), // add blnk.WithAPIKey(key) if Blnk runs in secure mode
	humanblnk.WithAccountLedger(walletsLedgerID), // made once by a setup script (tutorial chapter 1)
)

// A customer and their wallet. Identities.Create is not safe to repeat:
// in a sign-up handler, try hb.Identities.FindByCustomer first.
owner, err := hb.Identities.Create(ctx, humanblnk.Meta{"customer_id": "cus_123"})
wallet, err := hb.Accounts.OpenForCustomer(ctx, humanblnk.CustomerAccount{Owner: owner, Currency: usd}) // safe to repeat

// A card top-up. Stripe owes you the money until it settles, so it comes
// from Stripe's clearing account.
amount := money.MustParse("50.00", usd)
_, err = hb.Transactions.Record(ctx, humanblnk.Transaction{
	Ref:         humanblnk.Reference("deposit", "stripe", paymentIntentID), // the same on every retry
	From:        humanblnk.Single(humanblnk.ClearingAccount("stripe"), amount),
	To:          humanblnk.Single(humanblnk.AccountRef(wallet.ID), amount),
	Description: "Card top-up",
})
if errors.Is(err, humanblnk.ErrDuplicate) {
	err = nil // a retried webhook: already recorded
}

// A payout: reserve the money now, then settle or release it when the bank answers.
hold, err := hb.Holds.Place(ctx, humanblnk.Transaction{
	Ref:         humanblnk.Reference("payout", payoutID),
	From:        humanblnk.Single(humanblnk.AccountRef(wallet.ID), money.MustParse("20.00", usd)),
	To:          humanblnk.Single(humanblnk.World, money.MustParse("20.00", usd)),
	Description: "Payout to bank",
}, time.Now().Add(72*time.Hour))

acct, err := hb.Accounts.Get(ctx, wallet.ID)
fmt.Println(acct.Available) // "30.00": what the customer can spend now

if bankAccepted {
	_, err = hb.Holds.Capture(ctx, hold, money.Money{}) // all of it
} else {
	err = hb.Holds.Cancel(ctx, hold)
}

Testing without Blnk

humanblnktest runs your code against an in-memory Blnk, so your tests need no Blnk, Docker or network:

func TestPayout(t *testing.T) {
	hb := humanblnktest.New(t) // a fresh in-memory Blnk, closed when the test ends
	ada := hb.Customer(t, "USD")
	hb.Fund(t, ada, "100.00")

	svc := payouts.New(hb.Holds) // your code, given the real humanblnk
	// ... exercise svc ...

	if got := hb.Available(t, ada); got != "70.00" {
		t.Fatalf("Ada can spend %s, want 70.00", got)
	}
}

It copies the Blnk Core 0.15.6 behaviour humanblnk relies on: reused references refused, held splits released at once when a later posting fails, holds settled by transactions of their own, signed webhooks. humanblnk's contract tests run against it and a real Blnk alike, so a difference they cover fails CI. It isn't Blnk: nothing is queued, and a few features humanblnk doesn't use are left out (listed in tutorial chapter 13), so keep a few tests against a real Blnk too.

Learn more

  • Tutorial: fourteen chapters. Eleven walk through a real task (sign-up, deposits, transfers with fees, payouts, refunds, currency exchange, webhooks, reconciliation) with a program you can run against your own Blnk; the others cover errors and retries, testing your service without Blnk, and a cheat sheet.
  • Reference: every service and method, the rules humanblnk enforces, what's safe to retry, its errors and its limits. Also in doc.go.
  • money: exact amounts and currencies, usable on their own.

Packages

Package What it is
humanblnk Your books: Client, its services, and the vocabulary
money An exact amount in one currency: parse, add, round, convert to the smallest unit
humanblnktest humanblnk on an in-memory Blnk, for your tests
blnk A Blnk Core HTTP client. You use it to build the client (blnk.New, blnk.WithAPIKey); only humanblnk calls Blnk through it

Your own features (airtime top-ups, payouts, transfers) go in their own packages and import humanblnk; humanblnk never imports them.

Contributing

See CONTRIBUTING.md for the layout, the tests and how the tutorial is built.

License

MIT

Documentation

Overview

Package humanblnk keeps a wallet's or bank's books in Blnk Core 0.15.6. Callers work in bookkeeping terms and exact money (money.Money), and humanblnk makes Blnk follow the rules a money ledger needs, which Blnk alone leaves to the caller. It is a library, not a server, and it moves money only in Blnk: payment providers and banks stay the caller's to talk to.

Build one Client at start-up and share it:

hb := humanblnk.New(blnk.New(url, blnk.WithAPIKey(key)),
	humanblnk.WithAccountLedger(walletsLedgerID),
	humanblnk.WithCustomerLedgers(savingsLedgerID),
)

The ledgers themselves are made once, by a setup script, with Ledgers.Create.

A Client remembers the currency of up to 100,000 recently used accounts (about 20 MiB), so it needn't ask Blnk before every write. WithAccountCacheSize changes that; either way memory stays bounded however many accounts a long-running service touches. The tutorial at https://otyang.github.io/humanblnk/docs/ walks through each task with a program to run.

Vocabulary

  • A Ledger is a Blnk ledger: a named group of accounts, such as "Customer Wallets". Blnk's own General Ledger holds the system accounts.
  • An Account is a Blnk balance: money in one currency. A customer's account always has an owner (an Identity holding no personal data) and is opened with Accounts.OpenForCustomer, one per customer ledger and currency. Your company's own accounts are made with Accounts.CreateInternal, outside the customer ledgers. An Account is not Blnk's "accounts" feature, which stores bank account numbers and lives outside this package.
  • System accounts are your company's own, and the only accounts that may go below zero: World (money outside your books: deposits come from it, payouts go to it), FeeIncome, Adjustments, ClearingAccount(provider) for what a payment provider owes you until it settles, and any other SystemAccount(name).
  • A Transaction is what you ask for: money From some Postings To others, in one atomic write. One side may have several postings (a split, such as an amount plus a fee), not both. Transactions.Record moves the money; it is not a log of a move made elsewhere.
  • An Entry is what was recorded.
  • A hold reserves money until it is captured or cancelled (Holds.Place, Holds.Capture, Holds.Cancel).
  • A Reversal moves money back the way an entry moved it, in full or in part.

Services

A Client groups its operations by what they act on. Each service also has a type of its own (IdentityService, AccountService, ...), so code that needs only one can take just that.

  • Identities: Create, Get, UpdateMeta, FindByCustomer. Owners for customer accounts, holding only references such as customer_id.
  • Accounts: OpenForCustomer, CreateInternal, Get, List, and System for a system account by name and currency.
  • Transactions: Record, RecordAll, Preview, Reverse, Get, GetByReference, List. Moving money now, undoing it, and reading what was recorded. Reverse takes an entry's id, or a captured hold's: the id Capture returned, or the hold's own.
  • Holds: Place, Capture, Cancel. Reserving money now, settling it later.
  • Watches: Create, Get, List, Change, Delete. Thresholds that fire Blnk's balance.monitor webhook.
  • Reconciliation: Upload, AddRule, ChangeRule, RemoveRule, Run, RunLines, Result. Matching provider and bank statements against entries.
  • Ledgers: Create, Get, List, Rename.

VerifyWebhook checks a Blnk webhook's signature and age, then decodes it into an Event.

Rules

Every operation keeps the same rules:

  • Amounts reach Blnk only in their currency's smallest unit, with that currency's precision, never one a caller chose. Blnk balances are bare integers, so one transaction at another precision would mix units silently. Entries read back are checked against the same precision.
  • A transaction's currency must match every account it touches. Blnk does not check this: it would apply USD 10.00 to an NGN account as NGN 10.00.
  • Every money movement (Record, RecordAll, Place, and a partial Reverse) carries a reference, built from the caller's own ids with Reference and the same on every retry, so a retry cannot apply it twice. A retried Record or RecordAll returns ErrDuplicate with the entry; a retried Place returns the hold already placed (ErrHoldSettled if it was released since), and a Reverse retried with its Ref the reversal already made. ErrReferenceUsed means the reference is taken by something other than this write: a different transaction, or this one refused partway. Any reference works, "/" included; a split and a single transaction can't share one, so each write first checks that.
  • Writes are applied synchronously (Blnk's skip_queue), so a refusal such as ErrInsufficientFunds is returned, not discovered later as a rejected status.
  • A write applies in full or not at all, and a refused one moves nothing, not even for a moment. Blnk moves a split a posting at a time: applied, one refused partway has already moved its earlier postings, which Blnk refunds only later. So Record sends a split as a hold and captures it at once, and RecordAll its batch the same way: a hold refused partway is released before Blnk answers. The entry still reads as the one applied transaction it is.
  • Only system accounts may go below zero; a customer account never may. Blnk allows an overdraft per transaction, not per source, so a split taking from both (a refund of a transfer with a fee, say) goes to Blnk as an atomic batch, one transaction per source, and reads back as the split it is.
  • Reversals, full or partial, are numbered per entry. They take turns, even concurrently, and never undo more than the entry moved. A reversal takes from where the entry put the money, so a customer who spent it can't be pushed below zero.
  • Holds.Cancel only releases a hold; Transactions.Reverse is the only way to undo money that moved. Corrections stay in the books as entries of their own.
  • Customer data stays out of Blnk: identities hold references such as customer_id, nothing more.

Check Account.Available, not Account.Total, before letting a customer spend. From Accounts.Get, Available is Total less outgoing holds and queued debits; Accounts.List leaves out queued debits.

Retries

The blnk client already retries transient failures (3 times by default; see blnk.WithRetries), and only where a repeat is harmless. After that, a *blnk.APIError with a 4xx status means Blnk refused the call; a network error or a gateway timeout means the outcome is unknown, so repeat the call with the same arguments if it is safe to:

  • Safe: Transactions.Record and RecordAll (same Ref), Reverse (same Ref; a full reversal without one returns ErrAlreadyReversed once done), Holds.Place, Holds.Cancel, a full Holds.Capture, Accounts.OpenForCustomer, Identities.UpdateMeta, Ledgers.Rename, Reconciliation.ChangeRule, every read, and every delete (a repeat may return ErrNotFound).
  • Wait a few seconds first: a partial Holds.Capture. Blnk's commit takes no reference, and a partial capture still running in Blnk isn't visible yet, so a quick repeat can capture twice.
  • Not safe, so look first: Identities.Create (use FindByCustomer), Accounts.CreateInternal, Ledgers.Create, Watches.Create and Change (use List), and Reconciliation.Upload, AddRule, Run and RunLines.

Errors

Compare errors with errors.Is, never on the message:

  • ErrInsufficientFunds: the source account can't cover it; nothing moved.
  • ErrDuplicate: this exact write already happened, applied. Treat it as success: the existing entry comes back with it.
  • ErrReferenceUsed: this write did not apply; the reference holds a different transaction, or this one was refused partway (for a Reverse, its Ref names a reversal of another amount). After a refusal, record it again under a new reference; otherwise investigate how two writes got the same reference.
  • ErrNotFound: no such account, entry, hold, ledger, identity or watch.
  • ErrUnbalanced: From and To don't add up to the same amount.
  • ErrAlreadyReversed: nothing, or not that much, is left to reverse.
  • ErrHoldSettled: the hold was already captured, cancelled or expired; the id isn't a hold; a repeated Capture asked for a different amount, or more than is left; a split hold has a posting released (say, by its expiry), so it can't be captured whole; or a Place found its hold released.
  • ErrInvalid: refused before reaching Blnk, such as a missing reference, a negative amount, the wrong ledger, or reversing an entry that isn't applied. Don't retry it unchanged.
  • ErrReadBack: RecordAll applied, but reading the entries back failed. Don't record it again; look the entries up by reference.
  • money.ErrCurrencyMismatch: an amount's currency doesn't match the account's. money.ErrUnknownCurrency: a Money that was never set, or an unknown currency code.

When Blnk refused the call, the error also wraps Blnk's, for logs; a Blnk error humanblnk has no name for comes back as a *blnk.APIError. VerifyWebhook returns blnk.ErrBadSignature or blnk.ErrStale when the check fails, and a plain error for a body it can't read or decode.

Testing

Package humanblnktest runs a Client against an in-memory Blnk, so code built on humanblnk can be tested with no Blnk, Docker or network:

hb := humanblnktest.New(t)
ada := hb.Customer(t, "USD")
hb.Fund(t, ada, "100.00")

It copies the Blnk Core 0.15.6 behaviour humanblnk relies on, and humanblnk's contract tests run against it and a real Blnk alike.

Limits

  • A transaction can't be many to many: one side may have several postings, not both. RecordAll takes one posting on each side.
  • A split is reversed and captured only in full.
  • Transactions.List shows Blnk's own records, each posting of a split its own entry. A split or RecordAll batch, held and captured, is there twice: its postings held (StatusHeld) and their captures (StatusApplied), the captures under the transaction's reference. List with Status StatusApplied for what moved. Blnk can't filter "from or to" together with other conditions, so an account's full history is two List calls, one with From, one with To.
  • List calls return 1 to 100 items per page (20 by default).
  • A watch threshold is at most 2^53 in the currency's smallest unit. Watches.Change makes a new watch with a new ID, and both may fire once while it switches.
  • Reconciliation.RunLines takes at most 10,000 lines; upload a file for more.
  • Accounts.OpenForCustomer calls racing from different processes can each open an account. All of them then return the oldest; the others are left empty and marked with meta_data.duplicate_of.
  • A split Record, or a RecordAll, whose process stops between hold and capture leaves the money reserved, not moved: the destination doesn't have it yet, and the source can't spend it. Retried within RecordHold (15 minutes) it is captured; after that the hold expires, the money is released, and the reference is used (ErrReferenceUsed). Keep Blnk's worker running: it expires holds.
Example (Deposit)

A bank transfer arrives through Paystack: the provider owes you until it settles, so the money comes from its clearing account.

package main

import (
	"context"
	"errors"

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

var ctx = context.Background()

func main() {
	hb := humanblnk.New(blnk.New("https://blnk.internal"), humanblnk.WithAccountLedger("ldg_wallets"))
	ngn := currency.MustGet(currency.NGN)
	amount := money.MustParse("50000", ngn)

	_, err := hb.Transactions.Record(ctx, humanblnk.Transaction{
		Ref:         humanblnk.Reference("deposit", "paystack", "ps_ref_123"), // the provider's id
		From:        humanblnk.Single(humanblnk.ClearingAccount("paystack"), amount),
		To:          humanblnk.Single("bln_ada_ngn", amount),
		Description: "Bank transfer",
	})
	if errors.Is(err, humanblnk.ErrDuplicate) {
		err = nil // the webhook was delivered twice; it's already recorded
	}
	_ = err
}
Example (PayoutHold)

A payout reserves the money while the bank sends it, then settles or returns it when the provider reports back.

package main

import (
	"context"
	"time"

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

var ctx = context.Background()

func main() {
	hb := humanblnk.New(blnk.New("https://blnk.internal"))
	amount := money.MustParse("250", currency.MustGet(currency.USD))

	hold, err := hb.Holds.Place(ctx, humanblnk.Transaction{
		Ref:         humanblnk.Reference("payout", "po_7"),
		From:        humanblnk.Single("bln_ada_usd", amount),
		To:          humanblnk.Single(humanblnk.World, amount),
		Description: "Payout to bank",
	}, time.Now().Add(72*time.Hour))
	if err != nil {
		return
	}

	bankAccepted := true // from the provider's webhook
	if bankAccepted {
		_, _ = hb.Holds.Capture(ctx, hold, money.Money{}) // all of it
	} else {
		_ = hb.Holds.Cancel(ctx, hold)
	}
}
Example (TransferWithFee)

A transfer with a fee is one atomic split: Bola is never paid without your fee being recorded, or the other way round.

package main

import (
	"context"

	"github.com/shopspring/decimal"

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

var ctx = context.Background()

func main() {
	hb := humanblnk.New(blnk.New("https://blnk.internal"))
	usd := currency.MustGet(currency.USD)
	amount := money.MustParse("100", usd)
	fee, _ := amount.Percent(decimal.RequireFromString("1.5"), money.Up) // a business rule
	total, _ := amount.Add(fee)

	_, _ = hb.Transactions.Record(ctx, humanblnk.Transaction{
		Ref:  humanblnk.Reference("transfer", "tr_42"),
		From: humanblnk.Single("bln_ada_usd", total),
		To: humanblnk.Many(
			humanblnk.Posting{Account: "bln_bola_usd", Amount: amount},
			humanblnk.Posting{Account: humanblnk.FeeIncome, Amount: fee},
		),
		Description: "Transfer to Bola",
	})
}

Index

Examples

Constants

View Source
const (
	WatchBalance      = types.WatchBalance
	WatchCredits      = types.WatchCredits
	WatchDebits       = types.WatchDebits
	WatchOutgoingHeld = types.WatchOutgoingHeld
	WatchIncomingHeld = types.WatchIncomingHeld
	WatchHeldNet      = types.WatchHeldNet

	WatchAbove     = types.WatchAbove
	WatchAtLeast   = types.WatchAtLeast
	WatchBelow     = types.WatchBelow
	WatchAtMost    = types.WatchAtMost
	WatchEquals    = types.WatchEquals
	WatchNotEquals = types.WatchNotEquals
)

Watch fields and operators.

View Source
const (
	MatchAmount      = types.MatchAmount
	MatchDate        = types.MatchDate
	MatchReference   = types.MatchReference
	MatchDescription = types.MatchDescription
	MatchCurrency    = types.MatchCurrency

	MatchEquals      = types.MatchEquals
	MatchGreaterThan = types.MatchGreaterThan
	MatchLessThan    = types.MatchLessThan
	MatchContains    = types.MatchContains

	OneToOne  = types.OneToOne
	OneToMany = types.OneToMany
	ManyToOne = types.ManyToOne
)

Reconciliation fields, operators and strategies.

View Source
const (
	StatusApplied  = types.StatusApplied
	StatusHeld     = types.StatusHeld
	StatusVoid     = types.StatusVoid
	StatusRejected = types.StatusRejected
	StatusQueued   = types.StatusQueued
)

Entry statuses.

View Source
const (
	EventBalanceMonitor         = types.EventBalanceMonitor
	EventTransactionApplied     = types.EventTransactionApplied
	EventTransactionInflight    = types.EventTransactionInflight
	EventTransactionVoid        = types.EventTransactionVoid
	EventTransactionRejected    = types.EventTransactionRejected
	EventTransactionQueued      = types.EventTransactionQueued
	EventTransactionScheduled   = types.EventTransactionScheduled
	EventIdentityMetaUpdated    = types.EventIdentityMetaUpdated
	EventBalanceMetaUpdated     = types.EventBalanceMetaUpdated
	EventTransactionMetaUpdated = types.EventTransactionMetaUpdated
	EventLedgerMetaUpdated      = types.EventLedgerMetaUpdated
)

Webhook kinds, in Event.Kind.

View Source
const RecordHold = transactions.RecordHold

RecordHold is how long a split Record, or a RecordAll batch, holds the money while it captures it. Capture follows at once; the hold matters only if the process stops in between, when a retry within it finishes the write and after it the money is released.

Variables

View Source
var (
	ErrInsufficientFunds = types.ErrInsufficientFunds
	ErrDuplicate         = types.ErrDuplicate
	ErrReferenceUsed     = types.ErrReferenceUsed
	ErrNotFound          = types.ErrNotFound
	ErrUnbalanced        = types.ErrUnbalanced
	ErrAlreadyReversed   = types.ErrAlreadyReversed
	ErrHoldSettled       = types.ErrHoldSettled
	ErrInvalid           = types.ErrInvalid
	ErrReadBack          = types.ErrReadBack
)

Errors. Currency problems are money.ErrCurrencyMismatch and money.ErrUnknownCurrency.

View Source
var (
	World       = types.World
	FeeIncome   = types.FeeIncome
	Adjustments = types.Adjustments
)

System accounts.

Functions

func Reference

func Reference(kind string, parts ...string) string

Reference builds a transaction reference: Reference("payout", "po_123").

Types

type Account

type Account = types.Account

type AccountChange

type AccountChange = types.AccountChange

type AccountQuery

type AccountQuery = types.AccountQuery

type AccountRef

type AccountRef = types.AccountRef

func ClearingAccount

func ClearingAccount(provider string) AccountRef

ClearingAccount is money a payment provider owes you, or you owe it.

func SystemAccount

func SystemAccount(name string) AccountRef

SystemAccount is one of your company's own accounts, by name.

type AccountService

type AccountService = accounts.Service

The services a Client holds, named so callers can keep or pass just one: a handler that only places holds can take a *HoldService.

type Client

type Client struct {
	Identities     *identities.Service
	Accounts       *accounts.Service
	Transactions   *transactions.Service
	Holds          *holds.Service
	Watches        *watches.Service
	Reconciliation *reconcile.Service
	Ledgers        *ledger.Service
}

Client keeps your books in one Blnk instance. Its operations are grouped by what they act on: hb.Accounts.OpenForCustomer, hb.Transactions.Record, hb.Holds.Capture.

func New

func New(c *blnk.Client, opts ...Option) *Client

New returns a Client on c.

type Criterion

type Criterion = types.Criterion

type CustomerAccount

type CustomerAccount = types.CustomerAccount

type Entry

type Entry = types.Entry

type EntryQuery

type EntryQuery = types.EntryQuery

type Event

type Event = types.Event

func VerifyWebhook

func VerifyWebhook(secret string, r *http.Request, tolerance time.Duration) (Event, error)

VerifyWebhook checks a Blnk webhook's signature and age, then decodes it.

type HoldID

type HoldID = types.HoldID

type HoldService

type HoldService = holds.Service

The services a Client holds, named so callers can keep or pass just one: a handler that only places holds can take a *HoldService.

type Identity

type Identity = types.Identity

type IdentityService

type IdentityService = identities.Service

The services a Client holds, named so callers can keep or pass just one: a handler that only places holds can take a *HoldService.

type Ledger

type Ledger = types.Ledger

type LedgerService

type LedgerService = ledger.Service

The services a Client holds, named so callers can keep or pass just one: a handler that only places holds can take a *HoldService.

type MatchField

type MatchField = types.MatchField

type MatchOp

type MatchOp = types.MatchOp

type MatchRule

type MatchRule = types.MatchRule

type Meta

type Meta = types.Meta

type NewAccount

type NewAccount = types.NewAccount

type Option

type Option func(*config)

Option configures a Client.

func WithAccountCacheSize

func WithAccountCacheSize(n int) Option

WithAccountCacheSize sets how many accounts the Client remembers the currency of, so it needn't ask Blnk before every write: 100,000 unless set, about 20 MiB. The least recently used are forgotten first, so a long-running service's memory stays bounded however many accounts it touches.

func WithAccountLedger

func WithAccountLedger(id string) Option

WithAccountLedger sets the customer ledger Accounts.OpenForCustomer opens accounts in when CustomerAccount.Ledger is empty, such as the "Customer Wallets" ledger.

func WithCustomerLedgers

func WithCustomerLedgers(ids ...string) Option

WithCustomerLedgers names further ledgers that hold only customer accounts, such as "Savings". Accounts in them are opened with OpenForCustomer, never CreateInternal.

type Posting

type Posting = types.Posting

func Many

func Many(ps ...Posting) []Posting

Many is several postings, for one side of a split.

func Single

func Single(a AccountRef, m money.Money) []Posting

Single is one posting: From: humanblnk.Single(wallet, amount).

type Preview

type Preview = types.Preview

type ReconciliationService

type ReconciliationService = reconcile.Service

The services a Client holds, named so callers can keep or pass just one: a handler that only places holds can take a *HoldService.

type Reversal

type Reversal = types.Reversal

type RuleID

type RuleID = types.RuleID

type Run

type Run = types.Run

type RunID

type RunID = types.RunID

type RunResult

type RunResult = types.RunResult

type StatementID

type StatementID = types.StatementID

type StatementLine

type StatementLine = types.StatementLine

type Strategy

type Strategy = types.Strategy

type Transaction

type Transaction = types.Transaction

type TransactionService

type TransactionService = transactions.Service

The services a Client holds, named so callers can keep or pass just one: a handler that only places holds can take a *HoldService.

type Watch

type Watch = types.Watch

type WatchField

type WatchField = types.WatchField

type WatchID

type WatchID = types.WatchID

type WatchOp

type WatchOp = types.WatchOp

type WatchService

type WatchService = watches.Service

The services a Client holds, named so callers can keep or pass just one: a handler that only places holds can take a *HoldService.

Directories

Path Synopsis
Package blnk is a thin, typed HTTP client for the Blnk Core REST API (https://docs.blnkfinance.com/reference/overview).
Package blnk is a thin, typed HTTP client for the Blnk Core REST API (https://docs.blnkfinance.com/reference/overview).
blnkfake
Package blnkfake is a fake Blnk Core for tests: an httptest server that answers canned routes and records every request.
Package blnkfake is a fake Blnk Core for tests: an httptest server that answers canned routes and records every request.
docs
examples/01-setup command
Chapter 1: create the ledgers once, then build the Client your service uses from their ids.
Chapter 1: create the ledgers once, then build the Client your service uses from their ids.
examples/02-onboarding command
Chapter 2: onboard a customer, open their accounts and read balances.
Chapter 2: onboard a customer, open their accounts and read balances.
examples/03-money command
Chapter 3: exact amounts with money.
Chapter 3: exact amounts with money.
examples/04-deposits command
Chapter 4: a deposit through a payment provider, the retried webhook, and the provider settling.
Chapter 4: a deposit through a payment provider, the retried webhook, and the provider settling.
examples/05-transfers command
Chapter 5: a transfer between customers with a fee, checked first with a dry run.
Chapter 5: a transfer between customers with a fee, checked first with a dry run.
examples/06-payouts command
Chapter 6: holds.
Chapter 6: holds.
examples/07-airtime command
Chapter 7: a business service on top of humanblnk.
Chapter 7: a business service on top of humanblnk.
examples/07-airtime/airtime
Package airtime sells airtime, paid from the customer's wallet.
Package airtime sells airtime, paid from the customer's wallet.
examples/08-refunds command
Chapter 8: refunds, corrections and bounced payouts, all with Reverse.
Chapter 8: refunds, corrections and bounced payouts, all with Reverse.
examples/09-exchange command
Chapter 9: a currency exchange, two transactions that apply together or not at all.
Chapter 9: a currency exchange, two transactions that apply together or not at all.
examples/10-watches-and-webhooks command
Chapter 10: watch an account's balance, and receive Blnk's webhooks safely.
Chapter 10: watch an account's balance, and receive Blnk's webhooks safely.
examples/11-reconciliation command
Chapter 11: check a provider's settlement report against the books.
Chapter 11: check a provider's settlement report against the books.
examples/demo
Package demo is the setup the tutorial's examples share, so each one shows only the part it teaches.
Package demo is the setup the tutorial's examples share, so each one shows only the part it teaches.
site command
Command site builds the tutorial website in docs/ from the chapters in docs/site/chapters and the example programs in docs/examples, which are the source of truth for every line of code the site shows.
Command site builds the tutorial website in docs/ from the chapters in docs/site/chapters and the example programs in docs/examples, which are the source of truth for every line of code the site shows.
Package humanblnktest runs humanblnk against an in-memory Blnk, so code built on humanblnk can be tested with no Blnk, Docker or network:
Package humanblnktest runs humanblnk against an in-memory Blnk, so code built on humanblnk can be tested with no Blnk, Docker or network:
internal
accounts
Package accounts creates and reads accounts: Blnk balances, each money in one currency.
Package accounts creates and reads accounts: Blnk balances, each money in one currency.
core
Package core is what every service shares and no caller sees: the Blnk client, the currency check, conversion to and from Blnk's units, and error mapping.
Package core is what every service shares and no caller sees: the Blnk client, the currency check, conversion to and from Blnk's units, and error mapping.
events
Package events verifies and decodes Blnk webhooks.
Package events verifies and decodes Blnk webhooks.
holds
Package holds places, captures and cancels holds: money reserved now and settled later.
Package holds places, captures and cancels holds: money reserved now and settled later.
identities
Package identities creates and reads owners for customer accounts: Blnk identities that hold no personal data.
Package identities creates and reads owners for customer accounts: Blnk identities that hold no personal data.
ledger
Package ledger creates and reads Blnk ledgers, each a named group of accounts.
Package ledger creates and reads Blnk ledgers, each a named group of accounts.
memblnk
Package memblnk is an in-memory Blnk Core 0.15.6: an httptest server that speaks the part of Blnk's HTTP API humanblnk uses, so the whole of humanblnk, its blnk client included, runs against it unchanged.
Package memblnk is an in-memory Blnk Core 0.15.6: an httptest server that speaks the part of Blnk's HTTP API humanblnk uses, so the whole of humanblnk, its blnk client included, runs against it unchanged.
reconcile
Package reconcile matches outside statements (a provider's settlement report, a bank statement) against recorded entries: Blnk reconciliation.
Package reconcile matches outside statements (a provider's settlement report, a bank statement) against recorded entries: Blnk reconciliation.
transactions
Package transactions records, reverses and reads transactions.
Package transactions records, reverses and reads transactions.
types
Package types holds humanblnk's public vocabulary: the types, errors and helpers every service shares.
Package types holds humanblnk's public vocabulary: the types, errors and helpers every service shares.
watches
Package watches sets thresholds on accounts: Blnk balance monitors, which fire the webhook event "balance.monitor" while they hold.
Package watches sets thresholds on accounts: Blnk balance monitors, which fire the webhook event "balance.monitor" while they hold.
Package money is an exact amount in one currency (money/currency): parsed from the decimal a person or client wrote, never a float, and refused when it has more decimal places than its currency allows.
Package money is an exact amount in one currency (money/currency): parsed from the decimal a person or client wrote, never a float, and refused when it has more decimal places than its currency allows.
currency
Package currency is every currency humanblnk can hold: the ISO 4217 currencies in circulation and the crypto assets it supports, each with the number of decimal places its smallest unit has.
Package currency is every currency humanblnk can hold: the ISO 4217 currencies in circulation and the crypto assets it supports, each with the number of decimal places its smallest unit has.
currency/gen command
Command gen writes list.go, the currency table, from currencies.csv.
Command gen writes list.go, the currency table, from currencies.csv.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL