- Artigos
- Desenvolvimento
- Classes Abstratas em PHP: Modelando Métodos de Pagamento Sem Duplicação

Ao integrar vários gateways de pagamento em uma aplicação Laravel ou PHP pura, a tentação é copiar e colar. O Stripe exige uma chamada de API, o PayPal precisa de OAuth, e o crédito interno da loja consulta o banco de dados — mas todos os métodos ainda precisam validar valores, registrar transações e retornar uma resposta padronizada. Sem estrutura, você acaba com três classes parecidas que divergem com o tempo.
Classes abstratas resolvem isso ao permitir definir comportamentos compartilhados em um único lugar, exigindo que cada implementação concreta preencha as partes específicas do gateway. Elas ficam entre uma interface comum (apenas contrato) e uma classe regular (totalmente implementada). Se você já se perguntou quando usar uma classe abstrata em vez de uma interface no PHP, o processamento de pagamentos é um dos exemplos mais claros.
O Que Torna uma Classe Abstrata no PHP?
No PHP, você declara uma classe abstrata com a palavra-chave abstract. Não é possível instanciá-la diretamente — new PaymentMethod() geraria um erro. Em vez disso, você a estende e implementa os métodos abstratos declarados na classe pai.
Classes abstratas podem conter:
- Métodos abstratos — assinaturas sem corpo, obrigando as subclasses a implementá-los
- Métodos concretos — lógica completa compartilhada entre todos os tipos de pagamento
- Propriedades e construtores — estado comum como moeda, chaves de API ou dependências de log
Essa combinação é o que torna as classes abstratas poderosas para modelagem de domínio. Uma interface diz o que uma classe deve fazer; uma classe abstrata também pode dizer como parte do trabalho é executada.
Classe Abstrata vs Interface: Quando Usar Cada Uma
Tanto classes abstratas quanto interfaces suportam polimorfismo, mas servem a propósitos diferentes.
- Use uma interface quando classes não relacionadas precisam compartilhar um contrato — por exemplo, qualquer coisa que implemente
JsonSerializable - Use uma classe abstrata quando as subclasses compartilham implementação significativa e você quer reduzir duplicação
- O PHP permite herança simples, então você estende apenas uma classe abstrata, mas pode implementar várias interfaces
Para métodos de pagamento, a classe abstrata costuma ser a escolha certa porque todo gateway compartilha validação, logging e formatação de resposta — só a chamada de cobrança em si muda.
Construindo uma Hierarquia de Métodos de Pagamento
Imagine um checkout de e-commerce que aceita cartão via Stripe, PayPal e crédito interno da loja. Veja uma estrutura prática:
<?php
abstract class PaymentMethod
{
protected float $amount;
protected string $currency;
public function __construct(float $amount, string $currency = 'BRL')
{
$this->amount = $amount;
$this->currency = $currency;
}
abstract protected function processPayment(): array;
abstract public function getName(): string;
public function pay(): array
{
$this->validateAmount();
$this->logAttempt();
$result = $this->processPayment();
$this->logResult($result);
return $this->normalizeResponse($result);
}
protected function validateAmount(): void
{
if ($this->amount <= 0) {
throw new InvalidArgumentException('O valor deve ser maior que zero.');
}
}
protected function logAttempt(): void
{
error_log(sprintf(
'[Pagamento] Tentativa de cobrança %s: %.2f %s',
$this->getName(),
$this->amount,
$this->currency
));
}
protected function logResult(array $result): void
{
$status = $result['success'] ? 'SUCESSO' : 'FALHA';
error_log(sprintf('[Pagamento] Resultado %s: %s', $this->getName(), $status));
}
protected function normalizeResponse(array $result): array
{
return [
'gateway' => $this->getName(),
'amount' => $this->amount,
'currency' => $this->currency,
'success' => $result['success'],
'transaction_id' => $result['transaction_id'] ?? null,
'message' => $result['message'] ?? '',
];
}
}Observe o que acontece aqui. O método pay() funciona como template: define a sequência exata que todo pagamento segue. As subclasses nunca sobrescrevem pay() — implementam apenas as partes que variam.
Implementações Concretas
Cada gateway estende a classe abstrata e implementa os dois métodos abstratos:
<?php
class StripePayment extends PaymentMethod
{
private string $token;
public function __construct(float $amount, string $token, string $currency = 'BRL')
{
parent::__construct($amount, $currency);
$this->token = $token;
}
public function getName(): string
{
return 'stripe';
}
protected function processPayment(): array
{
// Substitua pela chamada real ao SDK do Stripe
$response = $this->chargeViaStripeApi($this->token, $this->amount);
return [
'success' => $response['status'] === 'succeeded',
'transaction_id' => $response['id'],
'message' => 'Cobrança realizada via Stripe.',
];
}
private function chargeViaStripeApi(string $token, float $amount): array
{
// Resposta simulada da API
return ['id' => 'ch_abc123', 'status' => 'succeeded'];
}
}
class PayPalPayment extends PaymentMethod
{
private string $orderId;
public function __construct(float $amount, string $orderId, string $currency = 'BRL')
{
parent::__construct($amount, $currency);
$this->orderId = $orderId;
}
public function getName(): string
{
return 'paypal';
}
protected function processPayment(): array
{
$captured = $this->capturarPedidoPayPal($this->orderId);
return [
'success' => $captured,
'transaction_id' => $this->orderId,
'message' => $captured ? 'Pedido PayPal capturado.' : 'Falha na captura PayPal.',
];
}
private function capturarPedidoPayPal(string $orderId): bool
{
return true; // Simulado
}
}
class StoreCreditPayment extends PaymentMethod
{
private int $userId;
public function __construct(float $amount, int $userId, string $currency = 'BRL')
{
parent::__construct($amount, $currency);
$this->userId = $userId;
}
public function getName(): string
{
return 'store_credit';
}
protected function processPayment(): array
{
$deducted = $this->deduzirDaCarteira($this->userId, $this->amount);
return [
'success' => $deducted,
'transaction_id' => 'wallet_' . uniqid(),
'message' => $deducted ? 'Crédito da loja aplicado.' : 'Saldo insuficiente.',
];
}
private function deduzirDaCarteira(int $userId, float $amount): bool
{
return true; // Simulado
}
}Cada classe tem foco claro. O Stripe cuida de cobranças por token, o PayPal captura um pedido existente e o crédito da loja debita da carteira. Nenhum deles reimplementa validação, logging ou normalização de resposta.
Usando Polimorfismo na Camada de Checkout
O controller de checkout não precisa saber qual gateway está ativo. Ele trabalha com o tipo abstrato:
<?php
function checkout(PaymentMethod $payment): array
{
return $payment->pay();
}
// Uso
$stripe = new StripePayment(99.90, 'tok_visa_4242');
$result = checkout($stripe);
$paypal = new PayPalPayment(49.00, 'ORDER-789');
$result = checkout($paypal);Esse é o ganho real. Você pode trocar estratégias de pagamento em tempo de execução, injetá-las pelo container de serviços do Laravel ou selecioná-las conforme a preferência do usuário — tudo sem alterar a lógica do checkout.
Armadilhas Comuns a Evitar
Classes abstratas são úteis, mas fáceis de usar mal. Tenha estes pontos em mente:
- Não coloque tudo na classe abstrata. Se um método se comporta de forma diferente em cada subclasse sem lógica compartilhada, mantenha-o abstrato ou mova para baixo.
- Cuidado com o problema da classe base frágil. Alterar um método concreto no pai pode quebrar subclasses que dependiam do comportamento antigo. Trate métodos compartilhados como contratos estáveis.
- Prefira composição quando o comportamento variar muito. Se o Stripe precisa de webhooks, o PayPal de redirects e o crédito da loja de transações no banco, injete essas dependências em vez de inflar a classe pai.
- Combine com interfaces quando necessário. Sua classe abstrata pode implementar
PaymentGatewayInterfaceenquanto fornece código reutilizável — isso oferece contrato e implementação compartilhada.
Quando Classes Abstratas Brilham em Projetos Reais
Além de pagamentos, classes abstratas aparecem em codebases PHP bem estruturados:
- Canais de notificação — formatação compartilhada, entrega específica por canal (e-mail, SMS, Slack)
- Geradores de relatório — cabeçalhos CSV e filtros de data comuns, saída específica por formato
- Classes base de repositório — construção de queries compartilhada com buscas específicas por entidade
- Handlers de form request — pipeline de validação comum com regras específicas por recurso
O padrão é sempre o mesmo: identifique os passos que nunca mudam, fixe-os em métodos concretos na classe abstrata e empurre as partes variáveis para métodos abstratos que as subclasses devem definir.
Considerações Finais
Classes abstratas não são relíquias de OOP antigo — são ferramentas práticas para manter codebases PHP sustentáveis conforme as integrações se multiplicam. No contexto de pagamentos, elas impõem um fluxo de checkout consistente e dão a cada gateway espaço para fazer seu trabalho. Na próxima vez que você adicionar o quarto ou quinto provedor, escreverá uma classe nova com dois métodos em vez de copiar mais cem linhas de validação e logging.
Comece mapeando os passos compartilhados. Se você consegue desenhar um fluxograma em que todos os caminhos seguem a mesma sequência com apenas uma caixa diferente no final, provavelmente uma classe abstrata é a abstração certa. Seu eu do futuro — e sua equipe — vão agradecer quando o próximo gateway entrar no backlog.