SDK for using https://app.tier.run in Node.js applications
npm install @tier.run/sdk
This is the SDK that can may be used to integrate Tier into your application. More details on the general concepts used by Tier may be found at https://app.tier.run/docs/.
tier: usage: tier [options] <command>
Options:
--api-url=<url> Set the tier API server base url.
Defaults to TIER_API_URL env, or https://api.tier.run/
--web-url=<url> Set the tier web server base url to use for login.
Defaults to TIER_WEB_URL env, or https://app.tier.run/
--key=<token> Specify the auth token for Tier to use.
Tokens can be generated manually by visiting
<https://app.tier.run/app/account/tokens>,
minted for a project by running 'tier login',
or set in the environment variable TIER_KEY.
--auth-type=<basic|bearer>
Tell Tier to use the specified auth type. Default: basic
--debug -v Turn debug logging on
--no-debug -V Turn debug logging off
--help -h Show this usage screen.
Commands:
login Log in the CLI by authorizing in your web browser.
Tokens are stored scoped to each project working directory
and API server, and will not be active outside of that
environment.
logout Remove a login token from your system.
projectDir Show the current project directory that login tokens are
scoped to.
push <jsonfile> Push the pricing model defined in <jsonfile> to Tier.
pull Show the current model.
whoami Get the organization ID associated with the current login.
fetch <path> Make an arbitrary request to a Tier API endpoint.
import { TierClient } from '@tier.run/sdk'
// or: const { TierClient } = require('@tier.run/sdk')
const tier = new TierClient()
The default options are likely fine, with the exception of
tierKey
, which is of course unique.
tierKey
: The API token minted on tier.run. Defaults to the value of theTIER_KEY
environment variable.authStore
: An object implementingget(cwd, apiUrl)
,set(cwd, apiUrl, token)
, anddelete(cwd, apiUrl)
methods. Default implementation stores tokens on the filesystem in~/.config/tier/tokens
, owned by the current user, with mode0o600
.authType
: Either'basic'
or'bearer'
. Default is'basic'
. In most cases, there is no reason to change this.apiUrl
: The url to hit for most API requests. Default:https://api.tier.run
.webUrl
: The url to hit for fetches to thetier.run
website. Default:https://app.tier.run
Load a Tier client with a token found in the authStore
(see
options above), based on the specified apiUrl and current project
directory.
Return the URL to the tier.js
browser API. Include this
tier.js
script on every page of your website.
Get the options to pass to the client-side call to
Tier.paymentForm()
.
Posts to /api/v1/stripe/options
Attempt to attach a payment method from a resolved stripe
SetupIntent
ID.
Call this on the server-side when Stripe redirects the user back
to the page where you called Tier.paymentForm()
, passing in the
string provided on the url search params.
The returned promise will resolve with the {status: "succeeded"}
SetupIntent object if the payment method was
attached.
If the SetupIntent requires additional attention, then the Promise will fail with an object indicating what needs to be done.
For example:
import { isTierError, TierClient } from '@tier.run/sdk'
const tier = TierClient.fromEnv()
// account settings page
app.get('/account', async (req, res) => {
const org = await lookupCurrentLoggedInUserSomehow(req)
const u = new URL(req.url, 'http://x')
if (u.searchParams.has('setup_intent')) {
// returning from Stripe payment method setup.
try {
await tier.stripeSetup(`org:${org}`, u.searchParams.get('setup_intent'))
// payment method is now attached!
} catch (er) {
if (isTierError(er)) {
// something bad happened!
// handle this like any other server error,
// log it, show error page, etc.
return serve5xxStatusPage(res, er)
} else {
// the user has to do something
return redirect(res, er.redirect_to_url.url, 303)
}
}
}
const stripeOptions = await tier.stripeOptions(`org:${org}`)
showAccountSetupPage(res)
})
Add a phase to the specified org's schedule. If not specified,
the effective
date is the current time.
Posts to /api/v1/append
Usage and limit amounts are based on best available data at the moment reported. Amounts reported by the Tier API are eventually consistent, usually within a few ms.
Check that a user has access to a feature (ie, it's included in
their plan, and they are not over their limit) using
tier.can()
. When a feature is successfully consumed, call
tier.report()
to tell Tier about it.
Our customer is attempting to consume 1 of the foo
feature. We
should allow if they're not over their limit, and report it once
the feature is delivered.
if (await tier.can('org:acme', 'feature:foo')) {
consumeOneFoo('acme')
await tier.report('org:acme', 'feature:foo')
} else {
// suggest they buy a bigger plan, maybe?
showUpgradePlanUX('acme')
}
In this case, the user might have remaining usage allowed by their plan, but not enough to do what we're trying.
if (await tier.can('org:acme', 'feature:foo', 10)) {
consumeTenFoos('acme')
await tier.report('org:acme', 'feature:foo', 10)
} else {
showSorryYouNeedToUpgradeMessage('acme')
}
Sometimes a "feature" is not a single function call. We might kick off a series of events or chain of messages, and only want to charge the user if the entire process succeeds.
if (await tier.can('org:acme', 'feature:foo')) {
// ok they can start it
myAPI.addToMessageBrokerSystem('acme', 'foo')
}
// elsewhere in my application somewhere, maybe another
// machine, some time later, who knows
const handleFinalStep = async org => {
// ok it worked!
// do something
await tier.report(`org:${org}`, 'feature:foo')
}
Note that this highlights a race condition! If the user can initiate many such processes, they may go over their limit. (Maybe that's what you want.)
In this example, the feature is again a chain of messages being passed between systems, but since it can take a while to complete, we don't want to let the user go over their limit. We also don't want to charge them if the process fails!
if (await tier.can('org:acme', 'feature:foo')) {
myAPI.addToMessageBrokerSystem('acme', 'foo')
// report the usage right away
await tier.report('org:acme', 'feature:foo')
}
const handleError = async org => {
// oh no, it failed!
// just report negative usage to "refund" the usage
await tier.report(`org:${org}`, 'feature:foo', -1)
}
const handleFinalStep = async org => {
// don't have to tell tier about it, because we already did.
}
Reports count
units of feature usage for the specified org.
Promise resolves when data has been accepted by Tier, rejects if there is an error report.
Returns true if the org is allowed to consume the feature in the
amount specified (default: 1) as of the now
date specified.
No side effects, does not increment usage.
if (await tier.can(org, feature, 10)) {
// org is allowed to use 10 of feature
// we haven't actually consumed them yet, however.
} else {
// org is not allowed to use 10 of feature
// they might be allowed to use fewer than that, though
}
Inverse of can()
.
Returns true if the org is not allowed to consume the feature
in the amount specified (default: 1) as of the now
date
specified.
No side effects, does not increment usage.
Look up various information about the org's associated customer as it exists in Stripe.
Included fields:
default_payment_method
The payment method which will be used to invoice the customer.delinquent
Booleanphone
,email
Customer contact informationdiscount
A discount applied to the customer, in cents.live_mode
Boolean. Whether the customer was created in test mode or live mode.url
A deep link into the Stripe dashboard for this customer.
Look up the org's schedule. This includes a phases
list of
Phase
objects, and a current
number indicating in the phases
array to the currently active phase.
Makes GET request to /api/v1/schedule
Fetch the user's current schedule, and return the name of the plan that is currently active.
Makes a request to the Tier API service to verify that it is reachable, and that the client's API token is valid.
Push the pricing model definition to Tier.
Pull the full pricing model from Tier.
Pull a pricing page data object from Tier.
If a name is provided, then the named pricing page settings will be fetched. Otherwise, it will pull the default pricing page (ie, the latest lexically-sorted version of each plan name in the model).
Any error encountered by Tier, whether a network failure or
non-2xx response code, will raise a TierError
.
This is similar to a regular Error object, but with the following data attached:
tierError.request
Data about the request being madetierError.request.method
tierError.request.url
stringtierError.request.headers
object, with anyauthorization
header redacted, so it is safe to logtierError.request.body
(optional)
tierError.response
Data about the response. Note that this will be missing if a response was not successfully received.tierError.response.status
numbertierError.response.headers
objecttierError.response.body
string
These methods are used internally by the Tier SDK, you probably don't need to call them yourself.
async fetchOK<T>(path: string, options: RequestInit): Promise<T>
async getOK<T>(path: string): Promise<T>
async postFormOK<T>(path: string, body: { [key: string]: any }): Promise<T>
async postOK<T>(path: string, body: { [key: string]: any }): Promise<T>
These each make a request to the Tier API, and verify that the
response is a 2xx
status code.