To check whether a user has paid, your extension needs a record of the payment it can trust, kept by a server that saw the payment. With Toolaby, toolaby.getUser() answers on the device with user.paid, the plan, the subscription's status and the trial, and toolaby.gate() checks again right before a paid action. The answer comes from a token Toolaby signs for that device, which the extension verifies itself, so most checks need no request.
This guide reads the user's status, checks before the paid action, checks a single feature, keeps the answer current and tests it. For ExtensionPay's equivalent, extpay.getUser() and user.paid, see Move from ExtensionPay.
Before you begin
- An extension wired to a tool:
toolaby.jsandtoolaby.config.jsbeside your code. See the Quickstart. - A plan under the tool's Pricing: once, monthly or yearly.
1. Read the user's status
import { toolaby } from './toolaby.js';
const user = await toolaby.getUser();
if (user.paid) {
showPro(user.plan?.name);
} else {
showUpgrade();
}getUser() makes no request: it answers what the device knows now. The fields you need most:
| Field | Tells you |
|---|---|
paid | Whether the user holds the tool: a subscription, a licence, a trial or access you gave. |
via | How: subscription, licence, trial, grant or account. null when unpaid. |
plan | The plan held, as Pricing defines it. |
subscription | Stripe's status (active, trialing, past_due…), when the period ends, and whether it is set to cancel then. |
trial | When a trial ends, and whether it is running. |
features | The feature keys this device holds. null means everything. |
usage | The free uses left on the Free plan. |
User lists every field.
2. Check before the paid action
Reading user.paid decides what you draw. Before the work you charge for, ask with gate(), which checks the signed token and counts a free use where the Free plan counts them:
import { toolaby } from './toolaby.js';
toolaby.startBackground({
handlers: {
async exportAll({ tableIds }) {
const permit = await toolaby.gate();
if (!permit.allowed) return { permit };
return { ok: true, file: await buildExport(tableIds) };
},
},
});The page that asked shows the paywall when the permit refuses:
const r = await chrome.runtime.sendMessage({ action: 'exportAll', tableIds });
if (r.permit) return toolaby.showPaywall(r.permit);
save(r.file);A paid user is allowed with no request. A free use makes one, to count it. See gate() for every answer.
3. Check one feature
Where plans unlock different features, ask about the feature by its key from Pricing:
const locked = !(await toolaby.has('export'));
exportButton.classList.toggle('locked', locked);has() is for display. At the action, gate({ feature: 'export' }) enforces it, and its refusal names the plans that sell the feature, so showPaywall() offers only those.
4. Keep the answer current
- Call
toolaby.refresh()once when your popup opens. It asks Toolaby, at most every five minutes on Live, and answers asgetUser()does. - In the background,
toolaby.onPaidfires when a device goes from unpaid to paid, andtoolaby.onChangeon every change ofuser.paid. - A purchase, a sign-in and a licence key are asked about when they happen, not at the next refresh.
A cancelled subscription keeps paid until its period ends. A full refund of a one-time payment, or a lost dispute, ends it at the extension's next check.
5. Offline
The token carries offlineUntil: how long the device is honoured without reaching Toolaby. Offline, getUser() and gate() answer from it, with offline: true on the permit. A new install that has never heard from Toolaby is given nothing on a guess.
6. Test it
On Test nothing is charged. Make a buyer of your own with the plan, then check the extension as that buyer:
npx -y toolaby@latest test-unlock <tool-id> --plan <plan-id>
npx -y toolaby@latest check --browser --as <plan-id>Or buy the plan in the paywall with the test card 4242 4242 4242 4242, any future date and any CVC.
Common questions
Can a user fake being paid?
Code on the user's machine can be changed, so no check inside an extension is impossible to patch. The answer the extension trusts is a token Toolaby signs for one device, so it cannot be forged or copied to another. Keep the paid work behind gate() in your background.
How do I check that a subscription is still active?
Read user.subscription.status and user.subscription.periodEnd, or simply user.paid, which stays true through a trial and until a cancelled period ends.
Does checking cost a request every time?
No. getUser() and has() never make one. gate() makes one only to count a free use, or to ask about a feature the device's token does not list.