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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ O de-para completo da v1 para a v2, quebra por quebra, está no
([#111], [#112]).
- Cobertura de testes medida no CI, num job só, com o número no log e no resumo
do job. Sem mínimo que reprove o build ([#111], [#112]).
- `PHPay\Support\StaticPixCode`, que compõe o QR Code estático de Pix (o BR
Code) a partir da chave, localmente e sem rede — como o padrão do BACEN manda.
Fecha a assimetria dos gateways que não expõem endpoint para isso, a Efí entre
eles, sem stub e sem sugerir que a API deles oferece o que não oferece. A suíte
exige o vetor de teste do manual do BACEN byte a byte, porque CRC errado gera um
código que não lê ([#109]).
- Verificador de links da documentação no gate, em `composer test:links`. Ele
confere que todo link relativo aponta para arquivo que existe e que toda
âncora corresponde a um título de verdade — título dentro de bloco de código
Expand Down Expand Up @@ -462,6 +468,7 @@ Primeira versão publicada no Packagist, sob licença MIT.
[#100]: https://github.com/phpay-io/phpay/pull/100
[#101]: https://github.com/phpay-io/phpay/issues/101
[#102]: https://github.com/phpay-io/phpay/pull/102
[#109]: https://github.com/phpay-io/phpay/issues/109
[#111]: https://github.com/phpay-io/phpay/issues/111
[#112]: https://github.com/phpay-io/phpay/pull/112
[#113]: https://github.com/phpay-io/phpay/issues/113
Expand Down
22 changes: 22 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,28 @@ mandar chave em toda requisição esbarraria no `409 idempotency_key_in_use`. Ga
novo só ganha setter se a documentação dele descrever o mecanismo — e a geração da
chave vem de `PHPay\Http\Idempotency::key()`, não de `random_bytes` espalhado.

## QR Code estático

Use **`PHPay\Support\StaticPixCode`**. Pelo padrão do BACEN um QR Code estático é
composto localmente a partir da chave, então não é Resource, não é capacidade e não
entra na enum `Capability` — é suporte, como o `Money`. O Asaas e a Woovi expõem
endpoint por conveniência; a Efí não expõe, e isso não é falta dela.

- **O CRC cobre o literal `6304`**, não para antes dele. Polinômio `0x1021`, C.I.
`0xFFFF`. É o erro mais comum de quem implementa o padrão, e gera um código que
não lê — o vetor oficial do manual do BACEN está na suíte exigindo o payload byte
a byte, e **não é teste opcional**: para o mesmo vetor, o cálculo errado devolve
`4AAD` em vez de `1D3D`.
- O tamanho no TLV é em **bytes**, não em caracteres: quem lê o payload anda em
bytes, e um nome com acento desalinharia todos os campos seguintes.
- O objeto é imutável, como o `Money` e o `Customer`: `with...()` devolve outro.
- **Só o payload**, nunca a imagem: renderizar exige codificador de QR Code, e isso
seria dependência nova numa biblioteca de pagamentos.
- O `merchantName` é exigido pelo padrão e **ignorado pelo app do pagador**, que
mostra o nome vindo do DICT. Está no PHPDoc para ninguém construir em cima dele.
- Não se valida o formato da chave — EVP, e-mail, telefone e CPF/CNPJ não se
parecem, e errar reprovaria chave boa.

## Assinatura de webhook

Use **`webhookSignature()`**. Cadastrar o endpoint é `SupportsWebhooks`; provar que
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ gateway não aceita Pix, não manda webhook ou não assina nada:
forma de pagamento de uma cobrança — e todos aceitam. As duas metades não
andam juntas em todo PSP: o QR Code estático está no Asaas e na Woovi, que
expõem endpoint para ele, e **não** na [Efí](docs/gateways/efi.md), cuja API Pix não tem
essa rota.
essa rota. Onde o gateway não expõe, a biblioteca compõe: ver
[QR Code estático](docs/qr-code-estatico.md).
- **`SupportsWebhooks`** significa *cadastrar endpoints pela API*. Nos outros,
o cadastro é no painel; a notificação vai por cobrança, no campo
`notification_url`. O Pagar.me ainda deixa **consultar e reenviar entregas**,
Expand Down Expand Up @@ -173,6 +174,7 @@ específico dele.
| [Cliente](docs/cliente.md) | o VO `Customer`, porque o mesmo campo tem seis grafias entre os gateways |
| [Valores monetários](docs/valores-monetarios.md) | `Money`, porque reais e centavos não se misturam |
| [Verificando o webhook que chega](docs/webhooks.md) | provar que a notificação veio do gateway |
| [QR Code estático](docs/qr-code-estatico.md) | compor o BR Code de uma chave Pix localmente, que é como o padrão manda |

**Gateways**

Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ específico dele.
| [Cliente](cliente.md) | o VO `Customer`, porque o mesmo campo tem seis grafias entre os gateways |
| [Valores monetários](valores-monetarios.md) | `Money`, porque reais e centavos não se misturam |
| [Verificando o webhook que chega](webhooks.md) | provar que a notificação veio do gateway |
| [QR Code estático](qr-code-estatico.md) | compor o BR Code de uma chave Pix localmente, que é como o padrão manda |

## Gateways

Expand Down
3 changes: 3 additions & 0 deletions docs/gateways/efi.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ PHPay::gateway($gateway)->pix()->destroy($chave);
> valor opcional e de um identificador livre — quem oferece endpoint faz por
> conveniência, não por necessidade. O `qrCode()` do `pixCharge()`, acima, é o
> QR Code **de uma cobrança**, que é outra coisa.
>
> Para o estático, use [`StaticPixCode`](../qr-code-estatico.md): ele compõe o
> payload a partir da chave, sem rede e sem gateway.

**Webhooks** — um por chave Pix, endereçado pela própria chave:

Expand Down
68 changes: 68 additions & 0 deletions docs/qr-code-estatico.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# QR Code estático

Um QR Code estático de Pix não se pede a ninguém: pelo padrão do BACEN ele é
**composto localmente**, a partir da chave. Quem oferece endpoint para isso — o
Asaas e a Woovi — faz por conveniência, e a Efí não ter um não a torna menos
PSP.

`PHPay\Support\StaticPixCode` compõe o payload, sem tocar a rede:

```php
use PHPay\Support\{Money, StaticPixCode};

$code = StaticPixCode::make(
key: 'fale@phpay.io',
merchantName: 'Sixtec LTDA',
merchantCity: 'BRASILIA',
);

echo $code->payload();
// 00020126350014br.gov.bcb.pix0113fale@phpay.io520400005303986...
```

Sem valor, o pagador digita quanto pagar — é o uso mais comum do estático. Com
valor e com identificador:

```php
$code = StaticPixCode::make('fale@phpay.io', 'Sixtec LTDA', 'BRASILIA')
->withAmount(Money::reais('100,50'))
->withTxid('PEDIDO1234');
```

O objeto é imutável: cada `with...()` devolve outro, como no `Money`.

## O que saber antes de usar

**O nome do recebedor não é o que o pagador vê.** O app mostra o nome que a
chave resolve no DICT; o campo 59 do payload é exigido pelo padrão e ignorado
pelo app do pagador. Ele é obrigatório e cosmético — não construa nada em cima
dele.

**Só o copia-e-cola.** Renderizar a imagem exige um codificador de QR Code, e o
runtime desta biblioteca é `ext-curl`, `ext-json`, `ext-openssl` e o Guzzle.
Passe o payload para o gerador que você já usa.

**O txid aceita letras e números, até 25.** Sem txid, o código leva `***`, que
é como o padrão diz "o recebedor não definiu um". `PEDIDO-1234` **não** passa:
hífen não é aceito, e um leitor que recusa o código não explica por quê.

**O valor vai em reais com ponto.** `withAmount(100)` é cem reais, porque reais
é a unidade do campo. Para centavos, `Money::centavos(10050)` — ver
[Valores monetários](valores-monetarios.md).

**A chave não é validada no formato.** EVP, e-mail, telefone e CPF/CNPJ têm
formas diferentes, e uma validação apressada reprovaria chave boa. São
recusadas a chave vazia e a que passa de 77 caracteres, que é o que cabe no
campo.

## Por que o CRC importa

O último campo do payload é um CRC-16 — polinômio `0x1021`, valor inicial
`0xFFFF` — e ele **cobre também o literal `6304`** que o anuncia. Parar o
cálculo antes desses quatro caracteres é o erro mais comum de quem implementa o
padrão, e o resultado é um código que simplesmente não lê: o erro aparece na
frente do pagador, não no seu log.

A suíte carrega o vetor de teste do próprio Manual de Padrões para Iniciação do
Pix e exige o payload **byte a byte**, incluindo o `1D3D` final. Para o mesmo
vetor, o cálculo que para antes do `6304` devolve `4AAD`.
Loading
Loading