> ## 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 账户完成一次预生产购买。你将注册一个应用、铸造带作用域的访问令牌，然后充值、浏览商户目录、购买礼品卡并揭示其兑换详情——全部在沙盒环境中进行，无真实资金流动。

<Info>
  **先决条件**

  * 一个 **Fluz 账户**——你将在下方步骤 1–2 中创建预生产 API 凭证并铸造访问令牌。
  * 请求将发送到 **沙盒** GraphQL 端点。这里不会对真实卡片扣款——参见 [预生产 vs. 线上环境](/concepts/environments)。
  * 每个 mutation 都需要唯一的 `idempotencyKey`（客户端生成的 UUID），以确保请求只被处理一次——参见 [幂等性](/concepts/idempotency)。
</Info>

## 开始之前

除铸造令牌（步骤 2）外，其他每次调用都是到同一个 GraphQL 端点的 `POST` 请求，并使用 Bearer 令牌进行认证：

<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 getWallet { getWallet { bankCards { bankCardId lastFourDigits } } }"
    }'
  ```
</CodeGroup>

<Tip>
  你的沙盒账户自带一张**预置的测试银行卡**，可立即跑通整个流程。可在 [Sandbox Accounts 页面](https://uni.staging.fluzapp.com/accounts-and-cards) 按照 [测试银行卡](/test-bank-cards) 列表添加更多测试卡或银行账户。
</Tip>

## 完整流程

<Steps>
  <Step title="注册并创建应用" icon="user-plus">
    前往 [fluz.app](https://fluz.app) 创建 Fluz 账户，然后打开 [开发者控制台](https://uni.staging.fluzapp.com/developers) 并创建一个新的 **Staging** 应用。凭证出现后，复制你的 `clientId` 和 `clientSecret`——密钥只显示一次。

    详细教程：[准备你的账户](/get-started/prepare-accounts)。
  </Step>

  <Step title="用凭证换取访问令牌" icon="key">
    使用应用凭证铸造一个短期有效、带作用域的用户访问令牌。此调用请求 OAuth 服务；后续所有调用都使用返回的令牌作为 Bearer 凭证。

    <CodeGroup>
      ```bash Mint 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": ["LIST_OFFERS", "LIST_PAYMENT", "MANAGE_PAYMENT", "PURCHASE_GIFTCARD", "REVEAL_GIFTCARD"]
          }
        }'
      ```

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

    保存返回的 `accessToken`，并在下方每个请求中以 `Authorization: Bearer <accessToken>` 发送。令牌短期有效（`expiresIn` 单位为秒）——请在服务端铸造并在过期前刷新。详情参见：[API 凭证](/get-started/api-credentials)。

    <Tip>
      作用域控制令牌的权限——仅包含你的流程所需的范围：`MANAGE_PAYMENT` 用于添加资金来源与充值，`LIST_OFFERS` 用于浏览目录，`PURCHASE_GIFTCARD` / `REVEAL_GIFTCARD` 用于购买与揭示。
    </Tip>

    <Warning>
      切勿在浏览器或移动端暴露 `clientSecret`。请在服务端铸造令牌，仅转发令牌。
    </Warning>
  </Step>

  <Step title="向你的 Fluz 余额充值" icon="wallet">
    预先充值 Fluz 余额通常能加快礼品卡购买速度，并可绕过某些频控检查。你可以通过两种方式充值：

    * **手动**，通过 [沙盒 Fluz 网站](https://uni.staging.fluzapp.com/manage-money)。
    * **编程方式**，通过 `depositCashBalance` mutation（如下）。

    <Accordion title="首先，获取支付方式 ID（getWallet）" icon="id-card">
      要通过 API 充值，你需要一个资金来源的 ID（例如 `bankCardId`）。运行 `getWallet` 并保存要使用的 ID。

      ```bash 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 getWallet { getWallet { bankCards { bankCardId lastFourDigits } bankAccounts { bankAccountId } } }"
        }'
      ```

      从响应中获取 `bankCardId` 或 `bankAccountId`。通过 API 管理资金来源需要 `MANAGE_PAYMENT` 作用域。更多详情：[查看资金来源](/features/view-funding-sources)。
    </Accordion>

    ### 发起充值

    使用 `depositCashBalance` mutation。它接收一个 `DepositCashBalanceInput` 对象。

    <CodeGroup>
      ```graphql Mutation theme={null}
      mutation depositCashBalance($input: DepositCashBalanceInput!) {
        depositCashBalance(input: $input) {
          cashBalanceDeposits {
            cashBalanceDepositId
            depositDisplayId
            depositAmount
            status
            cashBalanceDepositType
          }
          balances {
            cashBalance { availableBalance totalBalance pendingBalance }
            giftCardCashBalance { availableBalance totalBalance pendingBalance }
          }
        }
      }
      ```

      ```json Variables theme={null}
      {
        "input": {
          "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19",
          "amount": 100.00,
          "depositType": "CASH_BALANCE",
          "bankCardId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19"
        }
      }
      ```
    </CodeGroup>

    #### 输入字段

    <ParamField body="idempotencyKey" type="string" required>
      客户端生成的唯一 UUID，保证充值只被处理一次。
    </ParamField>

    <ParamField body="amount" type="Float" required>
      充值金额。
    </ParamField>

    <ParamField body="depositType" type="CashBalanceDepositType">
      目标余额。可为 `CASH_BALANCE`、`GIFT_CARD_BALANCE` 或 `RESERVE_BALANCE`。
    </ParamField>

    <ParamField body="Funding source" type="UUID">
      资金来源——提供 **一个** `bankAccountId`、`bankCardId` 或 `paypalVaultId`。
    </ParamField>

    <ParamField body="merchantCategoryCode" type="Int">
      仅用于 `GIFT_CARD_BALANCE`。四位数 MCC 分类码。使用 `getMccList` 获取有效值。
    </ParamField>

    <ParamField body="userCashBalanceId" type="UUID">
      当选择 `CASH_BALANCE` 时，指定要充值的具体消费账户。
    </ParamField>

    <Accordion title="示例响应" icon="code">
      ```json theme={null}
      {
        "data": {
          "depositCashBalance": {
            "cashBalanceDeposits": [
              {
                "cashBalanceDepositId": "5f5c5b5f-4c4b-4e4e-9e9e-4f4f4f4f4f4f",
                "depositDisplayId": "102370",
                "depositAmount": "100.00",
                "status": "COMPLETED",
                "cashBalanceDepositType": "CASH_BALANCE"
              }
            ],
            "balances": {
              "cashBalance": {
                "availableBalance": "100.00",
                "totalBalance": "100.00",
                "pendingBalance": "0.00"
              },
              "giftCardCashBalance": {
                "availableBalance": "0.00",
                "totalBalance": "0.00",
                "pendingBalance": "0.00"
              }
            }
          }
        }
      }
      ```
    </Accordion>

    <Note>
      充值可能即时到账，也可能因资金来源与结算类型不同而在 2–5 个工作日内完成。响应中的 `balances` 对象反映你当前的可用余额。随时重查请参见 [检查账户余额](/check-account-balance)。
    </Note>
  </Step>

  <Step title="浏览商户并选择一个优惠" icon="store">
    余额准备就绪后，使用 `getMerchants` 获取可用商户及其返现优惠的目录。

    <CodeGroup>
      ```graphql Query theme={null}
      query GetMerchantCatalog(
        $paginate: OffsetInput
        $offerTypes: OfferTypesInput
        $filterBy: FilterByInput
      ) {
        getMerchants(paginate: $paginate, offerTypes: $offerTypes, filterBy: $filterBy) {
          merchantId
          name
          slug
          logoUrl
          faceplateUrl
          offers {
            offeringMerchantId
            offerId
            type
            deliveryFormat
            barcodeType
            offerRates {
              maxUserRewardValue
              cashbackVoucherRewardValue
              boostRewardValue
              denominations
              allowedPaymentMethods
            }
          }
        }
      }
      ```

      ```json Variables theme={null}
      {
        "paginate": { "limit": 20, "offset": 0 },
        "offerTypes": { "giftCardOffer": true, "cardLinkedOffer": false }
      }
      ```
    </CodeGroup>

    <Tip>
      目录默认按返现百分比排序，最高的优惠会排在最前面。
    </Tip>

    #### 常用参数

    <ParamField query="name" type="String">
      按名称筛选商户。
    </ParamField>

    <ParamField query="paginate" type="OffsetInput">
      `{ limit, offset }`。默认与最大 `limit` 为 `20`。
    </ParamField>

    <ParamField query="offerTypes" type="OfferTypesInput">
      返回何种优惠类型的布尔标志，例如 `{ giftCardOffer: true, cardLinkedOffer: false }`。
    </ParamField>

    <ParamField query="filterBy" type="FilterByInput">
      在每个商户内筛选优惠，例如按 `deliveryFormat`（`URL`、`CODES`、`PIN_AS_CODE`、`PIN_WITH_URL`）筛选。
    </ParamField>

    <Note>
      **分页：** 响应可能返回少于你请求 `limit` 的结果。要拉取**完整**目录，请持续将 `offset` 按 `limit` 递增，当 API 返回空数组（`[]`）时停止。未筛选的目录很大——每天最多抓取一次，并使用 `name` 或 `offerTypes` 进行定向查询。
    </Note>

    <Accordion title="可选：获取某商户的最佳单一优惠（getOfferQuote）" icon="badge-percent">
      如果你已知道商户和金额，`getOfferQuote` 将直接返回当前可用的最佳优惠——包含实时库存信息。

      <CodeGroup>
        ```graphql Query theme={null}
        query getOfferQuote($input: GetOfferQuoteInput!) {
          getOfferQuote(input: $input) {
            offeringMerchantId
            offerId
            type
            hasStockInfo
            denominationsType
            termsAndConditions
            offerRates {
              maxUserRewardValue
              denominations
              allowedPaymentMethods
            }
            stockInfo {
              ... on StockInfoVariableType {
                __typename
                description
                maxDenomination
                minDenomination
              }
              ... on StockInfoFixedType {
                __typename
                denomination
                availableStock
              }
            }
          }
        }
        ```

        ```json Variables theme={null}
        {
          "input": {
            "slug": "starbucks",
            "denomination": 50.00,
            "paymentMethod": "FLUZPAY"
          }
        }
        ```
      </CodeGroup>

      `slug` 与 `denomination` 为必填。`paymentMethod` 默认为 `FLUZPAY`（你的 Fluz 余额），也可为 `BANK_CARD`、`BANK_ACCOUNT`、`PAYPAL`、`APPLE_PAY` 和 `GOOGLE_PAY`。
    </Accordion>

    <Warning>
      返现比例会定期变动。购买前务必确认当前比例。
    </Warning>
  </Step>

  <Step title="购买礼品卡" icon="credit-card">
    使用 `purchaseGiftCard` mutation。你可以通过两种方式指定要购买的内容：

    <Tabs>
      <Tab title="方案 A — 商户 slug（推荐）">
        传入 `merchantSlug`，Fluz 将自动应用该商户的最佳可用优惠。

        <CodeGroup>
          ```graphql Mutation theme={null}
          mutation purchaseGiftCard($input: PurchaseGiftCardInput!) {
            purchaseGiftCard(input: $input) {
              purchaseDisplayId
              purchaseAmount
              giftCard {
                giftCardId
                status
                termsAndConditions
              }
            }
          }
          ```

          ```json Variables theme={null}
          {
            "input": {
              "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19",
              "merchantSlug": "starbucks",
              "amount": 50.00,
              "balanceAmount": 50.00
            }
          }
          ```
        </CodeGroup>
      </Tab>

      <Tab title="方案 B — 指定 offer ID">
        传入你从目录中选择的 `offerId`，以购买该特定优惠。

        <CodeGroup>
          ```graphql Mutation theme={null}
          mutation purchaseGiftCard($input: PurchaseGiftCardInput!) {
            purchaseGiftCard(input: $input) {
              purchaseDisplayId
              purchaseAmount
              giftCard {
                giftCardId
                status
                termsAndConditions
              }
            }
          }
          ```

          ```json Variables theme={null}
          {
            "input": {
              "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19",
              "offerId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19",
              "amount": 50.00,
              "balanceAmount": 50.00
            }
          }
          ```
        </CodeGroup>
      </Tab>
    </Tabs>

    #### 输入字段

    <ParamField body="idempotencyKey" type="string" required>
      客户端生成的唯一 UUID，确保购买只被处理一次。
    </ParamField>

    <ParamField body="offerId / merchantSlug" type="UUID / String" required>
      二选一。`merchantSlug` 会自动选择最佳比例；`offerId` 指定特定优惠。
    </ParamField>

    <ParamField body="amount" type="Float" required>
      购买的礼品卡面额。
    </ParamField>

    <ParamField body="Funding source" type="UUID / Float">
      支付方式。使用 `balanceAmount`（Fluz 余额）、`bankAccountId`、`bankCardId` 或 `paypalVaultId`。你可以将 Fluz 余额与其他来源组合支付。
    </ParamField>

    <ParamField body="defaultToBalance" default="true" type="Boolean">
      当其他支付方式失败时回退到 Fluz 余额。设为 `false` 可禁用回退。
    </ParamField>

    <ParamField body="minRewardRate" type="Float">
      使用 `merchantSlug` 购买时可接受的最低奖励比例。
    </ParamField>

    <ParamField body="exclusiveRateId" type="UUID">
      强制使用特定的专属比例。在 `getMerchants` 中可找到类型为 `EXCLUSIVE_RATE_OFFER` 的优惠对应的值。
    </ParamField>

    <ParamField body="userCashBalanceId" type="UUID">
      要扣款的消费账户（现金余额）。
    </ParamField>

    <ParamField body="memo / transactionCategory / attachmentId" type="String / String / UUID">
      可选的费用元数据。`memo` 最长 255 字符；类别在首次使用时创建。参见 [添加费用详情](/features/add-expense-details)。
    </ParamField>

    <Accordion title="示例响应" icon="code">
      ```json theme={null}
      {
        "data": {
          "purchaseGiftCard": {
            "purchaseDisplayId": "1019688",
            "purchaseAmount": 50.00,
            "giftCard": {
              "giftCardId": "85de8b3e-4e72-462c-8ed1-a6f4982e22f7",
              "status": "ACTIVE",
              "termsAndConditions": "Except as required by law, Gift Cards cannot be transferred for..."
            }
          }
        }
      }
      ```
    </Accordion>

    <Info>
      保存响应中的 `giftCardId`——下一步你将用它来揭示卡片。如果购买失败，请查看 [礼品卡错误代码](/gift-card-error-codes)。
    </Info>
  </Step>

  <Step title="揭示礼品卡详情" icon="eye">
    最后，检索可用于兑换的详情（代码、PIN 和/或 URL）。

    <Accordion title="没有 giftCardId？先列出你的卡片（getGiftCards）" icon="list">
      如果你在步骤 3 中刚拿到 `giftCardId`，可跳过本节。否则，先列出你的礼品卡：

      ```graphql theme={null}
      query GetGiftCards {
        getGiftCards(paginate: { limit: 20, offset: 0 }) {
          giftCardId
          status
          createdAt
          deliveryFormat
          termsAndConditions
          merchant { merchantId name slug }
        }
      }
      ```

      你可以使用 `status` 和 `userCashBalanceId` 进行筛选，并使用 `paginate` 进行分页。
    </Accordion>

    ### 揭示兑换详情

    使用 `giftCardId` 调用 `revealGiftCardByGiftCardId`。

    <CodeGroup>
      ```graphql Mutation theme={null}
      mutation RevealGiftCard($giftCardId: UUID!) {
        revealGiftCardByGiftCardId(giftCardId: $giftCardId) {
          code
          pin
          url
          termsAndConditions
        }
      }
      ```

      ```json Variables theme={null}
      {
        "giftCardId": "7c57c381-4b19-49e4-bbb0-404a45166ee4"
      }
      ```
    </CodeGroup>

    <Accordion title="示例响应" icon="code">
      ```json theme={null}
      {
        "data": {
          "revealGiftCardByGiftCardId": {
            "code": "9877890000000000",
            "pin": "2014",
            "url": null,
            "termsAndConditions": "Except as required by law, Gift Cards cannot be transferred for..."
          }
        }
      }
      ```
    </Accordion>

    <Note>
      兑换字段因商户而异。有些卡仅返回字母数字 `code` 而无 `pin`；另一些仅返回 `url`。请始终基于 **`getGiftCards`** 返回的 `deliveryFormat` 渲染（而非 `getMerchants`）——商户的活动优惠在购买后可能变更，而 `getGiftCards` 反映卡片实际购买时的格式。
    </Note>

    <Warning>
      **详情未立即返回？** 使用指数退避轮询 `revealGiftCardByGiftCardId`：从 **300ms** 开始，每次翻倍（300 → 600 → 1200 → 2400ms…），最大延迟 **180000ms（3 分钟）**。一旦返回详情即停止。这样既兼顾响应性，又能降低负载并避免不必要的超时。
    </Warning>
  </Step>
</Steps>

## 大功告成 🎉

你已跑通完整交易——充值余额、浏览优惠、购买礼品卡并揭示其详情。接下来，探索更多 API：

<CardGroup cols={2}>
  <Card title="Virtual Cards" icon="credit-card" href="/features/virtual-cards">
    开立并管理可被网络受理的虚拟卡。
  </Card>

  <Card title="Wallets & Transfers" icon="wallet" href="/features/create-spend-accounts">
    开设消费账户并在其间转账。
  </Card>

  <Card title="Transaction Activity" icon="receipt" href="/features/get-all-transactions">
    拉取、筛选并标注交易历史。
  </Card>

  <Card title="Embedded Widgets" icon="book-copy" href="/developers/widgets">
    将 Fluz 的流程直接嵌入到你的自有界面中。
  </Card>
</CardGroup>

<Note>
  **想了解更多？** 通过 [support@fluz.app](mailto:support@fluz.app) 联系我们，与专家交流或申请演示。
</Note>
