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, credit refunds.
  • API → Ledger — HTTP-level reference, chart of accounts.
Plugipay — Payments that don't tax your success