> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fluz.app/llms.txt
> Use this file to discover all available pages before exploring further.

# 安全元素概览

> 在你的页面中直接嵌入由 Fluz 托管的框架，向用户展示其自己的虚拟卡详情，同时卡片数据永远不会到达你的服务器或你页面的 JavaScript。

<Warning>
  **仅限 Staging，且仅限展示。** Secure Elements 正在积极开发中。
  本页以及 [卡片展示](/build-a-platform/card-reveal)
  页面均使用 Fluz 的 staging 环境运行 —— 生产环境主机尚未确认。本节仅文档化 **卡片展示（Card Reveal）** 能力；实体卡的采集（代币化）尚未在此覆盖。
</Warning>

## 什么是 Secure Elements

Secure Elements 是一个 JavaScript SDK，`@fluz/secure-elements`，它会将一个隔离的、由 Fluz 托管的框架直接挂载到你页面中的某个容器元素。该框架负责渲染卡片数据；你的页面和服务器只会持有一个短期有效的不透明令牌，用于授权某个特定动作。

这是向用户展示其自有卡片详情的第三种方式，与你已具备的两种方式并列：

| 路径                                                                                                    | 卡片数据可读的位置                  | 需要具备的条件                                                                             |
| :---------------------------------------------------------------------------------------------------- | :------------------------- | :---------------------------------------------------------------------------------- |
| [`revealVirtualCardByVirtualCardId`](/api-reference/mutations/reveal-virtual-card-by-virtual-card-id) | 你的自有服务器，在 API 响应中          | `PCI_COMPLIANCE` —— 仅授予已证明符合 PCI DSS 的开发者的、由 Fluz 管理的 scope。（个人/私有应用免除此要求。）         |
| [嵌入式小部件](/developers/widgets)                                                                         | Fluz 托管的模态窗口，全屏覆盖在你的页面之上   | 除 OAuth 授权外无其他要求 —— Fluz 拥有整个界面，包括展示流程。                                             |
| **Secure Elements 卡片展示**                                                                              | 由 Fluz 托管的框架，内联挂载在你的页面布局之中 | 由你现有的 OAuth access token 铸造的客户端令牌。无需 `PCI_COMPLIANCE` 授权 —— 你的页面从不接收数据，因此不在其合规范围之内。 |

<Note>
  如果你已经在所有场景中使用了[嵌入式小部件](/developers/widgets)，那就不需要这个。Secure Elements 适用于无头或 API 驱动的集成，仍需向用户展示其 PAN、有效期和 CVV，但不想打开完整的小部件模态，也不想追求 `PCI_COMPLIANCE`。
</Note>

## 工作原理

```
Your backend  →  mint client token  →  Your frontend  →  SDK mounts Fluz frame  →  callback with result
```

<Steps>
  <Step title="你的后端铸造客户端令牌" icon="server">
    使用你已获取的 [Fluz OAuth access
    token](/build-a-platform/oauth-applications-overview) 置换一个短期有效的
    **客户端令牌**，其作用域限定为一次展示。
  </Step>

  <Step title="你的前端挂载框架" icon="app-window">
    将客户端令牌交给 `@fluz/secure-elements`，它会把 Fluz 托管的框架挂载到你提供的容器中 —— 内联于你的页面，而非模态窗口。
  </Step>

  <Step title="SDK 通过回调上报结果" icon="reply">
    你的页面从不读取原始卡数据。它只能接收到成功、错误或挂载事件。
  </Step>
</Steps>

## 先决条件

* 你的应用已在 Fluz 完成注册，并且在你的 access token 上启用了 `CREATE_VIRTUALCARD` scope。
* 你有一个状态为 `ACTIVE` 的虚拟卡 id，且归属为当前要展示的账户，用于在铸造客户端令牌时传入。

<Warning>
  **在你编写任何代码之前，请让 Fluz 将你的来源域加入允许名单。** 如果来源域未被 Fluz 预先批准，框架将拒绝渲染 —— 目前没有自助开关，所以这是唯一一个若被遗漏就会阻塞你的前置条件。

  请发送邮件至 [partnerships@fluz.app](mailto:partnerships@fluz.app)（或联系你的客户经理，如有）并提供你的**应用名称或 ID**，以及需要批准的每个**来源域** —— 包括你进行开发的每个 `http://localhost:PORT`，以及你的 staging 和生产域名。Fluz 会在后端将它们加入允许名单；完成后你这边无需任何配置。
</Warning>

## 环境

| 环境         | 基础 URL                            |
| :--------- | :-------------------------------- |
| Staging    | `https://staging.secure.fluz.app` |
| Production | `https://secure.fluz.app`         |

<Note>
  在 Fluz 的处理方集成最终确定之前，Staging 上的展示目前返回模拟结果。请使用 Staging 端到端验证你的集成 —— 生产环境的可用性将另行确认。
</Note>

`frameHostOrigin` 在 `createCardViewer` 上是可选的 —— 省略则默认为生产（`https://secure.fluz.app`）。若要指向 Staging，请显式传入。仅接受这两个精确的来源；否则一旦你调用 `createCardViewer`，就会立即抛出 `FluzElementsError`（`error.code === "INVALID_FRAME_HOST_ORIGIN"`），在任何框架挂载之前。

## 加载 SDK

`@fluz/secure-elements` 不会发布到 npm —— 使用 `<script>` 标签从 Fluz 的 CDN 加载其浏览器全局（IIFE）构建。它将暴露一个 `FluzSecureElements` 全局对象：

```html theme={null}
<script src="https://secure-cdn-staging.fluz.app/secure-elements/v0.1.0/index.global.js"></script>
<script>
  const { createCardViewer } = FluzSecureElements;

  const viewer = createCardViewer({
    /* ... */
  });
</script>
```

本页及[卡片展示](/build-a-platform/card-reveal)页面上的每个代码示例均假设你已加载上述脚本标签，并像上面那样从 `FluzSecureElements` 中解构出所需内容。

每个版本都会发布到一个不可变、锁定版本的路径（`.../v0.1.0/index.global.js`），以及一个浮动的 `.../latest/index.global.js`，始终指向最新发布。除原型外请固定到具体版本 —— `latest` 可能会在无通知的情况下发生变化。

<Info>
  目前只有 Staging 的 CDN 主机已上线（`secure-cdn-staging.fluz.app`）。
  生产环境的托管将与生产 API 的可用性一并确认。
</Info>

## 铸造客户端令牌

你的后端使用你已通过标准 [OAuth 授权流程](/client-facing-o-auth-grant-flow) 获得的 Fluz OAuth access token 调用此接口。**切勿将该 access token 发送到浏览器** —— 只有此端点返回的 `clientToken` / `loadToken` 对应该传到你的前端。

```text theme={null}
POST {baseUrl}/v1/client-token
Authorization: Bearer <your Fluz OAuth access token>
Content-Type: application/json

{
  "purpose": "reveal",
  "virtualCardId": "<uuid>"
}
```

```json theme={null}
{ "clientToken": "<token>", "loadToken": "<token>", "expiresIn": 300 }
```

成功铸造会返回 `201`。两个令牌均为单一用途且短期有效 —— 每次展示都需铸造一对新的。`clientToken` 授权实际的展示动作（`expiresIn` 秒，默认 300）；`loadToken` 的限制更严格（60 秒），因为它会出现在 URL 中 —— 参见[卡片展示](/build-a-platform/card-reveal)中的说明 —— 且除加载框架外将被拒绝用于任何其他场景。将两者直接传给 `createCardViewer`，且永远不要自行将 `clientToken` 放进 URL —— SDK 已确保其不会出现在 URL 中。

<Accordion title="错误响应">
  | 状态  | 错误                         | 含义                                               |
  | :-- | :------------------------- | :----------------------------------------------- |
  | 400 | `invalid_purpose`          | 缺少或无效的 `purpose`                                 |
  | 400 | `virtual_card_id_required` | 针对展示令牌缺少 `virtualCardId`                         |
  | 401 | `unauthorized`             | 缺少 access token                                  |
  | 401 | `invalid_token`            | access token 校验失败（格式错误、已过期、签名错误）                 |
  | 403 | `insufficient_scope`       | access token 缺少所需 scope                          |
  | 403 | `app_not_registered`       | 你的应用尚未注册 —— 请联系 Fluz                             |
  | 403 | `forbidden`                | 找不到卡片，或该用户并不拥有此卡                                 |
  | 429 | `rate_limited`             | 针对该用户的 client-token 请求过多 —— 参见 `Retry-After` 响应头 |
  | 500 | `internal_error`           | 未预期的服务器错误                                        |
</Accordion>

## 字段样式

`createCardViewer` 接受一个可选的 `style` 对象，会应用于其挂载的每个字段：

```js theme={null}
const viewer = createCardViewer({
  clientToken,
  loadToken,
  frameHostOrigin: "https://staging.secure.fluz.app",
  style: {
    color: "#1a1a1a",
    fontSize: "16px",
    fontWeight: "600",
    fontFamily: "Inter",
  },
});
```

在任何内容发送到框架之前，`style` 会先被校验。若某个值与下方文档不匹配，`await viewer.mount(...)` 将以 `FluzElementsError` 拒绝（`error.code === "INVALID_STYLE"`）—— 如果你自身允许可配置样式输入，请用 try/catch 包裹你的 `mount()` 调用。

| 属性           | 可接受的值                                                                 |
| :----------- | :-------------------------------------------------------------------- |
| `color`      | 十六进制颜色（`#1a1a1a` 或 `#111`）、`rgb(r, g, b)` 值，或 CSS 命名颜色（`"slategray"`） |
| `fontSize`   | 一个数字后跟 `px`、`pt`、`em` 或 `rem`（例如 `"16px"`）                            |
| `fontWeight` | `"normal"`、`"bold"`，或从 `"100"` 到 `"900"` 的 100 的倍数                    |
| `fontFamily` | 精确的系统字体名或 Google 字体系列名 —— 见下文                                         |

`fontFamily` 必须与两个允许列表之一**精确、区分大小写地匹配**：

* **系统字体** —— 常见的操作系统/网页安全字体（`system-ui`、`-apple-system`、`Helvetica Neue`、`Arial`、`Georgia`、`Menlo`，以及通用关键字 `monospace` / `serif` / `sans-serif` 等）。这些字体可立即渲染，无需网络请求。
* **Google 字体** —— 来自 Google Fonts 目录的任意系列（如 `"Roboto"`、`"Inter"`、`"IBM Plex Mono"` 等），需与 Google 列出的名称完全一致。SDK 会为你加载字体 —— 你无需添加 `<link>` 标签或 `@font-face` 规则。

<Note>
  Google 字体会在字段挂载后获取，而不是预先打包，因此在冷缓存的短暂时间内，字段会先用浏览器的后备字体渲染，然后再替换为你选择的字体。系统字体则没有此延迟。
</Note>

如果你希望自行校验字体选择或构建字体选择器，两个列表均已导出：

```js theme={null}
const { SYSTEM_FONTS, GOOGLE_FONTS, isAllowedFontFamily } = FluzSecureElements;

isAllowedFontFamily("Roboto"); // true
isAllowedFontFamily("roboto"); // false -- exact, case-sensitive match required
```

## 内容安全策略

如果你的页面设置了 CSP，请允许你所指向的框架主机：

```text theme={null}
frame-src https://staging.secure.fluz.app;  # or https://secure.fluz.app in production
```

## 安全模型

* 你的 OAuth access token 永不离开你的服务器。
* 你的前端持有的客户端令牌是不透明且单一用途的 —— 它不携带卡数据，也无法被重放到不同的卡片或动作上。
* 卡数据只可在由 Fluz 托管的框架内读取，并与页面自身的 JavaScript 隔离。每个已配置的字段都会作为各自的沙箱（`allow-scripts allow-same-origin allow-forms`）、`referrerPolicy="no-referrer"` 的 iframe 挂载 —— SDK 从不把卡数据放入它们之外的 DOM。
* 该框架只会在你已预注册到 Fluz 的来源域内渲染。

## 后续步骤

<CardGroup cols={2}>
  <Card title="卡片展示" icon="eye" href="/build-a-platform/card-reveal">
    创建卡片查看器、挂载它，并控制要展示的字段。
  </Card>

  {" "}

  <Card title="在线演示" icon="play" href="https://demo.secure.fluz.app/">
    查看在 Staging 上运行的卡片查看器，包括展示、仅展示 CVV，以及遮罩。
  </Card>

  {" "}

  <Card title="示例集成" icon="github" href="https://github.com/fluz-app/secure-elements-examples">
    可运行的纯 HTML 与 React 示例，均调用真实的 Staging 基础设施。
  </Card>

  <Card title="OAuth 应用" icon="handshake" href="/build-a-platform/oauth-applications-overview">
    如何获取将用于交换客户端令牌的 access token。
  </Card>
</CardGroup>
