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

# ISV Onboarding API

# 概述

本套 API 面向 **ISV（独立软件开发商）** 客户：A 端为其终端客户（company）发起开户申请，由 OristaPay 平台完成企业查册、合规审核、IDV、签署、自动开通钱包等全流程。

ISV 客户完成 onboarding 后，拿到 `companyCode` 与 `walletId`，可直接复用 [ISV 交易 API](./transaction-api) **代客户**完成转账、收款、Payout 等交易。**交易接口字段零变更**——A 端调用时只需正常传 `walletId`，OristaPay 会按 ISV ↔ 商户的归属关系自动完成鉴权。

<Note>
  本套 API 的鉴权、签名、请求头、响应信封、文件上传、错误码体系**与 RDPAY API 完全一致**，未在本文档重复说明的部分请参考 [ISV 交易 API](./transaction-api)。
</Note>

## 业务流程

```mermaid theme={null}
sequenceDiagram
    participant A as ISV (A)
    participant 平台 as OristaPay
    participant B as 商户 (B)

    rect rgb(242,247,250)
        note over A,平台: 提交申请
        A->>+平台: 文件上传 (FileUpload)
        平台-->>-A: fileKey
        A->>+平台: 提交开户申请 (SubmitApplication)
        平台->>平台: 资料校验
        平台-->>-A: 返回 applicationNo (同步)
    end

    rect rgb(235,245,255)
        note over A,平台: 后台异步审核
        平台->>平台: 企业查册
        平台->>+A: webhook ONBOARDING_APPLICATION_STATUS_NOTIFICATION (PENDING_USER)
        A-->>-平台: 200 OK
    end

    rect rgb(227,242,253)
        note over A,B: IDV + 签署
        A->>+平台: 获取 IDV 链接 (GetIdvLink)
        平台-->>-A: linkUrl
        A->>+B: 转发 H5 链接（邮件/短信/扫码）
        B->>+平台: 打开 H5 链接，完成 IDV → 签署
        Note over 平台,A: 成功事件不推 webhook，A 端通过 QueryApplication 拉进度
        opt IDV 失败（vendor 失败 / 人工 case 失败）
            平台->>+A: webhook ONBOARDING_KEY_PEOPLE_IDV_FAILED
            A-->>-平台: 200 OK
        end
    end

    rect rgb(220,237,200)
        note over A,平台: 终态
        平台->>平台: 全部审核节点通过 + 自动开通钱包
        平台->>+A: webhook ONBOARDING_APPLICATION_STATUS_NOTIFICATION (APPROVED)
        A-->>-平台: 200 OK
        A->>+平台: 查询企业完整资料 (QueryMainCompanyProfile)
        平台-->>-A: 企业详情 + walletId
    end
```

## 标识符约定

| 标识                 | 生成方                  | 用途                                |
| ------------------ | -------------------- | --------------------------------- |
| `extApplicationNo` | A 侧生成                | 外部申请单号，全局唯一；                      |
| `applicationNo`    | OristaPay 侧生成        | OristaPay 申请单号；查询、回调载荷的主键         |
| `companyCode`      | OristaPay 侧（审核通过后生成） | company 企业编码；用于查询完整资料、后续交易场景的企业身份 |
| `peopleId`         | OristaPay 侧（提交后生成）   | keyPeople 全局 ID；用于获取 IDV / 签署链接   |
| `walletId`         | OristaPay 侧（钱包开通后）   | 钱包 ID；后续交易接口使用                    |

# OnboardingAPI

## 接口索引

| # | Path                                          | 用途                               |
| - | --------------------------------------------- | -------------------------------- |
| 1 | `POST /storage/media/dg-putObject?mediaType=` | 文件上传                             |
| 2 | `POST /api/v1/onboarding/application/submit`  | 提交开户申请                           |
| 3 | `POST /api/v1/onboarding/application/query`   | 查询申请进度                           |
| 4 | `POST /api/v1/onboarding/idv/link`            | 获取 IDV 链接（**单一链接连续承载 IDV + 签署**） |
| 5 | `POST /api/v1/onboarding/link/refresh`        | 刷新链接过期时间                         |
| 6 | `POST /api/v1/onboarding/profile`             | 查询审核通过的完整企业资料                    |

***

## 1. 文件上传 (FileUpload)

**接口概述**

提交底层客户资料前,需使用该接口上传企业相关的文件。

**请求参数**

| 参数名       | 类型     | 是否必填 | 描述                               |
| :-------- | :----- | :--- | :------------------------------- |
| mediaType | string | 是    | 枚举: jpeg / jpg / png / pdf / gif |
| mediaFile | file   | 是    | 上传的文件,不超过20M                     |

**响应参数**

| 参数名     | 类型     | 描述                                |
| :------ | :----- | :-------------------------------- |
| code    | int32  | 响应码,表示请求处理结果。 如 1表示成功,其他值表示不同错误情况 |
| message | string | 响应消息,提供详细的处理信息或错误说明               |
| data    | string | 校验成功时返回,返回数据为文件的key               |

**响应示例**

```json theme={null}
{
	"code":1,
	"message":"success",
	"data":"123876789124.png"
}
```

## 2. 提交开户申请 (SubmitApplication)

**接口概述**

为下游商户（B）提交 company 开户申请。首次提交，或上一笔申请审核拒绝（`REJECTED`）后重新提交，均走此接口。

**请求参数**

| 参数名              | 类型          | 是否必填 | 描述                          |
| :--------------- | :---------- | :--- | :-------------------------- |
| extApplicationNo | string(128) | 是    | A 侧生成的外部申请单号，全局唯一           |
| profile          | Object      | 是    | company 企业资料表单，结构详见【企业资料表单】 |

**响应参数**

| 参数名     | 类型              | 描述                       |
| :------ | :-------------- | :----------------------- |
| code    | int32           | 响应码：`1` 表示成功，其他值表示不同错误情况 |
| message | string          | 响应消息                     |
| data    | ApplicationData | 申请受理成功时返回                |
| errors  | Array\<Errors>  | 资料校验失败（`code = 6802`）时返回 |

**ApplicationData 字段说明**

| 字段名                   | 类型     | 描述                         |
| :-------------------- | :----- | :------------------------- |
| applicationNo         | string | OristaPay 申请单号；驳回后重提会生成新单号 |
| applicationCreateTime | int64  | 申请创建时间，毫秒时间戳               |

**Errors 字段说明**

| 字段名     | 类型     | 描述          |
| :------ | :----- | :---------- |
| message | string | 见附录【档案错误描述】 |

**请求示例**

```json theme={null}
{
  "extApplicationNo": "EXT20260512001",
  "profile": {
    "entityDetail": { "businessType": 1, "incorpPlace": "HKG" },
    "businessDetail": { "list": [{ "subIndustryCode": "I300734" }] },
    "shareholder": { "list": [{ "shareholderId": 1 }] },
    "keyPeople": { "quorum": 2, "directorNum": 2, "people": [] },
    "accountOpeningQuestionnaire": {
      "accountOpeningPurposes": ["PAYMENT_FOR_HK_BUSINESS"],
      "expectedMonthlyVolume": "FROM_2_5M_TO_5M_HKD",
      "handleClientMoney": false
    }
  }
}
```

**响应示例（成功）**

```json theme={null}
{
  "code": 1,
  "message": "Success",
  "data": {
    "applicationNo": "A202605258664",
    "applicationCreateTime": 1747094400000
  }
}
```

**响应示例（资料校验失败）**

```json theme={null}
{
  "code": 6802,
  "message": "Profile validation failed.",
  "errors": [
    { "message": "[Business details]Industry code cannot be empty!" },
    { "message": "[Entity details]Please upload a valid proof of Business Registration" }
  ]
}
```

**关键约束**

* **幂等键 `(extApplicationNo)`**：同 `extApplicationNo` 重复提交，body 一致 → 返回原 `applicationNo`；body 不一致 → `6801`
* **同一商户防并发**：同一 B 的 `(CI 号 / BR 号)` 在 A 名下只允许一份**活跃**申请。当前申请进入终态（`APPROVED` / `REJECTED`）后才可再提交
* **驳回后重提**：必须使用**新的** `extApplicationNo`，提交后生成新的 `applicationNo`
* **查册失败 = 自动驳回**：API 路径下，无论是 **企业查册定级失败**（CI/BR 找不到等）还是 **查册比对不通过**（数据不一致），都会直接将申请置为 `REJECTED` 并推送 `REJECTED` webhook，A 需换 `extApplicationNo` 重新提交

## 3. 查询申请进度 (QueryApplication)

**接口概述**

按 `applicationNo` 或 `extApplicationNo` 查询申请进度（二选一）。

**请求参数**

| 参数名              | 类型     | 是否必填 | 描述             |
| :--------------- | :----- | :--- | :------------- |
| applicationNo    | string | 条件   | OristaPay 申请单号 |
| extApplicationNo | string | 条件   | A 侧申请单号        |

**响应参数**

| 参数名     | 类型                   | 描述             |
| :------ | :------------------- | :------------- |
| code    | int32                | 响应码            |
| message | string               | 响应消息           |
| data    | QueryApplicationData | `code = 1` 时返回 |

**QueryApplicationData 字段说明**

| 字段名                   | 类型     | 描述                                                             |
| :-------------------- | :----- | :------------------------------------------------------------- |
| applicationNo         | string | OristaPay 申请单号                                                 |
| extApplicationNo      | string | A 侧申请单号                                                        |
| applicationCreateTime | int64  | 申请创建时间，毫秒时间戳                                                   |
| applicationStatus     | string | 审核状态：`UNDER_REVIEW` / `PENDING_USER` / `APPROVED` / `REJECTED` |
| companyCode           | string | 企业编码（审核通过后返回）                                                  |
| walletId              | int64  | 钱包 ID（钱包开通后返回）                                                 |
| profile               | Object | 企业资料表单，见【企业资料表单】                                               |

<Note>
  **`applicationStatus` 取值**：

  * `UNDER_REVIEW`：受理中或审核中（覆盖查册中、人工审核中等所有非终态非待用户阶段）
  * `PENDING_USER`：后台查册通过，等待 keyPeople 完成 IDV / 签署；**此时（且仅此时）才可调 `GetIdvLink`**，之前调用返回 `6406`
  * `APPROVED`：审核通过、钱包已开通；取 `companyCode` / `walletId` 做后续业务
  * `REJECTED`：申请被驳回；API 场景下无法恢复，需换新的 `extApplicationNo` 重新提交

  query 与 webhook 的 `applicationStatus` 语义一致。如果 A 端因网络异常错过 `PENDING_USER` webhook，可主动调 query 看到同样的状态。
</Note>

> `profile.keyPeople.people[]` 中各 `KeyPeopleInfo` 含 `peopleId`（用于获取 IDV / 签署链接）、`idvStatus` / `mandateStatus` 两个状态字段（实时反映该人员的 IDV / 签署进度）。

**请求示例**

```json theme={null}
{
  "applicationNo": "A202605258664"
}
```

**响应示例**

```json theme={null}
{
  "code": 1,
  "message": "Success",
  "data": {
    "applicationNo": "A202605258664",
    "extApplicationNo": "EXT20260512001",
    "applicationCreateTime": 1747094400000,
    "applicationStatus": "PENDING_USER",
    "companyCode": "",
    "walletId": 0,
    "profile": {
      "entityDetail": { "...": "..." },
      "keyPeople": {
        "people": [
          {
            "peopleId": 1000123456,
            "lastNameEn": "Lo",
            "firstNameEn": "Sang",
            "idvStatus": "UNDER_REVIEW",
            "mandateStatus": "NOT_STARTED",
            "isDirector": true,
            "isUbo": false
          }
        ]
      }
    }
  }
}
```

## 4. 获取 IDV 链接 (GetIdvLink)

**接口概述**

为指定 `peopleId` 生成一次性 H5 链接。**单一链接连续承载 IDV → 签署两步**：B 用户打开链接后先完成 IDV，IDV 通过后页面自动进入签署流程；`isMandateUser = false` 的人员只走 IDV，IDV 完成即结束。

链接由 A 自行送达给 B 用户（邮件 / 短信 / 二维码扫码 / 跳转，A 决定）。

<Note>
  **前置条件**：必须先收到 `ONBOARDING_APPLICATION_STATUS_NOTIFICATION` 中 `applicationStatus = PENDING_USER` 的 webhook 后才能调用本接口。在此之前后端处于企业查册阶段，调用将返回 `6406`，提示等待 `PENDING_USER` webhook。
</Note>

**请求参数**

| 参数名           | 类型     | 是否必填 | 描述                                                                |
| :------------ | :----- | :--- | :---------------------------------------------------------------- |
| applicationNo | string | 是    | OristaPay 申请单号                                                    |
| peopleId      | int64  | 是    | 从 `QueryApplication` 返回的 `profile.keyPeople.people[].peopleId` 取得 |

**响应参数**

| 参数名     | 类型       | 描述             |
| :------ | :------- | :------------- |
| code    | int32    | 响应码            |
| message | string   | 响应消息           |
| data    | LinkData | `code = 1` 时返回 |

**LinkData 字段说明**

| 字段名        | 类型     | 描述                          |
| :--------- | :----- | :-------------------------- |
| linkUrl    | string | 一次性 H5 链接；用户访问后连续完成 IDV 与签署 |
| expireTime | int64  | 链接过期时间，毫秒时间戳；默认 **24 小时**   |

**请求示例**

```json theme={null}
{
  "applicationNo": "A202605258664",
  "peopleId": 1000123456
}
```

**响应示例**

```json theme={null}
{
  "code": 1,
  "message": "Success",
  "data": {
    "linkUrl": "https://customer.oristapay.com/h5/index?code=4e0af338-9605-4353-86db-f35c4fdb7959",
    "expireTime": 1747180800000
  }
}
```

**关键约束**

* 链接为**一次性**：B 完成完整流程（IDV + 签署）后链接失效；过期未使用同样失效
* 同一 `peopleId` 重复调用：**已有未过期链接** → 直接返回原链接；**已过期** → 自动作废原链接并生成新链接
* 该人员已完成全部任务（IDV 通过且 `isMandateUser=true` 时签署也完成）时再调用返回 `6001`

## 5. 刷新链接过期时间 (RefreshIdvLink)

**接口概述**

链接过期（`expireTime < now`）或临近过期时，A 调用此接口生成新链接；原链接立即失效。

**请求参数**

| 参数名           | 类型     | 是否必填 | 描述              |
| :------------ | :----- | :--- | :-------------- |
| applicationNo | string | 是    | OristaPay 申请单号  |
| peopleId      | int64  | 是    | keyPeople 全局 ID |

**响应参数**

`LinkData`：字段同 §4。

**请求示例**

```json theme={null}
{
  "applicationNo": "A202605258664",
  "peopleId": 1000123456
}
```

**响应示例**

同 §4。

**关键约束**

* 前置条件同 §4：必须先收到 `PENDING_USER` webhook，否则返回 `6406`
* 调用后原链接立即作废
* 若该人员已完成全部任务（IDV 通过且签署完成），调用返回 `6001`
* 若申请已进入终态（`APPROVED` / `REJECTED`），调用返回 `6001`

## 6. 查询审核通过的完整企业资料 (QueryCompanyProfile)

**接口概述**

按 `companyCode` 查询审核通过后的企业完整资料。

**请求参数**

| 参数名         | 类型     | 是否必填 | 描述                                           |
| :---------- | :----- | :--- | :------------------------------------------- |
| companyCode | string | 是    | 企业编码（审核通过后从 `QueryApplication` 或 Webhook 拿到） |

**响应参数**

| 参数名     | 类型             | 描述                 |
| :------ | :------------- | :----------------- |
| code    | int32          | 响应码                |
| message | string         | 响应消息               |
| data    | ProfileFormDto | 完整企业资料，结构见【企业资料表单】 |

**请求示例**

```json theme={null}
{
  "companyCode": "HKM12389h"
}
```

**响应示例**

```json theme={null}
{
  "code": 1,
  "message": "Success",
  "data": {
    "entityDetail": { "...": "..." },
    "businessDetail": { "...": "..." },
    "shareholder": { "...": "..." },
    "keyPeople": { "...": "..." }
  }
}
```

**关键约束**

* 仅可查询审核通过（`applicationStatus = APPROVED`）的企业；尚未通过的企业返回 `6001` + 提示 "Profile is not approved yet."
* `companyCode` 必须属于当前 A，否则返回 `6005`
* 资料以 OristaPay 侧最新审核结果为准，可能与提交时的 `profile` 不完全一致（审核员可补录或修正部分字段）

# Callback

回调请求体、签名验证、响应要求与 [ISV 交易 API · Callback](./transaction-api#callback) 完全一致，本文档仅定义新增的 `OpenBizType` 与载荷字段。

## 1. 新增的 OpenBizType 枚举

| 枚举值                                           | 触发时机                                        |
| :-------------------------------------------- | :------------------------------------------ |
| ONBOARDING\_APPLICATION\_STATUS\_NOTIFICATION | 申请状态变更（PENDING\_USER / APPROVED / REJECTED） |
| ONBOARDING\_KEY\_PEOPLE\_IDV\_FAILED          | 某个 keyPeople IDV 失败（vendor 失败或人工 case 失败）   |

## 2. 回调数据类型

### 2.1 申请状态变更 (OnboardingApplicationStatusNotification)

**触发时机（仅关键审核节点，节点变更不会逐个推送）**

| applicationStatus | 触发时机                               | A 端动作                                                                |
| :---------------- | :--------------------------------- | :------------------------------------------------------------------- |
| `PENDING_USER`    | 后台企业查册通过 → 可以开始 keyPeople 的 IDV/签署 | 此时（且仅此时）才可调用 `GetIdvLink`（在此之前调用会返回 `6406`）                          |
| `APPROVED`        | 申请最终审核通过、钱包已开通                     | 取出 `companyCode` / `walletId` 做后续业务                                  |
| `REJECTED`        | 申请被终态驳回（含查册失败、合规失败等）               | API 场景下被拒申请**无法恢复**，A 需重新提交申请（`extApplicationNo` 不能复用原值，否则命中幂等返回旧记录） |

<Note>
  * 申请刚提交（`UNDER_REVIEW` 状态首次进入）不会单独推送 webhook，A 通过 `SubmitApplication` 同步响应即知申请受理
  * 节点级变更（`NameScreening → RiskScreening` 等）**不再推送**，仅 `QueryApplication` 接口可查
  * 同一 `applicationStatus` 的重复推送应该幂等（按 `X-Nonce` 去重）
</Note>

**字段说明**

| 字段名               | 类型     | 描述                                       |
| :---------------- | :----- | :--------------------------------------- |
| applicationNo     | string | OristaPay 申请单号                           |
| extApplicationNo  | string | A 侧申请单号                                  |
| applicationStatus | string | `PENDING_USER` / `APPROVED` / `REJECTED` |
| companyCode       | string | 企业编码（`APPROVED` 时填入；其他状态为空字符串）           |
| walletId          | int64  | 钱包 ID（钱包开通后填入；其他状态为 `0`）                 |
| eventTime         | int64  | 事件触发时间，毫秒时间戳                             |

**示例（查册通过，可发起 IDV）**

```json theme={null}
{
  "applicationNo": "A202605258664",
  "extApplicationNo": "EXT20260512001",
  "applicationStatus": "PENDING_USER",
  "companyCode": "",
  "walletId": 0,
  "eventTime": 1747094400000
}
```

**示例（审核通过）**

```json theme={null}
{
  "applicationNo": "A202605258664",
  "extApplicationNo": "EXT20260512001",
  "applicationStatus": "APPROVED",
  "companyCode": "HKM12389h",
  "walletId": 12345678901234,
  "eventTime": 1747104400000
}
```

**示例（驳回 / 查册失败）**

```json theme={null}
{
  "applicationNo": "A202605258664",
  "extApplicationNo": "EXT20260512001",
  "applicationStatus": "REJECTED",
  "companyCode": "",
  "walletId": 0,
  "eventTime": 1747094500000
}
```

### 2.2 keyPeople IDV 失败 (OnboardingKeyPeopleIdvFailedNotification)

某个 keyPeople 的 IDV 失败时推送，A 端收到后可让 B 重做 IDV：调 `RefreshIdvLink` 拿新链接给 B。

**触发时机**

* IDV失败

**字段说明**

| 字段名              | 类型     | 描述                                 |
| :--------------- | :----- | :--------------------------------- |
| applicationNo    | string | OristaPay 申请单号                     |
| extApplicationNo | string | A 侧申请单号                            |
| peopleId         | string | keyPeople 全局 ID（字符串形式，保留 int64 精度） |
| eventTime        | int64  | 事件触发时间，毫秒时间戳                       |

**示例**

```json theme={null}
{
  "applicationNo": "A202605258664",
  "extApplicationNo": "EXT20260512001",
  "peopleId": "1000123456",
  "eventTime": 1747100100000
}
```

# 企业资料表单

`profile` 是提交开户申请（`SubmitApplication`）的核心载荷，也是查询接口返回的核心数据。结构因**业务类型**（合伙 / 有限 / 独资）和**注册地**不同而异。

## 1. 顶层结构

```text theme={null}
profile = {
  entityDetail,                  // 企业基本信息（必有）
  businessDetail,                // 业务信息（必有）
  shareholder,                   // 股东信息（仅"有限公司"必有）
  keyPeople,                     // 关键人物（必有，含 IDV / 签署对象）
  accountOpeningQuestionnaire    // 开户问题答卷（必有）
}
```

## 2. 支持矩阵

| 注册地 \ 商业类型            | 有限公司 | 合伙公司 | 独资公司 |
| :-------------------- | :--- | :--- | :--- |
| 香港 (`HKG`)            | ✓    | ✓    | ✓    |
| 其他（非 HKG，且 **非 CHN**） | ✓    | ✗    | ✗    |

## 3. 通用子结构

### 3.1 businessDetail.list\[] 元素

| 字段                | 类型                | 是否必填 | 描述                                                       |
| :---------------- | :---------------- | :--- | :------------------------------------------------------- |
| subIndustryCode   | string(10)        | 是    | 子行业 itemId，**必须存在于 OristaPay foundation 行业字典**，见附录【行业编码】 |
| yearsInBusiness   | string            | 是    | code 的字符串形式，见附录【经营年限】                                    |
| businessLocations | Array\<string>(3) | 是    | 商业地点列表，最多 3 个，见附录【国家地区编码】                                |
| lastYearSales     | string            | 是    | code 的字符串形式，见附录【上年销售额】                                   |
| industryDetails   | string(256)       | 是    | 行业补充说明                                                   |

> A 端**只需要传 `subIndustryCode`**，主行业 `industryCode` 由服务端按字典反查自动回填，即使 A 端填了也会被服务端覆盖。

### 3.2 KeyPeopleInfo 通用字段

所有业务类型的 `keyPeople.people[]` 都使用 `KeyPeopleInfo`。通用提交字段：

| 字段                            | 类型             | 是否必填 | 描述                                                                                         |
| :---------------------------- | :------------- | :--- | :----------------------------------------------------------------------------------------- |
| lastNameEn                    | string(128)    | 条件   | 中英文名必填其一                                                                                   |
| firstNameEn                   | string(128)    | 条件   | 中英文名必填其一                                                                                   |
| nameZh                        | string(128)    | 条件   | 中英文名必填其一                                                                                   |
| areaCode                      | string(8)      | 否    | 电话区号                                                                                       |
| mobileNumber                  | string(32)     | 否    | 手机号码                                                                                       |
| email                         | string(128)    | 否    | 邮箱地址                                                                                       |
| gender                        | int32          | 是    | `1` 男 / `2` 女                                                                              |
| birthdate                     | string         | 是    | 出生日期 `YYYY-MM-DD`                                                                          |
| region                        | string         | 是    | 证件所在国家 / 地区，见附录【国家地区编码】                                                                    |
| idType                        | int32          | 是    | `1` 中国大陆身份证 / `2` 中国香港身份证 / `3` 护照                                                         |
| idNumber                      | string         | 是    | 证件号码                                                                                       |
| isMandateUser                 | boolean        | 是    | 是否为签署用户。**keyPeople 中 `isMandateUser=true` 的人数必须 ≥ `keyPeople.quorum`**（最小法定人数）。一般为部分或全部董事 |
| ownedSharesPercent            | double(6,3)    | 条件   | 该人持股比例（百分比，0\~100，3 位小数）。**仅当 `isUbo=true` 时必填**                                           |
| isWalletAdmin                 | boolean        | 是    | 是否为钱包管理员。**整组 keyPeople 中必须有且仅有 1 人为 `true`**；该人会被自动设为企业的 wallet 超级管理员                     |
| overseaWorkCertificateFileKey | Array\<string> | 条件   | 海外工作证明文件 fileKey 列表。**仅当该人 `isWalletAdmin=true` 且 `region=CHN`（中国大陆居民）时必填**；最多 5 份         |

<Note>
  **钱包管理员两种典型组合**：

  1. **兼任**：某个董事 / UBO 同时勾 `isWalletAdmin=true`（一人两职）
  2. **独立**：在 `keyPeople` 列表里追加一个**仅作钱包管理员**的人（`isDirector` / `isUbo` / `isPartner` / `isOwner` 全部 `false`，只 `isWalletAdmin=true`），该人需走 IDV
</Note>

> `idType` 的"中国大陆身份证"是**个人证件**枚举，与"企业注册地"是不同维度；当企业注册地为 HKG 但董事 / 股东是中国大陆居民时，正常使用此 `idType`。

### 3.3 KeyPeopleInfo 中由 OristaPay 侧回写的运行时字段

提交时 A **不要填写**这些字段；通过 `QueryApplication` 读取时它们承载 OristaPay 侧的回写结果。

| 字段            | 类型     | 描述                                                                                           |
| :------------ | :----- | :------------------------------------------------------------------------------------------- |
| peopleId      | int64  | OristaPay 生成的人员全局 ID；获取 IDV / 签署链接时必须传此 ID                                                   |
| idvStatus     | string | IDV 进度：`NOT_STARTED` 未开始 / `UNDER_REVIEW` vendor 通过 + 人工审核中 / `COMPLETED` 人工通过 / `FAILED` 失败 |
| mandateStatus | string | 签署状态（仅 `isMandateUser = true` 时有意义）：`NOT_STARTED` 未签 / `SIGNED` 已签；非签署人为空字符串                 |

### 3.4 开户问卷 (accountOpeningQuestionnaire)

所有商业类型的 `profile` 都必须填写此模块。

| 字段                     | 类型           | 是否必填 | 描述                                                                                                                         |
| :--------------------- | :----------- | :--- | :------------------------------------------------------------------------------------------------------------------------- |
| accountOpeningPurposes | Array\<enum> | 是    | 开户目的，多选 1\~2 个；可选值：`PAYMENT_FOR_HK_BUSINESS` / `STORE_DIGITAL_ASSETS_FOR_HK_BUSINESS`                                      |
| expectedCounterparties | string(256)  | 否    | 预期业务交易对手（自由文本）                                                                                                             |
| expectedMonthlyVolume  | enum         | 是    | 预期月交易额（HKD），单选；可选值：`BELOW_1M_HKD` / `FROM_1M_TO_2_5M_HKD` / `FROM_2_5M_TO_5M_HKD` / `FROM_5M_TO_10M_HKD` / `ABOVE_10M_HKD` |
| handleClientMoney      | string       | 是    | 是否代客户处理资金（YES/NO）                                                                                                          |

**示例**

```json theme={null}
"accountOpeningQuestionnaire": {
  "accountOpeningPurposes": ["PAYMENT_FOR_HK_BUSINESS", "STORE_DIGITAL_ASSETS_FOR_HK_BUSINESS"],
  "expectedCounterparties": "Major HK retail merchants",
  "expectedMonthlyVolume": "FROM_2_5M_TO_5M_HKD",
  "handleClientMoney": "NO"
}
```

## 4. 合伙公司（仅香港）

**entityDetail**

| 字段               | 类型             | 是否必填 | 描述                               |
| :--------------- | :------------- | :--- | :------------------------------- |
| businessType     | int32          | 是    | `2` 合伙公司                         |
| incorpPlace      | string         | 是    | 公司注册地，仅支持 `HKG`                  |
| brNumber         | string(32)     | 是    | 商业登记证书编号                         |
| brFileKey        | string(128)    | 是    | Business Registration 文件 fileKey |
| incorpDate       | string         | 是    | 公司注册日期 `YYYY-MM-DD`              |
| website          | string(512)    | 否    | 网站 URL                           |
| regPlace         | string         | 是    | 公司注册所在地，见附录【国家地区编码】              |
| regAddress       | string(256)    | 是    | 公司注册详细地址，仅英文 / 数字 / 空格           |
| operatingPlace   | string         | 是    | 公司运营地                            |
| operatingAddress | string(256)    | 是    | 公司运营详细地址                         |
| nameEn           | string(128)    | 是    | 企业英文名                            |
| nameZh           | string(128)    | 否    | 企业中文名                            |
| paFileKey        | Array\<string> | 是    | Partnership Agreement 文件，最多 5 份  |

**keyPeople**：合伙人列表 2-6 人，`KeyPeopleInfo` 除通用字段外新增：

| 字段        | 类型   | 是否必填 | 描述               |
| :-------- | :--- | :--- | :--------------- |
| isPartner | bool | 是    | 是否为合伙人，固定 `true` |

**提交示例**

```json theme={null}
{
  "entityDetail": {
    "businessType": 2,
    "incorpPlace": "HKG",
    "brNumber": "br-number-partner",
    "incorpDate": "2025-05-01",
    "website": "www.example.com",
    "regPlace": "HKG",
    "regAddress": "Example partner register address",
    "operatingPlace": "HKG",
    "operatingAddress": "Example partner operating address",
    "nameEn": "Example Partnership",
    "nameZh": "示例合伙公司",
    "brFileKey": "4e0af338-9605-4353-86db-f35c4fdb7959.jpg",
    "paFileKey": ["06993a52-3069-41c9-a048-1601a3dfade4.jpg"]
  },
  "businessDetail": {
    "list": [
      {
        "subIndustryCode": "I300734",
        "yearsInBusiness": "1",
        "businessLocations": ["HKG"],
        "lastYearSales": "1",
        "industryDetails": "Digital asset platform"
      }
    ]
  },
  "keyPeople": {
    "quorum": 2,
    "directorNum": 0,
    "people": [
      {
        "lastNameEn": "Lo",
        "firstNameEn": "Sang",
        "nameZh": "罗生",
        "areaCode": "852",
        "mobileNumber": "61234567",
        "email": "lo@example.com",
        "isPartner": true,
        "isMandateUser": true,
        "isWalletAdmin": true
      },
      {
        "lastNameEn": "Yeung",
        "firstNameEn": "Sang",
        "nameZh": "杨生",
        "areaCode": "852",
        "mobileNumber": "61234568",
        "email": "yeung@example.com",
        "isPartner": true,
        "isMandateUser": true,
        "isWalletAdmin": false
      }
    ]
  },
  "accountOpeningQuestionnaire": {
    "accountOpeningPurposes": ["PAYMENT_FOR_HK_BUSINESS"],
    "expectedMonthlyVolume": "FROM_2_5M_TO_5M_HKD",
    "handleClientMoney": false
  }
}
```

## 5. 有限公司（香港）

**entityDetail**

| 字段                      | 类型             | 是否必填 | 描述                                               |
| :---------------------- | :------------- | :--- | :----------------------------------------------- |
| businessType            | int32          | 是    | `1` 有限公司                                         |
| incorpPlace             | string         | 是    | 公司所在地，`HKG`                                      |
| ciNumber                | string(32)     | 是    | 企业注册证书编号                                         |
| ciFileKey               | string(128)    | 是    | Certificate of Incorporation 文件                  |
| brNumber                | string(32)     | 是    | 商业登记证书编号                                         |
| brFileKey               | string(128)    | 是    | Business Registration 文件                         |
| incorpDate              | string         | 是    | 公司注册日期 `YYYY-MM-DD`                              |
| website                 | string(512)    | 否    | 网站 URL                                           |
| regPlace                | string         | 是    | 公司注册所在地                                          |
| regAddress              | string(256)    | 是    | 公司注册详细地址                                         |
| operatingPlace          | string         | 是    | 公司运营地                                            |
| operatingAddress        | string(256)    | 是    | 公司运营详细地址                                         |
| nameEn                  | string(128)    | 是    | 企业英文名                                            |
| nameZh                  | string(128)    | 否    | 企业中文名                                            |
| isFinancialInstitute    | int32          | 是    | `1` 受监管 / `2` 不受监管                               |
| financialRegulatorPlace | string         | 条件   | 受监管必填                                            |
| financialRegulator      | string(256)    | 条件   | 受监管必填                                            |
| financialLicenseType    | string(128)    | 条件   | 受监管必填                                            |
| regulatedProofKey       | Array\<string> | 条件   | 受监管证明文件，最多 5 份                                   |
| isListed                | int32          | 是    | `1` 上市 / `2` 非上市                                 |
| listingPlace            | string(3)      | 条件   | 上市公司必填                                           |
| listingExchange         | string(256)    | 条件   | 上市公司必填                                           |
| stockCode               | string(128)    | 条件   | 上市公司必填                                           |
| isGovOwned              | int32          | 是    | `1` 国有 / `2` 非国有                                 |
| ownedGovPlace           | string(3)      | 条件   | 国有必填                                             |
| maFileKey               | Array\<string> | 是    | Memorandum and Articles of Association 文件，最多 5 份 |

**shareholder**（`list` 与 `shareholderFileKey` 二选一）

| 字段                 | 类型                     | 是否必填 | 描述                                 |
| :----------------- | :--------------------- | :--- | :--------------------------------- |
| list               | Array\<ShareholderDto> | 二选一  | 结构化股东列表                            |
| shareholderFileKey | Array\<string>         | 二选一  | 股权架构图文件 fileKey 列表；传图时不需要再传 `list` |

> 仅"有限公司"且 `isFinancialInstitute=2` / `isListed=2` / `isGovOwned=2`（即都不豁免）时必须提供股权结构。

**ShareholderDto**

| 字段                 | 类型          | 是否必填 | 描述                                       |
| :----------------- | :---------- | :--- | :--------------------------------------- |
| shareholderId      | int64       | 是    | 股东唯一标识，请求侧自生成                            |
| sameId             | int64       | 否    | 标识"同一股东在结构中重复出现"；默认 `0`                  |
| parentId           | int64       | 否    | 父级 `shareholderId`；第一层填 `0`              |
| shareholderType    | int32       | 是    | `1` 个人股东 / `2` 企业股东                      |
| businessType       | int32       | 条件   | 企业股东商业类型，`shareholderType=2` 时必填         |
| incorpPlace        | string      | 条件   | 企业股东注册地。合伙 / 独资股东仅支持 `HKG`；**不接受 `CHN`** |
| ownedSharesPercent | double(6,3) | 是    | 对父级持股比例                                  |
| companyNameEn      | string(128) | 条件   | 企业股东英文名（中英名必填其一）                         |
| companyNameZh      | string(128) | 条件   | 企业股东本地名（中英名必填其一）                         |
| firstNameEn        | string(128) | 条件   | 个人股东英文名（中英名必填其一）                         |
| lastNameEn         | string(128) | 条件   | 个人股东英文姓（中英名必填其一）                         |
| nameZh             | string(128) | 条件   | 个人股东本地名（中英名必填其一）                         |

**keyPeople**

| 字段          | 类型                    | 是否必填 | 描述                                  |
| :---------- | :-------------------- | :--- | :---------------------------------- |
| quorum      | int32                 | 是    | 最小法定人数 1-99                         |
| directorNum | int32                 | 是    | 董事人数（须等于 `isDirector=true` 的人数）1-99 |
| people      | Array\<KeyPeopleInfo> | 是    | 列出企业所有 UBO 与董事                      |

`KeyPeopleInfo` 除通用字段外新增：

| 字段         | 类型      | 是否必填 | 描述            |
| :--------- | :------ | :--- | :------------ |
| isDirector | boolean | 是    | 是否为董事         |
| isUbo      | boolean | 是    | 是否为最终受益人（UBO） |

## 6. 有限公司（非香港）

字段绝大部分与 §5 相同，差异如下（未列字段同 §5）：

| 字段          | 类型             | 是否必填 | 描述                                                                                           |
| :---------- | :------------- | :--- | :------------------------------------------------------------------------------------------- |
| incorpPlace | string         | 是    | 非 `HKG` 的国家 / 地区编码；**不接受 `CHN`**                                                             |
| brNumber    | string(32)     | 否    | 商业登记证书编号（非 HK 地区可不填）                                                                         |
| brFileKey   | string(128)    | 否    | Business Registration 文件（非 HK 地区可不填）                                                         |
| coiFileKey  | Array\<string> | 条件   | Certificate of Incumbency。当 `incorpPlace` 为 `BMU` / `WSM` / `SYC` / `CYM` / `VGB` 时必填，最多 5 份 |

## 7. 独资公司（仅香港）

**entityDetail**

| 字段                   | 类型          | 是否必填 | 描述                       |
| :------------------- | :---------- | :--- | :----------------------- |
| businessType         | int32       | 是    | `3` 独资公司                 |
| incorpPlace          | string      | 是    | 仅支持 `HKG`                |
| brNumber             | string(32)  | 是    | 商业登记证书编号                 |
| brFileKey            | string(128) | 是    | Business Registration 文件 |
| incorpDate           | string      | 是    | 公司注册日期 `YYYY-MM-DD`      |
| website              | string(512) | 否    | 网站 URL                   |
| regPlace             | string      | 是    | 注册所在地                    |
| regAddress           | string(256) | 是    | 注册详细地址                   |
| operatingPlace       | string      | 是    | 运营地                      |
| operatingAddress     | string(256) | 是    | 运营详细地址                   |
| nameEn               | string(128) | 是    | 企业英文名                    |
| nameZh               | string(128) | 否    | 企业中文名                    |
| isFinancialInstitute | int32       | 否    | `1` 受监管 / `2` 不受监管       |

**keyPeople**：仅一位 owner，`KeyPeopleInfo` 除通用字段外新增：

| 字段      | 类型      | 是否必填 | 描述                |
| :------ | :------ | :--- | :---------------- |
| isOwner | boolean | 是    | 是否为独资公司拥有人（有且仅一位） |

## 8. 文件 fileKey 字段汇总

文件先通过文件上传接口拿到 `fileKey`，再填入 `profile`。

| profile 字段                                           | 文件含义                      | 类型                 |
| :--------------------------------------------------- | :------------------------ | :----------------- |
| entityDetail.brFileKey                               | 商业登记证书                    | string             |
| entityDetail.ciFileKey                               | 公司注册证书                    | string             |
| entityDetail.maFileKey\[]                            | 组织章程（M\&A）                | Array\<string> ≤ 5 |
| entityDetail.paFileKey\[]                            | 合伙协议                      | Array\<string> ≤ 5 |
| entityDetail.kycFileKey\[]                           | KYC 证明                    | Array\<string> ≤ 5 |
| entityDetail.regulatedProofKey\[]                    | 受监管金融机构证明                 | Array\<string> ≤ 5 |
| entityDetail.coiFileKey\[]                           | Certificate of Incumbency | Array\<string> ≤ 5 |
| keyPeople.people\[].idFileKey\[]                     | 个人证件照                     | Array\<string> ≤ 5 |
| keyPeople.people\[].overseaWorkCertificateFileKey\[] | 海外工作证明（CHN 钱包管理员必填）       | Array\<string> ≤ 5 |

文件类型与上限：`jpeg / jpg / png / pdf / gif`，单文件 ≤ 20 MB。

# 错误码

## 1. 业务错误码

| code   | 含义      | ISV onboarding 路径下的典型触发                                                    |
| :----- | :------ | :------------------------------------------------------------------------- |
| `1`    | 成功      | —                                                                          |
| `6001` | 通用业务失败  | 未明确分类的业务错误                                                                 |
| `6002` | 参数错误    | 必填字段缺失、字段类型不符                                                              |
| `6003` | 订单不存在   | `applicationNo` / `extApplicationNo` 不存在；`peopleId` 不存在                    |
| `6005` | 没有权限    | A 未开通 ISV onboarding 能力；A 试图查询非己 `applicationNo`                           |
| `6406` | 请求正在处理中 | 后台企业查册尚未通过，需先等待 `PENDING_USER` webhook 后再调 `GetIdvLink` / `RefreshIdvLink` |
| `6801` | 重复申请    | 同 `extApplicationNo` 不同 body；同一 B（CI / BR 号）已有活跃申请                         |
| `6802` | 资料错误    | `profile` 校验失败，伴随 `errors[]` 列表（详见附录【档案错误描述】）                              |
| `6803` | 申请达到上限  | A 当前周期已开户数超配额（若有约束）                                                        |

## 2. 各接口错误码对照

### 2.1 SubmitApplication

| 场景                            | 返回                                   |
| :---------------------------- | :----------------------------------- |
| 提交成功                          | `code = 1` + `data.applicationNo`    |
| 必填字段缺失 / 类型错误                 | `code = 6002`                        |
| A 未开通 ISV onboarding          | `code = 6005`                        |
| 同 `extApplicationNo`，body 一致  | `code = 1` + 返回原 `applicationNo`（幂等） |
| 同 `extApplicationNo`，body 不一致 | `code = 6801`                        |
| 同一 B（CI / BR 号）已有活跃申请         | `code = 6801`                        |
| `profile` 校验失败                | `code = 6802` + `errors[]`           |

### 2.2 QueryApplication

| 场景                                       | 返回                  |
| :--------------------------------------- | :------------------ |
| 查询成功                                     | `code = 1` + `data` |
| `applicationNo` / `extApplicationNo` 都未传 | `code = 6002`       |
| 申请不存在                                    | `code = 6003`       |
| 申请不属于当前 A                                | `code = 6005`       |

### 2.3 GetIdvLink

| 场景                                        | 返回                                                                                                                     |
| :---------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- |
| 成功                                        | `code = 1` + `data.linkUrl` + `expireTime`                                                                             |
| `applicationNo` 不存在 / 不属于当前 A             | `code = 6003` / `6005`                                                                                                 |
| `peopleId` 不存在 / 不属于此申请                   | `code = 6003`                                                                                                          |
| 该人员全部任务已完成                                | `code = 6001`                                                                                                          |
| 申请已进入终态（`APPROVED` / `REJECTED`）          | `code = 6001`                                                                                                          |
| **查册尚未通过**（在 `PENDING_USER` webhook 之前调用） | `code = 6406` + `message`: "Company search not passed yet. Wait for PENDING\_USER webhook before requesting IDV link." |

### 2.4 RefreshIdvLink

| 场景                                         | 返回                     |
| :----------------------------------------- | :--------------------- |
| 成功                                         | `code = 1`             |
| `applicationNo` / `peopleId` 不存在 / 不属于当前 A | `code = 6003` / `6005` |
| 该人员全部任务已完成                                 | `code = 6001`          |
| 申请已进入终态                                    | `code = 6001`          |
| 查册尚未通过（在 `PENDING_USER` webhook 之前调用）      | `code = 6406`          |

### 2.5 QueryCompanyProfile

| 场景                        | 返回                                                |
| :------------------------ | :------------------------------------------------ |
| 成功                        | `code = 1` + `data`                               |
| `companyCode` 不存在         | `code = 6003`                                     |
| `companyCode` 对应企业不属于当前 A | `code = 6005`                                     |
| 企业尚未审核通过                  | `code = 6001` + 提示 "Profile is not approved yet." |

## 3. 档案错误描述（`code = 6802`）

完整清单同 [ISV 交易 API · 档案错误描述](./transaction-api#档案错误描述)，按所属模块分组：

* `[Business details]`
* `[Entity details]`
* `[Key people]`
* `[Shareholder]`
* `[Others]`

本套 API 额外的拒绝场景：

* `[Entity details]Place of incorporation not supported: CHN` —— 企业层 `incorpPlace` / `regPlace` / `operatingPlace` 不允许 CHN
* `[Entity details]Place of incorporation not supported: <制裁国家代码>` —— 被制裁国家
* `[Entity details]Partnership / Sole proprietorship only support incorpPlace=HKG` —— 合伙 / 独资仅限香港
* `[Key people]Nationality not supported: <国家代码>` —— keyPeople 国籍为被制裁国家（CHN 特例放行）
* `[Wallet Admin]Work certification is necessary` —— region=CHN 的钱包管理员未传 `overseaWorkCertificateFileKey`
