Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,23 @@ e rode `php examples/asaas/charges.php` (ou `make asaas resource=charges`).
`PHPay\Efi\`, `PHPay\MercadoPago\` → `src/Gateways/<Gateway>/`. Gateway novo
precisa de um root novo no `composer.json` — não introduza `PHPay\Gateways\...`.

## Valores monetários

Use **`PHPay\Support\Money`**. Os gateways discordam da unidade — Asaas e Mercado
Pago querem reais decimais, os outros sete querem centavos inteiros — e errar não
quebra a integração, cobra o valor errado.

- Fábricas: `Money::reais()` (aceita float, int e string em notação brasileira) e
`Money::centavos()`. Acessores na instância: `toReais()` e `toCentavos()`.
- **Dentro dos gateways**, normalize com `Money::asReais()` ou `Money::asCentavos()`,
que aceitam `Money` ou número cru. Nunca leia o valor direto do parâmetro.
- Todo método que recebe valor aceita `Money|int` (ou `Money|int|float` nos de reais).
Número cru é lido na unidade que aquele gateway sempre esperou — compatibilidade.
- Onde o valor vive dentro do array de payload (Asaas, Mercado Pago, Efí), existe
`setAmount()`. Gateway novo com valor em array deve ter o mesmo.
- `Money::reais()` **recusa mais de duas casas decimais** de propósito. Não "conserte"
isso com arredondamento: é o que impede erro de um centavo na conciliação.

## Particularidades por gateway

- **Asaas** — `$sandbox` troca a base URL. Único com chaves Pix, porque é PSP.
Expand Down
62 changes: 44 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,30 +263,56 @@ Nos dois últimos, `isSandbox()` diz em qual ambiente você está:

### Unidade monetária

**Este é o erro mais caro de cometer**, porque a cobrança sai com valor errado
em vez de falhar:
Os gateways discordam sobre a unidade, e **errar não quebra a integração — ela
cobra o valor errado**. Mandar `100.50` num gateway de centavos cobra R$ 1,00,
e você só descobre na conciliação.

| Gateway | Unidade | R$ 100,50 é |
| --- | --- | --- |
| **Asaas** | Reais (decimal) | `100.50` |
| **Mercado Pago** | Reais (decimal) | `100.50` |
| **PagBank** | Centavos (inteiro) | `10050` |
| **Pagar.me** | Centavos (inteiro) | `10050` |
| **Cielo** | Centavos (inteiro) | `10050` |
| **Rede** | Centavos (inteiro) | `10050` |
| **AbacatePay** | Centavos (inteiro, mín. 100) | `10050` |
| **Woovi** | Centavos (inteiro) | `10050` |
| **Efí** | Centavos (inteiro) | `10050` |
Use `Money` e o problema deixa de existir: você diz a unidade que tem, o
gateway pede a unidade que precisa, e nenhum dos dois pode errar.

```php
use PHPay\Support\Money;

Nos gateways que usam centavos, o PHPay **recusa valor decimal na validação**,
antes de qualquer chamada:
$valor = Money::reais(100.50); // ou Money::centavos(10050)

$asaas->charge()->setAmount($valor); // vira 100.50
$pagbank->charge()->addItem('Item', $valor); // vira 10050
```

Também aceita string, que é o que um campo de formulário costuma entregar:

```php
$phpay->charge()->addItem('Item', 100.50);
// ValidationException: ... deve ser um inteiro em CENTAVOS maior que zero.
// R$ 10,50 é 1050.
Money::reais('100,50'); // notação brasileira
Money::reais('1.234,56'); // com separador de milhar
Money::reais('100.50'); // notação com ponto
```

E tem o que um total precisa:

```php
$unitario = Money::reais(59.90);

$unitario->multiply(2); // R$ 119,80
$unitario->plus(Money::reais(10)); // R$ 69,90
$unitario->format(); // 'R$ 59,90'
```

> `Money::reais(10 / 3)` **lança exceção** em vez de arredondar. Arredondamento
> silencioso é como nascem erros de um centavo na conciliação — arredonde você
> mesmo, ou use `Money::centavos()` para ser exato.

#### Passando número cru

Continua funcionando, e cada gateway lê na unidade que sempre esperou:

| Gateway | Unidade do número cru | R$ 100,50 |
| --- | --- | --- |
| **Asaas**, **Mercado Pago** | Reais (decimal) | `100.50` |
| **PagBank**, **Pagar.me**, **Cielo**, **Rede**, **AbacatePay**, **Woovi**, **Efí** | Centavos (inteiro) | `10050` |

Nos que usam centavos, o PHPay recusa decimal na validação. Mas é justamente
essa tabela que o `Money` torna desnecessária — **prefira o value object**.

---

## Gateways
Expand Down
7 changes: 4 additions & 3 deletions examples/abacatepay/charges.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use PHPay\AbacatePay\Resources\Charge\Charge;
use PHPay\Exceptions\PHPayException;
use PHPay\PHPay;
use PHPay\Support\Money;

require_once __DIR__ . '/../../vendor/autoload.php';

Expand Down Expand Up @@ -33,8 +34,8 @@
*/
$cobranca = $phpay
->setCustomer($cliente)
->addProduct('prod-1234', 'Assinatura PHPay', 2000) /* R$ 20,00 */
->addProduct('prod-5678', 'Camiseta', 5990, 2, 'Tamanho M') /* R$ 59,90 cada */
->addProduct('prod-1234', 'Assinatura PHPay', Money::reais(20.00)) /* R$ 20,00 */
->addProduct('prod-5678', 'Camiseta', Money::reais(59.90), 2, 'Tamanho M') /* R$ 59,90 cada */
->setUrls(
completionUrl: 'https://exemplo.test/obrigado',
returnUrl: 'https://exemplo.test/loja'
Expand All @@ -58,7 +59,7 @@

PHPay::gateway($gateway)->charge()
->setCustomerId((string) ($criado['data']['id'] ?? ''))
->addProduct('prod-1234', 'Assinatura PHPay', 2000)
->addProduct('prod-1234', 'Assinatura PHPay', Money::reais(20.00))
->setUrls('https://exemplo.test/obrigado', 'https://exemplo.test/loja')
->create();

Expand Down
4 changes: 3 additions & 1 deletion examples/asaas/charges.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use PHPay\Asaas\Resources\Charge\Charge;
use PHPay\Exceptions\PHPayException;
use PHPay\PHPay;
use PHPay\Support\Money;

require_once __DIR__ . '/../../vendor/autoload.php';

Expand All @@ -16,7 +17,6 @@

$charge = [
'billingType' => 'BOLETO',
'value' => 100.00,
'dueDate' => date('Y-m-d', strtotime('+3 days')),
'description' => 'Cobrança de teste do PHPay',
];
Expand All @@ -34,6 +34,8 @@
/* cria a cobrança criando também o cliente */
$chargeCreated = $phpay
->setCharge($charge)
/* o Money cuida da unidade: aqui vira reais, em outros gateways vira centavos */
->setAmount(Money::reais(100.00))
->setCustomer($customer)
->create();

Expand Down
5 changes: 3 additions & 2 deletions examples/cielo/charges.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPay\Cielo\Resources\Charge\Charge;
use PHPay\Exceptions\PHPayException;
use PHPay\PHPay;
use PHPay\Support\Money;

require_once __DIR__ . '/../../vendor/autoload.php';

Expand All @@ -25,7 +26,7 @@
$venda = $phpay
->setOrderId('pedido-' . time())
->setCustomer(['Name' => NAME])
->setPix(15700)
->setPix(Money::reais(157.00))
/* use uma chave estável do seu domínio para tornar o retry seguro */
->setRequestId('pedido-123456')
->create();
Expand All @@ -52,7 +53,7 @@
$comCartao = PHPay::gateway(new CieloGateway(CIELO_MERCHANT_ID, CIELO_MERCHANT_KEY))
->charge()
->setCustomer(['Name' => NAME])
->setCreditCard(15700, [
->setCreditCard(Money::reais(157.00), [
'CardNumber' => '0000000000000001',
'Holder' => 'Mario Lucas',
'ExpirationDate' => '12/2030',
Expand Down
5 changes: 3 additions & 2 deletions examples/pagarme/charges.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use PHPay\PagarMe\PagarMeGateway;
use PHPay\PagarMe\Resources\Charge\Charge;
use PHPay\PHPay;
use PHPay\Support\Money;

require_once __DIR__ . '/../../vendor/autoload.php';

Expand Down Expand Up @@ -33,7 +34,7 @@
*/
$pedido = $phpay
->setCustomer($customer)
->addItem('Assinatura PHPay', 10050)
->addItem('Assinatura PHPay', Money::reais(100.50))
->setPix(1800)
->create();

Expand All @@ -56,7 +57,7 @@
/* boleto, reaproveitando um cliente que já existe */
PHPay::gateway($gateway)->charge()
->setCustomerId((string) $pedido['customer']['id'])
->addItem('Camiseta', 5990, 2)
->addItem('Camiseta', Money::reais(59.90), 2)
->setBoleto(date('Y-m-d', strtotime('+5 days')), ['Não receber após o vencimento'])
->create();

Expand Down
7 changes: 4 additions & 3 deletions examples/pagbank/charges.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPay\PagBank\PagBankGateway;
use PHPay\PagBank\Resources\Charge\Charge;
use PHPay\PHPay;
use PHPay\Support\Money;

require_once __DIR__ . '/../../vendor/autoload.php';

Expand All @@ -31,8 +32,8 @@
*/
$pedido = $phpay
->setCustomer($customer)
->addItem('Assinatura PHPay', 10050)
->setQrCode(10050)
->addItem('Assinatura PHPay', Money::reais(100.50))
->setQrCode(Money::reais(100.50))
/* sem CRUD de webhook na API: a notificação é por pedido */
->setNotificationUrls(['https://exemplo.test/webhook/pagbank'])
->create();
Expand All @@ -52,7 +53,7 @@
$comCartao = PHPay::gateway(new PagBankGateway(TOKEN_PAGBANK_SANDBOX))
->charge()
->setCustomer($customer)
->addItem('Camiseta', 5990, 2)
->addItem('Camiseta', Money::reais(59.90), 2)
->setCharges([[
'reference_id' => 'cobranca-1',
'description' => 'Camiseta',
Expand Down
5 changes: 3 additions & 2 deletions examples/rede/charges.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPay\Rede\Enums\{TransactionKindEnum, TransactionStatusEnum};
use PHPay\Rede\RedeGateway;
use PHPay\Rede\Resources\Charge\Charge;
use PHPay\Support\Money;

require_once __DIR__ . '/../../vendor/autoload.php';

Expand All @@ -31,7 +32,7 @@
$transacao = $phpay
->setReference('pedido-' . time())
->setCard('5448280000000007', 'MARIO LUCAS', '12', '2030', '123')
->setPayment(2099, TransactionKindEnum::CREDIT, installments: 1, capture: true)
->setPayment(Money::reais(20.99), TransactionKindEnum::CREDIT, installments: 1, capture: true)
->setSoftDescriptor('PHPAY')
->create();

Expand Down Expand Up @@ -59,7 +60,7 @@
$emDuasEtapas = PHPay::gateway($gateway)->charge()
->setReference('pedido-2-etapas-' . time())
->setCard('5448280000000007', 'MARIO LUCAS', '12', '2030', '123')
->setPayment(5000, TransactionKindEnum::CREDIT, capture: false)
->setPayment(Money::reais(50.00), TransactionKindEnum::CREDIT, capture: false)
->create();

$phpay->capture((string) $emDuasEtapas['tid']);
Expand Down
7 changes: 4 additions & 3 deletions examples/woovi/charges.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use PHPay\Exceptions\PHPayException;
use PHPay\PHPay;
use PHPay\Support\Money;
use PHPay\Woovi\Enums\PixKeyTypeEnum;
use PHPay\Woovi\WooviGateway;

Expand All @@ -27,7 +28,7 @@
$cobranca = $phpay->charge()
->setCorrelationId('pedido-' . time())
->setCustomer(['name' => NAME, 'email' => EMAIL])
->create(10050); /* R$ 100,50 */
->create(Money::reais(100.50)); /* R$ 100,50 */

echo $phpay->charge()->getPixCode($cobranca) . PHP_EOL;

Expand All @@ -47,7 +48,7 @@

/* QR Code estático: sem valor, o pagador escolhe quanto pagar */
$phpay->pix()->staticQrCode('Caixa 1');
$phpay->pix()->staticQrCode('Mensalidade', 4990, 'mensalidade-2026');
$phpay->pix()->staticQrCode('Mensalidade', Money::reais(49.90), 'mensalidade-2026');

/* Webhooks com CRUD por API — também só aqui e no Asaas */
$phpay->webhook([
Expand All @@ -61,7 +62,7 @@
$phpay->subscription()
->setCustomer(['name' => NAME, 'email' => EMAIL])
->setDayGenerateCharge(10)
->create(4990);
->create(Money::reais(49.90));

/* Cliente avulso */
$phpay->customer(['name' => NAME, 'email' => EMAIL])->create();
Expand Down
5 changes: 3 additions & 2 deletions src/Gateways/AbacatePay/Resources/Charge/Charge.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPay\AbacatePay\Resources\Charge\Interface\ChargeInterface;
use PHPay\AbacatePay\Traits\HasAbacatePayClient;
use PHPay\Exceptions\{ApiException, ValidationException};
use PHPay\Support\Money;

/**
* billings of the AbacatePay API.
Expand Down Expand Up @@ -127,7 +128,7 @@ public function setProducts(array $products): ChargeInterface
public function addProduct(
string $externalId,
string $name,
int $price,
Money|int $price,
int $quantity = 1,
?string $description = null
): ChargeInterface {
Expand All @@ -141,7 +142,7 @@ public function addProduct(
'externalId' => $externalId,
'name' => $name,
'quantity' => $quantity,
'price' => $price,
'price' => Money::asCentavos($price),
];

if ($description !== null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace PHPay\AbacatePay\Resources\Charge\Interface;

use PHPay\Support\Money;

interface ChargeInterface
{
/**
Expand Down Expand Up @@ -49,7 +51,7 @@ public function setProducts(array $products): ChargeInterface;
public function addProduct(
string $externalId,
string $name,
int $price,
Money|int $price,
int $quantity = 1,
?string $description = null
): ChargeInterface;
Expand Down
17 changes: 17 additions & 0 deletions src/Gateways/Asaas/Resources/Charge/Charge.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPay\Asaas\Resources\Customer\Customer;
use PHPay\Asaas\Traits\HasAsaasClient;
use PHPay\Exceptions\{ApiException, ValidationException};
use PHPay\Support\Money;

class Charge implements ChargeInterface
{
Expand Down Expand Up @@ -59,6 +60,22 @@ public function setCharge(array $charge): ChargeInterface
return $this;
}

/**
* set the amount of the charge.
*
* Asaas takes reais as a decimal — pass a Money and the unit is handled
* for you, or a raw number, which is read as reais.
*
* @param Money|int|float $amount
* @return ChargeInterface
*/
public function setAmount(Money|int|float $amount): ChargeInterface
{
$this->charge['value'] = Money::asReais($amount);

return $this;
}

/**
* set query params
*
Expand Down
10 changes: 10 additions & 0 deletions src/Gateways/Asaas/Resources/Charge/Interface/ChargeInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace PHPay\Asaas\Resources\Charge\Interface;

use PHPay\Support\Money;

interface ChargeInterface
{
/**
Expand Down Expand Up @@ -67,6 +69,14 @@ public function setCustomerId(string $customerId): ChargeInterface;
*/
public function setCustomer(array $customer): ChargeInterface;

/**
* set the amount of the charge, in reais
*
* @param Money|int|float $amount
* @return ChargeInterface
*/
public function setAmount(Money|int|float $amount): ChargeInterface;

/**
* set charge
*
Expand Down
Loading
Loading