SaqSaq Docs
最佳实践

资金与精度

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

{ "amount": 99.90 }

代码中的小数精度

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

语言使用
JavaScript以分为单位的整数,或 decimal.js 库。
Python计算时使用 decimal.Decimal,仅在 API 处使用 float
Goshopspring/decimal 或以分为单位的整数。
JavaBigDecimal,绝不使用 double
PHPbcmath,或以分为单位的整数。
SQL/PostgresNUMERIC(15,2),绝不使用 FLOATREAL

推荐模式:内部以分存储

在数据库中以分为单位的整数存储,仅在 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('callback 与订单金额不一致');
}
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('callback 与订单金额不一致');
}

各操作最低限额

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

操作amount 最低额
POST /pix(收款)R$ 1,00
POST /withdraw(按密钥提现)R$ 0,01
POST /withdraw/qrcode(支付 QR Code)R$ 0,10
POST /internal-transferR$ 0,01

手续费

Saq 收取的手续费会通过 callback 在 serviceFeeCharged 字段中返回(单位为雷亚尔)。

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

财务对账时,请考虑:

数值含义
amount客户支付或您提现的金额。
serviceFeeCharged该操作的 Saq 手续费。
净额amount - serviceFeeCharged(入账)或 amount + serviceFeeCharged(总支出)。

校验 callback 中收到的金额

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

async function handleDepositCallback(tx: saqCallback, 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 中将 Decimalfloat 相加类型错误或精度丢失
直接使用 parseFloat(tx.amount) 而不四舍五入99.90 变成 99.9000000000001
不校验收到金额与预期金额部分支付被误判为已完成

On this page