Adapters

An adapter is Plugipay's binding to an upstream payment provider — Xendit, PayPal, Midtrans, or "manual" (your finance team settles out-of-band). Each adapter exposes a set of payment methods (card, qris, va_bca, gopay, etc.) that become selectable when you create a checkout session. The Python SDK exposes adapter listing, per-provider config updates, and the managed-onboarding flow behind plugipay.adapters. For the per-adapter config shape and which methods each one supports, see API → Adapters.

Namespace

plug.adapters       # _Adapters

Methods

list

plug.adapters.list() -> list[AdapterConfig]

Returns every adapter configured on this workspace. Not paginated — workspaces have at most a handful (one of each provider, plus manual). Returns a plain list.

for adapter in plug.adapters.list():
    print(adapter["kind"], adapter.get("status"), adapter.get("methods"))

update_xendit / update_paypal / update_midtrans / update_manual

plug.adapters.update_xendit(config: dict[str, Any]) -> AdapterConfig
plug.adapters.update_paypal(config: dict[str, Any]) -> AdapterConfig
plug.adapters.update_midtrans(config: dict[str, Any]) -> AdapterConfig
plug.adapters.update_manual(config: dict[str, Any]) -> AdapterConfig

Replaces the adapter's configuration. PUT semantics — pass the full config you want, not a partial patch. No auto idempotency key — wrap your own retry if needed. The config dict shape is provider-specific; see API → Adapters for each one. Common required fields:

  • Xendit (BYO): {"apiKey": "...", "webhookToken": "..."}
  • PayPal: {"clientId": "...", "clientSecret": "...", "mode": "live" | "sandbox"}
  • Midtrans: {"serverKey": "...", "clientKey": "...", "mode": "live" | "sandbox"}
  • Manual: {"methods": ["bank_transfer", "cash"], "instructions": "..."}
plug.adapters.update_xendit({
    "apiKey": os.environ["XENDIT_API_KEY"],
    "webhookToken": os.environ["XENDIT_WEBHOOK_TOKEN"],
})

managed_onboarding_state

plug.adapters.managed_onboarding_state() -> ManagedOnboardingState

Returns the current state of Plugipay's managed-adapter onboarding flow — the path where Plugipay creates and owns the upstream Xendit account on your behalf (rather than you bringing your own keys). Use this to render a status indicator on your onboarding UI.

state = plug.adapters.managed_onboarding_state()
print(state["status"])   # "not_started" | "pending_kyc" | "submitted" | "verified" | "rejected"

start_managed_onboarding

plug.adapters.start_managed_onboarding(
    *,
    kind: str = "xendit",
    details: dict[str, Any] | None = None,
) -> ManagedOnboardingState

Kicks off the managed-onboarding state machine. kind is the upstream provider ("xendit" is currently the only supported value). details is the KYC payload — business name, NPWP, owner ID, etc. See API → Adapters → Managed onboarding for the schema. SDK auto-generates an idempotency key.

state = plug.adapters.start_managed_onboarding(
    kind="xendit",
    details={
        "businessName": "PT Acme Indonesia",
        "businessType": "perseroan_terbatas",
        "npwp": "01.234.567.8-901.000",
        # ... more KYC fields
    },
)

simulate_managed_onboarding

plug.adapters.simulate_managed_onboarding(
    *, result: str = "verified",
) -> ManagedOnboardingState

Test-mode only. Force the onboarding state to a terminal value ("verified" or "rejected") so your integration tests can exercise the post-onboarding flow without waiting for real KYC. No-ops in live mode. No auto idempotency key.

plug.adapters.simulate_managed_onboarding(result="verified")

Managed vs BYO. "Managed" means Plugipay owns the provider account and routes everything for you (you don't see Xendit's dashboard). "BYO" (bring-your-own) means you already have a Xendit account and you're plugging its keys in — use update_xendit for that path. They're mutually exclusive per workspace.

Types

from plugipay import AdapterConfig, ManagedOnboardingState

AdapterConfig likely-read fields:

  • adapter["kind"]"xendit" | "paypal" | "midtrans" | "manual".
  • adapter.get("status")"active" | "disabled" | "pending".
  • adapter.get("methods") — list of payment methods this adapter exposes for your workspace.
  • adapter.get("mode")"live" | "sandbox" (for providers that have a sandbox).

ManagedOnboardingState likely-read fields:

  • state["status"]"not_started" | "pending_kyc" | "submitted" | "verified" | "rejected".
  • state.get("rejectionReason") — present when status == "rejected".
  • state.get("nextStep") — what the user should do next (free-form string).
  • state.get("submittedAt"), state.get("verifiedAt").

Full reference at API → Adapters.

Common patterns

Capability check before creating a session. Don't ask Plugipay to take card if the workspace's adapter doesn't support it:

available_methods = set()
for adapter in plug.adapters.list():
    if adapter.get("status") == "active":
        available_methods.update(adapter.get("methods", []))

requested = ["card", "qris", "va_bca"]
to_use = [m for m in requested if m in available_methods]
if not to_use:
    raise RuntimeError("No supported payment methods on this workspace")

session = plug.checkout_sessions.create(
    amount=149_000_00, currency="IDR",
    methods=to_use,
    success_url="...", cancel_url="...",
)

Onboarding state machine poll. While the user is on your onboarding page:

import time

state = plug.adapters.managed_onboarding_state()
while state["status"] in ("pending_kyc", "submitted"):
    time.sleep(5)
    state = plug.adapters.managed_onboarding_state()

if state["status"] == "verified":
    redirect_to_dashboard()
elif state["status"] == "rejected":
    show_rejection_reason(state.get("rejectionReason"))

In production, prefer subscribing to plugipay.adapter.onboarding.verified.v1 / …rejected.v1 events instead of polling.

Switching from sandbox to live PayPal. Replace the whole config (PUT semantics):

plug.adapters.update_paypal({
    "clientId": os.environ["PAYPAL_LIVE_CLIENT_ID"],
    "clientSecret": os.environ["PAYPAL_LIVE_CLIENT_SECRET"],
    "mode": "live",
})

The previous sandbox config is overwritten — there's no merge. Stash the old config before updating if you need a fallback.

Test fixture for E2E. Wire up onboarding in test mode then jump to verified:

plug.adapters.start_managed_onboarding(
    kind="xendit",
    details=test_fixture_details,
)
plug.adapters.simulate_managed_onboarding(result="verified")
# Now create checkout sessions, run E2E flows...

Errors

err.status err.code Cause
400 validation_error Missing required config field, bad mode value, unknown provider.
404 not_found Adapter kind not configurable on this workspace's plan.
409 invalid_state_transition start_managed_onboarding when already verified, simulate_* in live mode.
422 provider_rejected_credentials Upstream rejected the keys you supplied — wrong account, wrong mode, revoked.
403 insufficient_scope Key lacks plugipay:adapter:write.

All raise PlugipayError. See Errors.

Next

  • API → Adapters — per-provider config schemas and managed onboarding payload.
  • Checkout sessions — adapters power the methods field.
  • Payouts — adapter choice influences payout flow (managed vs manual).
Plugipay — Payments that don't tax your success