Receipts

A receipt is the immutable, customer-facing record of a successful payment — the PDF you'd email, the line in their portal history, the audit document your accountants want. Plugipay generates receipts automatically when a checkout session completes or an invoice is paid; the SDK lets you list and retrieve them. There is no create — receipts are derived, not authored. The Python SDK exposes two methods behind plugipay.receipts. For HTTP detail, see API → Receipts.

Namespace

plug.receipts       # _Receipts

Methods

list

plug.receipts.list(
    *,
    limit: int | None = None,
    cursor: str | None = None,
    source_type: str | None = None,
    customer_id: str | None = None,
    issued_after: str | None = None,
    issued_before: str | None = None,
) -> PageResult[ReceiptSummary]

Returns a paginated list of receipt summaries — a trimmed shape suitable for tables, audit logs, and dashboards. To see the full receipt body (line items, tax breakdown, hosted PDF URL), call get(receipt_id) on the id returned here.

  • source_type is "checkout_session" or "invoice".
  • issued_after / issued_before are ISO 8601 UTC strings.
  • customer_id filters to one buyer's history.
# Receipts for a customer this quarter
page = plug.receipts.list(
    customer_id="cus_01HX...",
    issued_after="2026-04-01T00:00:00Z",
    issued_before="2026-07-01T00:00:00Z",
    limit=100,
)
for r in page.data:
    print(r["id"], r["amount"], r["currency"], r["issuedAt"])
# Full pagination loop
cursor = None
while True:
    page = plug.receipts.list(limit=100, cursor=cursor)
    for receipt in page.data:
        archive_to_warehouse(receipt.raw)
    if not page.has_more:
        break
    cursor = page.cursor

get

plug.receipts.get(receipt_id: str) -> Any

Returns the full receipt payload as a plain dict — not a Resource dataclass. This is the only method on the namespace that breaks the "everything wraps in a dataclass" pattern, because receipts carry a rich, document-shaped payload (lines, tax, addresses, hosted PDF URL) that's easier to navigate as raw JSON than as obj.raw["…"].

receipt = plug.receipts.get("rcp_01HX...")
print(receipt["hostedReceiptUrl"])    # buyer-facing PDF
print(receipt["lines"])                # list of line dicts
print(receipt["total"], receipt["currency"])

Why no create? Receipts are an output of a successful payment. Plugipay generates exactly one receipt per settled payment — you couldn't create a second one against the same source if you tried. To customize what a receipt looks like, edit your templates (the kind="receipt" ones), not the receipts themselves.

Types

from plugipay import ReceiptSummary, PageResult

ReceiptSummary is a Resource subclass — the row shape returned by list. Likely-read fields:

  • summary["id"]rcp_ + ULID.
  • summary["sourceType"], summary["sourceId"].
  • summary["customerId"].
  • summary["amount"], summary["currency"].
  • summary["issuedAt"] — ISO 8601 UTC.
  • summary.get("number") — human-friendly receipt number (e.g. RCP-2026-001234).

The full receipt returned by get is not typed in the Python SDK — it's a plain dict. The shape is documented at API → Receipts → The receipt object. Notable nested fields:

  • receipt["lines"] — list of {description, amount, quantity, taxAmount?} dicts.
  • receipt["subtotal"], receipt["taxTotal"], receipt["total"].
  • receipt["billTo"]{name, email, taxId?, address?} snapshot at issue time.
  • receipt["hostedReceiptUrl"] — public PDF link.
  • receipt["templateId"] — which template rendered it.

Common patterns

Email-an-old-receipt flow. The hosted URL never expires; just resend it:

receipt = plug.receipts.get(receipt_id)
send_email(
    to=receipt["billTo"]["email"],
    subject=f"Your Plugipay receipt {receipt['number']}",
    body=f"Download: {receipt['hostedReceiptUrl']}",
)

CSV export for accountants.

import csv

with open("receipts.csv", "w", newline="") as f:
    w = csv.writer(f)
    w.writerow(["id", "number", "issuedAt", "amount", "currency", "customer"])
    cursor = None
    while True:
        page = plug.receipts.list(limit=100, cursor=cursor)
        for r in page.data:
            w.writerow([
                r["id"], r.get("number", ""), r["issuedAt"],
                r["amount"], r["currency"], r["customerId"],
            ])
        if not page.has_more:
            break
        cursor = page.cursor

Pre-fetch on demand. Summaries are cheap; full receipts are heavier. Render a table from list, then get only when the user clicks "View":

@app.route("/receipts/<receipt_id>")
def view_receipt(receipt_id: str):
    receipt = plug.receipts.get(receipt_id)
    return redirect(receipt["hostedReceiptUrl"])

Treat receipts as immutable snapshots. A buyer's name change after the fact does not update old receipts — the billTo block was frozen at issue time. If you need a "corrected" receipt, void the underlying invoice and create a new one (which generates a new receipt).

Bulk download to a local cache. When pulling receipts into your own document store, fetch summaries first then get only the ones you don't have:

import os
import json
import pathlib

cache = pathlib.Path("/var/data/receipts")
cache.mkdir(parents=True, exist_ok=True)

cursor = None
while True:
    page = plug.receipts.list(limit=100, cursor=cursor)
    for summary in page.data:
        target = cache / f"{summary['id']}.json"
        if not target.exists():
            full = plug.receipts.get(summary["id"])
            target.write_text(json.dumps(full))
    if not page.has_more:
        break
    cursor = page.cursor

This pattern keeps your local cache append-only and avoids re-fetching settled receipts on every run.

Errors

err.status err.code Cause
400 validation_error Bad source_type, malformed issued_after / issued_before.
404 not_found Receipt id doesn't exist or is in another workspace.
403 insufficient_scope Key lacks plugipay:receipt:read.

There are no state-transition errors on receipts (no mutation endpoints). Network and timeout failures surface as PlugipayNetworkError and PlugipayTimeoutError as usual — see Errors.

Next

Plugipay — Payments that don't tax your success