# 错误处理 (/zh/docs/pix-processamento/best-practices/errors)

<QuickLinks>
  <QuickLink href="/docs/pix-processamento/error-codes" title="错误代码" />

  <QuickLink href="/docs/pix-processamento/authentication" title="认证" />

  <QuickLink href="/docs/pix-processamento/best-practices/idempotency" title="幂等性" />
</QuickLinks>

PayZu 返回标准 HTTP 状态码。你的策略取决于错误类别。

<Mermaid
  chart="`
flowchart TD
  A[&#x22;调用 PayZu&#x22;]
  A --> B{&#x22;状态码&#x22;}
  B -->|&#x22;2xx&#x22;| OK[&#x22;成功&#x22;]
  B -->|&#x22;4xx (除 424/429 外)&#x22;| C[&#x22;你的 payload 错误<br/>不要重试&#x22;]
  B -->|&#x22;424&#x22;| G[&#x22;金融机构故障<br/>带退避重试&#x22;]
  B -->|&#x22;429&#x22;| D[&#x22;Rate limit<br/>指数退避&#x22;]
  B -->|&#x22;5xx&#x22;| E[&#x22;PayZu 错误<br/>带退避重试&#x22;]
  B -->|&#x22;Timeout&#x22;| F[&#x22;操作可能<br/>已经被应用&#x22;]

  click C &#x22;/zh/docs/pix-processamento/error-codes&#x22; &#x22;错误码&#x22;
  click D &#x22;#retry-helper&#x22; &#x22;Retry helper&#x22;
  click G &#x22;#retry-helper&#x22; &#x22;Retry helper&#x22;
  click E &#x22;#retry-helper&#x22; &#x22;Retry helper&#x22;
  click F &#x22;#timeout可能已经成功的陷阱&#x22; &#x22;超时陷阱&#x22;

  style OK fill:#14ce71,stroke:#0eb464,color:#ffffff
  style C fill:#ef4444,stroke:#dc2626,color:#ffffff
  style G fill:#f59e0b,stroke:#d97706,color:#ffffff
  style D fill:#f59e0b,stroke:#d97706,color:#ffffff
  style E fill:#f59e0b,stroke:#d97706,color:#ffffff
  style F fill:#f59e0b,stroke:#d97706,color:#ffffff
`"
/>

完整的 HTTP 状态码表和 `errorCode` 目录及各自的处理方式见[错误代码](/docs/pix-processamento/error-codes)；本页面聚焦策略：何时重试、如何退避以及记录哪些日志。

## Retry helper [#retry-helper]

在 `429`、`5xx` 和 `424` 时重试。其余 `4xx` **绝不**重试。

<Tabs items="['curl', 'Node.js']">
  <Tab value="curl">
    ```bash
    ATTEMPTS=4
    DELAY=1

    for i in $(seq 1 $ATTEMPTS); do
      STATUS=$(curl -s -o /tmp/resp.json -w "%{http_code}" \
        -X POST https://api.payzu.processamento.com/v1/pix \
        -H "Authorization: Bearer $TOKEN" \
        -H "Content-Type: application/json" \
        -d '{"amount":99.90,"clientReference":"order-1234"}')

      case $STATUS in
        2*) cat /tmp/resp.json; exit 0 ;;
        424|429|5*) sleep $DELAY; DELAY=$((DELAY*2)) ;;
        *) echo "错误 $STATUS"; cat /tmp/resp.json; exit 1 ;;
      esac
    done
    echo "已超过最大重试次数"
    exit 1
    ```
  </Tab>

  <Tab value="Node.js">
    ```ts
    async function withRetry<T>(
      fn: () => Promise<Response>,
      attempts = 4,
    ): Promise<T> {
      let lastErr: unknown;
      for (let i = 0; i < attempts; i++) {
        try {
          const res = await fn();
          if (res.ok) return res.json();

          if (res.status >= 400 && res.status < 500 && res.status !== 429 && res.status !== 424) {
            const body = await res.text();
            throw new Error(`Client error ${res.status}: ${body}`);
          }
        } catch (err) {
          lastErr = err;
        }

        const delay = Math.min(8000, 1000 * 2 ** i) + Math.random() * 250;
        await new Promise((r) => setTimeout(r, delay));
      }
      throw lastErr ?? new Error('Max retries exceeded');
    }
    ```
  </Tab>
</Tabs>

## Timeout：“可能已经成功”的陷阱 [#timeout可能已经成功的陷阱]

Timeout 不等同于失败。PayZu 可能已经接收、处理并保存了交易，只是响应没有返回。你的应用并不知道。

**解决方案**：使用唯一的 `clientReference`，重试前先查询。

```ts
async function createOrRetry(orderId: string, amount: number) {
  const ref = `order-${orderId}`;
  try {
    return await withRetry(() => postPix({ amount, clientReference: ref }));
  } catch (err) {
    // 尽管出错/超时，操作可能已经成功
    const existing = await fetch(
      `https://api.payzu.processamento.com/v1/pix?clientReference=${ref}`,
      { headers },
    ).then((r) => (r.ok ? r.json() : null));
    if (existing) return existing;
    throw err;
  }
}
```

## 错误的可观测性 [#错误的可观测性]

至少要记录以下字段：

| 字段                    | 原因                       |
| --------------------- | ------------------------ |
| `requestId`           | 来自 PayZu 错误响应。支持团队可直接追踪。 |
| 本地 `id`               | 你的标识符（订单、Pix 付款）。        |
| PayZu `id`            | 如果已存在。                   |
| `endToEndId`          | 在争议时用于在 Bacen 追踪。        |
| `clientReference`     | 通用的关联键。                  |
| HTTP status + message | 根本原因几乎总是在 `message` 中。   |
| 第 N 次 / 共 M 次         | 区分首次尝试与重试。               |

```ts
log.error('PayZu /pix 调用失败', {
  requestId: body.requestId,
  status: res.status,
  message: body.message,
  clientReference: ref,
  attempt: i + 1,
  attempts,
});
```

## 给终端用户的友好错误提示 [#给终端用户的友好错误提示]

不要直接暴露原始的 `message`。请按稳定的 `errorCode` 匹配，而不是按文案匹配：`message` 是葡萄牙语，且可能变动。schema 校验失败一律返回 `PZV001`，出错的字段在 `details[]` 里。

| `errorCode`         | HTTP | 给用户的提示                            |
| ------------------- | ---- | --------------------------------- |
| `PZA100`            | 401  | “配置错误。请联系支持团队并提供 `requestId` 代码。” |
| `PZV001`            | 400  | 逐个字段根据 `details[].field` 生成提示。    |
| `PZD600`            | 400  | “金额低于该操作允许的最小值。”                  |
| `PZC200`            | 422  | “余额不足，无法完成该操作。”                   |
| `PZI110` / `PZI111` | 424  | “金融机构暂时不稳定。请稍后再试。”                |
| `PZF500`            | 424  | “金融机构暂时不可用。请稍后再试。”                |
| `PZG429`            | 429  | “请求过多。请稍后再试。”                     |
| `PZI100`            | 500  | “系统暂时不可用。我们正在处理。”                 |

完整目录见[错误码](/docs/pix-processamento/error-codes)。

## 常见陷阱 [#常见陷阱]

| 陷阱                    | 症状                               |
| --------------------- | -------------------------------- |
| 在 `400` 上重试           | 对 API 形成 spam，相同错误重复 N 次         |
| 在 `401` 上重试却不轮换 token | Token 在日志中进一步泄漏                  |
| 没有退避（立即循环重试）          | 变成 rate limit，最终被封禁              |
| 退避没有 jitter           | N 个客户端同时打过来，形成 “thundering herd” |
| 把 timeout 当作最终失败      | 向用户重复扣款 2 次                      |
| 不记录 `requestId`       | 支持团队无法调查                         |

## 使用 `requestId` 开支持工单 [#使用-requestid-开支持工单]

保存了 `requestId`？直接发给支持团队。

<QuickLinks>
  <QuickLink href="https://suporte.payzu.com.br/portal/pt-br/newticket?departmentId=1103699000000006907&layoutId=1103699000000074011" title="开工单" />

  <QuickLink href="https://suporte.payzu.com.br/portal/pt-br/kb/payzu" title="知识库" />

  <QuickLink href="https://suporte.payzu.com.br" title="支持门户" />
</QuickLinks>