SaqSaq Docs
Boas práticas

Dinheiro e precisão

A Saq Pix API usa reais com casas decimais, não centavos. Atenção: muitos PSPs trabalham em centavos, então se você está migrando ou comparando integrações, o ponto decimal aqui não é opcional.

{ "amount": 99.90 }

Precisão decimal em código

Em JavaScript, 0.1 + 0.2 !== 0.3. Em Python Decimal é seguro mas float não. Em SQL, FLOAT perde precisão.

LinguagemUse
JavaScriptInteiro em centavos, ou biblioteca decimal.js.
Pythondecimal.Decimal ao calcular, float só na API.
Goshopspring/decimal ou inteiro em centavos.
JavaBigDecimal, nunca double.
PHPbcmath, ou inteiro em centavos.
SQL/PostgresNUMERIC(15,2), nunca FLOAT ou REAL.

Padrão recomendado: centavos internamente

Armazene como inteiro em centavos no seu DB e converta apenas na borda da API:

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('Valor divergente entre callback e pedido');
}
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}',
})
import "github.com/shopspring/decimal"

func CentsToReais(cents int64) decimal.Decimal {
    return decimal.NewFromInt(cents).Div(decimal.NewFromInt(100))
}

func ReaisToCents(reais decimal.Decimal) int64 {
    return reais.Mul(decimal.NewFromInt(100)).Round(0).IntPart()
}
<?php
function centsToReais(int $cents): float {
    return $cents / 100;
}

function reaisToCents(float $reais): int {
    return (int) round($reais * 100);
}

createPixCharge([
    'amount' => centsToReais($order->totalCents),
    'clientReference' => 'order-' . $order->id,
]);

$callbackAmountCents = reaisToCents($callback['amount']);
if ($callbackAmountCents !== $order->totalCents) {
    throw new RuntimeException('Valor divergente entre callback e pedido');
}

Limites mínimos por operação

A Saq valida no servidor. Pedido abaixo do mínimo retorna 400 Bad Request.

Operaçãoamount mínimo
POST /pix (cobrança)R$ 1,00
POST /withdraw (saque por chave)R$ 0,01
POST /withdraw/qrcode (pagar QR)R$ 0,10
POST /internal-transferR$ 0,01

Tarifa

A tarifa cobrada na Saq chega no callback no campo serviceFeeCharged (em reais).

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

Para conciliação financeira, considere:

ValorSignificado
amountO que o cliente pagou ou você sacou.
serviceFeeChargedTarifa Saq sobre a operação.
Líquidoamount - serviceFeeCharged (recebimento) ou amount + serviceFeeCharged (saída total).

Validar valor recebido no callback

Sempre confira que o callback bate com o pedido. Cliente pode pagar valor diferente (Pix permite QR sem valor fixo em alguns casos).

async function handleDepositCallback(tx: saqCallback, order: Order) {
  const callbackCents = reaisToCents(tx.amount);
  if (callbackCents !== order.totalCents) {
    log.warn('Valor divergente', {
      pedido: order.totalCents,
      recebido: callbackCents,
    });
    await flagForReview(order, tx);
    return;
  }
  await markOrderPaid(order, tx);
}

Armadilhas comuns

ArmadilhaSintoma
Enviar amount: 9990 achando que é centavosCobra R$ 9.990,00 do cliente
Armazenar amount como FLOAT no PostgresPerda de centavos em soma de muitas linhas
Somar Decimal com float em PythonErro de tipo ou precisão perdida
Confiar em parseFloat(tx.amount) sem arredondar99.90 vira 99.9000000000001
Não verificar valor recebido vs valor esperadoPagamento parcial passa como concluído

On this page