All lists are paginated:
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.
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:
{
totalDocs, totalPages, page, limit, hasNextPage, hasPrevPage
}Create customer#
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).
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_renewdefaultstrue. Whenfalse, no renew charge; expires at period end.renewal_mode:platform(charge scanner) orprovider(confirm sync only). Defaults from body → payment method →platform.skip_trial: trueskips trial even if the plan hastrial_days; payment-due creates often land inpendinguntilconfirmPayment.- Response includes
pricing(quote totals + line items). Preferquotefirst for preview, thencreateSubscriptionwith 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.
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.
const { reference, invoice, charge } = await commercial.initiatePayment({
customer: "acme-corp",
currency: "NGN",
input: {
callback_url: "https://app.example.com/paid",
},
});Response shape:
{
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.
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).
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#
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).