Isopace¶
A financial transaction framework for Go — ISO-8583 messaging and payment switching, plus the runtime, transaction pipeline, coordination, and security building blocks needed to run a switch.
Get started Architecture GitHub
Isopace is an independent, clean-room implementation in the spirit of jPOS, redesigned around idiomatic Go: type-safe field access, zero-copy decoding, a pluggable codec catalog, and goroutine-native concurrency. The module is stdlib-only — there is no third-party dependency anywhere in its graph.
v1.0.0-rc.1 — first release candidate (interim, may still change)
A feedback-seeking tag under a "may still change" banner — not the v1.0.0
contract yet (see the roadmap to v1).
It narrows what v1 will freeze: the core (iso8583, packager,
fieldcodec, lengthcodec, render) is a stability candidate; the
higher layers remain experimental until they soak (see the
versioning policy). The software Vault backend is for
development and testing — production PIN and key handling require a
certified HSM, which is not yet shipped.
Why Isopace¶
-
Type-safe field access
Get[iso8583.Decimal](m, 4)and tag-bound structs instead of a stringly-typedMap<Integer, ISOComponent>. The type is known at compile time; money is exact fixed-point, never a float. -
Allocation-free hot path
Zero-copy lazy decode: fields are sub-slices of the source buffer. A store-and-forward hop returns the original wire verbatim when nothing is dirty — allocation-free end to end.
-
Composable codec catalog
LengthCodec × FieldCodeccompose freely — ≈6 length × ≈12 value codecs replace jPOS's combinatorialIF*class zoo. Resolve by name from Go or JSON. -
One model, many renderings
A single read-only
Viewfeeds every renderer: the same*Messageprojects losslessly to ISO-8583 wire, JSON, protobuf, and ISO 20022. -
Correct by construction
The bitmap is always derived from present fields, so desync is impossible.
Messageis immutable with copy-on-write;Validate()returns every violation in one pass. -
stdlib-only, by design
No third-party dependency in the module graph. That keeps the dual-license (AGPL-3.0 / commercial) model free of transitive copyleft.
A switch in a few lines¶
q := teq.New()
isw, _ := q.Switch(connector.Config{Name: "isw", Addr: "isw.example:5000", Keyer: keyer})
up, _ := q.Switch(connector.Config{Name: "up", Addr: "up.example:6000", Keyer: keyer})
q.Start(ctx) // connectors dial in the background
resp, _ := q.To("isw").Request(ctx, frame) // route by name; auto-reconnects
q.ListenAndServe() // or run as a daemon until SIGINT/SIGTERM
Build and read a message with compile-time types — no casting:
s := packager.ISO87A()
c := iso8583.NewCodec(s)
m := iso8583.New(s)
_ = m.Set(0, "0200")
_ = m.Set(11, int64(123456)) // STAN
_ = m.Set(4, int64(2500)) // amount, minor units
wire, _ := c.Marshal(m, nil)
got, _ := c.Unmarshal(wire)
mti, _ := iso8583.Get[string](got, 0)
stan, _ := iso8583.Get[int64](got, 11)
The stack¶
The framework is layered with a strict one-directional dependency rule: the core
model knows the wire format only through the *Schema it references, and every
renderer consumes one read-only View.
flowchart TD
subgraph EnterpriseOps["Enterprise & Ops"]
rbac["rbac · store · ops"]
end
subgraph Coord["Coordination & Security"]
space["space"]
vault["vault"]
end
subgraph Proc["Processing"]
flow["flow — two-phase pipeline"]
end
subgraph Transport["Transport"]
link["link · listener · mux"]
end
subgraph Runtime["Runtime"]
runtime["runtime — component host"]
end
subgraph Render["Rendering"]
render["render/jsonio · protobuf · iso20022"]
end
subgraph Profiles["Profiles"]
packager["packager"]
end
subgraph Codecs["Codecs"]
codecs["fieldcodec · lengthcodec"]
end
subgraph Core["Messaging core"]
iso["iso8583 — Message · Codec · Schema"]
end
render --> iso
packager --> codecs
packager --> iso
codecs --> iso
flow --> iso
link --> iso
runtime --> iso
space -.-> flow
vault -.-> flow
classDef core fill:#0b3d59,stroke:#00b8a9,color:#fff;
class iso core;
Packages¶
| Layer | Package | Purpose |
|---|---|---|
| Messaging | iso8583 |
Immutable, zero-copy Message; Codec; Schema; typed Get[T] and struct binding; exhaustive validation |
| Codecs | fieldcodec, lengthcodec |
Orthogonal value × length codecs (ASCII/EBCDIC/BCD/binary, BER-TLV) resolvable by name |
| Profiles | packager |
ISO 8583:1987 A/B/C, 1993 A/B, Visa & Mastercard overlays, declarative loader |
| Rendering | render/* |
One message, many formats — from a read-only View |
| Transport | link, listener, mux |
Framed TCP (+TLS), graceful server, connection pool, request/response switch |
| Runtime | runtime |
Component host & lifecycle, deploy descriptors + hot redeploy, config, slog, observability |
| Processing | flow |
Two-phase Flow/Stage/Exchange pipeline: routing, journaling, retry, idempotency |
| Coordination | space |
Keyed tuple space + durable, crash-safe store-and-forward |
| Security | vault |
PIN blocks, MAC/CMAC, DUKPT, TR-31 key blocks, EMV ARQC/ARPC behind a Vault façade |
| Enterprise/Ops | rbac, store, ops |
RBAC + PBKDF2 auth, persistence, health/metrics/admin API, cluster membership |
Why not just use jPOS?¶
jPOS is excellent and battle-tested, but it is Java/AGPL and carries 25 years of design history. Isopace is a from-scratch Go implementation that keeps what makes jPOS great while improving on its data model — compile-time type safety, zero-copy parsing, an always-derived bitmap, first-class validation, and multi-format rendering — and operating naturally in cloud-native, high-throughput Go services. It is not a translation or port of jPOS: it is built clean-room from the ISO-8583 standard and public payments knowledge, with no jPOS source consulted.
A product of Teqpace Services Ltd. · Dual-licensed AGPL-3.0-or-later & commercial · Report a vulnerability