Loading...

This is taking longer than expected.

Back to the help centre

Team plans and billing

How the base's plans, add-ons, limits and cards attach to the team, who can manage them, and what the team billing package turns on.

Everything about pricing comes from Weblabor Base: plans, prices per country and interval, add-ons, limits, Stripe Checkout, the billing portal and the admin forms. See Turn on plans and billing and Create and price a plan on the base's site for all of that. This guide covers only what changes when the customer is a team.

The team is the customer

The kit ships a second billing adapter, packages/billing-team, next to the base's packages/billing-core. The base bills a User; this adapter bills a Team. Each team gets its own BillingAccount row, its own Stripe customer, its own subscription and its own entitlements. Nothing about billing is stored on the person.

BILLING_TEAM_ENABLED=true
BILLING_USER_ENABLED=false

BILLING_TEAM_ENABLED registers the team adapter. BILLING_USER_ENABLED registers the base's user adapter next to it, which you only want when your product sells to people and to teams at the same time. As shipped the user adapter is off, so /account/plans and the other account billing pages of the base do not exist: the team's pages replace them.

The switches of the base still apply on top: FEATURE_PLANS_ENABLED and FEATURE_ADDONS_ENABLED turn the plans and the add-ons on or off for everybody.

Where the team manages it

URL What it does
/team/plans The plans, with the team's current one marked. A free plan is subscribed on the spot; a paid one opens Stripe Checkout.
/team/add-ons The add-ons catalog, with the team's active ones. When the team has meters, their balances are shown above the catalog, as they are on the person's screen.
/team/add-ons/{addon} One add-on's detail page: every way to get it, the button that gets it, and a back link to the team's catalog.
/team/billing The subscription's state, the next payment, the invoices, and the link to the Stripe Billing Portal.
/team/payment-methods The team's cards.
/team/debt The team's outstanding balance: what it owes and the button to settle it. The team is sent here when it owes money.

None of these requires a subscription, so a team whose plan ended, or that owes money, can still reach them and pay. The owner and the members with manage plans on the team guard see them; everyone else is sent back to the team dashboard with a message saying to ask a team administrator for access, and the menu does not offer them the links in the first place. For those who manage plans, the team menu shows "Add-ons" right after "My plans" whenever plans and add-ons are on and the team's catalog has something to show: an add-on sold in the team's country or priced for every country, or a meter. With an empty catalog the link stays hidden, and what the person's own catalog holds does not change it.

Each of these belongs to the team, not to whoever opens it. A member who also pays for a personal account of their own never lands on their own billing pages from inside a team: the add-ons, the detail pages and the return from Stripe all stay on the team that is active.

The free plan

When plans are on, a team with no active plan is put on the free plan without asking: when its subscription ends, then once a day while it stays without one, and whenever its dashboard at /team opens. The free plan is the one of the team's country — the country saved on the team, otherwise its owner's, otherwise the general free plan. It is never guessed from where the visitor is connecting.

The free plan is not guaranteed. When there is no free plan for the team's country, when the team uses more than the free plan allows, or when the free plan could not be activated, the team stays without a plan, and every member sees a warning saying which of those it was. Only whoever manages the team's plan gets Go to plans; everyone else is told that whoever manages the plan has to resolve it.

A team without a plan gets nothing from one: every limit counts as zero. Nobody can be added or invited, and no file can be uploaded. Its screens stay open, so its members, roles and files can still be reached. When your account is left without a plan on the base's site explains each reason and the way back.

Limits counted per team

The plan's limits are declared and priced in the admin panel as in the base. What each one counts is app/Classes/TeamPlanLimits.php, the team's version of the base's PlanLimits:

Limit key What it counts
users The team's members plus its pending invitations.
activity_logs The activity log entries recorded on the team in the current month.

Add a public method per limit your product needs, with the key as its name and the current team as its optional argument, and a Daily variant when you want it on the usage charts:

public function projects(?object $owner = null)
{
    $team = $owner ?? team();
    return $team ? $team->projects()->count() : 0;
}

The dashboard at /team shows every limit with what the team has used. In code, team()->reachedLimit('users') answers whether the team is at the limit; the members list calls it to disable Create member, and the add-member form calls it again before saving.

Selling members beyond the plan

None of this needs code. A free plan, a paid plan with ten members included and an Extra member add-on charged by the unit are three records created in the admin panel:

Record Where What to set
Free plan /admin/billing-plans No price. Under Capacity and limits, users = 1.
Paid plan /admin/billing-plans A monthly price, say $20 USD. Under Capacity and limits, users = 10.
Extra member /admin/billing-addons Sold per unit on. Under Limits, users = 1. A monthly price per unit, say $1 USD.

The limit key is users, not "members": the admin lists the method names of app/Classes/TeamPlanLimits.php as they are written. Sold per unit is what makes the quantity matter — the team picks how many it wants, and the price and the limits multiply by it. An add-on sold per unit has to carry at least one limit, which is what the users row is for; it needs no capability, so leave Capability empty.

As the kit ships, the team adapter is the only one registered, so the add-on form shows no Context field and the add-on reaches the team's catalog as it is. That field appears only when the base's user adapter is registered beside it, as the last section of this guide describes, and there you pick the team.

Once the three records exist, a team on the paid plan holding four units of Extra member has fourteen places. /team/members/create refuses the fifteenth, member or invitation alike, with "You have reached the maximum number of Users allowed by your subscription". The team gets past it by buying more units at /team/add-ons or by moving to a larger plan, and the new places count from that moment. A pending invitation holds a place exactly like an active member, as the limits table in the previous section says, and whoever created the team already holds one — which is why a team on the free plan of this example cannot invite anybody until it buys a unit.

The per-unit mechanism itself is the base's, and it is documented there: Sell add-ons, under "Per unit and by tiers".

Add-ons and capabilities

An add-on switches a capability on. The capabilities a team can buy are declared as public methods of app/Classes/TeamPlanFeatures.php, the team's version of the base's PlanFeatures; the method name is the feature key the admin picks in the add-on form, and the method answers whether the capability is released in code. The class ships empty, so declare one before creating an add-on for teams:

public function advancedReports()
{
    return true;
}

Check it with team()->hasFeature('advancedReports'), a permanent synonym of team()->hasAddOn(). A policy does not call it: declare protected $requiredFeature on the policy and App\Policies\BasePolicy checks the team's entitlements for that key by itself, before any of the policy's own methods run. A visitor with no current team is denied there: the guard fails closed, because no team means no entitlements to read. The team's features column is a cache the billing account keeps up to date; the decision is always the account's entitlements, so a lifetime purchase survives a plan that stops including the add-on.

From the admin panel

/admin/teams shows each team with its plan and lets an administrator switch an add-on on or off for it, from the same panel the base uses for users. See Teams in the admin panel.

Selling to people and to teams

Set BILLING_USER_ENABLED=true to register both adapters. The base's account billing pages come back for the person, the team's pages stay for the team, and each plan and add-on declares in the admin form whether it is for users, for teams or for both.