Skip to main content

Change plans

import { Limitr } from '@formata/limitr';

const limitr = await Limitr.new(`
policy: {
credits: {
ai_token: { label: 'AI token', price: { amount: 0.00002 } }
}
plans: {
starter: {
default: true
entitlements: {
chat: {
limit: {
credit: 'ai_token', value: 100_000
resets: true, reset_sch: 'monthly:1'
}
}
}
topups: {
monthly_tokens: {
credit: 'ai_token', value: 50_000, included: true
resets: true, reset_sch: 'monthly:1'
}
}
}
pro: {
entitlements: {
chat: {
limit: {
credit: 'ai_token', mode: 'soft', value: 2_000_000
resets: true, reset_sch: 'monthly:1'
}
}
api_access: {}
sso: {}
}
topups: {
monthly_tokens: {
credit: 'ai_token', value: 250_000, included: true
resets: true, reset_sch: 'monthly:1'
}
}
}
}
}`);

limitr.addHandler('plans', (name, value) => {
if (name === 'customer-plan-changed') {
const event = JSON.parse(value as string);
console.log(` ${event.customer.id}: ${event.previous_plan} -> ${event.new_plan}`);
}
});

// Where Acme stands: chat usage, the credit pack, and its features.
async function report(label: string) {
const used = await limitr.value('acme', 'chat');
const limit = await limitr.limit('acme', 'chat', false);
const pack = await limitr.remainingCredit('acme', 'ai_token');
const features = await limitr.features('acme');
console.log(`${label}: used ${used} of ${limit}, pack ${pack}, features`, features);
}

await limitr.ensureCustomer('acme', 'starter', 'org');
// 20,000 past the limit come out of the pack
await limitr.allow('acme', 'chat', 120_000);
await report('on starter');

// Upgrade mid-month: keep the usage counted so far. The pack tops up to Pro's 250,000.
await limitr.setCustomerPlan('acme', 'pro', false);
await report('on pro');
await limitr.allow('acme', 'chat', 400_000);

// Downgrade, but keep SSO, which was part of the deal. The pack keeps its larger balance.
await limitr.giveEntitlement('acme', 'sso', 'pro');
await limitr.setCustomerPlan('acme', 'starter', false);
await report('back on starter');
console.log('more chat this month:', await limitr.allow('acme', 'chat', 1_000));

It prints:

on starter: used 120000 of 100000, pack 30000, features [ 'chat' ]
acme: starter -> pro
on pro: used 120000 of 2000000, pack 250000, features [ 'chat', 'api_access', 'sso' ]
acme: pro -> starter
back on starter: used 520000 of 100000, pack 250000, features [ 'chat', 'sso' ]
more chat this month: true

Upgrades​

  • Keep the usage. setCustomerPlan(id, plan, false) keeps the meters, so an upgrade mid-month doesn't hand out a fresh allowance: Acme had used 120,000 tokens, and still had on Pro.
  • Start over instead with setCustomerPlan(id, plan): in TypeScript the meters start over unless you pass false. (The engine's own set_customer_plan defaults to keeping them.)
  • Included credit packs top up. Acme had 30,000 tokens left in Starter's monthly pack, and moving to Pro raised it to Pro's 250,000. There's nothing extra to call. See Credit packs for the other behaviors.
  • The plan fee for the new plan: call ensureCustomerPlanQuantity(id) if you bill fees through Limitr (Plan fees). Proration is up to your billing.

Downgrades​

  • Over the new limits. Acme had used 520,000 tokens, past Starter's 100,000. Nothing is lost: the meter keeps its value. New usage past the limit is covered by the customer's grants first, and then denied on a hard limit until the month resets (a soft limit bills it as overage instead).
  • The pack keeps its larger balance. Acme kept the 250,000 tokens it had from Pro, rather than dropping to Starter's 50,000, so a downgrade never takes away credits already given. From the next reset, the pack follows Starter's: 50,000 a month.
  • Keeping a feature that was part of a deal: give it before the downgrade (giveEntitlement). Gives are stored on the customer, so they last through plan changes. See Trials and custom deals.
  • Features the new plan doesn't have are denied from the next call. features(id) reflects it right away.

Credit packs​

Included topups (credit packs that come with a plan) move with the customer. The new plan's included topup in the same credit decides what happens to the balance, with its plan_change:

plan_changeMoving onto the plan
top_up (default)The balance rises to the topup's value, and is never lowered.
replaceThe balance starts over at the topup's value.
keepThe balance stays as it is.
  • A plan with no matching pack removes the balance: if the new plan has no included topup in that credit for the customer's type, the included balance is gone after the change. To let customers keep it, give the new plan a pack in that credit, Ex. value: 0 with plan_change: 'keep' keeps the balance until its next reset.
  • Purchased packs stay. A pack the customer bought (applyCustomerTopup) is theirs: it stays through every plan change.

See Grants and topups for the details.

Orgs and users​

Users with no plan of their own are on their org's plan, so changing the org's plan changes theirs. A user with its own plan keeps it.

What stays with the customer​

Plan changes keep meters (with false), overrides, gives and takes, purchased grants, caps, and metadata. Included packs follow the new plan, as above. Each change sends customer-plan-changed (previous_plan, new_plan, customer) and customer-set.

Changing the plan itself​

To change what a plan includes for everyone on it, change the plan: edit the policy, or setPlan(id, stof) at runtime. Customers keep their meters, and the next call uses the new limits.