Core Conventions
Every domain package (registration, balanceinfo, transactionhistory,
transfercredit, transferdebit) follows the same small set of rules
below. Learn them once and every endpoint becomes predictable — you’re
just reading a field table, not learning a new pattern each time.
One package per ASPI portal category
Each domain package maps 1:1 onto one of the ASPI SNAP Developer
Site’s top-level
“API Services” categories, so “which package does this endpoint belong in”
is never a judgment call. The root snap package holds only what every
domain package needs: signing, the token lifecycle, inbound-request
verification, header assembly, response-code parsing, and Money.
One shape per endpoint
Every calling function has the same signature:
func Endpoint(ctx context.Context, t *snap.Transport, hb snap.HeaderBuilder, req XRequest) (XResponse, error)and does the same four things, in order:
- Marshal
reqinto JSON. - Set
hb.Bodyto those exact bytes, then sign and send viat.Do(ctx, hb)— the same bytes are used for both the signature and the wire body, so nothing can drift between them. - Check the response with
snap.CheckResponseStatus— HTTP status is authoritative over the SNAPresponseCodebody, never the other way around. A non-2xx HTTP status is never treated as success even if an embeddedresponseCodeclaims otherwise. - Unmarshal into
XResponseand return it.
A handful of read-only, GET, or URL-path-parameter endpoints (GetOAuthURL,
CardRegistrationInquiry) deviate from the JSON-body shape because the
standard itself defines them as a query string or path parameter — each is
called out explicitly in its reference page.
Field presence: Mandatory / Optional / Conditional
The SNAP standard marks every field Mandatory, Optional, or
Conditional (optional, but required together with some other
condition). This package expresses that as Go’s omitempty:
- A field the standard marks Mandatory has no
omitempty— it always appears on the wire, even as a zero value. - Every other field carries
omitempty.
omitempty tag, so the
reference pages’ “Presence” column shows both as Optional — Go
can’t tell them apart. If a field is actually Conditional, the
paragraph above the table says so directly (e.g. “OriginatorInfos is
Conditional”). Read that paragraph before assuming an Optional field
is always safe to skip.This package validates wire shape only, never the standard’s business rules (e.g. “exactly one of A or B must be set”) — the server does that.
json.RawMessage means “ambiguous or unspecified shape”
Most fields are string, bool, a nested struct, or *snap.Money. A
field is typed json.RawMessage instead when the Guides tab documents its
shape ambiguously (e.g. a value shown as a bare JSON number in one worked
example and a quoted string in another) — decoding it as a concrete Go type
would either reject a legitimate server response or silently coerce data
that wasn’t actually a string. additionalInfo is the most common case:
the standard leaves it fully open-ended per integration.
The shared Money type
type Money struct {
Value string `json:"value"`
Currency string `json:"currency"`
}Money is the {value, currency} shape used across every request or
response that carries a monetary amount. Value is a decimal string (e.g.
"200000.00", always two decimal places) — never a float, to avoid binary
floating-point rounding on money. Currency is ISO 4217 (e.g. "IDR").