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
confirmunless 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.