Subscriptions and payments

Create customers, subscribe, quote, initiate and confirm connector payments, and change plans.

All lists are paginated:

TypeScript
const { plans, pagination } = await commercial.listPlans({ page: 1, limit: 20, q: "pro" });
const { features } = await commercial.listFeatures({ page: 1 });
const { customers } = await commercial.listCustomers({ q: "acme" });
const { subscriptions } = await commercial.listSubscriptions({
  customer: "acme-corp",
  page: 1,
});

await commercial.getPlan("pro");
await commercial.getFeature("api_calls");
await commercial.getCustomer("acme-corp");
await commercial.getSubscription("com_sub_…");

const { invoices } = await commercial.listInvoices({
  customer: "acme-corp",
  status: "processing,paid",
  page: 1,
});
await commercial.getInvoice("com_inv_…");
await commercial.getInvoice({ invoiceId: "com_inv_…" });
await commercial.getInvoice(invoice);

Archived subscriptions are omitted from lists by default. Use include_archived: true or status: "archived" to see them.

TypeScript
await commercial.updateSubscriptionStatus({
  subscriptionId: "com_sub_…",
  status: "cancelled",
});

await commercial.archiveSubscription("com_sub_…");

Archive is only allowed for cancelled, expired, or suspended (not active/trial).

Pagination shape:

TypeScript
{
  totalDocs, totalPages, page, limit, hasNextPage, hasPrevPage
}

Create customer#

TypeScript
await commercial.createCustomer({
  customer_key: "acme-corp",
  name: "Acme Corp",
  email: "billing@acme.com",
});

email is first-class on the customer. Prefer storing it here so payment mappings can use {{customer.email}} instead of asking again in payment_input.

Create subscription (subscribe)#

Creates a subscription (and charges via the configured capability payment method when amount due > 0 and not trial-only).

TypeScript
const { subscription } = await commercial.createSubscription({
  customer: "acme-corp",
  plan: "pro",
  currency: "USD",
  discount_code: "LAUNCH20",
  tax_rate: "ng-vat",
  seats_purchased: 8,
  addons: [{ key: "extra_seat", quantity: 1 }],
  auto_renew: true,
  renewal_mode: "platform",
  skip_trial: false,
  metadata: { source: "api" },
  payment_input: {
    email: "billing@acme.com",
    return_url: "https://app.example.com/paid",
  },
});

await commercial.subscribe({
  customer: "acme-corp",
  plan: "pro",
  payment_input: { email: "billing@acme.com" },
});

Maps to POST /commercial/v1/subscriptions.

  • auto_renew defaults true. When false, no renew charge; expires at period end.
  • renewal_mode: platform (charge scanner) or provider (confirm sync only). Defaults from body → payment method → platform.
  • skip_trial: true skips trial even if the plan has trial_days; payment-due creates often land in pending until confirmPayment.
  • Response includes pricing (quote totals + line items). Prefer quote first for preview, then createSubscription with the same pricing fields.

payment_input values fill portal mappings with source: "input".

Payments (capability)#

Payment credentials live on an installed Cliodot capability. The portal maps capability action fields to commercial context (amount, plan.key, …) or runtime input. The SDK only sends runtime values + customer/subscription refs.

Use createSubscription for checkout. Use initiatePayment to charge an existing subscription/invoice without creating a new sub.

An app can have many payment methods (Paystack, Stripe, bank transfer, etc.). One is marked is_default. Omit payment_method to use the default; pass a method key (or id) to use a specific one.

TypeScript
const { payment_methods } = await commercial.listPaymentMethods();

await commercial.subscribe({
  customer: "acme-corp",
  plan: "pro",
  payment_method: "paystack",
  payment_input: { email: "billing@acme.com" },
});

Initiate a charge#

Creates/uses an invoice, runs the mapped capability action, and leaves the invoice in processing. It does not mark the payment paid — the third party must finish first.

TypeScript
const { reference, invoice, charge } = await commercial.initiatePayment({
  customer: "acme-corp",
  currency: "NGN",
  input: {
    callback_url: "https://app.example.com/paid",
  },
});

Response shape:

TypeScript
{
  ok: true,
  reference: "com_inv_…",
  invoice: { _id, status: "processing", currency, amount, total },
  charge: /* provider payload */
}

Maps to POST /commercial/v1/payments/initiate.

input keys must match portal mappings with source: "input" (or {{input.key}}). Prefer {{customer.email}} in mappings so you do not re-send email.

Confirm payment#

Call this from your backend after the provider confirms success (webhook, callback, or verify). Until then the invoice stays processing.

TypeScript
await commercial.confirmPayment({
  reference,
  status: "succeeded",
  provider_payment_id: "tx_123",
});

That marks the invoice paid / payment succeeded (or failed). Renewals deferred with renewal_mode: "provider" use the same confirm path.

Maps to POST /commercial/v1/payments/confirm.

Pricing quote / discounts#

Plans support multi-currency prices[] + default_currency. Opaque metadata on price rows/subscriptions is for external ids only (never used in billing math).

TypeScript
const { quote } = await commercial.quote({
  customer: "acme-corp",
  plan: "pro",
  currency: "NGN",
  discount_code: "LAUNCH20",
  seats_purchased: 8,
  metadata: { crm_deal_id: "D-100" },
});

await commercial.previewDiscount({
  code: "LAUNCH20",
  plan: "pro",
  currency: "USD",
});

await commercial.listDiscounts({ page: 1 });
await commercial.listTaxRates();
await commercial.listAddons();

Change plan / status / pending update#

TypeScript
await commercial.updatePendingSubscription({
  subscriptionId: "com_sub_…",
  plan: "pro",
  discount_code: "LAUNCH20",
  auto_renew: true,
  payment_input: { email: "billing@acme.com" },
});

await commercial.changePlan({
  subscriptionId: "com_sub_…",
  plan: "enterprise",
  apply_at: "immediate",
  usage_rollover: "carry_unused",
  discount_code: "LAUNCH20",
  payment_method: "paystack",
  payment_input: {
    email: "billing@acme.com",
  },
});

await commercial.updateSubscriptionStatus({
  subscriptionId: "com_sub_…",
  status: "cancelled",
});

updatePendingSubscription maps to PATCH /commercial/v1/subscriptions/:id and only works while status is pending.

apply_at: immediate | next_renewal. Immediate pending plan changes apply live entitlements only after payment success (or $0).