Checkout sessions

A checkout session is the short-lived "place to send a buyer" object. You create one with an amount, allowed payment methods, and success/cancel URLs; you redirect the buyer to its hosted URL; Plugipay handles the rest and emits webhooks when the session settles. The Python SDK wraps the five session endpoints behind plugipay.checkout_sessions. For the HTTP shape and full lifecycle, see API → Checkout sessions.

Namespace

plug.checkout_sessions      # _CheckoutSessions

Methods

create

plug.checkout_sessions.create(
    *,
    amount: int,
    currency: str,
    methods: list[str],
    success_url: str,
    cancel_url: str,
    customer_id: str | None = None,
    line_items: list[dict] | None = None,
    expires_in_sec: int | None = None,
    metadata: dict[str, str] | None = None,
    template_id: str | None = None,
) -> CheckoutSession

Creates a session. amount is integer minor units; currency is ISO 4217. methods is the list of payment methods to expose on the hosted page (["card", "qris", "va_bca", "gopay"] etc.) — see API → Adapters for what your workspace currently supports. The SDK auto-generates an idempotency key per call.

session = plug.checkout_sessions.create(
    amount=149_000_00,
    currency="IDR",
    methods=["card", "qris", "va_bca"],
    success_url="https://app.example.com/orders/12345/success",
    cancel_url="https://app.example.com/orders/12345/cancel",
    customer_id="cus_01HX...",
    metadata={"order_id": "12345"},
)
print(session["hostedUrl"])    # Redirect the browser here.

get

plug.checkout_sessions.get(session_id: str) -> CheckoutSession

Polls the session's current state. Prefer webhooks over polling: the plugipay.checkout_session.completed.v1 event fires when the buyer finishes paying — see Webhooks. But for synchronous flows (e.g. server-rendered "thanks for paying!" pages), get is fine.

session = plug.checkout_sessions.get("cs_01HX...")
match session.get("status"):
    case "completed":
        fulfill(session["metadata"]["order_id"])
    case "expired" | "cancelled":
        unstage_order(session["metadata"]["order_id"])

list

plug.checkout_sessions.list(
    *,
    limit: int | None = None,
    status: str | None = None,
    customer_id: str | None = None,
) -> PageResult[CheckoutSession]

Lists sessions. status is one of "open", "completed", "expired", "cancelled". Useful for reconciliation jobs:

# All sessions a given customer ever opened
cursor = None
while True:
    page = plug.checkout_sessions.list(limit=100, customer_id="cus_01HX...")
    for s in page.data:
        print(s["id"], s.get("status"), s["amount"])
    if not page.has_more:
        break
    cursor = page.cursor

cancel

plug.checkout_sessions.cancel(session_id: str) -> CheckoutSession

Voids the session. Only valid while status == "open" — cancelling a completed session returns 409. SDK sends an auto-generated idempotency key.

plug.checkout_sessions.cancel("cs_01HX...")

confirm

plug.checkout_sessions.confirm(session_id: str) -> CheckoutSession

Server-side confirmation — moves the session to completed without the buyer redirect. Only callable when the underlying payment is in a terminal-success state on the provider side. Most callers should let the hosted page handle this; confirm is for headless flows and reconciliation. No idempotency key is sent — wrap the call yourself if you need retry semantics.

plug.checkout_sessions.confirm("cs_01HX...")

Treat the hosted page as source of truth. Buyer drops off mid-payment, network flakes, browser closes — Plugipay still finishes the session on the provider's callback and emits a webhook. Don't race the buyer with your own confirm unless you've explicitly opted into a headless integration.

Types

from plugipay import CheckoutSession, PageResult

CheckoutSession is a Resource subclass. The fields you'll read most often:

  • session["id"]cs_ + ULID.
  • session["status"]"open" | "completed" | "expired" | "cancelled".
  • session["hostedUrl"] — where to send the buyer.
  • session["amount"], session["currency"] — what they'll pay.
  • session["expiresAt"] — ISO 8601 UTC.
  • session.get("metadata") — your pass-through bag.

Full field list at API → Checkout sessions → The session object.

Common patterns

Order ↔ session correlation via metadata. Stash your local order id in metadata on create, then read it back from the webhook payload:

session = plug.checkout_sessions.create(
    amount=order.total_minor,
    currency=order.currency,
    methods=["card", "qris"],
    success_url=f"https://app.example.com/orders/{order.id}/success",
    cancel_url=f"https://app.example.com/orders/{order.id}/cancel",
    metadata={"order_id": str(order.id)},
)
order.checkout_session_id = session["id"]
order.save()

In the webhook handler:

event = verify_webhook(raw_body, sig_header, secret)
if event.type == "plugipay.checkout_session.completed.v1":
    order_id = event.object["metadata"]["order_id"]
    fulfill(int(order_id))

Polling fallback for missing webhooks. Webhooks deliver at-least-once but a long outage can leave a session unreconciled. Cron a sweep:

from datetime import datetime, timedelta, timezone

cutoff = (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat()
page = plug.checkout_sessions.list(limit=100, status="completed")
for s in page.data:
    if s["createdAt"] >= cutoff and not local_db.is_fulfilled(s["id"]):
        fulfill_from_session(s)

Idempotent retry with your own key. If you need a deterministic key (e.g. derived from your order id), bypass the auto-key:

session = plug.request(
    method="POST",
    path="/api/v1/checkout-sessions",
    body={
        "amount": order.total_minor,
        "currency": order.currency,
        "methods": ["card"],
        "successUrl": "...",
        "cancelUrl": "...",
        "metadata": {"order_id": str(order.id)},
        "lineItems": [],
    },
    idempotency_key=f"order:{order.id}:checkout",
)
# session is a plain dict here, not a CheckoutSession dataclass.

Errors

err.status err.code Cause
400 validation_error Bad amount/currency, unknown method in methods, malformed URLs.
400 method_unavailable A method in methods isn't enabled on this workspace's adapter.
404 not_found Session id doesn't exist or is in another workspace.
409 invalid_state_transition cancel on a non-open session, confirm on a session whose payment isn't terminal-success.
409 idempotency_key_reused Same Idempotency-Key against a different body.
422 customer_currency_mismatch Passed customer_id whose default currency conflicts with this session.

All raise PlugipayError. See Errors.

Next

  • API → Checkout sessions — every field, every error code, every webhook.
  • Customers — pre-create a customer so card/method tokens persist across sessions.
  • Refunds — refund a completed session.
  • Webhooks — verify and react to plugipay.checkout_session.* events.
Plugipay — Payments that don't tax your success