UNPKG

@unitpay/sdk

Version:

Official UnitPay SDK for TypeScript — Node, browser, and edge. Portal sessions, subscriptions, invoices, credits, usage tracking.

197 lines (147 loc) 5.89 kB
# @unitpay/sdk Official UnitPay **server SDK** for JavaScriptNode, edge, and any runtime with `fetch`. Authenticates with a **secret key** (`upay_sk_`) for full server-to-server access. > Building a customer-facing UI? Use [`@unitpay/react`](../react) instead — it holds a > customer-scoped **portal token** inside `<UnitPayProvider>`. Portal tokens are not a > valid option for this server SDK. ```bash npm install @unitpay/sdk # or bun add @unitpay/sdk ``` ## Quick start ```ts import { UnitPay } from '@unitpay/sdk' const unitpay = new UnitPay({ apiKey: process.env.UNITPAY_SK! }) // apiKey falls back to the UNITPAY_API_KEY env var if omitted. // 1. Identify a customer const customer = await unitpay.customers.create({ name: 'Acme, Inc.', email: 'billing@acme.com', externalId: 'acme', // your internal id }) // 2. Gate a feature (the entitlement hot-path) const { access, remaining } = await unitpay.check({ customerId: customer.id, featureSlug: 'api_calls', }) if (!access) throw new Error('Upgrade required') // 3. Record usage await unitpay.track({ customerId: customer.id, eventName: 'api_call', quantity: 1 }) ``` ## Resources All methods require a secret key. | Resource | Methods | |---|---| | _client_ | `check(params)` · `track(event \| event[])` · `verifyWebhook` _(static)_ | | `customers` | `create` · `get` · `list` · `update` · `archive` · `check` · `checkBatch` · `entitlements` · `listInvoices` · `listPaymentMethods` · `listCreditAccounts` | | `subscriptions` | `create` · `get` · `list` · `change` · `cancel` · `uncancel` | | `portalSessions` | `create` · `revoke` | | `usage` | `track` | To list a customer's subscriptions, use `subscriptions.list({ customerId })`. ### Entitlement checks ```ts // Flat check — most common. const res = await unitpay.check({ customerId, featureSlug: 'seats', requestedUsage: 3 }) // res.access, res.remaining, res.limit, res.feature.type, … // Customer-scoped, and a batch variant for rendering an access matrix. await unitpay.customers.check(customerId, { featureSlug: 'seats' }) const { results } = await unitpay.customers.checkBatch(customerId, ['seats', 'api_calls']) ``` ### Subscriptions & payment outcomes `create` and `change` return a **`PaymentOutcome`** — discriminate on `kind`: ```ts const outcome = await unitpay.subscriptions.create({ customerId, planId }) switch (outcome.kind) { case 'created_no_charge': // free plan — nothing to collect case 'charged_inline': // paid off-session with a card on file break case 'requires_form': // no card on file — collect payment // Ship outcome.client to the browser and mount <PaymentForm> (@unitpay/react) break case 'invoice_sent': // hosted invoice emailed (send_invoice) break } ``` ### Customer identify (no upsert) There is no upsert endpoint. `create` on a duplicate `externalId` throws a `ConflictError`. For an idempotent identify, catch it and look up: ```ts import { ConflictError } from '@unitpay/sdk' async function identify(params) { try { return await unitpay.customers.create(params) } catch (err) { if (err instanceof ConflictError && params.externalId) { const { data } = await unitpay.customers.list({ externalId: params.externalId }) if (data[0]) return data[0] } throw err } } ``` ## Usage tracking Send one event, or an array of up to 100 in a single call: ```ts await unitpay.track({ customerId, eventName: 'api_call', quantity: 1 }) await unitpay.track([ { customerId, eventName: 'api_call', quantity: 5 }, { customerId, eventName: 'tokens', quantity: 1200 }, ]) ``` Pass an `idempotencyKey` per event to make retries safe. ## Pagination `list*` methods return a thenable, async-iterable page: ```ts const page = await unitpay.customers.list() // { data, hasMore, nextCursor } for await (const customer of unitpay.customers.list()) { // auto-follows cursors console.log(customer.id) } const all = await unitpay.customers.list().toArray() ``` ## Errors Typed hierarchy — switch on class or on `.code`: ```ts import { UnitPay, NotFoundError, RateLimitError, ApiError } from '@unitpay/sdk' try { await unitpay.customers.get('cus_bad') } catch (err) { if (err instanceof NotFoundError) { /* … */ } if (err instanceof RateLimitError) { console.log('retry after', err.retryAfter) } if (err instanceof ApiError) { console.log(err.code, err.requestId) } } ``` Exported: `ApiError`, `AuthenticationError`, `BadRequestError`, `ConflictError`, `ConnectionError`, `InternalServerError`, `NotFoundError`, `RateLimitError`, `TimeoutError`, `ValidationError`. ## Behavior - **Retries** on 429 / 409 / 5xx / transient network errors. Exponential backoff (500ms × 2ⁿ), jittered, capped 10s. Respects `Retry-After` on 429. - **Idempotency keys** auto-generated on every POST/DELETE. Override with `{ idempotencyKey }` in request options. - **Timeout** 30s default. Override per-client (`timeout: 10_000`) or per-request. - **AbortController** pass `{ signal }` to cancel. - **Events** `unitpay.on('request', )` / `on('response', )` for tracing. - **Custom fetch** inject via `{ fetch: myFetch }` (undici in Node, MSW in tests, etc.). ## Configuration ```ts new UnitPay({ apiKey: process.env.UNITPAY_SK!, // or UNITPAY_API_KEY env var baseUrl: 'https://api.useunitpay.com/v1', // default timeout: 30_000, retries: 2, idempotencyKeyPrefix: '', fetch: globalThis.fetch, }) ``` ## Webhook verification (Node only) ```ts const event = await UnitPay.verifyWebhook(rawBody, request.headers, process.env.WEBHOOK_SECRET!) ``` Requires the optional `svix` peer dep: `npm i svix`. ## Compatibility - Node 18 (native `fetch`, `crypto.randomUUID`) - Edge runtimes (Cloudflare Workers, Vercel Edge, Deno) ## License MIT