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

# 生产环境公钥与 API 操作类型

> 自助提交和轮换你的生产环境公钥：字段接受的精确格式、为什么 Portal 要你对挑战字符串签名、三种 API 操作类型的含义以及为什么 Withdraw 必须独立，还有轮换期间新旧密钥如何共存。

集成安全相关内容由三个页面分担：

| 问题 | 页面 |
| - | - |
| 如何对请求签名、验证响应，`A0001` / `A0002` 是什么意思？ | [认证与签名 FAQ](/docs/zh/support/auth-signature-faq) |
| 如何设置通知 URL 并按 IP 限制访问？ | [Portal 自助安全配置](/docs/zh/support/portal-security-config-faq) |
| 如何提交或轮换**生产环境**公钥，API 操作类型是什么？ | 本页 |

## 我能自己添加生产环境公钥吗？

可以。在 **Settings → Integration** 的 *Merchant Sign Configuration Details* 表格上方有一个 **Configure** 按钮。你可以自己从 Portal 提交密钥，不需要给任何人发邮件传文件。

自助提交和 Waffo 审核不是二选一——两者都有。当你确认时，对话框会明确说明：*once submitted, it will enter the review process*。密钥会立即以 `Pending` 状态注册为一个新版本，到达 `Active` 后才可使用。整个过程中你现有的密钥持续验证你的流量，所以一次提交永远不会中断线上支付。

| 状态 | 含义 |
| - | - |
| `Pending` | 已提交，等待审核 |
| `Active` | 已批准——正在验证你的请求签名 |
| `Rejected` | 未批准；原因记录在该版本上 |

## 公钥字段到底接受什么格式？

它很严格，对话框会显示一个对照所需长度的实时字符计数器，让你在提交前就能判断：

| 要求 | 值 |
| - | - |
| 算法 | SHA256withRSA |
| 密钥长度 | RSA 2048 位 |
| 编码 | Base64，X.509 |
| 字符串长度 | 正好 **392 个字符** |
| 包裹行 | 无——没有 `BEGIN PUBLIC KEY` / `END PUBLIC KEY` 行 |
| 空白字符 | 无——没有空格，没有换行 |

392 不是建议值。只有当你的输入正好是该长度时字段才通过校验，超过时计数器会变红。实际含义是：**只取你 PEM 文件中单行的 Base64 主体，别的一律不要。** 直接粘贴 PEM 文件会因为头、尾和换行而长度不对。[上线检查清单](/docs/zh/developer-docs/getting-started/go-live) 展示了如何把 PEM 精简为该字段需要的值，[认证与签名 FAQ](/docs/zh/support/auth-signature-faq) 有生成密钥对的 `openssl` 命令。

校验会产生三种不同的拒绝，告诉你上面哪一项不满足：**长度**不匹配、**非法字符**（不是合法 Base64 的内容——通常是多余的空格或包裹行），以及**算法**不匹配（一个合法的 Base64 字符串，但根本不是 2048 位 RSA 公钥）。

## 为什么 Portal 要我对一个挑战字符串签名？

为了证明你持有与所上传公钥配对的私钥。没有这一步，任何能打开该表单的人都可以注册一个自己并不掌控的密钥。

对话框内的流程：

1. 粘贴你的公钥。先进行格式检查。
2. 通过后，会出现一个 **Ownership Verification** 区块，其中有一个形如 `WAFFO_VERIFY_{your merchant ID}` 的挑战字符串，带复制按钮。
3. 在本地用与你刚粘贴的公钥配对的**私钥**、使用 SHA256withRSA 对该字符串签名，并将结果 Base64 编码。
4. 把签名粘贴到 **Signature Result**。

在操作类型、合法公钥和签名三者齐备之前，Confirm 保持禁用。随后签名会用你提交的公钥验证——不匹配的密钥对在提交时就被拒绝，所以你会立刻知道，而不是等审核之后。

对挑战字符串**严格按显示原样**签名，末尾不要有换行。这是密钥对实际正确却出现不匹配的最常见原因。

## 三种 API 操作类型是什么？

操作类型限定一个密钥对——以及随之签发的 API 密钥——允许用于什么。对话框提供三个按钮：

| 按钮 | 范围 |
| - | - |
| Payin | 支付、退款及相关查询 API |
| Payout | Payout 和转账 API |
| Withdraw | 提现 API |

你可以**同时选择 Payin 和 Payout**，Portal 会将其注册为一个覆盖两者的合并范围。之后你在表格中看到的是一行 Payin-and-Payout，而不是两行。

## 为什么我不能把 Withdraw 和 Payin 或 Payout 组合？

因为 Withdraw 是不同类别的操作：它把你自己已结算的资金转出，而不是把客户的钱收进来或付给第三方。Waffo 把它放在独立的凭据上，这样你的收银台或 Payout 服务所用的密钥永远不能用来转出你的余额，一个被攻破也不意味着另一个被攻破。

对话框是强制执行而不是警告：选择 **Withdraw** 会清除 Payin 和 Payout，选择其中任一个会清除 Withdraw。不存在任何包含 Withdraw 与其他项的组合。所以如果你通过 API 操作提现，请计划**至少两套凭据**——一套用于支付流量，另一套单独用于提现——并把它们放在密钥库的不同位置。

## 密钥轮换如何做到零停机？

关键机制：**提交永远不会原地修改密钥。** 每次被接受的提交都会创建一个新版本，每个版本都有**自己的 API 密钥**。哪个公钥来验证某个请求，由该请求上的 `X-API-KEY` 决定——一个 API 密钥，一个公钥。

这正是干净轮换得以实现的原因，因为同一操作类型下可以同时有多个版本处于 `Active`：

1. 生成新密钥对。旧的保持原位并继续使用。
2. 从 Portal 提交新公钥。它以 `Pending` 状态作为新版本出现。你的流量继续用旧密钥验证，不受影响。
3. 当它到达 `Active` 时，从 *Merchant Sign Configuration Details* 表格中读取其 **API 密钥**。
4. 在你的集成中，把 `X-API-KEY` **和**签名私钥**一起**切换，作为一次变更。它们是一套；只换其一会产生 `A0002`。
5. 在新凭据上验证流量，然后停止使用旧的。

由于步骤 4 期间两个版本都是 `Active`，你可以逐个服务切换，或跑一个金丝雀，而不会出现两套凭据都不可用的窗口。又因为两者是配对的，回滚只是把请求头和私钥切回去。

不要跳过步骤 3。用现有 API 密钥搭配新提交的公钥是最常见的轮换失败——旧 API 密钥仍指向旧公钥，所以你的新签名验证失败，而 Portal 中一切看起来配置正确。

## 我能提交同一个公钥两次吗？

不能。已在同一操作类型下注册的公钥会被拒绝，而不是创建重复版本。这是为了防止不小心「轮换」到你正在使用的密钥上，那样看起来像一次成功的轮换，实际什么都没变。

同一密钥**可以**在*不同*操作类型下提交，不过每个范围使用独立的密钥对是更好的做法——这正是划分范围的意义。

## 我能持有的密钥数量有限制吗？

有，每个操作类型有上限，会超出上限的提交会以专门的错误被拒绝。计数包含被拒绝的版本，所以放弃的尝试也占用名额。两个习惯能让你远离上限：有计划地轮换而不是试探性地轮换，以及在重新提交前先修复被拒绝提交的原因。

## 我在哪里能看到过去的提交记录以及某次为什么被拒？

**Historical Versions**，在同一页面配置表格的下方。每行是一次提交：

| 列 | 内容 |
| - | - |
| Version | 每个操作类型下的 `V1`、`V2`、…… |
| API Operation Type | 提交时所选的范围 |
| Merchant Public Key | 密钥，已遮蔽，带复制操作 |
| Status | `Pending` / `Active` / `Rejected` |
| Submission Time | 你提交的时间 |
| Reject Reason | 被拒版本的原因 |
| Action | 被拒版本上的 **View Reason**，打开完整文本 |

被拒不是终点，尝试次数也没有上限：阅读原因，修复，再作为新版本提交。列表分页显示，最新的在前，所以长期的轮换历史仍然可读。

## 密钥被激活或被拒时，谁会收到通知？

无论哪种情况，你账户的 **Super Admin** 用户都会收到邮件。激活邮件确认操作类型和遮蔽形式的已激活密钥；拒绝邮件包含原因。

如果提交密钥的人不是持有 Super Admin 邮箱的人，请提前安排——提交者从 Portal 得知结果，而邮件落在别处。查看 Historical Versions，而不是等转发的邮件。

## Sandbox 需要配置公钥吗？

不需要，也不能。Configure 按钮和 Historical Versions 只在 Sandbox 之外出现——在 Sandbox 中，Waffo 在开通 Sandbox 时为你生成密钥对，Sandbox 密钥材料直接显示在 Integration 页面上，你可以立即开始签名。

上线时的两个后果：

* **不要在生产环境复用 Sandbox 密钥材料。** 生成一对新的；生产环境公钥必须是你提交的、且私钥只有你持有的那一对。
* **API 域名和凭据一起切换。** 每个环境有自己的 API 地址和 API 密钥；地址在你所在环境的 Integration 页面上显示。

## 为什么我的生产环境公钥是遮蔽的，而 Sandbox 私钥不是？

因为它们不是同一类秘密。在生产环境中，页面默认遮蔽密钥材料并提供显示/隐藏控件，这样在共享屏幕或录制的通话中打开 Integration 页面不会暴露它。复制操作在不解除遮蔽的情况下始终可用，所以你永远不需要仅仅为了把值移到密钥库而显示它。

你的**生产环境私钥永远不在这个页面上**，也不应该在——Waffo 不持有它。如果你丢失了它，Portal 中没有任何操作能恢复：生成新密钥对并按上文轮换。

## Merchant Sign Configuration Details 表格里有什么？

你持有的每个操作类型一行，把凭据与其使用的 URL 关联起来：

| 列 | 是什么 |
| - | - |
| API Operation Type | 该行的范围 |
| Merchant Public Key | 当前在该范围内验证你请求的公钥，已遮蔽 |
| API Key | 该范围内请求需携带的 `X-API-KEY`，已遮蔽 |
| Refund Notify URL | 带 **Edit** 操作 |
| Chargeback Notify URL | 带 **Edit** 操作 |
| Tokenization Notify URL | 带 **Edit** 操作 |

URL 列是配置而不是密钥材料，修改无需审核——其校验规则和范围见 [Portal 自助安全配置](/docs/zh/support/portal-security-config-faq)。如果你持有用于卡信息采集的客户端凭据，它会在此表格中以独立一行出现。

***

轮换后签名仍然失败？按 [认证与签名 FAQ](/docs/zh/support/auth-signature-faq) 中的 `A0002` 检查清单逐项排查——轮换后的失败几乎总是因为 API 密钥和私钥已经不再互相匹配。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.