Toolaby

Licences for a Chrome extension

Sell a Chrome extension for one payment, with the license key, devices per buyer, team seats, refunds and the checks your code makes.

By the end of this guide, your extension sells a lifetime licence. A buyer pays once, on your Stripe account, and the licence unlocks the extension on the number of devices you set. Your code checks it with gate() and getUser(), and a refund revokes it.

Before you begin

  • An extension wired to a tool: toolaby.js and toolaby.config.js beside your code. See the Quickstart.
  • The Access card set, under the tool's Pricing. With Nothing free, the extension is paid up front. See Access and features.

1. Add a lifetime plan

Under the tool's Pricing, select New plan, the dashed card at the end of the plans:

FieldValue
Billingonce. Once makes a lifetime licence.
Amount, currencyAt least 0.50, in a currency your Stripe account can charge in.
NameOptional, such as Pro. Left empty, buyers read Lifetime.
Devices1 to 10: how many devices one buyer may use it on at once.
Sold per seatOn by default for a lifetime plan. See step 6.

The plan is a Price on your Stripe account. Its card shows tags such as 2 devices, and the plan's id: lifetime, or default for the tool's first plan. It reaches the Toolaby popup and the checkout page at their next open, with no release.

To change the amount later, add a plan and retire the old one. Licences already sold stay valid.

2. Send buyers to the checkout page

With Nothing free, the Toolaby popup shows the plans at every open until the device holds one. showPaywall() shows them after a refused permit. To open the checkout page from a button of your own, name the plan by its id, as its card under Pricing shows it:

popup.js
import { toolaby } from './toolaby.js';

document.getElementById('buy').addEventListener('click', () => toolaby.openPaymentPage('lifetime'));

openPaymentPage() opens the checkout page in a tab, with the plan preselected. Payment runs on Stripe's page, on your account. A lifetime licence can be bought signed out.

3. Unlock the buyer's device

The licence is recorded on the buyer's account. A device holds it through the account the extension is signed in to. After payment, the after-purchase page signs the extension in:

The buyer paidWhat happens
Signed inThe after-purchase page signs the extension in to that account at once.
Signed outThe licence attaches to the account that later verifies the address. The page's one button is the sign-in, with the address the purchase was made with.

When the device unlocks, onPaid fires in the background. On any other device, the buyer signs in with the same address: Already bought it? Sign in sits under the plans.

A licence also has a key, which Toolaby mails to the buyer as the record of the purchase. The buyer never types it into the extension: signing in with the address that bought unlocks every device. Toolaby stores only a hash of the key, so a lost key is replaced, not sent again.

If the extension still shows the Free plan after a purchase, the buyer signs in once with the address that bought. See Troubleshoot an extension.

4. Check the licence in your code

Gate the action you sell in the background, and return the permit when it refuses:

background.js
import { toolaby } from './toolaby.js';

toolaby.startBackground({
  handlers: {
    async exportAll() {
      const permit = await toolaby.gate({ feature: 'export' });
      if (!permit.allowed) return { permit };
      return { ok: true, file: await buildExport() };
    },
  },
});

When the licence holds the feature, gate() answers { allowed: true, isPremium: true, unlimited: true } with no request. The answer comes from an entitlement token: a JWT signed with your workspace's own key, bound to the device, valid for 15 minutes. The client verifies it with the public key inside the tool key.

In your popup, getUser() reads the device's state with no request. Call toolaby.refresh() once when the popup opens, so a purchase made elsewhere is known first:

popup.js
const user = await toolaby.refresh();
if (user.via === 'licence') planLabel.textContent = `Lifetime licence for ${user.email}`;
FieldFor a licence
paidtrue
via'licence'
planThe plan held, as Pricing defines it.
paidAtWhen the entitlement began.
emailThe signed-in account's address, else the licence's.
licenseKey{ present: true } when a licence key was activated on this device.

has('export') answers from the device, for a lock or a badge. It stops nothing by itself: the action still calls gate(), since code on the buyer's machine can be changed.

5. Set the device limit

The plan's Devices field sets how many devices one buyer may use at once. Every device holding the licence counts once, by its key or by signing in.

  • A further device is refused, with the reason, until the buyer signs one out or removes one on their account page.
  • The limit is checked again each time a device renews its access. Devices unlocked by the key keep their places; signed-in ones fill the rest in the order they signed in.
  • A device past the limit keeps its sign-in, with the Free plan, until another is signed out.
  • Licences already sold keep the number they were sold with.

On a device past the limit, user.deviceLimit is { allowed, held }, and gate() answers as the Free plan does. showPaywall() then shows In use on 2 devices already and the way to the account page.

On the account page, under Devices, a signed-in device has Sign out and one unlocked by a key has Remove. Either frees its place on the licence. On the customer's page under Customers, you can Release a device a key was typed into, or Sign out a signed-in one.

6. Sell seats to a team

A lifetime plan is sold per seat unless you switch it off. The checkout page asks For me or For a team. For a team, it takes a seat count from 2 to 100, and the button reads Buy 5 seats · €95.

The buyer receives a team licence: one purchase holding the seats. It has no key and unlocks nothing by itself. The buyer hands the seats out on their account page, under the tool:

  • Give sends a seat to an email address. The holder receives a licence of their own, with a key mailed as A seat for you. It attaches to their account when they sign in with that address.
  • Take back revokes the holder's licence and frees the seat.

To the extension, Toolaby and the dashboard, a seat is a licence. The buyer needs a seat only to use the tool themselves.

7. Refund or revoke a licence

On the customer's page under Customers:

ActionEffect
RefundRefunds the whole payment and revokes the licence at once. Stripe keeps its processing fee, and Toolaby keeps its own. Refunding a team licence revokes every seat.
RevokeAccess ends at the next check.
Resend keyMails a new key. The old key keeps working on the devices it activated, until twenty newer keys have replaced it, and activates no new one.

A refund of the whole payment sends payment.refunded. A partial refund, made on Stripe, leaves the licence and its seats as they are. A dispute suspends the licence while open. Won, the licence is active again; lost, it is revoked.

The extension learns of a refund or a revoke at its next check. Every 15 minutes, the client asks for a fresh entitlement token, issued only while the licence is live. When Toolaby is unreachable, the last token is honoured for 24 hours.

Next steps

On this page