Plans

A plan is a recurring price you charge customers on a fixed cadence — IDR 99,000 per month, USD 49 per year. Plans are the price catalog that subscriptions point at; the subscription itself owns the customer-and-billing relationship, the plan just owns the amount and the interval. This page covers the plugipay.plans namespace; for the full field reference see API: Plans, and for how plans fit alongside subscriptions and invoices see Concepts → Plan.

Namespace

plugipay.plans — every method on this namespace:

plugipay.plans.create(input)
plugipay.plans.get(id)
plugipay.plans.list(params?)
plugipay.plans.update(id, patch)
plugipay.plans.archive(id)

There's no hard delete — once a plan has been on a subscription, it has to stick around for historical records. archive is the soft-delete equivalent.

Methods

plans.create

Signature. plugipay.plans.create(input): Promise<Plan>

Creates a plan. The four required fields are name, currency, amount (in the currency's minor unit — rupiah, not thousand-rupiah), and interval. The SDK auto-attaches an Idempotency-Key.

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

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

const pro = await plugipay.plans.create({
  name: 'Pro Monthly',
  currency: 'IDR',
  amount: 99_000_00,            // → IDR 99,000.00 — minor units
  interval: 'month',
});

console.log(pro.id);             // → 'plan_01HX...'

Amount is in the currency's minor unit. For IDR that's "sen" (1/100 of a rupiah); for USD that's cents. 99000 means IDR 990 (probably not what you want); 9900000 is IDR 99,000. Double-check this against Conventions → Money before going live.

plans.get

Signature. plugipay.plans.get(id): Promise<Plan>

const plan = await plugipay.plans.get('plan_01HX...');
console.log(`${plan.name} — ${plan.amount} ${plan.currency} / ${plan.interval}`);

plans.list

Signature. plugipay.plans.list(params?): Promise<{ data: Plan[]; cursor: string | null; hasMore: boolean }>

Filters: active (boolean), order ('asc' / 'desc', on createdAt), limit, cursor. The default order is 'desc' — newest first — which is what dashboards want; for catalog UIs you usually want 'asc'.

const { data } = await plugipay.plans.list({ active: true, order: 'asc', limit: 100 });

for (const plan of data) {
  console.log(`${plan.id}: ${plan.name}`);
}

plans.update

Signature. plugipay.plans.update(id, patch): Promise<Plan>

PATCH semantics. Note what's not updatable: you can't change currency, amount, or interval on an existing plan — those would silently change the price every existing subscriber is paying. To change the price, create a new plan and migrate subscriptions (see Versioning a plan below).

const updated = await plugipay.plans.update('plan_01HX...', {
  name: 'Pro Monthly (legacy)',
  description: 'Closed to new signups',
  metadata: { gtm_tag: 'legacy_2026' },
});

plans.archive

Signature. plugipay.plans.archive(id): Promise<Plan>

Sets active: false and removes the plan from public price lists. Existing subscriptions continue to renew on it; only new subscriptions are blocked.

await plugipay.plans.archive('plan_01HX...');

Unarchiving is done via update({ active: true }).

Types

interface Plan {
  id: string;                  // 'plan_...'
  accountId: string;
  name: string;
  currency: 'IDR' | 'USD';
  interval: 'day' | 'week' | 'month' | 'year';
  amount: number;              // minor units
  active: boolean;
  createdAt: string;           // ISO-8601
  updatedAt: string;
}

For the full field reference (including server-set fields and the description / metadata shape on update), see API: Plans.

Common patterns

Build a public pricing page

Fetch only active plans, sorted by amount, grouped by currency:

async function publicPricing() {
  const { data } = await plugipay.plans.list({ active: true, order: 'asc', limit: 100 });
  const byCurrency: Record<string, Plan[]> = {};
  for (const p of data) {
    (byCurrency[p.currency] ??= []).push(p);
  }
  for (const list of Object.values(byCurrency)) {
    list.sort((a, b) => a.amount - b.amount);
  }
  return byCurrency;
}

Versioning a plan (price change)

You can't mutate amount on an existing plan, so price changes are a two-step:

// 1. Archive the old plan so nobody new signs up on it.
await plugipay.plans.archive('plan_pro_monthly_v1');

// 2. Create v2 at the new price.
const v2 = await plugipay.plans.create({
  name: 'Pro Monthly',
  currency: 'IDR',
  amount: 129_000_00,
  interval: 'month',
});

// 3. Migrate existing subscribers (one at a time — see subscriptions docs).

Migrating subscribers is a separate operation on the Subscriptions namespace; you typically grandfather existing subscribers on v1 and let them renew there, or call subscriptions.create for v2 with initialDiscount to handle proration.

Soft-deleting a plan

There's no delete. Use archive for the "this plan no longer exists from a product perspective" case:

await plugipay.plans.archive(planId);

Existing subscriptions on the plan continue to bill normally; they just can't be created against it after archival.

Build a "compare plans" matrix

For a feature-comparison table, attach feature flags via metadata:

await plugipay.plans.update('plan_pro', {
  metadata: {
    feature_priority_support: 'true',
    feature_advanced_analytics: 'true',
    feature_user_limit: '25',
  },
});

await plugipay.plans.update('plan_starter', {
  metadata: {
    feature_priority_support: 'false',
    feature_advanced_analytics: 'false',
    feature_user_limit: '3',
  },
});

Metadata is string–to–string; serialize booleans/numbers as strings and parse on the way out.

Errors

Code Status Cause
validation_error 400 Missing required field, bad currency, non-positive amount, unknown interval.
not_found 404 Plan ID doesn't exist or lives in another workspace.
forbidden 403 Key lacks the right scope.
conflict 409 (Rare) Plan archival when there are blocking dependent objects on certain enterprise configs.

See Errors for the SDK's error model and API errors for the full catalog.

Next

Plugipay — Payments that don't tax your success