> For the complete documentation index, see [llms.txt](https://docs.coda.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.coda.co/https-coda-payments.gitbook.io-zhong-wen-coda-private-technical-documentation/codapay/tuo-guan-zu-jian-ji-cheng/ka-pian/fen-bie-shou-quan-he-shou-qu-kuan-xiang/shen-fen-yan-zheng-he-bu-huo-tong-zhi.md).

# 身份验证和捕获通知

本文档介绍了专门用于授权和捕获分离的 Webhook 通知。这些通知可确保您的后端在交易经历授权、捕获或取消阶段时保持同步。

> **注意：**&#x6388;权和捕获分离需要启用 POST 通知。您的账户必须同时启用这两项功能。请联系您的 Codapay 客户经理启用它们。

#### 概述 <a href="#overview" id="overview"></a>

Codapay 使用 webhook 在交易达到最终状态或授权和捕获流程中的关键里程碑时通知您的“完成通知 URL”。

> 请确保已在发布商门户中配置“完整通知 URL”。有关详细说明，请参阅[“设置付款”指南。](https://claude.ai/chat/cb09ca98-8996-43b7-87cb-cd20a04c1dea#)

#### 事件类型 <a href="#event-types" id="event-types"></a>

为了实现授权和捕获的分离，您的端点应准备好处理以下`eventType`值：

| `authorization_successful` | 用户已成功授权交易（例如，3DS）。                                                    |
| -------------------------- | --------------------------------------------------------------------- |
| `authorization_failed`     | 用户授权失败或银行拒绝了请求                                                        |
| `cancelled`                | 您已决定在成功授权后、捕获前取消交易。                                                   |
| `capture_initiated`        | 系统已开始收取授权资金。                                                          |
| `capture_successful`       | 款项已成功扣款。交易完成。                                                         |
| `capture_failed`           | <p>授权成功后，捕获过程失败。</p><h4 id="notification-payload-structure"><br></h4> |

#### 通知请求体结构 <a href="#notification-payload-structure" id="notification-payload-structure"></a>

通知以请求的形式发送，`POST`请求体为 JSON 格式。

| eventType             | string  | 所报告的具体事件类型（例如，`capture_successful`）                                                  |
| --------------------- | ------- | ------------------------------------------------------------------------------------ |
| timestamp             | string  | 通知时间戳                                                                                |
| data.txnId            | string  | Coda 生成的唯一交易 ID                                                                      |
| data.orderId          | string  | 订单的商家端唯一标识符                                                                          |
| data.amountValue      | decimal | 以结算货币向用户收取的最终金额。                                                                     |
| data.amountCurrency   | string  | 收费金额的 ISO 4217 货币代码（例如，`MYR`）                                                        |
| data.originalValue    | decimal | 商品原价（未进行任何货币转换）                                                                      |
| data.originalCurrency | string  | 原价的 ISO 4217 货币代码                                                                    |
| data.resultCode       | integer | 交易状态码。如果成功，`0`则表示交易成功；其他值表示交易失败。                                                     |
| data.resultDesc       | string  | 对交易结果进行易于理解的描述。                                                                      |
| data.shopper          | object  | 包含购物者详细信息的对象，例如 ID、电子邮件或合作伙伴特定标识符。                                                   |
| data.paymentMethod    | object  | 有关所用支付工具的信息（例如，卡类型、后四位数字）。                                                           |
| data.transactionData  | object  | <p>一个灵活的对象，包含特定于交易或支付渠道的补充元数据。</p><h4 id="successful-notification-example"><br></h4> |

#### 成功通知示例 <a href="#successful-notification-example" id="successful-notification-example"></a>

以下示例显示了一条`CAPTURE_SUCCESSFUL`通知，表明资金已成功收回：

```
{
  "eventType": "capture_successful",
  "timestamp": "1776084951007",
  "data": {
    "txnId": "7760845022800001729",
    "orderId": "12321312321314",
    "amountValue": 1,
    "amountCurrency": "EUR",
    "originalValue": 1,
    "originalCurrency": "EUR",
    "resultCode": 0,
    "shopper": {},
    "paymentMethod": {},
    "transactionData": {}
  }
}
```

#### 通知失败示例 <a href="#failed-notification-example" id="failed-notification-example"></a>

当发生诸如`authorization_failed`、`capture_failed`或`cancelled`之类的事件时，`resultCode`将包含一个非零值。

```
{
  "eventType": "cancelled",
  "timestamp": "1776085975305",
  "data": {
    "txnId": "7760859208170001746",
    "orderId": "12321312321314",
    "amountValue": 1,
    "amountCurrency": "EUR",
    "originalValue": 1,
    "originalCurrency": "EUR",
    "resultCode": 221,
    "shopper": {},
    "paymentMethod": {},
    "transactionData": {}
  }
}
```

> [您可以在这里](https://coda-payments.gitbook.io/coda-public-technical-documentation/codapay/error-codes)找到完整的错误代码及其含义列表。

#### 通知签名验证 <a href="#notification-signature-verification" id="notification-signature-verification"></a>

每个 POST 通知都包含两个标头，使您可以验证请求是否来自 Codapay 且未被篡改：

标题描述

`X-Request-Time`

请求的时间戳，以时间戳毫秒表示（数字字符串）

`X-Signature`

Codapay 使用您的商户密钥计算的 HMAC-SHA512 十六进制摘要

**工作原理**

Codapay 和您的服务器都同意使用同一个 UTF-8 字符串进行签名：

```
X-Request-Time + "." + raw POST body (UTF-8)
```

**使用您的商户密钥**对该字符串应用 HMAC-SHA512 算法，生成**128 个字符的小写十六进制**摘要。

Codapay 会将此摘要放在`X-Signature`邮件头中发送。您的服务器必须重新计算相同的十六进制摘要，并将其与接收到的值进行比较。

**验证步骤**

1. 读取传入请求中的请求头`X-Request-Time`和请求头`X-Signature`。
2. 读取请求的**原始字节。**
3. 构建签名字符串：（`requestTime + "." + body`解释为 UTF-8）。
4. 计算预期签名：`HMAC-SHA512(secretKey, signedString)`→ 小写十六进制。
5. 使用**恒定时间**`expectedHex`比较与`X-Signature`头部值进行比较，以防止计时攻击。比较不区分大小写。

如果值不匹配，则拒绝请求。

**示例 — Java**

```
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

public class SignatureValidator {

    private static final String HMAC_SHA512 = "HmacSHA512";

    public static boolean validate(String secretKey, String requestTime, byte[] body, String xSignature) {
        String bodyUtf8 = new String(body, StandardCharsets.UTF_8);
        String text = requestTime + "." + bodyUtf8;
        String expected = hmacSha512Hex(secretKey, text);
        return MessageDigest.isEqual(
            expected.toLowerCase().getBytes(),
            xSignature.toLowerCase().getBytes()
        );
    }

    private static String hmacSha512Hex(String secretKey, String text) {
        try {
            Mac mac = Mac.getInstance(HMAC_SHA512);
            mac.init(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), HMAC_SHA512));
            byte[] raw = mac.doFinal(text.getBytes(StandardCharsets.UTF_8));
            return HEX.formatHex(raw);
        } catch (Exception e) {
            throw new IllegalStateException("HMAC-SHA512 not available", e);
        }
    }
}
```

#### 回复通知 <a href="#responding-to-notifications" id="responding-to-notifications"></a>

为了确认收到通知，您的服务器必须响应一个包含`200 OK`以下 JSON 正文的响应：

```
{
  "ResultCode": 0
}
```

重试策略：如果未收到有效响应，Codapay 将每隔 5 分钟重新发送一次通知，共发送 3 次。如果所有尝试均失败，系统将向您的技术联系人发送电子邮件提醒。

[<br>](https://coda-payments.gitbook.io/coda-public-technical-documentation/codapay/hosted-component-integration/cards/authorize-and-capture-a-payment-separately)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.coda.co/https-coda-payments.gitbook.io-zhong-wen-coda-private-technical-documentation/codapay/tuo-guan-zu-jian-ji-cheng/ka-pian/fen-bie-shou-quan-he-shou-qu-kuan-xiang/shen-fen-yan-zheng-he-bu-huo-tong-zhi.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
