@firmhouse/firmhouse-sdk

中文文档
1.2.0发布于 2026/09/04MITTypeScriptESM + CJS构建来源已验证近 30 天下载 6575

A JavaScript / TypeScript SDK for interacting with the Firmhouse GraphQL API in a structured and type-safe way.

原始使用文档
文档目录5 节
  1. Features
  2. Install
  3. Documentation
  4. Usage
  5. Embedded Adyen checkout

Firmhouse SDK

A JavaScript / TypeScript SDK for interacting with the Firmhouse GraphQL API in a structured and type-safe way.

The Firmhouse SDK is designed to make it easier for developers to interact with the Firmhouse API by providing a fully typed interface and handling errors in a structured way.

Features

  • Fully typed SDK with TypeScript, so you don't have to worry about field names and types.
  • Provides smooth IDE experience with auto-completion and type hints.
  • Handles errors in a structured way.
  • Supports both Storefront and Write access tokens and restricts operations based on the access type.
  • Gives you the option to include/exclude related resources in the response, without writing messy GraphQL queries.
  • Supports applying discount codes and calculating discounted cart totals.
  • Provides helpers for subscriptions, ordered products, and extra fields.
  • Provides an embedded Adyen checkout, so you can render the payment step in your own storefront instead of redirecting to a hosted payment page.

Install

npm install @firmhouse/firmhouse-sdk

Documentation

You can find the documentation for the SDK here.

Usage

import { Access, FirmhouseClient } from '@firmhouse/firmhouse-sdk';
import { assignSubscriptionUtils, calculateCartTotals, mapExtraFieldsByFieldId } from '@firmhouse/firmhouse-sdk/utils';

const apiToken = 'YOUR_PROJECT_ACCESS_TOKEN';

const client = new FirmhouseClient({
  apiToken,
});

const { results: products } = await client.products.fetchAll();
const { results: plans } = await client.plans.fetchAll();
const product = await client.products.fetchById('123');
const cartToken = await client.carts.createCartToken();
await client.carts.addProduct(cartToken, {
  productId: products[0].id,
  quantity: 2,
});

// Apply a discount code and calculate the discounted cart totals.
await client.carts.applyDiscountCode(cartToken, 'WELCOME10');
const cart = await client.carts.get(cartToken, {
  appliedPromotions: {
    includeRelations: {
      promotion: true,
      discountCode: true,
    },
  },
});
const {
  payNowSubtotalCents, // Signup amount before discount.
  payNowDiscountCents, // Discount applied at signup.
  payNowTotalCents, // Signup amount after discount.
  monthlySubtotalCents, // Monthly amount before discount.
  monthlyDiscountCents, // Discount applied each month.
  monthlyTotalCents, // Monthly amount after discount.
} = calculateCartTotals(cart);
await client.carts.removeDiscountCode(cartToken);

const writeAccessApiToken = 'YOUR_PROJECT_ACCESS_TOKEN_WITH_WRITE_ACCESS';
const writeAccessClient = new FirmhouseClient({
  apiToken: writeAccessApiToken,
  accessType: Access.write,
});

const project = await writeAccessClient.projects.getCurrent({
  extraFields: true,
  promotions: true,
  taxRates: true,
});

const invoices = await writeAccessClient.invoices.fetchAll();

await client.selfServiceCenterToken.create('[email protected]', 'https://myapp.com/ssc/token-login');
const selfServiceCenterLoginToken = 'TOKEN_RECEIVED_FROM_THE_LOGIN_LINK';
const subscription = await writeAccessClient.subscriptions.getBySelfServiceCenterLoginToken(selfServiceCenterLoginToken);

const subscriptionWithUtils = assignSubscriptionUtils(subscription);
const upcomingOrderDate = subscriptionWithUtils.getClosestUpcomingOrderDate();
const upcomingOrderProducts = subscriptionWithUtils.getClosestUpcomingOrderOrderedProducts();

const extraFieldsById = mapExtraFieldsByFieldId(subscription.extraFields);
const extraFieldAnswer = extraFieldsById['EXTRA_FIELD_ID'];

calculateCartTotals uses the largest active promotion when multiple promotions are present; promotions do not stack. Discounts are capped at the subtotal, and shipping is not included.

Embedded Adyen checkout

Adyen projects can render the payment step inside your storefront with Adyen Web Drop-in instead of sending the customer to a hosted payment page. client.payments returns the browser-safe session configuration, and the helpers turn it into the options that Drop-in expects.

Install @adyen/adyen-web yourself; the SDK does not depend on it.

import { AdyenCheckout, Dropin } from '@adyen/adyen-web';
import { buildAdyenCheckoutOptions, buildAdyenDropinOptions, resolveAdyenCheckoutEntry } from '@firmhouse/firmhouse-sdk/utils';

const entry = resolveAdyenCheckoutEntry(new URLSearchParams(window.location.search));
let paymentToken = sessionStorage.getItem(`paymentToken:${cartToken}`);

// The customer is back from a redirect, so the payment is already with Adyen.
if (entry.mode === 'redirect' && paymentToken) {
  const status = await client.payments.waitForCheckoutStatus(cartToken, paymentToken);
  if (status.paymentStatus === 'PAID' && status.successUrl) {
    window.location.assign(status.successUrl);
    return;
  }
  // The payment was refused or expired. It is reopened, so Drop-in can be mounted again.
}

if (!paymentToken) {
  // `payment` is null when the checkout has nothing to pay for.
  const { payment } = await client.carts.createSubscription(cartToken, 'https://myshop.com/checkout', 'https://myshop.com/thanks');
  if (!payment) return;
  paymentToken = payment.token;
  sessionStorage.setItem(`paymentToken:${cartToken}`, paymentToken);
}

const session = await client.payments.createAdyenSession(cartToken, paymentToken);
const checkout = await AdyenCheckout(buildAdyenCheckoutOptions(session));
new Dropin(checkout, buildAdyenDropinOptions(session)).mount('#dropin-container');

Payment methods such as iDEAL, Bancontact and a 3D Secure challenge take the customer to another page, and Adyen returns them to your checkout with sessionId and redirectResult query parameters. resolveAdyenCheckoutEntry detects that. Read the outcome before anything else on a redirect return: Firmhouse confirms the payment through a webhook that often lands before the customer is back, and a session cannot be created for a payment that has already been paid.

Keep the payment token from createSubscription somewhere that survives the redirect, such as sessionStorage. Do not finalise the cart again on the redirect return: once the payment is paid, createSubscription no longer returns it, so there would be no token to read the outcome with.

To report the outcome to the browser sooner than the webhook does, you can hand the result back to Adyen while the payment is still open:

const checkout = await AdyenCheckout(buildAdyenCheckoutOptions(session, entry));
checkout.submitDetails({ details: { redirectResult: entry.redirectResult } });

Requesting a session for the same payment again returns the session that is still active, so reloading the checkout page keeps the customer on the same payment, and a refused payment can be retried in place.

All client.payments methods need a storefront access token and the subscription token of the checkout. buildAdyenDropinOptions mirrors the card, PayPal, and Google Pay settings of the project, so settings such as requiring the cardholder name apply to your checkout without hardcoding them, and PayPal saves the customer's details for renewals whenever the session stores the payment method. Merge your own presentational options into the result to style Drop-in.

版本与发布记录

@firmhouse/firmhouse-sdk 共发布 8 个版本,首次发布于 2024/06/27;过去 12 个月发布了 4 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.2.0latest2026/09/042 个月前
1.1.22026/08/212 个月前
1.1.12026/08/212 个月前
1.1.02026/08/212 个月前
1.1.0-alpha.1alpha2024/08/152 年前
1.0.12024/06/272 年前

关于 @firmhouse/firmhouse-sdk 的常见问题

@firmhouse/firmhouse-sdk 是什么?

A JavaScript / TypeScript SDK for interacting with the Firmhouse GraphQL API in a structured and type-safe way.

如何安装 @firmhouse/firmhouse-sdk?

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

@firmhouse/firmhouse-sdk 支持 TypeScript 吗?

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

@firmhouse/firmhouse-sdk 支持 ESM 还是 CommonJS?

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

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

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

@firmhouse/firmhouse-sdk 可以商用吗?

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

@firmhouse/firmhouse-sdk 的下载量有多少?

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

@firmhouse/firmhouse-sdk 还在维护吗?

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