@bachs/sdk

中文文档
2.0.0发布于 2026/10/23MITTypeScriptESM + CJS零依赖近 30 天下载 3084

Official Bachs server-side TypeScript SDK

原始使用文档
文档目录6 节
  1. Implemented resources
  2. Payouts
  3. Webhooks
  4. Request behavior and errors
  5. Logging
  6. Verification

Bachs TypeScript SDK

Official server-side TypeScript SDK for Bachs. SDK version 1.0.0 and backend API /v1 are separate version systems. Licensed under MIT; see LICENSE.

Server-side Node.js 22 or newer. Native fetch and crypto, no runtime dependencies. ES-module import and CommonJS require share the same runtime classes and errors.

npm install @bachs/[email protected]
import { Bachs } from '@bachs/sdk';

const bachs = new Bachs({ apiKey: process.env.BACHS_API_KEY });

const product = await bachs.products.create({
  name: 'Pro Plan',
  price: { currency: 'USD', amount: '12.00' },
  billing_cycle: { interval: 'month', frequency: 1 }
}, { idempotencyKey: 'unique-operation-key-retained-by-your-app' });

const checkout = await bachs.checkoutSessions.create({
  product_cart: [{ product_id: product.id }],
  customer: { email: '[email protected]', name: 'Synthetic Customer' },
  success_url: 'https://your-public-app.example/success'
}, { idempotencyKey: 'another-unique-operation-key' });

// Redirect the customer to checkout.checkout_url in your application.
// A redirect alone does not prove payment or establish access rights.
const session = await bachs.checkoutSessions.get(checkout.checkout_id);

Configure the SDK on your backend; keep the secret key out of frontend code. Constructing a client sends no request. The environment comes from the key prefix: sk_sandbox_ keys use the sandbox and sk_live_ keys use production, and bachs.environment says which. Passing environment is optional; if it contradicts the key, the client refuses to start with BachsConfigError rather than send a sandbox key to production or the reverse. A key with an unrecognised prefix needs environment set explicitly. baseURL can override the endpoint for a proxy/local test server. The SDK does not provide cross-account Connect headers or browser credential management.

Implemented resources

ResourceMethodsResponsibility
productscreate, get, listReusable catalog offerings, including recurring pricing
checkoutSessionscreate, getHosted checkout for a product cart or raw pricing
paymentsget, listPayment status and history; no beta direct charges
subscriptionsget, list, cancelCustomer subscription lookup and immediate/period-end cancellation
customerPortalcreateSession(customerId)A short-lived hosted billing URL for an authorized customer
balancesgetBalance buckets in their own currencies
bankslist, resolveAccountBank codes and account-name lookup
payoutDestinationscreate, get, listDestination records and provider review/usability state
payoutQuotescreateCross-currency source/recipient amounts and quote expiry
payoutscreate, get, listOwn-balance payout request, status and history
webhooksconstructEventVerify raw signed bytes and return the event envelope

Creation uses (input, options?), retrieval uses (id, options?), and listing uses (filters?, options?). subscriptions.cancel uses (id, input?, options?); customerPortal.createSession uses (customerId, options?). balances.get has only options.

list returns one page. listAll(filters?, options?) on products, payments, subscriptions, payoutDestinations and payouts is an async iterator over every item, fetching each page only when it is reached and following cursor or offset paging as each resource needs:

for await (const payout of bachs.payouts.listAll({ status_filter: 'completed' })) {
  console.log(payout.id, payout.amount);
}
``` Payment list items retain `id`; a retrieved payment retains `payment_id`. Payout and destination lists retain their own distinct envelopes.

Money remains decimal strings and fields remain in Bachs snake_case. Fixed product `price_type` can be omitted; fixed `amount` is required. Free/custom inputs support their verified omitted/null money fields. Checkout requires a cart or raw pricing, not both. Deprecated `return_url`, Connect split/account fields and beta payment-method saving are not exposed in the checkout input type.

## Subscription management

```ts
const page = await bachs.subscriptions.list({ customer_id: 'cust_synthetic' });
const scheduled = await bachs.subscriptions.cancel('sub_synthetic', {
  cancel_at_period_end: true
});
// For immediate cancellation, use cancel_at_period_end: false.
const portal = await bachs.customerPortal.createSession('cust_synthetic');

Bachs manages recurring billing after checkout. The merchant must authorize the requesting customer before lookup, cancellation or portal creation. A portal URL carries a credential: redirect the customer to it and do not log/share it. The SDK does not provide a customer portal UI, entitlement rules or renewal orchestration.

Renewal verification limitation: Bachs owns automatic recurring billing. This release has verified the first subscription payment and immediate/period-end cancellation responses in sandbox, but has not observed a second billing-cycle payment or its renewal webhook. No supported public sandbox cycle-advance/test-clock operation was found in the inspected API and CLI. Renewal behavior remains unverified by this SDK acceptance run; do not treat initial payment or sample/replayed events as renewal evidence.

Payouts

const quote = await bachs.payoutQuotes.create({
  from_currency: 'USD', to_currency: 'NGN', amount: '120.00'
});
const payout = await bachs.payouts.create({
  destination: 'pd_synthetic', quote_id: quote.quote_id,
  reference: 'unique-payout-reference-retained-by-your-app'
}, { idempotencyKey: 'durable-operation-key-from-your-app' });

This is illustrative, not a request to send funds. Cross-currency payouts use quote_id; same-currency payouts use amount in the destination currency. Supply exactly one. amount/currency describe the recipient side; fee/total_debited use source_currency. A quote is not a payout, and an accepted/pending payout is not final delivery. Use retrieval/history and verified payout-result events to track progress.

Supply a unique payout reference and retain it for that operation, separately from its idempotency key. Although the API marks reference optional, the tested sandbox rejected an omitted reference with "Reference already exists for this organization". The SDK preserves API optionality; this workaround does not change its request type.

Use the provider's is_usable flag when preparing destinations. SDK payout creation does not secretly resolve/create a destination, fetch balances or impose a guessed admin policy. banks.resolveAccount can return resolved: false with a message without an HTTP error. Send bank codes from banks.list, not bank names.

Webhooks

import { webhooks } from '@bachs/sdk';

const event = webhooks.constructEvent(
  rawRequestBody,
  requestHeaders,
  webhookSigningSecret
);
// Persist/check event.id atomically in your application before applying effects.

rawRequestBody is a string or Uint8Array before JSON parsing. Headers can be a standard Headers object or a Node-style header record. The signing secret is separate from the API key. The standalone helper needs no API client; bachs.webhooks exposes the same helper.

Prefer X-Bachs-Signature-V2; verification accepts any matching v1 digest during rotation. Legacy X-Bachs-Timestamp/X-Bachs-Signature are also supported. Malformed V2 does not fall back to legacy. HMAC-SHA256 signs the exact timestamp plus raw bytes; comparison uses constant-time crypto. Default timestamp tolerance is 300 seconds in either direction, configurable through { toleranceSeconds }. Unknown event types and extra fields are preserved. Only verified valid JSON/event envelopes are returned.

The helper does not store processed IDs, guarantee exactly-once effects or run business handlers. The merchant owns durable deduplication, HTTP acknowledgements, customer authorization and access changes. Do not reserialize JSON before signature verification.

Request behavior and errors

  • timeoutMs: default 30000 for the complete operation, including attempts, delays and response reading. Per-request override supported; range 1–2147483647 milliseconds to avoid Node timer overflow.
  • maxRetries: default two additional GET attempts; client/read override supported. Network/body-transfer failures and HTTP 429/500/502/503/504 can retry. Other HTTP rejections and invalid successful response bodies do not retry.
  • Waits start around 500/1000 ms with a small random spread. Valid Retry-After hints are honored. No further attempt when the required wait cannot fit within the remaining deadline.
  • Writes are sent once, including writes with idempotencyKey. No generated keys or durable SDK key storage. A deliberate later repeat must retain the key and request; backend retention is not guaranteed by the SDK.
  • signal supports cancellation. Stopping local work after a send cannot undo provider processing.

Logging

The SDK logs through the logger option (any object with debug, info, warn and error methods, such as console or a pino logger). Without one, warnings go to console.warn and nothing else is printed. logLevel or the BACHS_LOG environment variable (debug, info, warn, off) sets the lowest level; the default is warn. Warnings cover an unrecognised key prefix and a retry budget used up; info logs the environment once per client; debug logs each request's method, path, status code, duration, attempt number and request ID. The API key, request and response bodies, portal URLs and webhook secrets are never logged at any level.

BachsApiError carries statusCode (the HTTP status code), errorCode (the Bachs error code from the body), the raw body, and optional fieldErrors, requestId and retryAfter. BachsNetworkError retains its cause and, as responseStatusCode, any status code already observed; timeout and caller cancellation have distinct subclasses. BachsResponseError means a successful statusCode with unusable JSON/shape. BachsConfigError rejects local configuration/request misuse. BachsWebhookError rejects invalid webhook authentication/envelopes.

Write 5xx, post-send network/cancellation/timeout failures and unusable successful write responses expose outcomeUnknown: true. Do not interpret an error as proof no operation exists; reconcile before creating a new operation key. Provider bodies and portal URLs may contain sensitive data; the SDK never logs them.

Response guards validate the fields promised by supported types, return the actual object and retain additional fields as unknown. Cadence requiredness and universal response completeness remain contract questions. Nested provider fields not fully typed remain unknown rather than receiving unchecked assertions.

Verification

Run npm install, then npm run check: strict positive/negative type fixtures, both builds and 30 local scenarios covering client, resources and webhook/reliability behavior. Local tests cannot establish real provider processing or webhook delivery.

GitHub Actions runs the same npm run check command on Node 22 and 24 after installing locked dependencies with npm ci. It runs on pushes and pull requests, and can be started manually from the Actions tab. These checks use local fixtures, require no Bachs API key, and do not publish the package.

node examples/sandbox-check.mjs --key-file <local-file> uses the real sandbox for read-only SDK checks and saves sanitized local results. Only after approval, --create-approved-records additionally creates one USD12 monthly product, one unpaid checkout and one USD1-to-NGN quote. It never pays a checkout, sends a payout, cancels a subscription, creates a portal session or creates a destination. New writes use recorded unique operation keys and preserve uncertain outcomes. Do not rerun the write mode blindly: each new run describes new operations.

Sandbox acceptance verified an initial USD12 subscription purchase, matching checkout/payment/subscription records, hosted portal access, both SDK cancellation modes, sandbox destination create/get/list, and a completed USD10-to-NGN payout with a USD1 fee. A genuine signed payout webhook was forwarded by the official Bachs CLI, correlated to that payout and rejected after byte tampering. The documented sandbox bank fixture returned resolved: false but was approved and usable when saved; positive account-name resolution and production destination review were not established. Genuine renewal remains unverified as described above. These are bounded sandbox observations, not guarantees of production settlement, public webhook delivery retries or application deduplication. Final release review remains required; a green test count alone is not stable-release acceptance.

Both development logs are local working records. Never stage, commit, push or include them in the package.

版本与发布记录

@bachs/sdk 共发布 3 个版本,首次发布于 2026/07/15;过去 12 个月发布了 3 个版本。

版本发布日期距今
2.0.0latest2026/10/2315 天前
1.0.02026/10/1820 天前
0.0.12026/07/153 个月前

关于 @bachs/sdk 的常见问题

@bachs/sdk 是什么?

Official Bachs server-side TypeScript SDK

如何安装 @bachs/sdk?

在项目目录运行 npm install @bachs/sdk,或使用 pnpm add / yarn add / bun add。

@bachs/sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bachs/sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@bachs/sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@bachs/sdk 可以商用吗?

@bachs/sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bachs/sdk 的下载量有多少?

根据 npm 官方下载统计,@bachs/sdk 在 2026/10/08 – 2026/11/06 这 30 天内被下载 3,084 次,日均约 103 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bachs/sdk 还在维护吗?

最新版本 2.0.0 发布于 2026/10/23(15 天前);至今共发布 3 个版本,其中过去 12 个月发布了 3 个;首次发布于 2026/07/15。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。