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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ examples/mercadopago/credentials.php
examples/pagbank/credentials.php
examples/pagarme/credentials.php
examples/cielo/credentials.php
examples/rede/credentials.php

# configurações locais do Claude Code (pessoais, não versionar)
.claude/settings.local.json
8 changes: 7 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Orientações para o Claude Code trabalhar neste repositório.
PHPay (`phpay-io/phpay`) é uma **biblioteca PHP** (não uma aplicação) que padroniza a
integração com gateways de pagamento brasileiros. Hoje suporta **Asaas** (as cinco
capacidades), **Mercado Pago**, **PagBank** e **Pagar.me** (clientes, cobranças,
assinaturas), **Cielo** (cobranças e recorrência) e **Efí** (cobranças).
assinaturas), **Cielo** (cobranças e recorrência), **Rede** e **Efí** (cobranças).

Requisitos: PHP `^8.1` para consumir a lib; `^8.2` para rodar o ambiente de dev
(Pest 3 e Termwind 2 exigem 8.2+). Dependências de runtime: `ext-curl`, `ext-json`,
Expand Down Expand Up @@ -144,6 +144,12 @@ e rode `php examples/asaas/charges.php` (ou `make asaas resource=charges`).
inteiro em centavos** — os validadores recusam decimal, porque mandar `10.50` onde
se espera `1050` cobra onze centavos. Pix é `qr_codes` do pedido (um só por pedido,
copia-e-cola em `qr_codes[0].text`), não uma `charge`.
- **Rede** — **host de OAuth separado do host de API**, e o caminho do token muda por
ambiente (`oauth2/token` vs `redelabs/oauth2/token`) — está em `RedeEnvironment`,
fora do trait, para o gateway ler sem puxar os verbos HTTP. **O token expira**: é o
único gateway com ciclo de vida de credencial, tratado em `Resources/Authorization`
com margem de 30s antes do vencimento. O `Charge` pede um token a cada chamada e
injeta como Bearer por requisição, em vez de fixar no header do client.
- **Cielo** — **dois hosts separados por tipo de operação**, não por domínio: escritas
em `api.cieloecommerce...`, consultas em `apiquery.cieloecommerce...`. O **mesmo
recurso** usa os dois, por isso `HasHttpClient::request()` aceita um client opcional
Expand Down
79 changes: 65 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- [PagBank](#pagbank)
- [Pagar.me](#pagarme)
- [Cielo](#cielo)
- [Rede](#rede)
- [Efí](#efí)
- [Exemplos executáveis](#exemplos-executáveis)
- [Migrando da v1](#migrando-da-v1)
Expand Down Expand Up @@ -68,13 +69,13 @@ Trocar de gateway é trocar a linha do construtor.

## Gateways suportados

| Capacidade | Interface | Asaas | Mercado Pago | PagBank | Pagar.me | Cielo | Efí |
| --- | --- | :---: | :---: | :---: | :---: | :---: | :---: |
| Clientes | `SupportsCustomers` | ✅ | ✅ | ✅ | ✅ | — | — |
| Cobranças | `SupportsCharges` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Assinaturas | `SupportsSubscriptions` | ✅ | ✅ | ✅ | ✅ | ✅ | — |
| Webhooks | `SupportsWebhooks` | ✅ | — | — | — | — | — |
| Chaves Pix | `SupportsPixKeys` | ✅ | — | — | — | — | — |
| Capacidade | Interface | Asaas | Mercado Pago | PagBank | Pagar.me | Cielo | Rede | Efí |
| --- | --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| Clientes | `SupportsCustomers` | ✅ | ✅ | ✅ | ✅ | — | — | — |
| Cobranças | `SupportsCharges` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Assinaturas | `SupportsSubscriptions` | ✅ | ✅ | ✅ | ✅ | ✅ | — | — |
| Webhooks | `SupportsWebhooks` | ✅ | — | — | — | — | — | — |
| Chaves Pix | `SupportsPixKeys` | ✅ | — | — | — | — | — | — |

Duas colunas merecem explicação, porque a ausência de ✅ **não** quer dizer que o
gateway não aceita Pix ou não manda webhook:
Expand Down Expand Up @@ -235,6 +236,7 @@ que cada um faz em vez de inventar um padrão:
| **PagBank** | `$sandbox` no construtor — troca a URL |
| **Cielo** | `$sandbox` no construtor — troca **as duas** URLs |
| **Efí** | `$sandbox` no construtor — troca a URL |
| **Rede** | `$sandbox` no construtor — troca **as duas** URLs e o caminho do token |
| **Mercado Pago** | Prefixo do token (`TEST-`); host único, sem `$sandbox` |
| **Pagar.me** | Prefixo da chave (`sk_test_`); host único, sem `$sandbox` |

Expand All @@ -260,6 +262,7 @@ em vez de falhar:
| **PagBank** | Centavos (inteiro) | `10050` |
| **Pagar.me** | Centavos (inteiro) | `10050` |
| **Cielo** | Centavos (inteiro) | `10050` |
| **Rede** | Centavos (inteiro) | `10050` |
| **Efí** | Centavos (inteiro) | `10050` |

Nos gateways que usam centavos, o PHPay **recusa valor decimal na validação**,
Expand Down Expand Up @@ -601,6 +604,54 @@ $id = $recorrencia['Payment']['RecurrentPayment']['RecurrentPaymentId'];
$phpay->updateAmount($id, 19900);
$phpay->deactivate($id);
$phpay->reactivate($id);
### Rede

Adquirente, e a forma mais estreita da biblioteca junto com o Efí: **só
cobranças**. Não há recurso de cliente nem assinatura gerenciável — a
transação tem um campo `subscription`, mas é uma flag para a adquirente, não
algo que você liste ou cancele.

A particularidade é a autenticação: **OAuth2 `client_credentials` num host
separado do de API**, com o caminho do token diferente em cada ambiente. E o
token **expira** — o PHPay renegocia sozinho quando isso acontece.

```php
use PHPay\Rede\Enums\TransactionKindEnum;
use PHPay\Rede\RedeGateway;

/* nenhuma chamada de rede aqui: o token é negociado no primeiro uso */
$gateway = new RedeGateway(REDE_PV, REDE_TOKEN);

$transacao = PHPay::gateway($gateway)->charge()
->setReference('pedido-1')
->setCard('5448280000000007', 'MARIO LUCAS', '12', '2030', '123')
->setPayment(2099, TransactionKindEnum::CREDIT) // R$ 20,99
->setSoftDescriptor('PHPAY')
->create();
```

Fluxo em duas etapas — autoriza agora, captura quando o pedido for separado:

```php
$phpay->setPayment(5000, capture: false)->create();

$phpay->capture($tid);
$phpay->refund($tid, 1000); // estorna R$ 10,00
```

O código de retorno `"00"` significa aprovada:

```php
use PHPay\Rede\Enums\TransactionStatusEnum;

TransactionStatusEnum::approved($phpay->getStatus($tid));
```

Num processo longo, dá para inspecionar ou descartar o token em mãos:

```php
$gateway->authorization()->hasValidToken();
$gateway->authorization()->forget();
```

### Efí
Expand Down Expand Up @@ -679,13 +730,13 @@ Dois pontos merecem auditoria de quem vem da v1:

### Cobertura por gateway

| | Asaas | Mercado Pago | PagBank | Pagar.me | Cielo | Efí |
| --- | :---: | :---: | :---: | :---: | :---: | :---: |
| Cobranças | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Clientes | ✅ | ✅ | ✅ | ✅ | — | 🕥 |
| Assinaturas | ✍️ | ✅ | ✅ | ✅ | ✅ | 🕥 |
| Webhooks | ✅ | — | — | leitura ✅ | — | 🕥 |
| Pix | ✅ | ✅ | ✅ | ✅ | ✅ | 🕥 |
| | Asaas | Mercado Pago | PagBank | Pagar.me | Cielo | Rede | Efí |
| --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| Cobranças | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Clientes | ✅ | ✅ | ✅ | ✅ | — | — | 🕥 |
| Assinaturas | ✍️ | ✅ | ✅ | ✅ | ✅ | — | 🕥 |
| Webhooks | ✅ | — | — | leitura ✅ | — | — | 🕥 |
| Pix | ✅ | ✅ | ✅ | ✅ | ✅ | 🕥 | 🕥 |

**✅** pronto · **✍️** parcial · **🕥** planejado · **—** não existe na API do gateway

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"PHPay\\MercadoPago\\": "src/Gateways/MercadoPago/",
"PHPay\\PagBank\\": "src/Gateways/PagBank/",
"PHPay\\PagarMe\\": "src/Gateways/PagarMe/",
"PHPay\\Cielo\\": "src/Gateways/Cielo/"
"PHPay\\Cielo\\": "src/Gateways/Cielo/",
"PHPay\\Rede\\": "src/Gateways/Rede/"
}
},
"autoload-dev": {
Expand Down
71 changes: 71 additions & 0 deletions examples/rede/charges.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

use PHPay\Exceptions\PHPayException;
use PHPay\PHPay;
use PHPay\Rede\Enums\{TransactionKindEnum, TransactionStatusEnum};
use PHPay\Rede\RedeGateway;
use PHPay\Rede\Resources\Charge\Charge;

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

require_once __DIR__ . '/credentials.php';

/*
| Nenhuma chamada de rede acontece aqui: o token OAuth é negociado na primeira
| vez que um recurso precisa dele, e renegociado sozinho quando expira.
*/
$gateway = new RedeGateway(REDE_PV, REDE_TOKEN);

/**
* @var Charge $phpay
*/
$phpay = PHPay::gateway($gateway)->charge();

try {
/*
| Autoriza e captura de uma vez. Passe capture: false para autorizar agora
| e capturar depois.
|
| Todo valor é inteiro em CENTAVOS: R$ 20,99 é 2099.
*/
$transacao = $phpay
->setReference('pedido-' . time())
->setCard('5448280000000007', 'MARIO LUCAS', '12', '2030', '123')
->setPayment(2099, TransactionKindEnum::CREDIT, installments: 1, capture: true)
->setSoftDescriptor('PHPAY')
->create();

$tid = (string) $transacao['tid'];

/* "00" significa aprovada */
$codigo = $phpay->getStatus($tid);

if ($codigo !== null && TransactionStatusEnum::approved($codigo)) {
echo "Aprovada\n";
}

/* consulta pela referência do seu sistema */
$phpay->findByReference('pedido-1');

/* estorno parcial e total, em centavos */
$phpay->refund($tid, 1000);
$phpay->refund($tid);

/*
| Fluxo em duas etapas: autoriza agora, captura quando o pedido for
| separado. Entre as duas, o valor fica reservado no limite do portador
| mas não vira cobrança.
*/
$emDuasEtapas = PHPay::gateway($gateway)->charge()
->setReference('pedido-2-etapas-' . time())
->setCard('5448280000000007', 'MARIO LUCAS', '12', '2030', '123')
->setPayment(5000, TransactionKindEnum::CREDIT, capture: false)
->create();

$phpay->capture((string) $emDuasEtapas['tid']);

/* num processo longo, dá para inspecionar ou descartar o token em mãos */
var_dump($gateway->authorization()->hasValidToken());
} catch (PHPayException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
13 changes: 13 additions & 0 deletions examples/rede/credentials.example.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

/*
| Copie este arquivo para credentials.php e preencha com as credenciais de
| sandbox. credentials.php é ignorado pelo git — nunca commite credencial real.
|
| O PV e o token de sandbox saem da área logada do portal de desenvolvedores,
| criando um projeto associado à e.Rede:
| https://developer.userede.com.br/
*/

const REDE_PV = '';
const REDE_TOKEN = '';
9 changes: 9 additions & 0 deletions src/Gateways/Rede/Enums/TransactionKindEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace PHPay\Rede\Enums;

enum TransactionKindEnum: string
{
case CREDIT = 'credit';
case DEBIT = 'debit';
}
27 changes: 27 additions & 0 deletions src/Gateways/Rede/Enums/TransactionStatusEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace PHPay\Rede\Enums;

/**
* return codes of a Rede transaction, as sent in returnCode.
*
* only the codes worth branching on are listed — Rede publishes dozens of
* decline reasons, and treating them as an exhaustive enum would break every
* time they add one.
*/
enum TransactionStatusEnum: string
{
case APPROVED = '00';
case PENDING = '220';

/**
* whether the given return code means the transaction went through.
*
* @param string $returnCode
* @return bool
*/
public static function approved(string $returnCode): bool
{
return $returnCode === self::APPROVED->value;
}
}
34 changes: 34 additions & 0 deletions src/Gateways/Rede/Interface/RedeGatewayInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace PHPay\Rede\Interface;

use PHPay\Contracts\SupportsCharges;
use PHPay\Rede\Resources\Authorization\Authorization;
use PHPay\Rede\Resources\Charge\Charge;

/**
* the Rede gateway offers charges only.
*
* it is an acquirer, and the narrowest shape in the library alongside Efí.
* There is no customer resource, no webhook CRUD and no Pix key management.
* The transaction payload does carry a `subscription` flag, but that marks a
* recurring charge for the acquirer — it is not a subscription you can list,
* change or cancel, so SupportsSubscriptions is deliberately not declared.
*/
interface RedeGatewayInterface extends SupportsCharges
{
/**
* get resource charge from gateway.
*
* @return Charge
*/
public function charge(): Charge;

/**
* the OAuth authorization, exposed so a long-running process can inspect
* or reset the token it holds.
*
* @return Authorization
*/
public function authorization(): Authorization;
}
50 changes: 50 additions & 0 deletions src/Gateways/Rede/RedeEnvironment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace PHPay\Rede;

/**
* hosts and paths of e.Rede, which differ in more than the domain: the token
* endpoint itself sits at a different path in each environment.
*
* kept apart from the client trait so the gateway can read the environment
* without pulling in the http verbs it does not use.
*/
final class RedeEnvironment
{
/**
* base uri of the transactions API
*
* @param bool $sandbox
* @return string
*/
public static function api(bool $sandbox): string
{
return $sandbox
? 'https://sandbox-erede.useredecloud.com.br/v2/'
: 'https://api.userede.com.br/erede/v2/';
}

/**
* base uri of the authorization host
*
* @param bool $sandbox
* @return string
*/
public static function oauth(bool $sandbox): string
{
return $sandbox
? 'https://rl7-sandbox-api.useredecloud.com.br/'
: 'https://api.userede.com.br/';
}

/**
* path of the token endpoint
*
* @param bool $sandbox
* @return string
*/
public static function tokenPath(bool $sandbox): string
{
return $sandbox ? 'oauth2/token' : 'redelabs/oauth2/token';
}
}
Loading
Loading