Adapters

An adapter is Plugipay's connection to a downstream payment provider — Xendit, Midtrans, PayPal, or the built-in manual adapter for bank-transfer flows. Configuring an adapter is what activates a checkout method: enable the Xendit adapter and qris / va / ewallet start working on checkout sessions; enable PayPal and the paypal method works. This page covers the plugipay.adapters namespace; for the underlying HTTP surface see API: Adapters, and for end-to-end onboarding flows see Concepts → Adapters.

Namespace

plugipay.adapters — every method on this namespace:

plugipay.adapters.list()
plugipay.adapters.updateXendit(config)
plugipay.adapters.updatePaypal(config)
plugipay.adapters.updateMidtrans(config)
plugipay.adapters.updateManual(config)
plugipay.adapters.managedOnboardingState()
plugipay.adapters.startManagedOnboarding(input?)
plugipay.adapters.simulateManagedOnboarding(input?)   // test-mode only

There are two flavors of adapter setup: direct (you bring your own provider credentials and PATCH them in via updateXendit / updatePaypal / etc.) and managed (Plugipay handles onboarding with the provider on your behalf via startManagedOnboarding).

Methods

adapters.list

Signature. plugipay.adapters.list(): Promise<AdapterConfig[]>

Returns every adapter known for this workspace and whether it's configured. Unconfigured adapters appear with configured: false; their publicConfig is empty.

import { PlugipayClient } from '@forjio/plugipay-node';

const plugipay = new PlugipayClient({
  keyId: process.env.PLUGIPAY_KEY_ID!,
  secret: process.env.PLUGIPAY_SECRET!,
});

const adapters = await plugipay.adapters.list();
for (const a of adapters) {
  console.log(`${a.kind}: ${a.configured ? 'configured' : 'not set up'}`);
}

adapters.updateXendit / updatePaypal / updateMidtrans / updateManual

Signature. Each update* method is (config: Record<string, unknown>) => Promise<AdapterConfig>.

PUT semantics — the config you pass replaces the stored config in full. The exact shape of config is provider-specific; pass the credentials Plugipay's onboarding doc tells you to. See API: Adapters for the per-provider config schema.

// Xendit direct configuration:
await plugipay.adapters.updateXendit({
  publicKey: 'xnd_public_production_...',
  secretKey: 'xnd_secret_production_...',
  webhookToken: '...',
});

// PayPal:
await plugipay.adapters.updatePaypal({
  clientId: 'AY...',
  clientSecret: '...',
  mode: 'live',
});

// Midtrans:
await plugipay.adapters.updateMidtrans({
  serverKey: 'Mid-server-...',
  clientKey: 'Mid-client-...',
  mode: 'production',
});

// Manual (bank transfer) — set up displayed bank account details:
await plugipay.adapters.updateManual({
  bankName: 'Bank Central Asia',
  bankAccountNumber: '1234567890',
  bankAccountHolder: 'PT Forjio Teknologi Indonesia',
  instructions: 'Please include your order ID in the transfer reference.',
});

Secrets in config are write-only. When you list() an adapter, only publicConfig is returned — the secret keys you PUT are never reflected back. Treat the PUT call as setting state, not as a read-modify-write.

adapters.managedOnboardingState

Signature. plugipay.adapters.managedOnboardingState(): Promise<ManagedOnboardingState>

For workspaces using Plugipay's managed onboarding (where Plugipay sets up the provider account for you), this reports current progress: 'not_started', 'pending' (KYC / verification in flight), 'verified', or 'failed'.

const state = await plugipay.adapters.managedOnboardingState();
console.log(state.state, state.provider, state.details);

adapters.startManagedOnboarding

Signature. plugipay.adapters.startManagedOnboarding(input?): Promise<ManagedOnboardingState>

Kicks off managed onboarding for a provider. Default kind is 'xendit'. The SDK auto-attaches an Idempotency-Key. Pass details for any provider-specific KYC inputs Plugipay needs.

const state = await plugipay.adapters.startManagedOnboarding({
  kind: 'xendit',
  details: {
    businessName: 'PT Acme Indonesia',
    businessType: 'pt',
    npwp: '01.234.567.8-999.000',
  },
});

if (state.state === 'pending') {
  console.log('KYC submitted; awaiting Xendit verification (usually 1-2 business days)');
}

adapters.simulateManagedOnboarding

Signature. plugipay.adapters.simulateManagedOnboarding(input?): Promise<ManagedOnboardingState>

Test-mode only. Forces the managed-onboarding flow to a target state for integration testing.

// In your test setup:
await plugipay.adapters.simulateManagedOnboarding({ result: 'verified' });
// Now subsequent `managedOnboardingState()` calls return state: 'verified'.

Calling this in live mode returns 403 forbidden.

Types

type AdapterKind = 'xendit' | 'paypal' | 'midtrans' | 'manual';

interface AdapterConfig {
  kind: AdapterKind;
  configured: boolean;
  publicConfig?: Record<string, unknown>;
  updatedAt?: string;
}

interface ManagedOnboardingState {
  state: 'not_started' | 'pending' | 'verified' | 'failed';
  provider: string;
  details?: Record<string, unknown>;
}

For the per-provider publicConfig schemas (what's safe to read, what fields you write but can't read back), see API: Adapters.

Common patterns

Bootstrap a workspace with Xendit direct

You signed up for Xendit yourself; you have the credentials. Wire them in once:

async function configureXendit(env: 'live' | 'test') {
  await plugipay.adapters.updateXendit({
    publicKey: process.env[`XENDIT_PUBLIC_${env.toUpperCase()}`]!,
    secretKey: process.env[`XENDIT_SECRET_${env.toUpperCase()}`]!,
    webhookToken: process.env[`XENDIT_WEBHOOK_TOKEN_${env.toUpperCase()}`]!,
  });
  console.log(`Xendit ${env} configured.`);
}

Check what methods are available before checkout

Before showing your checkout method picker, list adapters and figure out which methods are usable:

async function availableMethods(): Promise<CheckoutMethod[]> {
  const adapters = await plugipay.adapters.list();
  const methods: CheckoutMethod[] = [];
  for (const a of adapters) {
    if (!a.configured) continue;
    if (a.kind === 'xendit')   methods.push('qris', 'va', 'ewallet', 'card');
    if (a.kind === 'midtrans') methods.push('qris', 'va', 'ewallet');
    if (a.kind === 'paypal')   methods.push('paypal');
    if (a.kind === 'manual')   methods.push('retail');
  }
  return [...new Set(methods)];
}

Drive managed onboarding from a UI

For a customer-facing setup wizard:

async function startOnboardingForUser(input: {
  businessName: string;
  businessType: string;
  npwp: string;
}) {
  const state = await plugipay.adapters.startManagedOnboarding({
    kind: 'xendit',
    details: input,
  });
  return state;
}

async function pollOnboardingStatus() {
  return plugipay.adapters.managedOnboardingState();
}

In your UI, poll pollOnboardingStatus every minute or react to the corresponding webhook events.

End-to-end test the verified-state path

For tests that need to exercise the post-onboarding flow:

beforeEach(async () => {
  await plugipay.adapters.simulateManagedOnboarding({ result: 'verified' });
});

test('checkout works once onboarding is verified', async () => {
  const session = await plugipay.checkoutSessions.create({
    amount: 10_000_00,
    currency: 'IDR',
    methods: ['qris'],
    successUrl: 'https://example.com/ok',
    cancelUrl: 'https://example.com/cancel',
  });
  expect(session.status).toBe('open');
});

Reset an adapter

There's no delete; the way to "unconfigure" is to PUT an empty config. The exact accepted-as-empty shape varies by provider, but this is the canonical pattern:

async function disableXendit() {
  await plugipay.adapters.updateXendit({});
  // After this, adapters.list() reports kind: 'xendit', configured: false.
}

Errors

Code Status Cause
validation_error 400 Missing required provider field, malformed credential format.
provider_credentials_invalid 401 Credentials don't pass Plugipay's smoke test against the provider.
forbidden 403 Calling simulateManagedOnboarding in live mode, or key lacks adapter-write scope.
state_conflict 409 Starting managed onboarding when already 'verified'.

Next

  • Checkout sessions — the consumer of adapter capabilities.
  • Payouts — some adapters (Xendit) also handle disbursements.
  • API: Adapters — HTTP reference and per-provider config schemas.
Plugipay — Payments that don't tax your success