# Money and precision (/en/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="Glossary" />
</QuickLinks>

The PayZu Pix API uses **reais with decimal places**. Heads up: many PSPs work in cents, so if you're migrating or comparing integrations, the decimal point here is not optional.

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

## Decimal precision in code [#decimal-precision-in-code]

In JavaScript, `0.1 + 0.2 !== 0.3`. In Python `Decimal` is safe but `float` is not. In SQL, `FLOAT` loses precision.

| Language         | Use                                                          |
| ---------------- | ------------------------------------------------------------ |
| **JavaScript**   | Integer in cents, or `decimal.js` library.                   |
| **Python**       | `decimal.Decimal` when calculating, `float` only at the API. |
| **Go**           | `shopspring/decimal` or integer in cents.                    |
| **Java**         | `BigDecimal`, never `double`.                                |
| **PHP**          | `bcmath`, or integer in cents.                               |
| **SQL/Postgres** | `NUMERIC(15,2)`, never `FLOAT` or `REAL`.                    |

### Recommended pattern: cents internally [#recommended-pattern-cents-internally]

Store as an integer in cents in your DB and convert only at the API edge:

<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('Amount mismatch between callback and order');
    }
    ```
  </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>

## Minimum limits per operation [#minimum-limits-per-operation]

PayZu validates on the server. A request below the minimum returns `400 Bad Request`.

| Operation                                                                                               | Minimum `amount` |
| ------------------------------------------------------------------------------------------------------- | ---------------- |
| [`POST /pix`](/docs/pix-processamento/endpoints/pix-operations/post_pix) (charge)                       | **R$ 1.00**      |
| [`POST /withdraw`](/docs/pix-processamento/endpoints/withdrawals/post_withdraw) (Pix payment by key)    | **R$ 0.01**      |
| [`POST /withdraw/qrcode`](/docs/pix-processamento/endpoints/withdrawals/post_withdraw_qrcode) (pay QR)  | **R$ 0.10**      |
| [`POST /internal-transfer`](/docs/pix-processamento/endpoints/internal-transfer/post_internal_transfer) | **R$ 0.01**      |

These are the API schema minimums. Your account effective minimum and maximum are in `GET /user` (`cashInTicketMin`, `cashInTicketMax`, `cashOutTicketMin`, `cashOutTicketMax`) and the call is validated against those — read them before building the amount. The refusal states the amount applied and the `errorCode` varies per route: `PZD600` on `POST /pix`, `PZS600` on `POST /withdraw` and `POST /withdraw/qrcode`, `PZC602` on `POST /internal-transfer`.

## Fee [#fee]

The fee charged by PayZu arrives in the callback in the `serviceFeeCharged` field (in reais).

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

For financial reconciliation, consider:

| Value               | Meaning                                                                                   |
| ------------------- | ----------------------------------------------------------------------------------------- |
| `amount`            | What the customer paid or you withdrew.                                                   |
| `serviceFeeCharged` | PayZu fee on the operation.                                                               |
| Net                 | `amount - serviceFeeCharged` (incoming) or `amount + serviceFeeCharged` (total outgoing). |

## Validate received amount in the callback [#validate-received-amount-in-the-callback]

Always check that the callback matches the order. The customer may pay a different amount (Pix allows QR without a fixed amount in some cases).

```ts
async function handleDepositCallback(tx: PayzuCallback, order: Order) {
  const callbackCents = reaisToCents(tx.amount);
  if (callbackCents !== order.totalCents) {
    log.warn('Amount mismatch', {
      order: order.totalCents,
      received: callbackCents,
    });
    await flagForReview(order, tx);
    return;
  }
  await markOrderPaid(order, tx);
}
```

## Common pitfalls [#common-pitfalls]

| Pitfall                                           | Symptom                                   |
| ------------------------------------------------- | ----------------------------------------- |
| Sending `amount: 9990` thinking it's cents        | Charges R$ 9,990.00 from the customer     |
| Storing `amount` as `FLOAT` in Postgres           | Loss of cents when summing many rows      |
| Adding `Decimal` with `float` in Python           | Type error or lost precision              |
| Trusting `parseFloat(tx.amount)` without rounding | `99.90` becomes `99.9000000000001`        |
| Not checking received amount vs expected amount   | Partial payment goes through as completed |