{"id":15118509,"url":"https://github.com/tierrun/node-sdk","last_synced_at":"2025-09-28T00:32:46.177Z","repository":{"id":62667376,"uuid":"554388803","full_name":"tierrun/node-sdk","owner":"tierrun","description":"The easiest way to add pricing to your SaaS. Get billing over with.","archived":false,"fork":false,"pushed_at":"2023-08-29T16:23:29.000Z","size":868,"stargazers_count":21,"open_issues_count":5,"forks_count":5,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-14T14:55:23.301Z","etag":null,"topics":["javascript","nodejs","typescript"],"latest_commit_sha":null,"homepage":"https://tierrun.github.io/node-sdk/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tierrun.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-19T18:24:32.000Z","updated_at":"2025-01-09T05:23:00.000Z","dependencies_parsed_at":"2024-06-19T04:11:38.052Z","dependency_job_id":null,"html_url":"https://github.com/tierrun/node-sdk","commit_stats":{"total_commits":314,"total_committers":1,"mean_commits":314.0,"dds":0.0,"last_synced_commit":"96e96fe99e96064fa1abe884bf3e695f7475a641"},"previous_names":[],"tags_count":107,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tierrun%2Fnode-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tierrun%2Fnode-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tierrun%2Fnode-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tierrun%2Fnode-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tierrun","download_url":"https://codeload.github.com/tierrun/node-sdk/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234475315,"owners_count":18839358,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","nodejs","typescript"],"created_at":"2024-09-26T01:46:18.471Z","updated_at":"2025-09-28T00:32:40.897Z","avatar_url":"https://github.com/tierrun.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# tier Node SDK\n\nSDK for using https://tier.run in Node.js applications\n\n[Generated typedocs](https://tierrun.github.io/node-sdk/)\n\n## INSTALLING\n\nFirst, install [the Tier binary](https://tier.run/docs/install).\n\nRun `tier connect` to authorize Tier to your Stripe account, or\nprovide a `STRIPE_API_KEY` environment variable.\n\n```bash\nnpm install tier\n```\n\n## Overview\n\nThis is the SDK that can may be used to integrate Tier into your\napplication. More details on the general concepts used by Tier\nmay be found at \u003chttps://www.tier.run/docs/\u003e.\n\nThe SDK works by talking to an instance of the Tier binary\nrunning as a sidecar, using `tier serve`.\n\n## USAGE\n\nNote: support for using the Tier JavaScript client in web\nbrowsers is **EXPERIMENTAL**. Whatever you do, please don't put\nyour private Stripe keys where web browsers can see them.\n\nThis module exports both a zero-dependency client class, suitable\nfor use in non-Node.js environments such as edge workers and\nDeno, as well as a simple functional SDK that manages spinning up\nthe sidecar automatically.\n\n### Automatic Mode, Remote API Service\n\nThis works on any server-side contexts where you can set\nenvironment variables.\n\nSet the `TIER_BASE_URL` and `TIER_API_KEY` environment variables\nto the URL to the remote Tier service and the API key for the\nservice.\n\nImport the main module, and use the methods provided.\n\n```ts\n// Set process.env.TIER_BASE_URL and process.env.TIER_API_KEY\n\n// hybrid module, either form works\nimport tier from 'tier'\n// or\nconst { default: tier } = require('tier')\n// that's it, it'll talk to the API server you set\n```\n\n### Automatic Mode, Sidecar on Localhost\n\nThis works if you have Tier installed locally.\n\nDon't set any environment variables, just import the main module,\nand use the API methods provided.\n\nA Tier API sidecar process will be started on the first API\nmethod call. It will listen on a port determined by the process\nID, and automatically shut down when the process terminates.\n\nTo operate on live Stripe data (that is, to start the sidecar in\nlive mode), set `TIER_LIVE=1` in the environment.\n\n```ts\n// hybrid module, either form works\nimport tier from 'tier'\n// or\nconst { default: tier } = require('tier')\n// that's it, it'll start the sidecar as needed\n```\n\nNote that you must have previously run [`tier\nconnect`](https://tier.run/docs/cli/connect) to authorize Tier to\naccess your Stripe account, or set the `STRIPE_API_KEY`\nenvironment variable.\n\nThis requires that Node's `child_process` module is available, so\ndoes not work with environments that do not have access to it. If\n`fetch` is not available, then the optional `node-fetch`\ndependency will be loaded as a polyfill.\n\nIf you want a client instance that is automatically configured by\nthe environment settings, with an on-demand started tier API\nsidecar, you can call:\n\n```ts\nconst client = await tier.fromEnv()\n```\n\n### Custom Client Custom Mode\n\nTo use Tier in an environment where `child_process.spawn` is not\navailable, or where you cannot set environment variables, you\ncan load and instantiate the client yourself:\n\n```ts\n// hybrid module, either works\nimport { Tier } from 'tier/client'\n// or\nconst { Tier } = require('tier/client')\n// or, if using deno or CFW and you don't have import maps:\nimport { Tier } from 'https://unpkg.com/tier@^5/dist/mjs/client.js'\n\nconst tier = new Tier({\n  // Required: the base url to the running `tier serve` instance\n  baseURL: tierAPIServiceURL,\n\n  // optional, defaults to '', set an API key to access the service\n  apiKey: tierAPIKey,\n\n  // optional, if set will catch all API errors.\n  // Note that this makes the promises from API calls resolve,\n  // unless the onError function re-throws!  Use with caution!\n  //\n  // onError: (er: TierError) =\u003e {\n  //   console.error(er)\n  //   throw er\n  // }\n\n  // Optional, only needed if fetch global is not available\n  // fetchImpl: myFetchImplementation,\n\n  // Optional, defaults to false, will make a lot of\n  // console.error() calls.\n  // debug: false\n\n  // Optional, can be used to terminate all actions by this client\n  // signal: myAbortSignal\n})\n```\n\nThen call API methods from the tier instance.\n\nThis is how you can use the Tier SDK from Cloudflare Workers,\nDeno, and other non-Node JavaScript environments.\n\n### Error Handling\n\nAll methods will raise a `TierError` object if there's a non-2xx\nresponse from the Tier sidecar, or if a response is not valid\nJSON.\n\nThis Error subclass contains the following fields:\n\n- `status` - number, the HTTP status code received\n- `code` - Short string representation of the error, something like `not_found`\n- `message` - Human-readable explanation of the error.\n- `path` - The API path being accessed\n- `requestData` - The data sent to the API path (query string for\n  GETs, request body for POSTs.)\n- `responseData` - The response data returned by the API\n  endpoint.\n\n## API METHODS\n\n### `subscribe(org, plan, { info, trialDays, paymentMethodID })`\n\nSubscribe an org to the specified plan effective immediately.\n\nPlan may be either a versioned plan name (for example,\n`plan:bar@1`), or \"feature plan\" name (for example\n`feature:foo@plan:bar@1`), or an array of versioned plan names\nand feature plan names.\n\nIf no effective date is provided, then the plan is effective\nimmediately.\n\nIf `info` is provided, it updates the org with info in the same\nway as calling `updateOrg(org, info)`.\n\nIf `trialDays` is a number greater than 0, then a trial phase\nwill be prepended with the same features, and the effective date\nwill on the non-trial phase will be delayed until the end of the\ntrial period.\n\nIf a string `paymentMethodID` is specified, then it will be used\nas the billing method for the subscription.\n\n### `schedule(org, phases, { info, paymentMethodID })`\n\nCreate a subscription schedule phase for each of the sets of\nplans specified in the `phases` array.\n\nEach item in `phases` must be an object containing:\n\n- `features` Array of versioned plan names (for example,\n  `plan:bar@1`), or \"feature plan\" names (for example,\n  `feature:foo@plan:bar@1`)\n- `effective` Optional effective date.\n- `trial` Optional boolean indicating whether this is a trial or\n  an actual billed phase, default `false`\n\nIf no effective date is provided, then the phase takes effect\nimmediately. Note that the first phase in the list MUST NOT\nhave an effective date, and start immediately.\n\nIf `info` is provided, it updates the org with info in the same\nway as calling `updateOrg(org, info)`.\n\nIf a string `paymentMethodID` is specified, then it will be used\nas the billing method for the subscription.\n\n### `checkout(org, successUrl, { cancelUrl, features, trialDays, requireBillingAddress, tax })`\n\nGenerate a Stripe Checkout flow, and return a `{ url }` object.\nRedirect the user to that `url` to have them complete the\ncheckout flow. Stripe will redirect them back to the\n`successUrl` when the flow is completed successfully.\n\nOptional parameters:\n\n- `cancelUrl` if provided, then the user will be redirected to\n  the supplied url if they cancel the process.\n- `features` Either a versioned plan name (for example,\n  `plan:bar@1`), or \"feature plan\" name (for example\n  `feature:foo@plan:bar@1`), or an array of versioned plan names\n  and feature plan names. If provided, then the user will be\n  subscribed to the relevant plan(s) once they complete the\n  Checkout flow. If not provided, then the Checkout flow will\n  only gather customer information.\n- `trialDays` Number of days to put the user on a \"trial plan\",\n  where they are not charged for any usage. Only allowed when\n  `features` is provided.\n- `requireBillingAddress` If set to `true`, then the user will be\n  required to add a billing address to complete the checkout\n  flow.\n- `tax` Configure automatic tax collection\n\n### `updateOrg(org, info)`\n\nUpdate the specified org with the supplied information.\n\n`info` is an object containing the following fields:\n\n- `email` string\n- `name` string\n- `description` string\n- `phone` string\n- `metadata` Object with any arbitrary keys and `string` values\n- `invoiceSettings` An object which may contain a\n  `defaultPaymentMethod` string. If set, it will be attached as\n  the org's default invoice payment method.\n\nNote that any string fields that are missing will result in that\ndata being removed from the org's Customer record in Stripe, as\nif `''` was specified.\n\n### `cancel(org)`\n\nImmediately cancels all current and pending subscriptions for the\nspecified org.\n\n### `lookupLimits(org)`\n\nRetrieve the usage data and limits for an org.\n\n```json\n{\n  \"org\": \"org:user\",\n  \"usage\": [\n    {\n      \"feature\": \"feature:storage\",\n      \"used\": 341,\n      \"limit\": 10000\n    },\n    {\n      \"feature\": \"feature:transfer\",\n      \"used\": 234213,\n      \"limit\": 10000\n    }\n  ]\n}\n```\n\n### `lookupLimit(org, feature)`\n\nRetrieve the usage and limit data for an org and single feature.\n\n```json\n{\n  \"feature\": \"feature:storage\",\n  \"used\": 341,\n  \"limit\": 10000\n}\n```\n\nIf the org does not have access to the feature, then an object is\nreturned with `usage` and `limit` set to `0`.\n\n```json\n{\n  \"feature\": \"feature:noaccess\",\n  \"used\": 0,\n  \"limit\": 0\n}\n```\n\n### `report(org, feature, [n = 1], [options = {}])`\n\nReport usage of a feature by an org.\n\nThe optional `n` parameter indicates the number of units of the\nfeature that were consumed.\n\nOptions object may contain the following fields:\n\n- `at` Date object indicating when the usage took place.\n- `clobber` boolean indicating that the usage amount should\n  override any previously reported usage of the feature for the\n  current subscription phase.\n\n### `can(org, feature)`\n\n`can` is a convenience function for checking if an org has used\nmore of a feature than they are entitled to and optionally\nreporting usage post check and consumption.\n\nIf reporting consumption is not required, it can be used in the\nform:\n\n```js\nconst answer = await tier.can('org:acme', 'feature:convert')\nif (answer.ok) {\n  //...\n}\n```\n\nreporting usage post consumption looks like:\n\n```js\nconst answer = await tier.can('org:acme', 'feature:convert')\nif (!answer.ok) {\n  return ''\n}\nanswer.report().catch(er =\u003e {\n  // error occurred reporting usage, log or handle it here\n})\n\n// but don't wait to deliver the feature\nreturn convert(temp)\n```\n\n### `whois(org)`\n\nRetrieve the Stripe Customer ID for an org.\n\n```json\n{\n  \"org\": \"org:user\",\n  \"stripe_id\": \"cus_v49o7xMpZaMbzg\"\n}\n```\n\n### `lookupOrg(org)`\n\nRetrieve the full org info, with `stripe_id`, along with email,\nname, description, phone, metadata, and invoiceSettings.\n\n### `lookupPaymentMethods(org)`\n\nReturn a `PaymentMethodsResponse` object, containing the org name\nand an array of their available payment method IDs.\n\n```json\n{\n  \"org\": \"org:acme\",\n  \"methods\": [\"pm_card_3h39ehaiweheawfhiawhfasi\"]\n}\n```\n\nIf the org does not have any payment methods, then the returned\nobject will contain an empty array.\n\n### `whoami()`\n\nRetrieve information about the current logged in Stripe account.\n\n### `lookupPhase(org)`\n\nRetrieve the current schedule phase for the org. This provides a\nlist of the features and plans that the org is currently\nsubscribed to, which can be useful information when creating a\nuser interface for upgrading/downgrading pricing plans.\n\n```json\n{\n  \"effective\": \"2022-10-13T16:52:11-07:00\",\n  \"features\": [\"feature:storage@plan:free@1\", \"feature:transfer@plan:free@1\"],\n  \"plans\": [\"plan:free@1\"]\n}\n```\n\nNote: This should **not** be used for checking entitlements and\nfeature gating. Instead, use the `Tier.lookupLimit()` method and check\nthe limit and usage for the feature in question.\n\nFor example:\n\n```\n// Do not do this!  You will regret it!\nconst phase = await Tier.lookupPhase(`org:${customerID}`)\nif (phase.plans.some(plan =\u003e plan.startsWith('plan:pro')) {\n  showSpecialFeature()\n}\n```\n\nInstead, do this:\n\n```js\nconst usage = await Tier.lookupLimit(`org:${customerID}`, 'feature:special')\nif (usage.limit \u003c usage.used) {\n  showSpecialFeature()\n}\n```\n\n### `pull()`\n\nFetches the pricing model from Stripe.\n\n### `pullLatest()`\n\n**Experimental**\n\nFetches the pricing model from Stripe, but only shows the plans\nwith the highest versions (lexically sorted). This can be useful\nin building pricing pages in your application.\n\nPlan versions are sorted numerically if they are decimal\nintegers, or lexically in the `en` locale otherwise.\n\nSo, for example, the plan version `20test` will be considered\n\"lower\" than `9test`, because the non-numeric string causes it to\nbe lexically sorted. But the plan version `20` sill be\nconsidered \"higher\" than the plan version `9`, because both are\nstrictly numeric.\n\nFor example, if `Tier.pull()` returns this:\n\n```json\n{\n  \"plans\": {\n    \"plan:mixednum@9test\": {},\n    \"plan:mixednum@9999999\": {},\n    \"plan:mixednum@0test\": {},\n    \"plan:mixednum@1000\": {},\n    \"plan:alpha@dog\": {},\n    \"plan:alpha@cat\": {},\n    \"plan:longnum@1000\": {},\n    \"plan:longnum@99\": {},\n    \"plan:foo@1\": {},\n    \"plan:foo@0\": {},\n    \"plan:bar@7\": {},\n    \"plan:foo@2\": {},\n    \"plan:bar@0\": {}\n  }\n}\n```\n\nthen `Tier.pullLatest()` will return:\n\n```js\n{\n  plans: {\n    // these are all sorted numerically, because the versions\n    // are simple positive integers without any leading 0\n    // characters.\n    'plan:foo@2': {},\n    'plan:bar@7': {},\n    'plan:longnum@1000': {},\n    // 'dog' and 'cat' sorted lexically, 'd' \u003e 'c'\n    'plan:alpha@dog': {},\n    // these are sorted lexically, because even though SOME of\n    // are strictly numeric, this one is not.\n    'plan:mixednum@9test': {}\n  }\n}\n```\n\n### `push(model)`\n\nCreates the `Product` and `Price` objects in Stripe corresponding\nto the supplied pricing Model (as would be found in a\n[`pricing.json` file](https://tier.run/docs/pricing.json)).\n\nReturns an object detailing which features were created, and\nwhich either had errors or already existed. Note that a\nsuccessful response from this method does not mean that _all_ of\nthe features were created (since, for example, some may already\nexist), only that _some_ of them were.\n\n### `async withClock(name: string, present?: Date)`\n\nCreate a test clock with the given name, and return a `Tier`\nclient configured to use that clock.\n\n### `async advance(present: Date)`\n\nAdvance the clock to the specified date.\n\nRejects if the client was not created by `tier.withClock()`.\n\n### Class: `Answer`\n\n`Answer` is the type of object returned by `tier.can()`.\n\n#### `answer.ok`\n\n`ok` reports if the program should proceed with a user request or\nnot. To prevent total failure if `can()` needed to reach the sidecar\nand was unable to, `ok` will fail optimistically and report true.\nIf the opposite is desired, clients can check `err`.\n\n#### `answer.err`\n\nAny error encountered fetching the `Usage` record for the org and\nfeature.\n\n#### `answer.report([n = 1])`\n\nReport the usage in the amount specified, default `1`.\n\n#### `answer.limit`\n\nNumber specifying the limit for the feature usage.\n\n#### `answer.used`\n\nNumber specifying the amount of the feature that the org has\nconsumed.\n\n#### `answer.remaining`\n\nNumber specifying the amount of feature consumption that is\nremaining.\n\n### Class: `TierError`\n\n`TierError` is a subclass of `Error` which is raised whenever\nTier encounters a problem fetching data.\n\n- `message`: the `message` field from the sidear, if present, or\n  `\"Tier request failed\"`\n- `path`: the path on the sidecar API that was requested\n- `requestData`: the data that was sent to the sidecar\n- `status`: the HTTP response status code from the sidecar, if a\n  response was returned\n- `code`: response error code returned by the sidecar, if present\n- `responseData`: the raw HTTP body sent by the sidecar\n- `cause`: If triggered by an underlying system or JSON.parse\n  error, it will be provided here.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftierrun%2Fnode-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftierrun%2Fnode-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftierrun%2Fnode-sdk/lists"}