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

# 添加费用明细

你可以为任何交易添加**备注**、**类别**和/或**附件**（例如收据、发票、采购订单）。可以在原始交易发生时添加注释，或之后通过 `updateTransactionMetadata` mutation 进行更新。

支持注释的场景：

* 存款（`depositCashBalance1`）
* 礼品卡购买（`purchaseGiftCard`）
* 钱包转账（`createTransfer`, `transferInternalBalance`）
* 虚拟卡创建（`createVirtualCard`）
  * 不支持 `attachment`

***

## 工作原理

1. 可选：如果你想附加文件，先通过 REST 上传端点上传。你会获得一个 `attachmentId`。
2. 将 `memo`、`transactionCategory` 和/或 `attachmentId` 传入你的 mutation 输入中——可在交易发生时传入，或之后通过 `updateTransactionMetadata` 传入。
3. 通过 `getTransactions`、`getUserPurchases` 或 mutation 响应读取注释。`attachmentUrl` 字段会返回一个短期有效的签名 URL 用于文件访问。

***

## 第 1 步：上传附件（可选）

> 这是一个 REST 端点，而不是 GraphQL mutation

### 端点

`POST /api/v1/file-upload/transaction-memo-attachment`

### 认证

`Authorization: Bearer <YOUR_USER_ACCESS_TOKEN>`

### 请求

以 `multipart/form-data` 发送文件，字段名为 `file`。

**接受类型：** `application/pdf`, `image/png`

> ⚠️ 交易附件不接受 JPEG。

### 示例请求

```bash theme={null}
curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/file-upload/transaction-memo-attachment \
  -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>" \
  -F "file=@receipt.pdf"
```

### 响应

```json theme={null}
{
  "attachmentId": "3f2a1b4c-e5d6-7890-abcd-ef1234567890"
}
```

复制该 `attachmentId` —— 你将在下一步将其传入 mutation 输入中。

> `attachmentId` 的作用域限定在你的账户内。当你提交 mutation 时，系统会验证该文件是否存在于你账户的存储中。来自其他账户的 ID 将被拒绝。

### 上传错误

| 原因               | 状态  | 详情                  |
| ---------------- | --- | ------------------- |
| 请求中未包含文件         | 400 | Missing file        |
| 文件类型不允许（例如 JPEG） | 400 | `INVALID_ARGUMENTS` |

***

## 第 2 步：为交易添加注释

你可以在原始交易发生时提供注释，**或**在之后进行更新。

### 选项 A — 交易发生时

以下 mutations 在其输入中接受可选字段 `memo`、`transactionCategory` 和 `attachmentId`：

* `depositCashBalance` → `DepositCashBalanceInput`
* `purchaseGiftCard` → `PurchaseGiftCardInput`
* `createTransfer` → `CreateTransferInput`
* `transferInternalBalance` → `TransferInternalBalanceInput`

#### 注释字段

| 字段                    | 类型     | 描述                                      |
| --------------------- | ------ | --------------------------------------- |
| `memo`                | String | 自由文本备注。最多 255 个字符。                      |
| `transactionCategory` | String | 类别标签。自由形式——类别会在首次使用时自动创建，如果再次传入相同名称则复用。 |
| `attachmentId`        | String | 上传端点返回的 ID。必须在提交 mutation 前完成文件上传。      |

#### 示例 — 带注释的礼品卡购买

```graphql theme={null}
mutation purchaseGiftCard($input: PurchaseGiftCardInput!) {
  purchaseGiftCard(input: $input) {
    purchaseDisplayId
    purchaseAmount
    memo
    transactionCategory
    attachmentUrl
    giftCard {
      giftCardId
      status
    }
  }
}
```

```json theme={null}
{
  "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19",
  "offerId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19",
  "amount": 50.00,
  "balanceAmount": 50.00,
  "memo": "Team lunch — Q2",
  "transactionCategory": "Meals & Entertainment",
  "attachmentId": "3f2a1b4c-e5d6-7890-abcd-ef1234567890"
}
```

***

### 选项 B — 交易之后（`updateTransactionMetadata`）

使用此 mutation 为任意已有交易添加或更新注释。

> 部分更新语义：只会更新你传入的字段。未包含的字段保持不变。传入 `null` 可清空字段。

#### Mutation

```graphql theme={null}
mutation updateTransactionMetadata($input: UpdateTransactionMetadataInput!) {
  updateTransactionMetadata(input: $input) {
    record_id
    memo
    transactionCategory
    attachmentUrl
  }
}
```

#### UpdateTransactionMetadataInput

| 字段                    | 类型     | 必填 | 描述                                    |
| --------------------- | ------ | -- | ------------------------------------- |
| `recordId`            | UUID!  | 是  | 要更新的交易的 `record_id`。                  |
| `memo`                | String | 否  | 自由文本备注。最多 255 个字符。省略则不变；传入 `null` 清空。 |
| `transactionCategory` | String | 否  | 类别标签。省略则不变；传入 `null` 清空。              |
| `attachmentId`        | String | 否  | 上传端点返回的 ID。省略则不变；传入 `null` 清空。        |

#### 必需的作用域

`LIST_PAYMENT` 和 `LIST_PURCHASES`

#### 示例 — 添加备注和类别

```json theme={null}
{
  "recordId": "550e8400-e29b-41d4-a716-446655440000",
  "memo": "Q1 vendor payment",
  "transactionCategory": "Operating Expenses"
}
```

#### 示例 — 为现有交易附加文件

```json theme={null}
{
  "recordId": "550e8400-e29b-41d4-a716-446655440000",
  "attachmentId": "3f2a1b4c-e5d6-7890-abcd-ef1234567890"
}
```

#### 示例 — 清空备注

```json theme={null}
{
  "recordId": "550e8400-e29b-41d4-a716-446655440000",
  "memo": null
}
```

#### 示例响应

```json theme={null}
{
  "data": {
    "updateTransactionMetadata": {
      "record_id": "550e8400-e29b-41d4-a716-446655440000",
      "memo": "Q1 vendor payment",
      "transactionCategory": "Operating Expenses",
      "attachmentUrl": "https://storage.googleapis.com/..."
    }
  }
}
```

#### 错误

| 原因                        | 错误                                                                             |
| ------------------------- | ------------------------------------------------------------------------------ |
| 找不到 `recordId` 或属于其他账户    | `INVALID_ARGUMENTS` — transaction not found                                    |
| 交易类型不支持元数据                | `INVALID_ARGUMENTS` — transaction does not support metadata                    |
| `attachmentId` 不是有效的 UUID | `INVALID_ARGUMENTS` — Invalid attachment ID                                    |
| 存储中未找到文件（未上传，或账户不匹配）      | `INVALID_ARGUMENTS` — Attachment file not found. Please upload the file first. |

***

## 读取注释

以下接口会返回注释：

| 查询 / Mutation               | 类型                   | 字段                                             |
| --------------------------- | -------------------- | ---------------------------------------------- |
| `getTransactions`           | `Transaction`        | `memo`, `transactionCategory`, `attachmentUrl` |
| `getUserPurchases`          | `UserPurchase`       | `memo`, `transactionCategory`, `attachmentUrl` |
| `updateTransactionMetadata` | `Transaction`        | `memo`, `transactionCategory`, `attachmentUrl` |
| `depositCashBalance`        | `CashBalanceDeposit` | `attachmentUrl`                                |

> ⚠️ `attachmentUrl` 是签名 URL，生成后会很快过期。请不要存储它——当你需要显示或访问文件时重新获取该交易。
