Python SDK quickstart

End-to-end: from pip install to a working checkout URL in under 30 lines of code. We'll create a customer, open a checkout session for them, print the hosted URL, and then read the session back.

Prerequisites

  • Python 3.9+ in an activated virtualenv.
  • A Plugipay test-mode key pair (pk_test_* + sk_test_*). Mint one in Settings → API keys, or follow Portal → API keys.
  • plugipay installed: pip install plugipay (see Installation).

Set the credentials as environment variables — that's the convention every example on these pages assumes:

export PLUGIPAY_KEY_ID=pk_test_xxxxxxxxxxxxxxxx
export PLUGIPAY_KEY_SECRET=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx

The 30-line round-trip

Save as quickstart.py:

import os
from plugipay import PlugipayClient, PlugipayError


def main():
    with PlugipayClient(
        key_id=os.environ["PLUGIPAY_KEY_ID"],
        secret=os.environ["PLUGIPAY_KEY_SECRET"],
    ) as plug:
        # 1. Create (or update) a customer.
        customer = plug.customers.create(
            email="ada@example.com",
            name="Ada Lovelace",
        )
        print(f"Customer: {customer['id']}  ({customer.get('email')})")

        # 2. Open a hosted checkout session for them.
        session = plug.checkout_sessions.create(
            amount=125_000,        # IDR 125,000 (minor units = rupiah)
            currency="IDR",
            methods=["qris", "va", "ewallet"],
            success_url="https://yourapp.com/checkout/success",
            cancel_url="https://yourapp.com/checkout/cancel",
            customer_id=customer["id"],
        )
        print(f"Checkout URL: {session['hostedUrl']}")
        print(f"Session id:   {session['id']}")

        # 3. Read the session back to verify.
        fetched = plug.checkout_sessions.get(session["id"])
        print(f"Status: {fetched['status']}")


if __name__ == "__main__":
    try:
        main()
    except PlugipayError as exc:
        print(f"Plugipay failed: {exc.status} {exc.code} — {exc.message}")
        if exc.request_id:
            print(f"  request_id: {exc.request_id}")
        raise

Run it:

python quickstart.py

Expected output:

Customer: cus_01HXXXXXXXXXXXXXXXXXXXXXXX  (ada@example.com)
Checkout URL: https://plugipay.com/c/cs_01HXXXXXXXXXXXXXXXXXXXXXXX
Session id:   cs_01HXXXXXXXXXXXXXXXXXXXXXXX
Status: open

Open the checkout URL in a browser to see the hosted page in test mode — pick a payment method, simulate, and the session status will move to succeeded.

What just happened

Step by step:

1. The client

with PlugipayClient(key_id=..., secret=...) as plug:

PlugipayClient is the only top-level object you'll use. The with block ensures the underlying httpx.Client is closed when you're done — if you skip it, call plug.close() yourself before your process exits. For long-lived servers (Flask, FastAPI, Django), construct one PlugipayClient at startup and reuse it everywhere; don't put it in a with block.

The constructor validates that both arguments are non-empty and raises ValueError otherwise. The pk_test_* / sk_test_* prefixes encode the environment — test keys can't touch live data and vice versa.

2. Create the customer

customer = plug.customers.create(
    email="ada@example.com",
    name="Ada Lovelace",
)

All SDK methods take keyword arguments. Under the hood, the SDK builds a POST /api/v1/customers request with the JSON body Plugipay expects, signs it with HMAC-SHA256, and auto-attaches an Idempotency-Key header (so retrying the same call won't create a duplicate customer).

The return value is a Customer — a thin dict-backed object. Read it like a dict:

customer["id"]            # 'cus_01HXX…'
customer["email"]         # 'ada@example.com'
customer.get("phone")     # None — field absent
customer.raw              # full dict, including server fields not promoted to attributes

customer["nonexistent"] raises KeyError; customer.get("nonexistent") returns None. Use whichever fits.

3. Open the checkout session

session = plug.checkout_sessions.create(
    amount=125_000,
    currency="IDR",
    methods=["qris", "va", "ewallet"],
    success_url="https://yourapp.com/checkout/success",
    cancel_url="https://yourapp.com/checkout/cancel",
    customer_id=customer["id"],
)

A few conventions worth noting:

  • amount is an integer in minor units. For IDR that's rupiah (no decimals); for USD it'd be cents. 125_000 = Rp 125.000. See Concepts → Amounts for the full list.
  • methods is a list of payment-method codes — what shows up on the hosted page. "qris", "va", "ewallet", "card", etc. See API → Checkout sessions for all options.
  • success_url and cancel_url become the redirect targets after checkout. They're passed as successUrl / cancelUrl on the wire — the SDK does the snake-case ↔ camelCase translation for you.

The returned CheckoutSession carries id, hostedUrl, status, expiry, and the full echoed request. Redirect your buyer to session["hostedUrl"].

4. Read it back

fetched = plug.checkout_sessions.get(session["id"])
print(fetched["status"])

Get methods take the resource id as a positional argument — everything else is keyword-only. status will be open immediately after creation and transitions through succeeded, failed, or cancelled as the buyer interacts with the hosted page.

5. Errors

except PlugipayError as exc:
    print(exc.status, exc.code, exc.message, exc.request_id)

Every failure — network timeout, bad credentials, server 5xx, validation error, missing resource — raises PlugipayError or a subclass:

  • PlugipayNetworkError (DNS/connect/TLS)
  • PlugipayTimeoutError (request exceeded timeout)
  • PlugipaySignatureError (webhook verification only)

Catch the base class to handle everything in one place; catch a subclass if you want to retry transport-level failures differently from API-level ones. See Errors for the full hierarchy and a retry recipe.

Next steps

You now have:

  • A customer record (cus_…) you can charge again later.
  • A live hosted-checkout URL.
  • A pattern (create → act → read back) that works for every other resource: invoices, subscriptions, refunds, payouts, plans.

Try these next:

  • Replace methods=[…] with methods=["qris"] and watch the hosted page collapse to a single QR. Useful for in-person flows.
  • Switch to pk_live_* / sk_live_* keys, point success_url at your real domain, and you're in production.
  • Add the webhook listener so your server learns when the payment succeeds — redirects to success_url aren't reliable on mobile.

Next

  • Authentication — env vars, platform keys, custom httpx clients.
  • Errors — the exception hierarchy, retry patterns.
  • Webhooks — verify Plugipay → you deliveries.
  • Reference — every method on every namespace.
Plugipay — Payments that don't tax your success