Duas APIs com as mesmas credenciais, e é isso que dá ao Efí quatro das cinco capacidades:
| API | Host | Autenticação | Recursos |
|---|---|---|---|
| Cobranças | cobrancas.api.efipay.com.br |
OAuth2 | charge() — boleto |
| Pix | pix.api.efipay.com.br |
OAuth2 + mTLS | pix(), webhook(), subscription(), pixCharge() |
Clientes ficam de fora: nenhuma das duas APIs mantém cadastro de cliente.
O gateway não faz chamada de rede no construtor. Cada API tem o seu token, pedido na primeira vez que é necessário e renovado sozinho quando expira — um gateway vivo num worker de fila não passa a tomar 401.
use PHPay\Efi\EfiGateway;
use PHPay\Support\{Customer, Money};
$gateway = new EfiGateway(CLIENT_ID, CLIENT_SECRET);
$cobranca = PHPay::gateway($gateway)->charge([
'description' => 'Assinatura PHPay',
'expire_at' => date('Y-m-d', strtotime('+3 days')),
])
->setAmount(Money::reais('100,50'))
->setCustomer(new Customer('Mário Lucas', '12345678909'))
->create();Sem configurar nada, o boleto sai com 2% de multa e 0,033% de juros ao
dia — é o que este gateway sempre mandou. setConfigurations() troca isso:
$gateway->charge([...])
->setAmount(Money::reais('100,50'))
->setCustomer($cliente)
->setConfigurations([
'fine' => 250, // 2,5%
'interest' => 100, // 0,1% ao dia
'days_to_write_off' => 40, // baixa automática, 0 a 120 dias
])
->create();Os dois campos usam escalas diferentes, e é a própria Efí que documenta isso:
fineé percentual × 100 (200= 2%) einteresté percentual × 1000 (33= 0,033%). Por isso os dois são inteiros — 2,5% de multa é250, nunca2.5. A biblioteca recusa decimal com uma mensagem que repete as duas escalas, em vez de deixar o valor errado passar.
Boleto sem encargo:
->setConfigurations(['fine' => 0, 'interest' => 0])Array vazio não serve para isso e é recusado: ele não diz "sem encargo", e
a Efí não documenta se o objeto configurations pode ser omitido.
interest também aceita a forma de objeto, com type em daily ou monthly
(omitido, a Efí calcula ao dia):
->setConfigurations(['fine' => 200, 'interest' => ['value' => 330, 'type' => 'monthly']])A escala da forma mensal a Efí não documenta, então a biblioteca não
converte nada: o valor vai como você escreveu. Campo de configurations que
esta versão não modela também passa adiante — ao contrário do array de
configuração do gateway, aqui o array é payload da Efí, e recusar um campo que
funciona quebraria quem o manda.
O payload da Efí não tem valor no nível da cobrança — tem items. O
setAmount() com a description é o atalho que monta um item; para mais de
uma linha, use setItems():
$gateway->charge(['expire_at' => date('Y-m-d', strtotime('+3 days'))])
->setCustomer($cliente)
->setItems([
['name' => 'Camiseta', 'value' => 4990, 'amount' => 2],
['name' => 'Frete', 'value' => 1500],
])
->create();Com itens explícitos, description e value deixam de ser exigidos: eles
pertencem ao atalho, não ao gateway. value é em centavos, como no resto
da Efí.
Toda requisição da API Pix é por mTLS, inclusive a do token. Passe o
certificado .p12 (ou .pem) da aplicação, que você baixa no painel do Efí:
$gateway = new EfiGateway(CLIENT_ID, CLIENT_SECRET, certificate: '/caminho/certificado.p12');Com senha, ou vindo de uma variável de ambiente — o comum em container e serverless:
use PHPay\Http\Certificate;
new EfiGateway(CLIENT_ID, CLIENT_SECRET, certificate: new Certificate('/caminho/certificado.p12', 'senha'));
new EfiGateway(CLIENT_ID, CLIENT_SECRET, certificate: Certificate::fromBase64(getenv('EFI_CERTIFICATE_BASE64')));O
fromBase64()grava o certificado num arquivo temporário com permissão0600, apagado quando o processo termina. A senha nunca aparece numvar_dump().
O Efí entrega certificado e chave num arquivo só. Os formatos aceitos, o par
.crt + .key que outros PSPs entregam e o que o Certificate confere estão
em Certificado e mTLS.
Certificado de homologação só funciona com $sandbox = true, e o de produção,
com false. Sem certificado, a API de Cobranças continua funcionando e a API
Pix responde com uma ValidationException clara, não com um erro de TLS.
Cobrança Pix — imediata por padrão, com vencimento quando há setDueDate():
$cobranca = $gateway->pixCharge()
->setAmount(Money::reais('123,45'))
->setKey('sua-chave-pix') // chave da conta Efí que recebe
->setCustomer(new Customer('Mário Lucas', '12345678909'))
->setDescription('Pedido 1234')
->setExpiration(3600) // segundos
->create();
$qr = $gateway->pixCharge()->qrCode($cobranca['loc']['id']);
$qr['qrcode']; // copia e cola
$qr['imagemQrcode']; // PNG em base64
/* com vencimento: multa, juros e desconto como num boleto */
$gateway->pixCharge()
->setAmount(Money::reais(250))
->setKey('sua-chave-pix')
->setCustomer(new Customer('Sixtec LTDA', '12345678000199'))
->setDueDate('2026-12-31', validityAfterDue: 15)
->create();
/* devolução, total ou parcial */
$gateway->pixCharge()->refund($endToEndId, Money::reais(10));pixCharge() é um extra do gateway concreto, como o webhookDeliveries()
do Pagar.me: o charge() da facade já é o boleto, e mudar o retorno dele
quebraria quem está na v2.
Esse refund() não é a capacidade SupportsRefunds, e a Efí não a declara.
São três incompatibilidades, todas da API dela e não da biblioteca: o valor é
obrigatório (o BACEN exige valor na devolução, enquanto a capacidade
promete que valor ausente estorna integral), o endereçamento é pelo
endToEndId do Pix recebido e não pela cobrança, e a devolução vive na API Pix
— o charge() da Efí é boleto, que não estorna por aqui. Declarar a capacidade
e depois recusar de dentro dela seria pior que não declarar.
Chaves Pix — só chaves aleatórias (EVP) são gerenciáveis pela API:
$chave = PHPay::gateway($gateway)->pix()->createKey()['chave'];
PHPay::gateway($gateway)->pix()->getAll();
PHPay::gateway($gateway)->pix()->destroy($chave);Sem QR Code estático. A API Pix da Efí não expõe endpoint para criar um, então aqui não existe o
staticQrCode()do Asaas e da Woovi. Pelo padrão do BACEN, um QR Code estático é um BR Code composto a partir da chave, de um valor opcional e de um identificador livre — quem oferece endpoint faz por conveniência, não por necessidade. OqrCode()dopixCharge(), acima, é o QR Code de uma cobrança, que é outra coisa.Para o estático, use
StaticPixCode: 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:
PHPay::gateway($gateway)
->webhook(['chave' => $chave, 'webhookUrl' => 'https://loja.com/webhook/pix'])
->create();O mTLS vale nos dois sentidos: por padrão o Efí só entrega para um servidor que valide o certificado dele. Se o seu não consegue (hospedagem compartilhada, balanceador que termina o TLS),
skipMtlsChecking()desliga a checagem — e aí valide a origem de outro jeito, como umhmacna URL.
Pix Automático — o pagador autoriza uma vez no app do banco, e cada ciclo é debitado sem nova aprovação:
use PHPay\Efi\Enums\{AccountTypeEnum, PeriodicityEnum};
$assinaturas = PHPay::gateway($gateway)->subscription();
/* 1. o location que o QR Code de autorização aponta */
$location = $assinaturas->createLocation();
/* 2. a recorrência: o que o pagador autoriza */
$recorrencia = $assinaturas
->setCustomer(new Customer('Mário Lucas', '12345678909'))
->setContract('CONTRATO-2026-001') // até 35 caracteres
->setDescription('Plano mensal')
->setAmount(Money::reais('49,90')) // ou setMinimumAmount(), para valor variável
->setPeriodicity(PeriodicityEnum::MONTHLY, '2026-10-01')
->allowRetries() // até 3 tentativas em 7 dias
->setLocation($location['id'])
->create();
$assinaturas->find($recorrencia['idRec'])['dadosQR']; // copia e cola para o pagador autorizar
/* 3. a cobrança de cada ciclo */
$assinaturas
->setReceiver('12345-6', AccountTypeEnum::CHECKING, '0001')
->createCharge($recorrencia['idRec'], Money::reais('49,90'), '2026-11-05');
$assinaturas->cancel($recorrencia['idRec']);