Billing

The billing namespace is the merchant's subscription to Fulkruma itself — not the merchant's billing of their own end customers. Read plans, the current plan and subscription, usage, and invoices; start a hosted checkout to upgrade; cancel. Under the hood this is all powered by Plugipay (the Pattern 2 partner-billing flow), but the SDK exposes a flat surface. For HTTP fields, see API: Billing.

Namespace

fulkruma.billing — every method:

fulkruma.billing.plans()
fulkruma.billing.currentPlan()
fulkruma.billing.subscription()
fulkruma.billing.usage()
fulkruma.billing.invoices(params?)
fulkruma.billing.checkout(input)
fulkruma.billing.cancel()

Five reads, a checkout and a cancel. There's no "upgrade in place" — every plan change goes through checkout, which returns a hosted Plugipay URL.

Methods

billing.plans

Signature. fulkruma.billing.plans(): Promise<Array<Record<string, unknown>>>

Every plan Fulkruma offers: free, starter, growth, scale. Each has an id, name, price (in IDR), currency ('IDR') and features (display strings). No credentials needed.

const plans = await fulkruma.billing.plans();
for (const p of plans as Array<{ id: string; name: string; price: number }>) {
  console.log(`${p.name} — Rp${p.price.toLocaleString('id-ID')}/month`);
}

billing.currentPlan

Signature. fulkruma.billing.currentPlan(): Promise<Record<string, unknown>>

The workspace's plan and its limits: plan, planName, ordersLimit, warehousesLimit, licenseKeysLimit, apiKeysLimit, webhookEndpointsLimit, rateLimit, biteshipShipmentsLimit (each -1 for unlimited) and billingCycleEnd.

const current = await fulkruma.billing.currentPlan();
console.log(current.planName, current.ordersLimit);

billing.subscription

Signature. fulkruma.billing.subscription(): Promise<Record<string, unknown>>

The subscription's state: plan, planName, status (lowercase: active, canceling, …), currentPeriodStart, currentPeriodEnd, cancelAt. Use this for "what's my state?" checks.

const sub = await fulkruma.billing.subscription();
if (sub.status === 'canceling') showRenewBanner(sub.currentPeriodEnd as string);

billing.usage

Signature. fulkruma.billing.usage(): Promise<Record<string, unknown>>

This month's counters against the plan: ordersFulfilled / ordersLimit, shipmentsCreated, licensesIssued, and resetAt (the start of next month).

const usage = await fulkruma.billing.usage();
console.log(`${usage.ordersFulfilled} / ${usage.ordersLimit} orders this month`);

billing.invoices

Signature. fulkruma.billing.invoices(params?: { limit?: number; cursor?: string }): Promise<{ data: Array<Record<string, unknown>>; cursor: string | null; hasMore: boolean }>

Invoices for Fulkruma's own subscription, newest first. limit defaults to 20, max 50. Pass the returned cursor to get the next page while hasMore is true. Each invoice has id, plan, amount, currency, status, paidAt, receiptUrl, createdAt.

let cursor: string | undefined;
do {
  const page = await fulkruma.billing.invoices({ limit: 50, cursor });
  for (const inv of page.data) console.log(inv.id, inv.amount, inv.status);
  cursor = page.hasMore ? page.cursor ?? undefined : undefined;
} while (cursor);

billing.checkout

Signature. fulkruma.billing.checkout(input: { plan: 'STARTER' | 'GROWTH' | 'SCALE'; email?: string; name?: string; currency?: 'IDR' | 'USD' }): Promise<{ subscriptionId: string; invoiceId: string; checkoutSessionId: string; checkoutUrl: string }>

Starts a Plugipay subscription for the plan and returns the hosted page where the first payment is made. email is required when you call with an API key (a key has no email of its own; the portal fills it from the signed-in user). currency defaults by the caller's country — IDR in Indonesia, USD elsewhere.

const { checkoutUrl } = await fulkruma.billing.checkout({
  plan: 'GROWTH',
  email: 'owner@your-store.example',
});
// Redirect the merchant's browser to checkoutUrl

The hosted page handles card capture, 3DS and the partner-billing routing back to Fulkruma. When the invoice is paid, Plugipay notifies Fulkruma and the plan changes.

billing.cancel

Signature. fulkruma.billing.cancel(): Promise<Record<string, unknown>>

Cancels the subscription at period end — the merchant keeps the plan until the current period closes. The request carries no body; the response is the updated subscription (as subscription() returns it, with status: 'canceling'). A workspace with no paid subscription gets its current state back unchanged.

const sub = await fulkruma.billing.cancel();

Common patterns

Render a billing dashboard

async function billingDashboard() {
  const [current, sub, usage, plans] = await Promise.all([
    fulkruma.billing.currentPlan(),
    fulkruma.billing.subscription(),
    fulkruma.billing.usage(),
    fulkruma.billing.plans(),
  ]);
  return { current, sub, usage, plans };
}

Parallelize the reads — they have no dependencies.

Upgrade flow

async function upgradeTo(plan: 'STARTER' | 'GROWTH' | 'SCALE', ownerEmail: string) {
  const { checkoutUrl } = await fulkruma.billing.checkout({ plan, email: ownerEmail });
  return checkoutUrl;  // your route handler returns a 302 to this
}

Fulkruma sends no webhook event for plan changes; re-read subscription() when the merchant comes back from the hosted page.

Errors

Code Status Cause
VALIDATION 400 plan not one of STARTER / GROWTH / SCALE, a malformed email, or no email at all for an API-key caller.
NO_ACCOUNT 403 The credentials resolve to no workspace.
CHECKOUT_FAILED 500 Plugipay refused to start the subscription.
CANCEL_FAILED 500 Plugipay refused the cancellation.
PLAN_NOT_CONFIGURED 503 The plan has no Plugipay price set up yet.

Next