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

# 上传文档

> 提交 KYC/KYB 文档以进行用户验证

## 概览

为完成 KYC/KYB 验证，您可能需要上传支持文档，例如身份证图像、地址证明和资金来源文档。

<Info>
  文档必须是清晰、未经更改的 JPG、PNG 或 PDF 格式图像，每个文件最大 10MB。
</Info>

## 文档类型

### 个人文档

<Tabs>
  <Tab title="身份文档">
    **带照片的政府签发的身份证**

    * `PASSPORT` - 护照
    * `DRIVER_LICENSE` - 驾照
    * `NUIP` - 哥伦比亚身份证
    * `INE` - 墨西哥选举身份证
    * `IFE` - 墨西哥选举身份证（旧）
    * `RFC` - 墨西哥税号
    * `SSN` - 美国社会保障
    * `CURP` - 墨西哥人口登记
    * `CPF` - 巴西税号
    * `DNI` - 国民身份证
    * `DUI` - 唯一身份证件

    **要求：**

    * 正面图像（始终必需）
    * 背面图像（如适用）
    * 清晰、可读的文本
    * 所有角落可见
    * 无眩光或阴影
  </Tab>

  <Tab title="地址证明">
    **显示当前地址的文档**

    * `PROVE_OF_ADDRESS_UTILITY_BILL` - 水电费账单
    * `PROVE_OF_ADDRESS_BANK_STATEMENT` - 银行对账单
    * `PROVE_OF_ADDRESS_LEASE_AGREEMENT` - 租赁协议

    **要求：**

    * 在过去 3 个月内签发
    * 显示全名
    * 显示完整地址
    * 来自认可的提供商/机构
  </Tab>

  <Tab title="资金来源">
    **收入来源文档**

    * `SOURCE_OF_FUNDS_BANK_STATEMENT` - 银行对账单
    * `SOURCE_OF_FUNDS_PAYSLIP` - 工资单
    * 就业信函
    * 商业财务报表

    **要求：**

    * 最近的（最近 3 个月）
    * 显示定期收入
    * 与声明的来源匹配
    * 来自官方机构
  </Tab>
</Tabs>

### 公司文档

* `NIT` - 哥伦比亚税号
* `PROOF_OF_COMPANY_FORMATION` - 成立文档
* `ARTICLES_OF_INCORPORATION` - 公司章程
* `INCORPORATION_DOCUMENTS` - 法律文档
* `KYB_REPORT` - 了解您的业务报告
* `OTHER` - 其他商业文档

## 上传个人文档

```bash theme={null}
POST /api/v2/users/person/document
Content-Type: multipart/form-data
```

<CodeGroup>
  ```javascript Node.js theme={null}
  const uploadDocument = async (userId, documentType, frontFile, backFile = null) => {
    const formData = new FormData();
    formData.append('userId', userId);
    formData.append('documentType', documentType);
    formData.append('frontDocument', frontFile);
    
    if (backFile) {
      formData.append('backDocument', backFile);
    }
    
    const response = await fetch(
      'https://sandbox.killb.app/api/v2/users/person/document',
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${accessToken}`
        },
        body: formData
      }
    );
    
    if (!response.ok) {
      const error = await response.json();
      throw new Error(`上传失败: ${error.message}`);
    }
    
    return await response.json();
  };

  // 使用
  const frontImage = new File([blob], 'passport-front.jpg', { type: 'image/jpeg' });
  await uploadDocument(userId, 'PASSPORT', frontImage);
  ```

  ```python Python theme={null}
  def upload_document(user_id, document_type, front_file, back_file=None, access_token):
      """上传 KYC 文档以进行用户验证"""
      
      files = {
          'frontDocument': ('front.jpg', front_file, 'image/jpeg')
      }
      
      if back_file:
          files['backDocument'] = ('back.jpg', back_file, 'image/jpeg')
      
      data = {
          'userId': user_id,
          'documentType': document_type
      }
      
      response = requests.post(
          'https://sandbox.killb.app/api/v2/users/person/document',
          headers={
              'Authorization': f'Bearer {access_token}'
          },
          files=files,
          data=data
      )
      
      if response.status_code != 201:
          raise Exception(f"上传失败: {response.json()}")
      
      return response.json()

  # 使用
  with open('passport_front.jpg', 'rb') as front_file:
      upload_document(user_id, 'PASSPORT', front_file, access_token=token)
  ```

  ```php PHP theme={null}
  <?php
  function uploadDocument($userId, $documentType, $frontPath, $backPath = null, $token) {
      $url = 'https://sandbox.killb.app/api/v2/users/person/document';
      
      $fields = [
          'userId' => $userId,
          'documentType' => $documentType,
          'frontDocument' => new CURLFile($frontPath)
      ];
      
      if ($backPath) {
          $fields['backDocument'] = new CURLFile($backPath);
      }
      
      $ch = curl_init($url);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
      curl_setopt($ch, CURLOPT_HTTPHEADER, [
          'Authorization: Bearer ' . $token
      ]);
      
      $response = curl_exec($ch);
      return json_decode($response, true);
  }
  ?>
  ```
</CodeGroup>

## 上传公司文档

```bash theme={null}
POST /api/v2/users/company/document
Content-Type: multipart/form-data
```

```javascript theme={null}
const uploadCompanyDocument = async (userId, documentType, documentFile) => {
  const formData = new FormData();
  formData.append('userId', userId);
  formData.append('documentType', documentType);
  formData.append('frontDocument', documentFile);
  
  const response = await fetch(
    'https://sandbox.killb.app/api/v2/users/company/document',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`
      },
      body: formData
    }
  );
  
  return await response.json();
};
```

## 文档指南

### 照片质量

<AccordionGroup>
  <Accordion title="图像要求" icon="camera" defaultOpen>
    ✅ **良好：**

    * 清晰、聚焦的图像
    * 所有文本可读
    * 所有角落可见
    * 良好的照明
    * 原始颜色
    * 最低 300 DPI

    ❌ **不良：**

    * 模糊或失焦
    * 眩光或阴影
    * 裁剪的角落
    * 黑白副本
    * 截图
    * 低分辨率
  </Accordion>

  <Accordion title="文件格式" icon="file">
    **接受的格式：**

    * JPEG/JPG（推荐）
    * PNG
    * PDF（用于对账单/信函）

    **大小限制：**

    * 最大：每个文件 10 MB
    * 最小：100 KB
    * 推荐：1-3 MB
  </Accordion>

  <Accordion title="内容指南" icon="check">
    * 无编辑或更改
    * 仅当前/有效文档
    * 完整文档可见
    * 原始文档（无复印件）
    * 首选彩色图像
    * 最近的照片（地址证明在 6 个月内）
  </Accordion>
</AccordionGroup>

### 拍摄好照片

<Steps>
  <Step title="使用良好照明">
    自然光效果最好。避免闪光灯。
  </Step>

  <Step title="平坦表面">
    将文档放在平坦、对比鲜明的表面上
  </Step>

  <Step title="正确对齐">
    文档应该笔直且居中
  </Step>

  <Step title="填满画面">
    文档应占据图像的大部分
  </Step>

  <Step title="检查质量">
    上传前验证所有文本可读
  </Step>
</Steps>

## 客户端验证

上传前验证文档：

```javascript theme={null}
const validateDocument = (file) => {
  const errors = [];
  
  // 文件大小
  const maxSize = 10 * 1024 * 1024; // 10MB
  if (file.size > maxSize) {
    errors.push('文件太大。最大 10MB。');
  }
  
  if (file.size < 100 * 1024) {
    errors.push('文件太小。最小 100KB。');
  }
  
  // 文件类型
  const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'application/pdf'];
  if (!allowedTypes.includes(file.type)) {
    errors.push('无效的文件类型。使用 JPG、PNG 或 PDF。');
  }
  
  // 图像尺寸（对于图像）
  if (file.type.startsWith('image/')) {
    const img = new Image();
    img.src = URL.createObjectURL(file);
    
    img.onload = () => {
      if (img.width < 800 || img.height < 600) {
        errors.push('图像分辨率太低。最低 800x600。');
      }
    };
  }
  
  return errors;
};
```

## 跟踪上传状态

监控文档处理：

```javascript theme={null}
const trackDocumentStatus = async (userId) => {
  // 获取用户详情
  const user = await fetch(`/api/v2/users/${userId}`, {
    headers: { 'Authorization': `Bearer ${token}` }
  }).then(r => r.json());
  
  console.log('当前状态:', user.status);
  console.log('备注:', user.note);
  
  // 如果待处理，检查合规 URL
  if (user.status === 'PENDING') {
    console.log('在以下位置完成 KYC:', user.complianceUrl);
  }
  
  return user;
};
```

## 文档更新的 Webhooks

订阅 USER 事件以接收通知：

```json theme={null}
{
  "event": "user.kyc_submitted",
  "data": {
    "userId": "user-id",
    "documentsSubmitted": ["PASSPORT", "PROVE_OF_ADDRESS_UTILITY_BILL"],
    "status": "PENDING"
  }
}
```

```json theme={null}
{
  "event": "user.kyc_approved",
  "data": {
    "userId": "user-id",
    "status": "ACTIVE"
  }
}
```

## 处理拒绝

如果文档被拒绝：

```json theme={null}
{
  "event": "user.kyc_rejected",
  "data": {
    "userId": "user-id",
    "reason": "文档图像质量不足",
    "rejectedDocuments": ["PASSPORT"],
    "status": "REJECTED"
  }
}
```

**需要采取的行动：**

1. 通知用户拒绝
2. 从 `reason` 解释具体问题
3. 请求重新提交
4. 提供上传指南
5. 监控重新提交

## 文档保留

<Info>
  文档根据监管要求安全存储和保留。用户可以根据 GDPR/隐私法请求删除。
</Info>

## 最佳实践

<AccordionGroup>
  <Accordion title="用户体验" icon="face-smile">
    * 显示实时上传进度
    * 提交前预览图像
    * 提供照片指南
    * 如果质量差，允许重拍
    * 在步骤之间保存进度
  </Accordion>

  <Accordion title="移动优化" icon="mobile">
    * 使用设备相机捕获
    * 自动优化图像大小
    * 上传前压缩
    * 处理网络中断
    * 显示数据使用估算
  </Accordion>

  <Accordion title="错误处理" icon="shield-exclamation">
    * 首先在客户端验证文件
    * 显示特定错误消息
    * 允许轻松重试
    * 失败时保留表单数据
    * 记录上传错误
  </Accordion>
</AccordionGroup>

## 合规 URL 替代方案

不要自己处理文档上传，而是将用户重定向到 KillB 的合规 URL：

```javascript theme={null}
const user = await createUser(userData);

// 重定向到 KillB 的 KYC 门户
window.location.href = user.complianceUrl;
```

**优势：**

* KillB 处理整个 KYC 流程
* 内置文档捕获
* 自动验证
* 移动优化
* 多语言支持
* 包含活体检测

<Tip>
  使用合规 URL 是最简单的集成路径。用户在 KillB 的门户上完成 KYC，您会收到 webhook 通知。
</Tip>

## 下一步

<CardGroup cols={2}>
  <Card title="管理用户" icon="users-gear" href="/zh/guides/users/manage-users">
    查询和更新用户信息
  </Card>
</CardGroup>
