# 表單

> 將 Kanorio 表單放置在您自己的前端，並為您處理機器人防護、檔案上傳、同意與聯絡人收集。

> Markdown version of https://kanorio.com/zh-Hant/developers/guides/forms. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp
>
> Translated from the English original: https://kanorio.com/en/developers/guides/forms.md

請在後台的 **行銷 › 線上表單** 建立表單，然後將其顯示在您的前端。Kanorio 會檢查機器人與垃圾訊息、儲存提交內容、建立或更新聯絡人、發送確認與團隊郵件，並觸發 `customers.form.submitted` Webhook。

開始之前，請從後台複製表單 ID。如果瀏覽器從您自己的網域呼叫 API，請將該網域新增至 [網站設定 › 開發者](https://app.kanorio.com/settings/developer) 下的 **允許的來源**。`localhost` 始終是被允許的。

## 選項 1：表單元素

`<KanorioForm>` 可渲染所有問題類型、檔案上傳與同意核取方塊，並為您執行機器人防護。這是最快的方法，且無需升級即可獲得新的防護規則。

```tsx
import { KanorioProvider, KanorioForm } from "@kanorio/elements";
import "@kanorio/elements/elements.css";

export function ContactSection() {
  return (
    <KanorioProvider siteUrl="https://s-xxxxxxxxxxxx.kanorio.com">
      <KanorioForm formId="cform12345678901234567890" siteName="Sourdough Studio" />
    </KanorioProvider>
  );
}
```

若未使用 React，請使用 `<kanorio-form site-url="…" form-id="…">`。請參閱 [Elements](https://kanorio.com/zh-Hant/developers/elements)。

## 選項 2：您自己的標記

當您想要完全控制 HTML 時，請直接使用 Content Delivery API。

### 1. 載入表單

`GET /api/v1/forms/{id}` 會回傳問題、同意設定以及表單是否正在接受回答。如果 `availability.accepting` 為 `false`，請顯示關閉訊息而非輸入欄位。

### 2. 在顯示表單時取得機器人檢查

`GET /api/v1/forms/{id}/challenge` 會回傳此提交的機器人檢查。其 `provider` 由網站擁有者在 **網站設定 › 機器人防護** 中設定：

- `kanorio` (預設)：`altcha` 中的內建工作量證明挑戰。SDK 會為您解決（在中階手機上約需半秒），並將結果作為 `_tk` 發送。
- `turnstile`、`recaptcha` 或 `hcaptcha`：擁有者自己的金鑰。請使用 `siteKey` 和 `action`（Turnstile 亦需 `cData`）渲染該提供者的元件，並將其權杖作為 `captchaToken` 傳遞。伺服器會根據網站設定決定驗證哪個提供者，因此來自任何其他提供者的權杖將被拒絕。

每次提交請取得新的挑戰：挑戰與元件權杖皆為單次使用。

### 3. 提交

發送以問題 ID 為鍵的答案，並附上防護欄位。蜜罐 (Honeypot) 欄位必須為空。

```ts
import { forms } from "@kanorio/sdk/forms";

const SITE_URL = "https://s-xxxxxxxxxxxx.kanorio.com";
const FORM_ID = "cform12345678901234567890";
const client = forms.client({ siteUrl: SITE_URL });

// when the form is shown
const renderedAt = Date.now();
const challenge = await client.challenge(FORM_ID);

// when the visitor submits
// built-in: the SDK solves the challenge; third-party: pass the widget token
const protection = await forms.protectionFromChallenge(FORM_ID, challenge, renderedAt, {
  captchaToken: challenge.provider === "kanorio" ? undefined : widgetToken,
});

const result = await client.submit(FORM_ID, {
  answers: { name: "Ada", email: "ada@example.com", message: "Do you ship?" },
  consent: "checked",
  pagePath: window.location.pathname,
  protection,
});

if (!result.ok) {
  // result.error.code, and result.error.issues for INVALID_ANSWERS
}
```

若未使用 SDK，請使用此內容 `POST /api/v1/forms/{id}/submit`（顯示內建檢查；若使用第三方提供者，請發送 `captchaToken` 取代 `_tk`）：

```json
{
  "answers": { "name": "Ada", "email": "ada@example.com" },
  "consent": "checked",
  "pagePath": "/contact",
  "_hp": "",
  "_ts": 1793520000000,
  "_tk": "<solved ALTCHA payload>",
  "_ctx": "cform12345678901234567890"
}
```

成功的提交會回傳 `200` 與 `{}`。如果表單在此期間關閉，內容則為 `{ "state": "closed" }`、`"not_open"` 或 `"full"`。

| 錯誤代碼 | 處理方式 |
|---|---|
| `INVALID_ANSWERS` | 顯示 `details.issues` 中的訊息，並以問題 ID 作為鍵值 |
| `VERIFICATION_EXPIRED` | 取得新的驗證挑戰（並重設小工具）後重新提交 |
| `DUPLICATE_SUBMISSION` | 告知訪客表單已送出 |
| `RATE_LIMITED` | 等待 `Retry-After` 指定的時間後再重試 |

### 檔案上傳

對於檔案問題，請先使用 `POST /api/v1/forms/{id}/upload-request` 請求上傳票證，並附上問題 ID、檔案名稱、類型與大小，以及來自 `forms.uploadProtectionFromChallenge(FORM_ID, challenge, renderedAt, solved)` 的防護欄位：使用內建檢查時，這與您提交時使用的已解決挑戰相同；使用第三方提供者時，它會使用挑戰的 `uploadToken`，因為元件權杖為單次使用且會在提交時檢查。使用回傳的 `headers` 將檔案 `PUT` 至 `uploadUrl`，然後將 `uploadId` 作為該問題的答案發送。超過問題限制的檔案會失敗並回傳 `413 FILE_TOO_LARGE`。

## 提交後

- 提交內容會出現在後台以及 Management API 的 `submissions` 下。
- 聯絡人會根據電子郵件或電話答案進行建立或更新。
- 確認與團隊郵件會按照表單設定發送。
- Webhook 訂閱者會收到 `customers.form.submitted`。

## 從您的伺服器轉發

您可以從自己的伺服器提交，例如 Next.js 伺服器動作。速率限制適用於每位訪客，因此直接從瀏覽器提交最能保護您的配額。如果您進行轉發，請保持瀏覽器的防護欄位不變。
