@doola/js

中文文档
0.3.0发布于 2026/10/09MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 4271

Loader, TypeScript types and a server session helper for embedding doola formation in your product.

原始使用文档
文档目录10 节
  1. How it fits together
  2. Before you start
  3. Install
  4. 1. Mint a session on your server
  5. 2. Mount in the browser
  6. 3. Take payment, then confirm it
  7. Documentation
  8. Versioning
  9. Support and security
  10. License

@doola/js

npm CI OpenSSF Scorecard license

Embed doola US company formation in your product. Your customer forms an LLC or C Corp, with the state filing, EIN, registered agent and documents, inside your app and under your brand.

This package is small on purpose (under 1 KB gzipped) and contains no UI. It loads doola's versioned loader from js.doola.com, which mounts an iframe served from sdk.doola.com. Every screen, field and validation lives inside that iframe:

  • Sensitive data never touches your code. SSNs, ITINs and signatures are typed into doola's origin and sent to doola's API. They never appear in your JavaScript, your error tracker, your session replay or your logs.
  • You never redeploy for our changes. State requirements, IRS forms and UI improvements ship inside the iframe. This package pins the shape of the call, never the behavior behind it.

Full documentation: docs.doola.com/sdk. This page is the short version: install, the three steps, and where to read more.

How it fits together

  1. Your server mints a short-lived session for the signed-in customer, using your secret key.
  2. Your page calls loadDoola() and appends the element it creates. The customer completes the formation wizard inside the iframe.
  3. On submit you get onFormed({ companyId }). You take payment in your own checkout, and your server confirms it to doola. Nothing is filed and nothing is spent until you do.

Before you start

You need two keys. The publishable key selects the environment for the browser, and the secret key does the same on your server.

KeyPrefixWhere it livesHow to get it
Publishablepk_test_ or pk_live_Your frontend. Public by design.Partner portal, under SDK, then Install. It identifies you and selects your branding.
Secretdk_test_ or dk_live_Your server only. Never in a browser or bundle.Partner portal, under Settings, then API Keys.

Test and live are separate stacks with separate data. Use a matching pair:

EnvironmentKeysPartner APIEmbedded app
Testpk_test_, dk_test_https://api.test.doola.comhttps://sdk.test.doola.com
Livepk_live_, dk_live_https://api.doola.comhttps://sdk.doola.com

Install

npm install @doola/js

TypeScript types are included. The package is ESM and CommonJS, with two entry points: @doola/js runs in the browser only, and @doola/js/server runs on your server, on Node 18 or later or any runtime with the web-standard fetch.

1. Mint a session on your server

Your server is the only place your secret key lives. Add one authenticated route that creates a customer session for the signed-in customer. It needs no database. createSessionHandler builds the route: it takes the web-standard Request and returns a Response. That makes it a Next.js route handler as written, and a one-line wrapper in Remix (action), Hono, Bun, Deno and Cloudflare Workers, where process.env needs the nodejs_compat flag.

// POST /doola-session
import { createSessionHandler } from '@doola/js/server';

export const POST = createSessionHandler({
  // Your dk_ secret key. Its prefix selects the API host. A missing key is refused per
  // request, so `next build` passes without it, and a pk_ key throws here.
  apiKey: process.env.DOOLA_API_KEY,

  // Return null when nobody is signed in. The route answers 401, and the loader reports
  // partner_session_expired.
  getCustomer: async (request) => {
    const user = await getSignedInUser(request); // your own auth
    // externalCustomerId is optional but recommended: it is matched before the email, so a
    // customer who changes their email with you stays the same doola customer.
    return user ? { email: user.email, externalCustomerId: user.id } : null;
  },

  // The browser gets the status, plus doola's error code when doola refused the customer.
  // This says why, for your logs: a revoked key, an outage, or a field doola rejected.
  onFailure: (failure) => console.error('doola session failed', failure),
});

The token acts as that one customer and expires in minutes. The loader calls your route again to renew it, so a doola session never outlives your own login. Protect the route like any other authenticated POST, including your CSRF protection.

Express, Fastify and other languages: Create sessions.

2. Mount in the browser

import { loadDoola } from '@doola/js';

const doola = await loadDoola({
  publishableKey: 'pk_live_…',

  // Called on mount, on every renewal and on Try again. Always fetch a fresh session.
  fetchAccessToken: async () => {
    const r = await fetch('/doola-session', { method: 'POST' });
    if (!r.ok) {
      // Reject with the status and doola's error code: the loader maps them to error types.
      const { code } = await r.json().catch(() => ({}));
      throw Object.assign(new Error('doola session'), { status: r.status, code });
    }
    return r.json();
  },

  // Can fire more than once, so keep it idempotent.
  onAuthError: (error) => {
    if (error.type === 'partner_session_expired') location.assign('/login');
    if (error.type === 'email_in_use' || error.type === 'customer_revoked') showSupportMessage();
    // Your mapping between users and externalCustomerId, not the customer's to fix.
    if (error.type === 'external_id_conflict') console.error('doola session conflict', error);
  },

  // The customer submitted the wizard. Start your checkout for this company.
  onFormed: ({ companyId }) => startCheckout(companyId),
});

// Mount into an element on your page: <div id="doola"></div>
const element = doola.create();
document.getElementById('doola')!.append(element);

create() takes no arguments. The app decides what to show from the customer's state: the wizard for a new customer, their company once one exists.

Keep one instance per page: options are fixed by the first loadDoola() call. Frameworks, sign-out and the full-screen mode on phones: Embed the SDK.

3. Take payment, then confirm it

onFormed hands you a company id and nothing else, on purpose. Anything delivered into your page's JavaScript can be edited in DevTools, so your server works out what is owed. When your checkout starts:

  1. Look the company up with your secret key, using Get a company. Charge only while its formationSubmissionStatus is AWAITING_PAYMENT.
  2. Check it belongs to the signed-in customer. Pass the company's doolaCustomerId to Get a customer and compare the email.
  3. Price it on your server from the company's state, entityType and attached services, plus the state fee from List state filing fees. Never read a price from the browser.
  4. Charge, then confirm with Confirm payment. Nothing is filed until you do:
// On your server, after the charge succeeds.
const DOOLA_API = 'https://api.doola.com'; // https://api.test.doola.com with a dk_test_ key

function doolaKey(): string {
  const key = process.env.DOOLA_API_KEY; // your dk_ secret key
  if (!key) throw new Error('DOOLA_API_KEY is not set');

  return key;
}

async function confirmPayment(companyId: string, paymentReference: string): Promise<void> {
  const path = `/v1/partner/companies/${encodeURIComponent(companyId)}/payment-confirmed`;

  const r = await fetch(`${DOOLA_API}${path}`, {
    method: 'POST',
    headers: { authorization: doolaKey(), 'content-type': 'application/json' },
    // Your own reference (a payment intent id, an order id). Logged, never verified.
    body: JSON.stringify({ partnerReference: paymentReference }),
  });

  // Idempotent, so a failed call is safe to retry.
  if (!r.ok) throw new Error(`doola payment-confirmed failed with ${r.status}`);
}

onFormed can fire again for the same company, for example when a founder comes back to an unpaid formation and asks to pay. Key your orders by companyId so you never charge twice.

After you confirm, replace the element you mounted in step 2: element.replaceWith(doola.create()). Its payment screen does not watch for your confirmation, so only a new element shows the founder their company. Each create() is a separate iframe, so never append a second one beside the old. From then on, the Partner API's webhooks tell you how the formation is going.

Edge cases, refunds and reconciliation: Take payment.

Documentation

ReadFor
QuickstartA working embed and a test payment in about 15 minutes
How it worksKeys, sessions and the formation lifecycle
Take paymentYour checkout, the confirmation call and every edge case
Client referenceEvery option, method and callback
Server reference@doola/js/server and the Partner API endpoints you use
ErrorsonAuthError, onLoadError and what to do about each
Security and dataContent Security Policy, Trusted Types and data flows
TestingThe test environment and the sandbox playground
Go-live checklistEverything to check before live keys
Partner APIWebhooks, documents and required actions after payment

Versioning

@doola/js follows semver, and the changelog lists every release. The loader at js.doola.com/v1 updates in place and stays compatible with every @doola/js release in that major, so fixes reach your site without an upgrade or a deploy.

Support and security

License

MIT

版本与发布记录

@doola/js 共发布 5 个版本,首次发布于 2026/09/24;过去 12 个月发布了 5 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.3.0latest2026/10/091 个月前
0.2.02026/09/281 个月前
0.1.22026/09/241 个月前
0.1.12026/09/241 个月前
0.1.02026/09/241 个月前

关于 @doola/js 的常见问题

@doola/js 是什么?

Loader, TypeScript types and a server session helper for embedding doola formation in your product.

如何安装 @doola/js?

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

@doola/js 支持 TypeScript 吗?

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

@doola/js 支持 ESM 还是 CommonJS?

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

@doola/js 可以商用吗?

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

@doola/js 的下载量有多少?

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

@doola/js 还在维护吗?

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