Skip to content
All use cases

IN PRACTICE / PAYMENTS

Payments & stateful flows

Money paths have to be legible.

Read the manifest
payments.bloccs06 NODES / DIRECTED FLOW
Payments & stateful flows: an isometric view of the six-node network described below

Typed connections / Top pip = declared effect

01 / 06

charge

Receives the charge request as a typed Charge@1.

Node reference

THE ARCHITECTURE

Every hop has
a responsibility.

bloccs lays the flow out as a typed graph: validate and dedupe before anything moves, branch on state, then capture and book the ledger as separate supervised sinks. Idempotency and the DB/HTTP boundaries are declared, so a retry can't double-charge and a stray effect can't compile.

Implementation status

Where this stands today: idempotency, DB reads and writes, outbound capture, and supervised recovery are all shipped in 0.8 — the core of this flow runs on real adapters. As with any source, charge is fed by your web layer; bloccs doesn't serve HTTP itself.

SOURCE OF TRUTH

From shape
to source.

The network is declared in TOML. Compile it with its referenced node manifests and bloccs checks the wiring, schemas, and capabilities before emitting a Broadway supervision tree.

COMPILEmix bloccs.compile

Node implementations live in the referenced nodes/*.bloccs files.

Manifest documentation
payments.bloccs
[network]
id      = "payments"
version = "0.1.0"
runtime = "beam"

[nodes]
charge   = { use = "nodes/charge.bloccs" }
validate = { use = "nodes/validate.bloccs" }
dedupe   = { use = "nodes/dedupe.bloccs" }
branch   = { use = "nodes/branch.bloccs" }
capture  = { use = "nodes/capture.bloccs" }
ledger   = { use = "nodes/ledger.bloccs" }

[[edges]]
from = "charge.created"
to   = "validate.charge"

[[edges]]
from = "validate.valid"
to   = "dedupe.charge"

[[edges]]
from = "dedupe.fresh"
to   = "branch.charge"

# Fan-out: capture at the provider and book the ledger entry.
[[edges]]
from = "branch.authorized"
to   = ["capture.charge", "ledger.entry"]

[expose]
in  = { charge = "charge.created" }
out = { captured = "capture.captured", booked = "ledger.booked" }

[supervision]
strategy     = "rest_for_one"
max_restarts = 3
max_seconds  = 60

[deploy]
concurrency = { capture = 1, ledger = 1 }
06

One node. One responsibility.

The kind describes its role. The capability declares its reach.

01

charge

Source+HTTP

Receives the charge request as a typed Charge@1.

02

validate

Node

Checks amount, currency, and shape. Pure and deterministic.

03

dedupe

Node+DB

Looks up the idempotency key so a retried request can't double-charge.

04

branch

Split

Routes on charge state — only authorized charges proceed to capture.

05

capture

Sink+HTTP

Captures at the payment provider. The outbound call is a scoped capability.

06

ledger

Sink+DB

Writes the double-entry ledger row. Runs alongside capture under supervision.

Build this one for real.

Declare the graph. Check the contracts. Let the BEAM run it.

Read the docs
{:bloccs, "~> 0.9"}