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

# 3DS 转发

> 说明合作伙伴自管模式支持的三种 3DS 验证方式、交互流程和 Webhook 字段。

## 📄 正文

无论您是交易所、钱包还是平台方，都可以使用自有应用中的短信、邮件或站内通知渠道完成持卡人的在线支付验证（3DS）。DCS 作为持牌发卡机构，负责与卡组织完成 3DS 协议交互、生成和校验验证码，并将验证所需的信息安全地转发给接入机构。

3DS（3D Secure）是在线支付的安全验证协议，会在交易过程中对持卡人进行额外身份核验，以降低盗刷风险。在合作伙伴自管模式下，DCS 提供 **3 种 3DS 验证方式**，接入机构可按自身希望掌控验证流程的程度，选择合适的方式。

## 三种模式速览

| 模式                   | 谁发验证码               | 谁校验             | 接入机构要做的事                  | 触发的 `flowsType` |
| -------------------- | ------------------- | --------------- | ------------------------- | --------------- |
| 模式一：DCS 直接发码         | DCS                 | DCS             | **无需任何工作**                | 不下发 Webhook     |
| 模式二：接入机构发码（OTP 代发）   | 接入机构                | DCS             | 提供 Webhook 接收 OTP，解密后发给用户 | `OTP_DELEGATE`  |
| 模式三：引导用户到接入机构验证（OOB） | 接入机构（通过自有应用或其他渠道引导） | 接入机构，再将结果返回 DCS | 接收 Webhook 通知、引导用户认证并返回结果 | `OOB`           |

> 采用哪种模式由您的卡 Program 配置决定。**配置 3DS 模式：DCS**；**接入对接：接入机构**。如需变更，请联系 DCS 团队。

## 3DS 转发整体流程

下图为 3DS 验证请求在各方之间转发的整体往返：DCS 收到卡组织的验证请求后转发给接入机构，接入机构（按所选模式）完成验证并原路返回结果，DCS 再以 Webhook 通知挑战状态更新。具体由谁发码、由谁判定，见下方各模式详解。

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/pa-3ds-flow-light.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=d511d34a591d3c65bd775e4d8ff5e59f" alt="3DS 转发整体流程时序图" width="638" height="536" data-path="imgs/diagrams/pa-3ds-flow-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/pa-3ds-flow-dark.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=8573fc2defc29533878f56339909bc84" alt="3DS 转发整体流程时序图" width="638" height="536" data-path="imgs/diagrams/pa-3ds-flow-dark.svg" />
</Frame>

***

## 模式一：DCS 直接发送验证码

DCS 直接给持卡人发送短信/邮件验证码并完成校验。这种模式下**接入机构无需做任何额外工作**，DCS 自动处理验证码的生成、发送与核验，不会下发 3DS Webhook。

适合：尚未自建消息通道、希望最快上线的接入机构。

***

## 模式二：接入机构发送验证码（OTP\_DELEGATE）

由接入机构用自己的渠道把验证码发给持卡人，DCS 负责校验。这种模式下，接入机构需提供 Webhook 接收 DCS 推送的 OTP，解密后转发给用户。

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/pa-3ds-otp-delegate-light.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=34bca070429143f2cca3b95c4edd3a01" alt="接入机构代发验证码（OTP_DELEGATE）时序图" width="638" height="598" data-path="imgs/diagrams/pa-3ds-otp-delegate-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/pa-3ds-otp-delegate-dark.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=48635918437182333f947d209e1134ff" alt="接入机构代发验证码（OTP_DELEGATE）时序图" width="638" height="598" data-path="imgs/diagrams/pa-3ds-otp-delegate-dark.svg" />
</Frame>

### Webhook 字段（3DS 挑战通知）

3DS 相关 Webhook 的类型为 `AUTHORISATION_3DS_CHALLENGE`，通过 `flowsType` 区分模式。模式二下 `flowsType=OTP_DELEGATE`。

| 字段                    | 类型     | 说明                                                             |
| --------------------- | ------ | -------------------------------------------------------------- |
| `challengeId`         | string | 3DS 挑战 ID                                                      |
| `status`              | string | 挑战状态：`INIT` / `NOTICED` / `RECEIVED` / `APPROVED` / `REJECTED` |
| `cardId`              | string | 卡 ID                                                           |
| `expiryTime`          | string | 挑战过期时间                                                         |
| `currency`            | string | 发起交易的币种                                                        |
| `amount`              | number | 发起交易的金额                                                        |
| `merchantId`          | string | 商户 ID                                                          |
| `merchantName`        | string | 商户名称                                                           |
| `merchantCountry`     | string | 商户国家                                                           |
| `mcc`                 | string | 商户类别码                                                          |
| `flowsType`           | string | 认证流程类型：`OOB` / `OTP_DELEGATE`                                  |
| `challengeMethodType` | string | 挑战方法类型：`DELEGATE_SCA_V1` / `SMS_OTP` / `EMAIL_OTP`             |
| `otpPasscode`         | string | OTP 验证码（**AES-GCM 加密**），仅 `flowsType=OTP_DELEGATE` 时有值         |
| `phoneNumber`         | string | 持卡人手机号（**AES-GCM 加密**），仅 `flowsType=OTP_DELEGATE` 时有值          |
| `email`               | string | 持卡人邮箱（**AES-GCM 加密**），仅 `flowsType=OTP_DELEGATE` 时有值           |
| `iv`                  | string | AES-GCM 加密用的 IV（Base64 编码），仅 `flowsType=OTP_DELEGATE` 时有值      |

> Webhook 的整体投递、外层包裹与签名校验，请见 [Webhook 事件与数据结构](../webhooks/events-and-schema)。

### 解密 OTP（接入机构）

`otpPasscode`、`phoneNumber`、`email` 使用接入机构的 `secretKey`，以 AES-GCM 模式加密；Webhook 中随附 `iv`。接入机构收到后用 `secretKey + iv` 解密。

* **算法**：AES-128-GCM（NoPadding）
* **密钥**：接入机构的 `secretKey`，Base64 编码
* **IV**：Webhook 的 `iv` 字段，Base64 编码，12 字节
* **认证标签（Auth Tag）**：128 位，附在密文末尾
* **密文格式**：Base64 编码（解码后为 `ciphertext + authTag`）

```java theme={null}
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class AesGcmDecryptor {

    public static String decrypt(String base64Ciphertext, String base64Key, String base64IV) {
        byte[] key = Base64.getDecoder().decode(base64Key);
        byte[] iv = Base64.getDecoder().decode(base64IV);
        byte[] ciphertext = Base64.getDecoder().decode(base64Ciphertext);

        SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
        GCMParameterSpec spec = new GCMParameterSpec(128, iv);

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);

        byte[] plaintext = cipher.doFinal(ciphertext);
        return new String(plaintext, StandardCharsets.UTF_8);
    }
}

// 使用示例
String otpPasscode = decrypt(webhook.getOtpPasscode(), secretKey, webhook.getIv());
String phoneNumber = decrypt(webhook.getPhoneNumber(), secretKey, webhook.getIv());
String email       = decrypt(webhook.getEmail(), secretKey, webhook.getIv());
```

> 模式二中**校验仍由 DCS 完成**：接入机构只负责把解密后的验证码发送给用户，无需回传验证结果；最终结果通过 `status=APPROVED/REJECTED` 的 Webhook 通知。

***

## 模式三：引导用户到接入机构验证（OOB）

OOB（Out-Of-Band，带外认证）模式下，接入机构通过自有应用或其他渠道引导持卡人完成认证（如生物识别、应用内确认），**由接入机构判定**是否通过，再将结果返回 DCS。此模式下 Webhook 的 `flowsType=OOB`、`challengeMethodType=DELEGATE_SCA_V1`，且**不会发送** `otpPasscode` / `phoneNumber` / `email` / `iv`。

### 回传认证结果（接入机构）

接入机构判定完成后，调用 `POST /open-api/3ds-challenge/v1/authenticate` 把结果回传给 DCS。

**最小请求：**

```json theme={null}
{
  "challengeId": "<Webhook 推送的 challengeId>",
  "approveStatus": "APPROVE"
}
```

**请求字段：**

| 字段              | 类型     |  必填 | 说明                                     |
| --------------- | ------ | :-: | -------------------------------------- |
| `challengeId`   | string |  是  | 发起交易前的 3DS 挑战 ID（来自 OOB Webhook）；最大 50 |
| `approveStatus` | string |  是  | 挑战操作：`APPROVE` / `REJECT`；最大 20        |

**响应：** 所有 `/open-api/` 接口共用统一响应结构 `{ code, message, messageDetail, data }`。成功时 `data` 回显挑战当前状态：

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": "success",
  "messageDetail": null,
  "data": {
    "challengeId": "CHL_1a2b...",
    "status": "APPROVED",
    "cardId": "CARD_8f3a...",
    "expiryTime": "2026-06-17T08:05:00Z",
    "currency": "USD",
    "amount": 100.00,
    "merchantId": "M_001",
    "merchantName": "Acme Store",
    "merchantCountry": "SG",
    "mcc": "5732"
  }
}
```

| 响应字段              | 说明                                                             |
| ----------------- | -------------------------------------------------------------- |
| `challengeId`     | 3DS 挑战 ID                                                      |
| `status`          | 挑战状态：`INIT` / `NOTICED` / `RECEIVED` / `APPROVED` / `REJECTED` |
| `cardId`          | 卡 ID                                                           |
| `expiryTime`      | 挑战过期时间                                                         |
| `currency`        | 发起交易的币种                                                        |
| `amount`          | 发起交易的金额                                                        |
| `merchantId`      | 商户 ID                                                          |
| `merchantName`    | 商户名称                                                           |
| `merchantCountry` | 商户国家                                                           |
| `mcc`             | 商户类别码                                                          |

> 响应结构说明：业务成败请以 `code` 为准（如 `SYS_SUCCESS`；错误码说明见[授权拒绝与错误码](./decline-codes)）；`message` 为简要说明；`messageDetail` 是可选的展示对象（含 `message` / `title` / `type` / `icon` / `action` / `linkTitle` / `linkUrl`），仅供前端引导，不应作为判断成败的依据。

<Warning>
  请在 `expiryTime` 之前回传结果。该时限来自 3DS 厂商下发的挑战有效期；过期未回传时，DCS 会把挑战置为 `REJECTED`，不会自动批准。
</Warning>

***

## 接入要点

* **Content-Type**：所有 `/open-api/` 请求请显式设置 `Content-Type: application/json`。
* **鉴权**：调用 `/open-api/3ds-challenge/v1/authenticate` 与接收 Webhook 的签名校验，遵循 [鉴权指南](../../integration-resources/authentication)。
* **Webhook 接口**：模式二、模式三都要求接入机构预先配置可接收 `AUTHORISATION_3DS_CHALLENGE` 的 Webhook 地址（由接入机构提供给 DCS）。
* **模式三回传时限**：必须在厂商下发的 `expiryTime` 前回传；逾期挑战置为 `REJECTED`。
* **OTP 失败处理**：模式二同步等待接入机构 Webhook 最多 7 秒。超时、返回失败、解密失败或发送失败时，不会重发，也不会改由 DCS 代发，当前挑战直接置为 `REJECTED`。接入机构需在 7 秒内完成解密和发送，并同步返回 `phoneNumberTail` / `email`。

## 下一步

3DS 验证通过后，交易进入授权环节。前往 [授权](./authorization) 了解授权决策如何转发给接入机构，再看 [授权与清算](./auth-and-settlement) 理解资金的冻结与最终清算。
