Ledger
The ledger is Plugipay's double-entry accounting feed. Every charge, refund, payout, platform fee, and tax adjustment posts as one or more entries here. Each entry carries an account Code (e.g. "revenue", "refunds", "platform_fee"), a Direction ("debit" or "credit"), and a TxID linking it to the originating business event. The Go SDK exposes the two ledger methods (and the related reports) behind client.Ledger and client.Reports. For the chart-of-accounts and per-event posting rules, see API → Ledger.
Field on the Client
client.Ledger — type *plugipay.LedgerResource. Installed by NewClient; shares the parent *http.Client, base URL, key, and OnBehalfOf default. Safe for concurrent use.
client.Reports — aggregations on top of the same data (P&L, cash flow). Covered briefly below; the same patterns apply.
Methods
List
Signature. func (r *LedgerResource) List(ctx context.Context, params LedgerListParams) (Page[LedgerEntry], error)
Cursor-paginated stream of ledger entries. Filter by TxID (all entries for one transaction), Code (one account), or SourceType + SourceID (entries originating from a specific session / invoice / refund). Order is "asc" or "desc" on PostedAt.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
limit := 200
order := "desc"
page, err := client.Ledger.List(ctx, plugipay.LedgerListParams{
Limit: &limit,
Order: &order,
})
if err != nil {
return err
}
for _, e := range page.Data {
log.Printf("%s %s %s %d %s tx=%s",
e.PostedAt, e.Code, e.Direction, e.Amount, e.Currency, e.TxID)
}
All entries for a single transaction (always sum to zero by direction):
tx := "tx_01HX..."
page, err := client.Ledger.List(ctx, plugipay.LedgerListParams{TxID: &tx})
All revenue posts in May:
code := "revenue"
page, err := client.Ledger.List(ctx, plugipay.LedgerListParams{Code: &code})
Balances
Signature. func (r *LedgerResource) Balances(ctx context.Context) ([]LedgerBalance, error)
Returns the current balance per account Code — a flat list of (Code, Debits, Credits, Balance). Useful for a "current standing" panel in your finance dashboard.
balances, err := client.Ledger.Balances(ctx)
if err != nil { return err }
for _, b := range balances {
log.Printf("%-20s debits=%d credits=%d balance=%d",
b.Code, b.Debits, b.Credits, b.Balance)
}
Reports (related)
client.Reports exposes two pre-aggregated reports. Both take ReportRangeParams{From, To, Currency *string} (ISO dates):
pnl, err := client.Reports.PnL(ctx, plugipay.ReportRangeParams{
From: "2026-05-01", To: "2026-05-31",
})
if err != nil { return err }
log.Printf("revenue=%d refunds=%d fees=%d tax=%d net=%d",
pnl.Revenue, pnl.Refunds, pnl.PlatformFees, pnl.Tax, pnl.Net)
cf, err := client.Reports.CashFlow(ctx, plugipay.ReportRangeParams{
From: "2026-05-01", To: "2026-05-31",
})
for _, b := range cf.Buckets {
log.Printf("%s in=%d out=%d net=%d", b.Day, b.Inflow, b.Outflow, b.Net)
}
These are derived from the same ledger entries; if you need a slice the reports don't expose, walk the ledger directly with List.
Types
type LedgerEntry struct {
ID string `json:"id"` // "le_..."
AccountID string `json:"accountId"`
TxID string `json:"txId"` // group key — entries in a tx sum to zero
Code string `json:"code"` // revenue | refunds | platform_fee | tax | ...
Direction string `json:"direction"` // "debit" | "credit"
Amount int64 `json:"amount"` // smallest currency unit, always positive
Currency string `json:"currency"`
SourceType string `json:"sourceType"` // checkout_session | invoice | refund | payout | ...
SourceID string `json:"sourceId"`
Memo *string `json:"memo"`
PostedAt string `json:"postedAt"`
}
type LedgerBalance struct {
Code string `json:"code"`
Debits int64 `json:"debits"`
Credits int64 `json:"credits"`
Balance int64 `json:"balance"` // credits - debits, signed by account kind
}
Direction and the account-kind convention: API → Ledger.
Common patterns
Reconcile against your accounting system
The reliable pattern: walk all entries in a date range with Order: "asc" for stable cursor behavior, group by TxID, and journal-entry each group on your side:
func entriesByTx(ctx context.Context, c *plugipay.Client, fromTx, toTx string) (map[string][]plugipay.LedgerEntry, error) {
asc := "asc"
limit := 500
var cursor *string
grouped := map[string][]plugipay.LedgerEntry{}
for {
page, err := c.Ledger.List(ctx, plugipay.LedgerListParams{
Order: &asc, Limit: &limit, Cursor: cursor,
})
if err != nil { return grouped, err }
for _, e := range page.Data {
grouped[e.TxID] = append(grouped[e.TxID], e)
}
if !page.HasMore { return grouped, nil }
cursor = page.Cursor
}
}
Within each TxID group, sum-by-direction is guaranteed to be zero — if it isn't, you've found a bug worth reporting (include the TxID and the RequestID from any related error).
Audit a single business event
Given an invoice id, find every ledger entry produced by it:
srcType := "invoice"
srcID := "in_01HX..."
page, err := c.Ledger.List(ctx, plugipay.LedgerListParams{
SourceType: &srcType,
SourceID: &srcID,
})
for _, e := range page.Data {
log.Printf("%s %s %d (%s)", e.Code, e.Direction, e.Amount, deref(e.Memo))
}
A paid invoice typically posts: debit accounts_receivable, credit revenue, credit tax (if applicable), debit platform_fee — all sharing one TxID.
Daily P&L snapshot
pnl, err := c.Reports.PnL(ctx, plugipay.ReportRangeParams{
From: time.Now().AddDate(0, 0, -1).Format("2006-01-02"),
To: time.Now().Format("2006-01-02"),
})
if err != nil { return err }
log.Printf("yesterday: net=%d refunds=%d", pnl.Net, pnl.Refunds)
Wire this into a daily Slack post — cheap visibility into where you stand.
Context cancellation on long walks
ctx, cancel := context.WithTimeout(parentCtx, 30*time.Second)
defer cancel()
page, err := c.Ledger.List(ctx, plugipay.LedgerListParams{Limit: &limit})
// if 30s passes, the SDK returns *plugipay.Error{Code: "canceled"} promptly.
Errors
The ledger is read-only, so the failure modes are limited:
Code |
Status |
Cause |
|---|---|---|
validation_error |
400 | Bad From / To format on reports, unknown Code filter. |
rate_limited |
429 | Too many calls. Back off. |
insufficient_scope |
403 | Key lacks plugipay:ledger:read. |
var pe *plugipay.Error
if errors.As(err, &pe) && pe.Code == "rate_limited" {
// back off — see Errors page for the retry recipe
}
Full mechanics: Errors.
Next
- Payouts — outflows here post into the ledger.
- Refunds — debit
revenue, creditrefunds. - API → Ledger — HTTP-level reference, chart of accounts.