# 资金与精度 (/zh/docs/pix-processamento/best-practices/money)

<QuickLinks>
  <QuickLink href="/docs/pix-processamento/endpoints/pix-operations/post_pix" title="POST /pix" />

  <QuickLink href="/docs/pix-processamento/endpoints/withdrawals/post_withdraw" title="POST /withdraw" />

  <QuickLink href="/docs/pix-processamento/glossary" title="术语表" />
</QuickLinks>

PayZu Pix API 使用**带小数位的雷亚尔**。请注意：许多 PSP 以分为单位运作，因此如果您正在迁移或对比集成方案，这里的小数点不是可选项。

```json
{ "amount": 99.90 }
```

## 代码中的小数精度 [#代码中的小数精度]

在 JavaScript 中，`0.1 + 0.2 !== 0.3`。在 Python 中 `Decimal` 是安全的，但 `float` 不是。在 SQL 中，`FLOAT` 会损失精度。

| 语言               | 使用                                          |
| ---------------- | ------------------------------------------- |
| **JavaScript**   | 以分为单位的整数，或 `decimal.js` 库。                  |
| **Python**       | 计算时使用 `decimal.Decimal`，仅在 API 处使用 `float`。 |
| **Go**           | `shopspring/decimal` 或以分为单位的整数。             |
| **Java**         | `BigDecimal`，绝不使用 `double`。                 |
| **PHP**          | `bcmath`，或以分为单位的整数。                         |
| **SQL/Postgres** | `NUMERIC(15,2)`，绝不使用 `FLOAT` 或 `REAL`。      |

### 推荐模式：内部以分存储 [#推荐模式内部以分存储]

在数据库中以分为单位的整数存储，仅在 API 边界处转换：

<Tabs items="['Node.js', 'Python']">
  <Tab value="Node.js">
    ```ts
    function centsToReais(cents: number): number {
      return cents / 100;
    }

    function reaisToCents(reais: number): number {
      return Math.round(reais * 100);
    }

    await createPixCharge({
      amount: centsToReais(order.totalCents),
      clientReference: `order-${order.id}`,
    });

    const callbackAmountCents = reaisToCents(callback.amount);
    if (callbackAmountCents !== order.totalCents) {
      throw new Error('callback 与订单金额不一致');
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python
    from decimal import Decimal

    def cents_to_reais(cents: int) -> Decimal:
        return Decimal(cents) / Decimal(100)

    def reais_to_cents(reais: float | Decimal) -> int:
        return int((Decimal(str(reais)) * Decimal(100)).quantize(Decimal('1')))

    create_pix_charge({
        'amount': float(cents_to_reais(order.total_cents)),
        'clientReference': f'order-{order.id}',
    })
    ```
  </Tab>
</Tabs>

## 各操作最低限额 [#各操作最低限额]

PayZu 在服务端校验。低于最低限额的请求会返回 `400 Bad Request`。

| 操作                                                                                                        | `amount` 最低额 |
| --------------------------------------------------------------------------------------------------------- | ------------ |
| [`POST /pix`](/docs/pix-processamento/endpoints/pix-operations/post_pix)（收款）                              | **R$ 1,00**  |
| [`POST /withdraw`](/docs/pix-processamento/endpoints/withdrawals/post_withdraw)（按密钥提现）                    | **R$ 0,01**  |
| [`POST /withdraw/qrcode`](/docs/pix-processamento/endpoints/withdrawals/post_withdraw_qrcode)（支付 QR Code） | **R$ 0,10**  |
| [`POST /internal-transfer`](/docs/pix-processamento/endpoints/internal-transfer/post_internal_transfer)   | **R$ 0,01**  |

以上是 API schema 层面的最低额。你账户实际生效的最低与最高单笔金额在 `GET /user` 的 `cashInTicketMin`、`cashInTicketMax`、`cashOutTicketMin`、`cashOutTicketMax` 中，调用按这些值校验——请在计算金额前先读取。被拒绝时消息中会给出实际生效的金额，`errorCode` 因路由而异：`POST /pix` 为 `PZD600`，`POST /withdraw` 与 `POST /withdraw/qrcode` 为 `PZS600`，`POST /internal-transfer` 为 `PZC602`。

## 手续费 [#手续费]

PayZu 收取的手续费会通过 callback 在 `serviceFeeCharged` 字段中返回（单位为雷亚尔）。

```json
{
  "amount": 99.90,
  "serviceFeeCharged": 0.99,
  "status": "COMPLETED"
}
```

财务对账时，请考虑：

| 数值                  | 含义                                                                   |
| ------------------- | -------------------------------------------------------------------- |
| `amount`            | 客户支付或您付款的金额。                                                         |
| `serviceFeeCharged` | 该操作的 PayZu 手续费。                                                      |
| 净额                  | `amount - serviceFeeCharged`（入账）或 `amount + serviceFeeCharged`（总支出）。 |

## 校验 callback 中收到的金额 [#校验-callback-中收到的金额]

务必确认 callback 与订单一致。客户可能支付不同的金额（在某些场景下 Pix 允许 QR Code 不固定金额）。

```ts
async function handleDepositCallback(tx: PayzuCallback, order: Order) {
  const callbackCents = reaisToCents(tx.amount);
  if (callbackCents !== order.totalCents) {
    log.warn('金额不一致', {
      pedido: order.totalCents,
      recebido: callbackCents,
    });
    await flagForReview(order, tx);
    return;
  }
  await markOrderPaid(order, tx);
}
```

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

| 陷阱                                  | 症状                            |
| ----------------------------------- | ----------------------------- |
| 误以为是分，发送 `amount: 9990`             | 向客户收取 R$ 9.990,00             |
| 在 Postgres 中将 `amount` 存为 `FLOAT`   | 多行求和时丢失分                      |
| 在 Python 中将 `Decimal` 与 `float` 相加  | 类型错误或精度丢失                     |
| 直接使用 `parseFloat(tx.amount)` 而不四舍五入 | `99.90` 变成 `99.9000000000001` |
| 不校验收到金额与预期金额                        | 部分支付被误判为已完成                   |