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_xenditfor 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 whenstatus == "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
methodsfield. - Payouts — adapter choice influences payout flow (managed vs manual).