﻿# PM Dropin 错误码参考

::: tip  

本文档面向对接商户，说明接入 PM Dropin 支付组件时，在各调用入口 / 时序阶段可能收到的`code`及其含义，以及应该如何处理。本文档**不区分**错误码是由 SDK 前端产生还是服务端返回

**商户只需要关注：**我在哪个入口、哪个阶段、收到什么 `code`、它意味着什么。

:::

## 0. 总览

### 0.1 如何阅读本文档

组件 API 只有两类入口：

| **入口** | **形式**                                                    | **携带code**                                      | **对应章节**     |
| -------- | ----------------------------------------------------------- | ------------------------------------------------- | ---------------- |
| 订阅事件 | `instance.on('load' /'form-check' /'payButtonClick' / ...)` | 仅 `load` 带 code， 其余为状态/通知               | §1、§2.2 等    |
| 触发动作 | `instance.emit('canMakePayment' / ...)`，返回 **Promise**   | resolve = 成功; reject = 失败，`err.code`为错误码 | §2.3、§3、 §4 |

四类支付组件统一按以下**时序阶段**展开描述：

**初始化（`ready` → `load`）→ 用户交互（卡表单 `form-check` / 系统支付按钮 `payButtonClick`）→ 支付请求(`emit('canMakePayment')`)→ 商户下单收尾 (Apple Pay 需 `paySuccess` / `payFail`)**。

### 0.2 统一约定

| **约定**    | **说明**                                                                                                                                                                            |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 成功码      | 事件入口成功为 `SUCCESS` ； `emit('canMakePayment')` 成功为 `APPLY_SUCCESS` （二者含义相同，出现位置不同）                                                                          |
| 失败响应    | 统一为 `{ code, msg, displayMsg? }` ： `code` 见各章表格； `msg` 为英文机器文案（或服务端返回文案）； `displayMsg` 为双语 `{ en, ar }` **可直接展示的文案**（目前主要在卡组件出现） |
| code 透传   | 服务端受理阶段返回的错误码会**原样**透传给商户，SDK 不做翻译；本文档无法穷举，通用处理见附录 B                                                                                      |
| 组件 `type` | `load` 事件返回 `type` ： `card` / `applepay` / `googlepay` / `userinfocollect` ，可用于区分组件                                                                                    |

### 0.3 完整流程（以 Apple Pay 为例，最完整链路）

```JavaScript
const applePay = PMdropin.create('applepay', options)

applePay.on('load', (res) => { res.code === 'SUCCESS' /* 才挂载/启用组件 */ })
applePay.on('payButtonClick', () => { /* 用户点击系统按钮 → 发起支付 */ })

// 阶段①：拉起 Apple Pay 系统弹层 → 用户授权 → SDK 完成商户验证与取 token
applePay.emit('canMakePayment', payload)
  .then(res => {
    // 阶段②：成功 —— res.code === 'APPLY_SUCCESS'，拿 res.data.paymentToken
    const paymentToken = res.data.paymentToken
    // 阶段③：商户调用自己的下单/支付接口（demo 中的 orderAndPay）
    return postOrderAndPay({ paymentToken })        // 商户自有后端接口
  })
  .then(orderRes => {
    if (orderRes.code === 'APPLY_SUCCESS') applePay.emit('paySuccess')  // 通知组件支付成功，完成系统弹层
    else applePay.emit('payFail')
  })
  .catch(err => { /* err.code / err.msg 见 §3 */ applePay.emit('payFail') })

  ```

## 1. 通用：组件初始化加载 instance.on('load')

创建组件后 SDK 会拉取组件初始化配置，完成后通过 `load` 事件告知结果（四种组件同构，仅 `type` 不同）：

```JavaScript
const card = PMdropin.create('card', options)
card.on('load', (data) => {
  if (data.code === 'SUCCESS') {
    // 加载成功，可挂载并启用支付组件
  } else {
    // 加载失败，见下表
  }
})
```

</br>

| code                         | msg(默认值)                                   | 描述                                                                                                                                                                         | 范围         |
| ---------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| `SUCCESS`                    | 空                                            | **初始化成功**。组件可正常使用                                                                                                                                               | 全部         |
| `DROPIN_INIT_FAIL`           | `Failed to initialize '{type}' Dropin!`       | **初始化阶段：**组件创建后未能在规定时间内完成初始化（多为 create 配置、密钥/环境参数问题）。建议检查 create 参数后刷新重试                                                  | 全部         |
| `DROPIN_INIT_ACTIONS_FAIL`   | `Failed to register '{type}' Dropin actions!` | **初始化阶段：**组件内部动作注册失败或等待初始化超时。建议刷新重试                                                                                                           | 全部         |
| `DROPIN_INIT_GOOGLEPAY_FAIL` | `Failed to init 'googlePay' button!`          | **初始化阶段：**Google Pay 按钮初始化失败——通常是 Google Pay JS 加载失败，或当前设备/浏览器不支持（HTTPS、系统版本、地区限制）。建议检查页面协议是否为 HTTPS、设备是否支持 | 仅 googlepay |
| `HTTP_ERR`                   | `An error occurred with the http request!`    | **初始化阶段：**拉取初始化配置的请求发生网络层错误。建议检查网络后重试                                                                                                       | 全部         |
| `&lt;HTTP 状态码&gt;`        | statusText                                    | **初始化阶段：**服务端返回了错误 HTTP 状态（如 `403` / `500`）。code 即为状态码数字                                                                                          | 全部         |
| `&lt;后端业务码&gt;`         | 服务端 msg                                    | **初始化阶段：**初始化配置请求被服务端拒绝（如参数/密钥/商户状态异常）。按服务端 msg 处理，必要时联系技术支持                                                                | 全部         |

同一页面创建多个组件时，各组件会**分别**触发各自的 `load`，请分别监听。

## 2. Card（卡支付）

### 2.1 卡组件时序

```JavaScript
load(成功) → 用户填写卡信息 → on('form-check') 实时反馈校验状态
          → 点击支付 → emit('canMakePayment')
              ├─ 本地表单校验（见 2.2）
              ├─ 服务端受理（见 2.3）
              └─ 网络/超时（见 2.2）
```

### 2.2 表单校验阶段（on('form-check') 与 canMakePayment 本地校验）

<b>`form-check`事件</b>：用户输入过程中实时触发，负载为表单校验状态（**不含 code**），结构示例：
`{ isFormValid, invalidReason, displayMsg, fieldStatus: { cardnum, expdate, cvv, name } }。`

商户可监听它控制"支付"按钮的可点击状态：`isFormValid === true` 才允许发起支付。

<b>`emit('canMakePayment')`被本地校验拦截</b>时（表单无效直接 reject，不会请求服务端）：

| code              | msg(默认值)                                   | 描述                                                                                                                                                                               | 范围 |
| ----------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
| `FORM_INVALID`    | 见描述                                        | 提交支付时**表单校验未通过**：卡号/有效期/CVV/姓名缺失或格式错误（`msg` 给出具体原因，如 `No selected card`），或推荐卡模式下未选中卡。displayMsg 为字段双语文案。让用户修正后重试 | card |
| `REQUEST_TIMEOUT` | error.message 或 `Unknown`                    | 支付请求**超时**（默认 15 秒未收到回执）。可提示稍后重试                                                                                                                           | card |
| `UNKNOWN_ISSUE`   | `Failed to register '{type}' Dropin actions!` | **未知/内部异常**：请求网络错误或 SDK 内部未预期异常。建议重试，仍失败联系技术支持                                                                                                 | card |

### 2.3 服务端受理阶段（emit('canMakePayment') 的 Promise 结果）

表单校验通过后，SDK 将卡信息提交服务端受理：

```JavaScript
card.emit('canMakePayment', payload)
  .then(res  => { /* 成功 */ })
  .catch(err => { /* err.code / err.msg / err.displayMsg */ })
```

| code                     | msg(默认值) | 描述                                                                                                                                                        | 范围 |
| ------------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
| `APPLY_SUCCESS`          | 服务端返回  | **受理成功。** `res` 内含服务端返回的数据（如支付 token 等），可继续商户侧下单流程                                                                          | card |
| `PARAMETER_INVALID`      | 服务端 msg  | **服务端参数校验失败：**提交的数据非法（卡信息不完整等）。按 msg 修正后重试                                                                                 | card |
| `SAVE_CARD_INFO_ERROR`   | 服务端 msg  | **保存卡信息失败**（服务端写入异常）。稍后重试，仍失败联系技术支持                                                                                          | card |
| `FORM_INVALID`           | 服务端 msg  | **服务端判定表单字段非法**（与 2.2 同名，但发生在服务端校验）。引导用户核对卡信息                                                                           | card |
| `NO_SELECTED_CARD`       | 服务端 msg  | **未选择要使用的卡**（推荐卡场景）。请用户先选卡                                                                                                            | card |
| `USER_INFO_NOT_COMPLETE` | 服务端 msg  | **附加的用户信息采集未完成：**需要先引导用户在 `userinfocollect` 组件完成必填信息（如部分市场要求的收件人/证件信息），再重新支付                            | card |
| `REQUEST_TIMEOUT`        | 服务端 msg  | **服务端处理超时。**可提示稍后重试                                                                                                                          | card |
| `UNKNOWN_ISSUE`          | 服务端 msg  | 服务端返回的未知错误                                                                                                                                        | card |
| 其它后端码               | 服务端 msg  | **服务端自定义错误码：** SDK 不识别时原样透传，同时 `displayMsg` 固定为通用兜底文案 `ERROR` ( `Something went wrong, please try again.` )。通用处理见附录 B | card |

<b>关于`displayMsg`</b>：卡组件的失败响应会附带 `displayMsg`（`{ en, ar }`双语），可直接按用户语言展示；

其中 `FORM_INVALID` 场景的 `displayMsg` 是字段名映射结构，需要拼接提示（如 `Field not valid: Card number`）。

### 2.4 其它动作（无 code）

`setDisabled` / `getFormValid` / `switchTheme` / `focus` 等动作返回布尔值或表单状态，不含错误码；

`switchTheme` 等失败时其 Promise 会 reject 但无业务 code 含义。

## 3. Apple Pay

### 3.1 Apple Pay 时序（注意收尾动作）

```JavaScript
load(成功) → 用户点击系统支付按钮 → on('payButtonClick') → 商户 emit('canMakePayment')
  → 弹出 Apple Pay 系统弹层 → 用户授权
      → SDK 内部：商户验证（applePaySession）→ 取 token（retrievePaymentToken）
  → resolve(res: APPLY_SUCCESS + data.paymentToken) 或 reject(err)
  → 商户拿 paymentToken 调自己的下单接口
  → 下单成功 emit('paySuccess')  / 下单失败 emit('payFail')   ← 决定系统弹层的完成/失败展示
```

::: tip 重要：

Apple Pay 弹层的最终收尾由**商户**控制。`canMakePayment resolve` 后请务必先完成商户侧下单/支付，再调用 `paySuccess`；不要在下单成功前调用 `paySuccess`。

:::

### 3.2 参数拦截 / 本地校验阶段

以下 code 在调用 `canMakePayment` 后**立即**（弹层弹出前）返回：

| code                          | msg(默认值)               | 描述                                                                                                                                                                                        | 范围     |
| ----------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `REPEAT_REQUEST`              | `The request is repeated` | **重复请求：**上一次支付流程尚未结束（弹层未关闭/未回执）时再次发起。请等待上次回调后再调用                                                                                                 | applepay |
| `AMOUNT_INVALID`              | `Amount is incorrect.`    | **金额格式非法：**传入的 `totalAmount` 不是合法金额（需为整数或至多 2 位小数的数字）。修正参数后重试                                                                                        | applepay |
| `MIT_PARAMS_VALIDATION_ERROR` | 聚合英文错误文本          | **代扣（MIT/订阅）参数校验失败：**支付参数携带订阅计划（`subscriptionPlan`）或 `mitManagementUrl` 时校验未通过。`msg` 为聚合的错误描述（如 `subject is required`），按附录 C 对照修正后重试 | applepay |

### 3.3 用户授权 / 服务端受理阶段（弹层内）

| code                           | msg(默认值) | 描述                                                                                                            | 范围     |
| ------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------- | -------- |
| `APPLY_SUCCESS`                | 服务端返回  | **授权受理成功。** `res.data.paymentToken` 为支付凭证，进入商户下单流程                                         | applepay |
| `APPLE_PAY_SESSION_ERROR`      | error 信息  | **商户验证失败：** SDK 向服务端换取商户验证（`applePaySession`）时网络异常/失败，Apple Pay 弹层中断。可提示重试 | applepay |
| `RETRIEVE_PAYMENT_TOKEN_ERROR` | error 信息  | **取 token 失败：**获取支付凭证（`retrievePaymentToken`）时网络异常/失败。可提示重试                            | applepay |
| 其它后端码                     | 服务端 msg  | 服务端在商户验证 / 取 token 阶段返回的**业务错误码原样透传**（如余额/额度/风控类拒绝）。通用处理见附录 B        | applepay |

### 3.4 用户行为与内部异常

| code                      | msg(默认值)         | 描述                                                                                             | 范围     |
| ------------------------- | ------------------- | ------------------------------------------------------------------------------------------------ | -------- |
| `APPLE_PAY_CANCEL`        | `applepay canceled` | **用户主动取消：**用户在系统弹层点了取消。不是错误，无需报错提示，重新引导支付即可               | applepay |
| `APPLEPAY_INTERNAL_ERROR` | `error.message`     | **SDK 内部异常：**创建 Apple Pay 会话失败（多为环境不支持、配置缺失）。检查设备/环境与配置后重试 | applepay |

## 4. Google Pay

### 4.1 时序

```JavaScript
load(成功) → 用户点击 Google Pay 系统按钮 → on('payButtonClick') → 商户 emit('canMakePayment')
  → 弹出 Google Pay 系统弹层 → 用户选卡授权
      → SDK 内部：取 token（retrievePaymentToken）→ 3DS/风控（apply3DS）
  → resolve(res: APPLY_SUCCESS + data.paymentToken) 或 reject(err)
  → 商户拿 paymentToken 调自己的下单接口（无系统弹层需要收尾）
  ```

### 4.2 参数拦截 / 本地校验阶段

| code                          | msg(默认值)               | 描述                                                                           | 范围      |
| ----------------------------- | ------------------------- | ------------------------------------------------------------------------------ | --------- |
| `REPEAT_REQUEST`              | `The request is repeated` | **重复请求：**上一次支付流程未结束时再次发起。等待上次回调后再调用             | googlepay |
| `AMOUNT_INVALID`              | `Amount is incorrect.`    | **金额格式非法：** `totalAmount` 不是整数或至多 2 位小数的数字。修正参数后重试 | googlepay |
| `MIT_PARAMS_VALIDATION_ERROR` | 聚合英文错误文本          | **代扣（MIT/订阅）参数校验失败。** `msg` 为聚合错误描述，按附录 C 修正后重试   | googlepay |

### 4.3 用户授权 / 服务端受理阶段

| code                          | msg(默认值)       | 描述                                                                                                                   | 范围      |
| ----------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------- | --------- |
| `APPLY_SUCCESS`               | 服务端返回        | **授权受理成功。** `res.data.paymentToken` 为支付凭证，进入商户下单流程                                                | googlepay |
| `CANCELED`                    | Google 返回       | **用户主动取消：** Google Pay 系统弹层被关闭（Google Pay 原生的 `statusCode`，SDK 原样透传）。非错误，重新引导支付即可 | googlepay |
| 其它 Google 原生 `statusCode` | Google 返回       | Google Pay JS 返回的其它状态（如参数错误类，由 Google 定义），SDK 原样透传。可按 Google Pay 官方文档对照处理           | googlepay |
| 其它后端码                    | 服务端 msg        | 服务端在取 token / 3DS 阶段返回的业务错误码原样透传。通用处理见附录 B                                                  | googlepay |
| `no_paymentToken`             | `no paymentToken` | **SDK 内部兜底：** 服务端响应中缺少支付凭证。正常情况不应出现，出现请联系技术支持并附请求信息                          | googlepay |
| 异常                          | 缺少参数          | **SDK 内部兜底：** 内部调用参数缺失。正常情况不应出现                                                                  | googlepay |

## 5. userinfocollect（用户信息采集）

- 组件加载错误与 §1 完全一致（load 事件，type = 'userinfocollect'）。

- form-check 事件负载为采集表单状态（{ isValid, invalidFields, needCollect, formData }），不含 code：商户可据此判断用户是否已填完必填信息、是否需要展示采集表单。

- 该组件自身无支付业务错误码。若商户在卡支付时收到 USER_INFO_NOT_COMPLETE（见 §2.3），即表示采集信息未完成，请先引导用户完成采集后再支付。

## 6. 附录

### 附录 A：code 速查索引表

| code                                            | 出现入口                               | 阶段           | 支付方式                    | 一句话含义                               |
| ----------------------------------------------- | -------------------------------------- | -------------- | --------------------------- | ---------------------------------------- |
| `SUCCESS`                                       | `on('load')`                           | 初始化         | 全部                        | 组件加载成功                             |
| `APPLY_SUCCESS`                                 | `emit('canMakePayment')` resolve       | 支付受理       | card / applepay / googlepay | 支付受理成功，拿到 `paymentToken` 可下单 |
| `DROPIN_INIT_FAIL` / `DROPIN_INIT_ACTIONS_FAIL` | `on('load')`                           | 初始化         | 全部                        | 组件初始化失败                           |
| `DROPIN_INIT_GOOGLEPAY_FAIL`                    | `on('load')`                           | 初始化         | googlepay                   | Google Pay 按钮初始化失败/环境不支持     |
| `HTTP_ERR` / `HTTP 状态码`                      | `on('load')`                           | 初始化         | 全部                        | 拉取初始化配置网络/服务端错误            |
| `REQUEST_TIMEOUT`                               | `emit('canMakePayment')` reject        | 支付           | card（服务端亦可能返回）    | 超时                                     |
| `FORM_INVALID`                                  | `emit('canMakePayment')` reject        | 表单校验       | card                        | 卡表单无效（本地或服务端判定）           |
| `NO_SELECTED_CARD`                              | `emit('canMakePayment')` reject        | 表单校验       | card                        | 推荐卡未选中                             |
| `PARAMETER_INVALID`                             | `emit('canMakePayment')` reject        | 服务端受理     | card                        | 服务端参数校验失败                       |
| `SAVE_CARD_INFO_ERROR`                          | `emit('canMakePayment')` reject        | 服务端受理     | card                        | 保存卡信息失败                           |
| `USER_INFO_NOT_COMPLETE`                        | `emit('canMakePayment')` reject        | 服务端受理     | card                        | 用户附加信息采集未完成                   |
| `UNKNOWN_ISSUE`                                 | `on('load')` / `canMakePayment` reject | 各阶段         | card                        | 未知错误（本地或服务端）                 |
| `AMOUNT_INVALID`                                | `emit('canMakePayment')` reject        | 参数校验       | applepay / googlepay        | 金额格式非法                             |
| `MIT_PARAMS_VALIDATION_ERROR`                   | `emit('canMakePayment')` reject        | 参数校验       | applepay / googlepay        | MIT/订阅参数校验失败                     |
| `REPEAT_REQUEST`                                | `emit('canMakePayment')` reject        | 请求拦截       | applepay / googlepay        | 上一次支付流程未结束时重复请求           |
| `APPLE_PAY_SESSION_ERROR`                       | `emit('canMakePayment')` reject        | 弹层内商户验证 | applepay                    | 换取商户验证失败                         |
| `RETRIEVE_PAYMENT_TOKEN_ERROR`                  | `emit('canMakePayment')` reject        | 弹层内取 token | applepay                    | 取支付凭证失败                           |
| `APPLE_PAY_CANCEL` / `CANCELED`                 | `emit('canMakePayment')` reject        | 用户交互       | applepay / googlepay        | 用户取消（非错误）                       |
| `APPLEPAY_INTERNAL_ERROR`                       | `emit('canMakePayment')` reject        | 内部           | applepay                    | 创建支付会话内部异常                     |

### 附录 B：未列出的服务端错误码，怎么处理？

收到**不在本手册表格内**的 `code` 时：

1. 先看 msg：它是服务端返回的错误描述（多为英文），是最直接的定位信息；卡组件还可看 displayMsg（双语文案）直接展示给用户。

2. 判断来源阶段：

- 来自 on('load') → 初始化配置被拒绝（密钥/商户状态/参数问题）；

- 来自 emit('canMakePayment') reject → 支付受理被服务端拒绝（常见为余额不足、卡被拒、风控拦截、银行侧拒绝等）。

3. 处理建议：

- 把收到的 code + msg 原样透传/记录（不要自行改文案）；

- 向用户展示服务端文案或通用失败提示，不要对同一笔交易自动重试（可能造成重复扣款），引导用户重新发起；

- 联系技术支持时请提供：商户号、时间、支付组件类型、收到的 code/msg、请求流水（requestId）。

### 附录 C：MIT / 订阅参数校验（MIT_PARAMS_VALIDATION_ERROR）错误文本解读

失败时 `msg` 会返回一条或多条（`; ` 连接）的英文描述，对照下表修正 `canMakePayment` 入参：

| msg 片段                                            | 含义                                                  | 正确取值示例                                              |
| --------------------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------- |
| `The mitManagementUrl is invalid`                   | `mitManagementUrl` 缺失或不是 `http(s)://` 开头的链接 | 必须以 `http(s)://` 开头                                  |
| `subject is required`                               | 订阅标题缺失                                          | `subscriptionPlan.subject` 为字符串                       |
| `totalPeriods must be greater than or equal to 1`   | 总期数 &lt; 1 或非整数                                | `subscriptionPlan.totalPeriods &ge; 1`                    |
| `periodUnit must be one of D, W, M, Y`              | 扣款周期单位非法                                      | `subscriptionPlan.periodRule.periodUnit &isin; D/W/M/Y`   |
| `periodCount must be greater than or equal to 1`    | 每期扣款间隔非法                                      | `periodRule.periodCount &ge; 1`                           |
| `amount must be greater than or equal to 0`         | 每期金额非法                                          | `periodAmount.amount &ge; 0`                              |
| `currency must be equal to XXX`                     | 订阅币种与当前订单币种不一致                          | `periodAmount.currency` 等须等于订单 `currencyCode`       |
| `currency must be a 3-character string`             | 币种不是 3 位字符串                                   | 如 `USD`、`SAR`                                           |
| `firstPeriodStartDate must be in the future`        | 首期开始日期是过去时间                                | 需为未来的 RFC3339 时间（如 `2026-10-01T00:00:00+08:00`） |
| `xxx must be of type xxx`                           | 字段类型错误                                          | 按提示传入对应类型                                        |
| `xxx is required` / `Invalid subscriptionPlan: ...` | 嵌套对象（如 `trialConfig`）缺失或内部字段错误        | 按提示补全嵌套字段                                        |
