> ## 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.

# 向他人发送卡片

> 生成托管虚拟卡链接，观察收件人领取，并管理链接全生命周期——在沙盒环境端到端完成。

本快速入门将演练完整的“发送卡片”生命周期。你将创建预发布应用、铸造具备范围的 Bearer 令牌、选择卡片项目、检索有资金的消费账户、生成托管虚拟卡链接、体验收件人激活流程、验证发卡，然后停用未使用的链接。

完成后，你将成功执行每个公开的“发送卡片”操作：

* `generateVCShareLinks`
* `getVCShareLinks`
* `deactivateVCShareLinks`

——全部在沙盒环境中进行，不会发生任何真实客户资金流动。

<Info>
  **先决条件**

  * 一个 **Fluz 账户**——你将在下方步骤 1–2 中创建预发布 API 凭据并铸造具备范围的访问令牌。
  * 请求会发送到 **沙盒** GraphQL 端点。托管收件人链接使用标准的 Fluz 激活体验，但所有 API 操作都使用你的预发布环境。
  * 你的账户必须**启用发送卡片**（`send_virtual_cards_enabled`），否则每个发送卡片操作都会返回权限错误。
  * 本快速入门使用有资金的 **Spend Account（消费账户）** 作为资金来源。卡片在**被领取时**才会注资，而不是在生成链接时。
</Info>

## 在你开始之前

除铸造令牌（步骤 2）外，本指南中的每个请求都使用你的 Bearer 访问令牌发送到 Fluz GraphQL API。

<CodeGroup>
  ```bash Endpoint & headers theme={null}
  POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql

  Authorization: Bearer <YOUR_USER_ACCESS_TOKEN>
  Content-Type: application/json
  ```

  ```bash Example request (cURL) theme={null}
  curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \
    -H "Authorization: Bearer <YOUR_USER_ACCESS_TOKEN>" \
    -H "Content-Type: application/json" \
    -d '{
      "query":"query { getVirtualCardOffers { offerId programName rewardValue } }"
    }'
  ```
</CodeGroup>

<Tip>
  下面的每个 GraphQL 操作都使用此端点和你的 Bearer 令牌。唯一的例外是步骤 2，它通过 OAuth 服务用你的应用凭据交换访问令牌。
</Tip>

## 完整流程

<Steps>
  <Step title="创建预发布应用" icon="user-plus">
    创建一个 Fluz 账户，然后打开开发者控制台并创建一个 **Staging（预发布）** 应用。

    当你的应用创建完成，你将获得：

    * `clientId`
    * `clientSecret`

    将这两个值妥善保存。你的 `clientSecret` 只会显示一次。

    如果你尚未创建预发布应用，请参见 [准备你的账户](/get-started/prepare-accounts)。
  </Step>

  <Step title="用凭据换取具备范围的 Bearer 令牌" icon="key">
    将你的应用凭据发送至 OAuth 服务，生成短期有效的用户访问令牌。

    该令牌将用于授权后续的所有“发送卡片”操作。

    <CodeGroup>
      ```bash Mint access token (cURL) theme={null}
      curl -X POST https://oauth-service.fluzapp.com/graphql \
        -H "Content-Type: application/json" \
        -d '{
          "query":"mutation ($clientId:String!,$clientSecret:String!,$scopes:[Scope!]!){generateUserAccessToken(clientId:$clientId,clientSecret:$clientSecret,scopes:$scopes){accessToken expiresIn scopes}}",
          "variables":{
            "clientId":"<APPLICATION_CLIENT_ID>",
            "clientSecret":"<APPLICATION_CLIENT_SECRET>",
            "scopes":[
              "CREATE_SHARE_LINK",
              "LIST_PAYMENT"
            ]
          }
      }'
      ```

      ```json Response theme={null}
      {
        "data": {
          "generateUserAccessToken": {
            "accessToken": "eyJhbGciOi...",
            "expiresIn": 3600,
            "scopes": [
              "CREATE_SHARE_LINK",
              "LIST_PAYMENT"
            ]
          }
        }
      }
      ```
    </CodeGroup>

    保存返回的 `accessToken`。

    你将按如下方式使用它：

    ```http theme={null}
    Authorization: Bearer <YOUR_USER_ACCESS_TOKEN>
    ```

    用于后续的每一个请求。

    <Info>
      `CREATE_SHARE_LINK` 授权所有“发送卡片”操作。

      本快速入门还请求了 `LIST_PAYMENT`，以便你能检索为托管卡片提供资金的消费账户。
    </Info>

    <Warning>
      **不要在浏览器或移动应用中暴露你的** `clientSecret`。\*\*

      在服务端生成 Bearer 令牌，并仅将访问令牌转发给你的客户端。
    </Warning>
  </Step>

  <Step title="检索有资金的消费账户（Spend Account）" icon="wallet">
    托管虚拟卡从你的某个 **Spend Account（消费账户）** 注资。

    与常规虚拟卡不同，**在生成链接时不会预留资金。**

    相反，仅当收件人成功领取卡片时，所选消费账户才会被扣款。

    检索你可用的消费账户。

    <CodeGroup>
      ```graphql Query theme={null}
      query GetSpendAccounts {
        getSpendAccounts {
          userCashBalanceId
          nickname
          availableBalance
          currency
        }
      }
      ```

      ```json Response theme={null}
      {
        "data": {
          "getSpendAccounts": [
            {
              "userCashBalanceId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
              "nickname": "Primary Spend Account",
              "availableBalance": 5000,
              "currency": "USD"
            }
          ]
        }
      }
      ```
    </CodeGroup>

    保存 `userCashBalanceId`。

    你将在生成托管卡链接时提供该值。

    <Note>
      所选消费账户必须属于你的账户，并且在**收件人领取卡片时**拥有足够资金可用。如果在领取时可用余额不足，发卡将失败。
    </Note>
  </Step>

  <Step title="选择卡片项目" icon="layers">
    每张托管卡都基于某个虚拟卡\*\*优惠（offer）\*\*发行。

    该优惠决定发行项目、可用奖励，以及适用于每张生成卡片的限额。

    检索你的账户可用的优惠。

    <CodeGroup>
      ```graphql Query theme={null}
      query GetVirtualCardOffers {
        getVirtualCardOffers {
          offerId
          programName
          rewardValue
          programLimits {
            dailyLimit
            weeklyLimit
            monthlyLimit
          }
        }
      }
      ```

      ```json Response theme={null}
      {
        "data": {
          "getVirtualCardOffers": [
            {
              "offerId": "11111111-2222-3333-4444-555555555555",
              "programName": "Virtual Card",
              "rewardValue": "1.5%",
              "programLimits": {
                "dailyLimit": 500,
                "weeklyLimit": 2000,
                "monthlyLimit": 5000
              }
            }
          ]
        }
      }
      ```
    </CodeGroup>

    保存你希望发行所用项目的 `offerId`。

    <Info>
      **所选优惠必须：**

      * 处于活动状态
      * 启用了分享

      如果生成失败是因为该优惠不符合“发送卡片”资格，请与 Fluz 代表确认哪些优惠已启用托管分享。
    </Info>
  </Step>

  <Step title="生成两个托管卡链接" icon="link">
    现在可以创建托管的“发送卡片”链接了。

    每个生成的链接代表**一张未来的虚拟卡**。

    在此阶段：

    * 尚未发行任何卡片。
    * 尚未扣除任何资金。
    * 每个链接都以 `PENDING` 状态开始。

    仅当**有收件人领取链接**时，所选消费账户才会被扣款。

    在本快速入门中我们将生成**两个**链接：

    * **链接 A**——你将以收件人身份领取它。
    * **链接 B**——你将保持未领取，稍后用于撤销演示。

    <CodeGroup>
      ```graphql Mutation theme={null}
      mutation GenerateVCShareLinks($input: GenerateVCShareLinksInput!) {
        generateVCShareLinks(input: $input) {
          shareLinks
        }
      }
      ```

      ```json Variables theme={null}
      {
        "input": {
          "cardLimit": 25,
          "offerId": "<OFFER_ID>",
          "quantity": 2,
          "daysUntilExpiration": 14,
          "shareMethod": "GENERATE_URL",
          "userCashBalanceId": "<USER_CASH_BALANCE_ID>"
        }
      }
      ```

      ```json Response theme={null}
      {
        "data": {
          "generateVCShareLinks": {
            "shareLinks": [
              "https://fluz.app/virtual-prepaid-card/3f8ac...c1",
              "https://fluz.app/virtual-prepaid-card/9b2dd...77"
            ]
          }
        }
      }
      ```
    </CodeGroup>

    响应会为每张生成的卡片返回一个托管激活 URL。

    保存这两个 URL。

    我们将把它们称为：

    * **链接 A**
    * **链接 B**

    在本快速入门的其余部分中使用。

    ### 了解生成设置

    <ParamField body="cardLimit" type="Int" required>
      每张卡在被领取时加载的金额。

      每张生成的卡片都会获得自己独立的限额。
    </ParamField>

    <ParamField body="offerId" type="UUID" required>
      要发行的托管虚拟卡项目。

      该优惠必须处于活动状态并启用“发送卡片”。
    </ParamField>

    <ParamField body="quantity" type="Int" required>
      要生成的托管链接数量。

      每个请求的数量都会创建一个分享请求和一个托管 URL。
    </ParamField>

    <ParamField body="shareMethod" type="ShareMethodType" required>
      控制收件人如何接收他们的链接。

      * `GENERATE_URL` 返回托管 URL 供你自行分发。
      * `EMAIL` 自动向收件人发送电子邮件。
      * `PHONE_NUMBER` 向收件人发送短信。
    </ParamField>

    <ParamField body="userCashBalanceId" type="UUID" required>
      为每张生成卡片提供资金的消费账户。

      尽管该字段在 GraphQL 架构中看似可选，但在实际使用中是必填的。
    </ParamField>

    <ParamField body="daysUntilExpiration" type="Int">
      收件人可领取托管链接的时长。

      如省略，则使用项目默认值。

      生成的到期日也会成为已发行卡片的锁定日期。
    </ParamField>

    <Accordion title="改为通过电子邮件或短信投递卡片" icon="mail">
      你也可以不自行分发托管 URL，由 Fluz 向每位收件人发送他们自己的激活链接。

      ### 电子邮件

      ```json theme={null}
      {
        "input": {
          "cardLimit": 25,
          "offerId": "<OFFER_ID>",
          "quantity": 2,
          "shareMethod": "EMAIL",
          "recipientListEmail": [
            "alice@example.com",
            "bob@example.com"
          ],
          "userCashBalanceId": "<USER_CASH_BALANCE_ID>"
        }
      }
      ```

      ### 短信

      ```json theme={null}
      {
        "input": {
          "cardLimit": 25,
          "offerId": "<OFFER_ID>",
          "quantity": 2,
          "shareMethod": "PHONE_NUMBER",
          "recipientListPhone": [
            "+18883606660",
            "+18885551234"
          ],
          "userCashBalanceId": "<USER_CASH_BALANCE_ID>"
        }
      }
      ```

      收件人列表长度**必须与** `quantity` **完全一致**。

      如果不匹配，将验证失败并且**不会创建任何链接**。

      手机号码必须：

      * 包含国家代码
      * 不包含空格
      * 以字符串形式提供

      示例：

      ```text theme={null}
      +18883606660
      ```
    </Accordion>

    <Warning>
      **生成托管链接并不会预留资金。**

      如果在收件人领取卡片时，所选消费账户的可用余额不足，发卡将失败。
    </Warning>

    <Info>
      **生成响应有意只返回托管 URL。**

      它**不会**返回：

      * `shareRequestBatchId`
      * `shareRequestDisplayId`

      你将在下一步中检索这些信息。
    </Info>
  </Step>

  <Step title="检索已生成的分享请求" icon="list-checks">
    虽然你已经拥有托管 URL，但通常你还需要底层的分享请求记录。

    这些记录包含：

    * 生命周期状态
    * 批次标识符
    * 显示 ID
    * 已发行虚拟卡 ID
    * 到期信息

    在生成之后，立即检索你的待处理分享请求。

    <CodeGroup>
      ```graphql Query theme={null}
      query GetVCShareLinks($input: GetVCShareLinksInput!) {
        getVCShareLinks(input: $input) {
          shareRequestBatchId
          shareRequestDisplayId
          shareObjectStatus
          linkExpirationDate
          virtualCardId
          linkUrl
        }
      }
      ```

      ```json Variables theme={null}
      {
        "input": {
          "shareObjectStatuses": [
            "PENDING"
          ]
        }
      }
      ```

      ```json Response theme={null}
      {
        "data": {
          "getVCShareLinks": [
            {
              "shareRequestBatchId": "BATCH123",
              "shareRequestDisplayId": "SR-000001",
              "shareObjectStatus": "PENDING",
              "virtualCardId": null,
              "linkExpirationDate": "2026-08-01T00:00:00Z",
              "linkUrl": "https://fluz.app/virtual-prepaid-card/3f8ac...c1"
            },
            {
              "shareRequestBatchId": "BATCH123",
              "shareRequestDisplayId": "SR-000002",
              "shareObjectStatus": "PENDING",
              "virtualCardId": null,
              "linkExpirationDate": "2026-08-01T00:00:00Z",
              "linkUrl": "https://fluz.app/virtual-prepaid-card/9b2dd...77"
            }
          ]
        }
      }
      ```
    </CodeGroup>

    将返回的每个 `linkUrl` 与生成时返回的 URL 进行匹配。

    保存：

    * 共享的 `shareRequestBatchId`
    * 两个 `shareRequestDisplayId` 值

    你将在稍后停用未使用链接时用到它们。

    此时两个链接都应为：

    ```text theme={null}
    Status: PENDING
    Virtual Card ID: null
    ```

    这确认了：

    * 链接已成功创建
    * 尚无收件人领取
    * 尚未发行任何虚拟卡
  </Step>

  <Step title="以收件人身份领取其中一个链接" icon="smartphone">
    在浏览器中打开**链接 A**。

    这正是你的收件人看到的体验。

    托管流程引导收件人完成卡片激活，无需任何 API 集成。

    在激活过程中，收件人将：

    1. 打开托管激活页面。
    2. 登录或创建 Fluz 账户。
    3. 完成身份验证。
    4. 完成双重身份验证。
    5. 如未留存，添加账单地址。
    6. 创建卡片 PIN。
    7. 收到他们的托管虚拟卡。

    仅在完成最后一步后，Fluz 才会：

    * 发行虚拟卡
    * 从你的消费账户为其注资
    * 将其分配给收件人

    收件人成为**仅该卡片**的授权持有人。

    他们**不会**获得以下访问权限：

    * 你的 Fluz 账户
    * 你的消费账户
    * 你的余额
    * 任何其他已生成的卡片

    卡片发行后，收件人可以：

    * 查看卡片
    * 在线消费
    * 将卡添加至 Apple Pay 或 Google Wallet（如受支持）
    * 查看后续交易

    <Info>
      **保持链接 B 不操作。**

      我们将在下一部分演示链接撤销。
    </Info>
  </Step>

  <Step title="验证卡片已发行" icon="badge-check">
    在收件人领取**链接 A**后，再次检索分享请求。

    这次你会看到已领取的链接从 `PENDING` 变为 `ISSUED`。

    <CodeGroup>
      ```graphql Query theme={null}
      query GetVCShareLinks($input: GetVCShareLinksInput!) {
        getVCShareLinks(input: $input) {
          shareRequestDisplayId
          shareObjectStatus
          virtualCardId
          linkUrl
        }
      }
      ```

      ```json Variables theme={null}
      {
        "input": {
          "shareRequestBatchIds": [
            "BATCH123"
          ]
        }
      }
      ```

      ```json Response theme={null}
      {
        "data": {
          "getVCShareLinks": [
            {
              "shareRequestDisplayId": "SR-000001",
              "shareObjectStatus": "ISSUED",
              "virtualCardId": "c8dbe6d8-2f2f-4c90-8d7e-15ef5b06e387",
              "linkUrl": "https://fluz.app/virtual-prepaid-card/3f8ac...c1"
            },
            {
              "shareRequestDisplayId": "SR-000002",
              "shareObjectStatus": "PENDING",
              "virtualCardId": null,
              "linkUrl": "https://fluz.app/virtual-prepaid-card/9b2dd...77"
            }
          ]
        }
      }
      ```
    </CodeGroup>

    你的两个链接现在应处于不同的生命周期状态。

    | 链接       | 期望状态      | 含义               |
    | -------- | --------- | ---------------- |
    | **链接 A** | `ISSUED`  | 收件人已成功领取并创建了虚拟卡。 |
    | **链接 B** | `PENDING` | 该链接尚未被领取。        |

    出现 `virtualCardId` 表明发卡已成功完成。

    <Accordion title="理解“发送卡片”的状态" icon="info">
      每个托管的“发送卡片”都会经历四种生命周期状态之一。

      | 状态        | 含义                  |
      | --------- | ------------------- |
      | `PENDING` | 托管链接已生成但尚未被领取。      |
      | `ISSUED`  | 收件人已成功领取链接并获得虚拟卡。   |
      | `USED`    | 已发行卡片已完成至少一笔交易。     |
      | `EXPIRED` | 托管链接自然到期，或在被领取前被停用。 |

      只有 `PENDING` 状态的链接可以被停用。

      一旦卡片达到 `ISSUED`，停用原始链接**不会**撤销该卡片。
    </Accordion>
  </Step>

  <Step title="停用未使用的链接" icon="lock">
    假设第二位收件人并不需要他们的卡片，或者某个批次意外生成。

    你可以使用以下任一方式撤销任何**未被领取**的托管链接：

    * `shareRequestBatchIds`
    * `shareRequestDisplayIds`

    在本快速入门中，我们将使用其显示 ID 仅停用**链接 B**。

    <CodeGroup>
      ```graphql Mutation theme={null}
      mutation DeactivateVCShareLinks($input: DeactivateVCShareLinksInput!) {
        deactivateVCShareLinks(input: $input)
      }
      ```

      ```json Variables theme={null}
      {
        "input": {
          "shareRequestDisplayIds": [
            "SR-000002"
          ]
        }
      }
      ```

      ```json Response theme={null}
      {
        "data": {
          "deactivateVCShareLinks": "1 share requests successfully deactivated!"
        }
      }
      ```
    </CodeGroup>

    停用会立即阻止该托管链接被领取。

    如果收件人之后打开该链接，他们将看到已到期或已撤销的体验，而不是激活流程。

    <Warning>
      停用只会影响**未被领取**的链接。

      如果收件人已经领取了卡片（`ISSUED` 或 `USED`），则无法再通过托管链接撤销它。

      要停止已发行卡片的消费，请使用适当的虚拟卡生命周期控制，例如 [锁定虚拟卡](/features/lock-virtual-card)。
    </Warning>
  </Step>

  <Step title="确认最终生命周期" icon="clipboard-check">
    最后再检索一次分享请求。

    <CodeGroup>
      ```graphql Query theme={null}
      query GetVCShareLinks($input: GetVCShareLinksInput!) {
        getVCShareLinks(input: $input) {
          shareRequestDisplayId
          shareObjectStatus
          virtualCardId
        }
      }
      ```

      ```json Variables theme={null}
      {
        "input": {
          "shareRequestBatchIds": [
            "BATCH123"
          ]
        }
      }
      ```

      ```json Response theme={null}
      {
        "data": {
          "getVCShareLinks": [
            {
              "shareRequestDisplayId": "SR-000001",
              "shareObjectStatus": "ISSUED",
              "virtualCardId": "c8dbe6d8-2f2f-4c90-8d7e-15ef5b06e387"
            },
            {
              "shareRequestDisplayId": "SR-000002",
              "shareObjectStatus": "EXPIRED",
              "virtualCardId": null
            }
          ]
        }
      }
      ```
    </CodeGroup>

    你已经完成了完整的“发送卡片”生命周期演练。

    | 链接       | 最终状态      | 结果            |
    | -------- | --------- | ------------- |
    | **链接 A** | `ISSUED`  | 已成功被收件人领取并发行。 |
    | **链接 B** | `EXPIRED` | 在被领取前成功撤销。    |

    至此，你已经成功使用了三个公开的“发送卡片”操作：

    * ✅ `generateVCShareLinks`
    * ✅ `getVCShareLinks`
    * ✅ `deactivateVCShareLinks`
  </Step>
</Steps>

## 大功告成 🎉

你已完成托管“发送卡片”的全流程——从生成、收件人激活、生命周期跟踪到链接撤销。

在此过程中你已：

* 生成托管虚拟卡链接。
* 选择为收件人卡片提供资金的消费账户。
* 检索并跟踪分享请求记录。
* 以收件人身份领取托管卡片。
* 验证从 `PENDING` 到 `ISSUED` 的状态转换。
* 撤销未使用的托管链接。
* 确认最终的 `EXPIRED` 状态。

接下来，你可以探索更高级的“发送卡片”工作流。

<CardGroup cols={2}>
  <Card title="发送卡片概览" icon="send" href="/features/send-cards">
    收件人体验、项目规则、生命周期状态以及完整错误参考。
  </Card>

  <Card title="生成分享链接" icon="link" href="/features/generate-share-links">
    每个“发送卡片”操作的详细 API 参考。
  </Card>

  <Card title="获取消费账户" icon="wallet" href="/features/get-spend-accounts">
    检索并管理用于为托管卡片注资的消费账户。
  </Card>

  <Card title="管理虚拟卡" icon="credit-card" href="/features/lock-virtual-card">
    在卡片已发行给收件人后进行锁定与管理。
  </Card>
</CardGroup>

<Note>
  **想了解更多？**

  通过 [support@fluz.app](mailto:support@fluz.app) 联系我们，与专家沟通或申请演示。
</Note>
