AcapaPay · Laravel SDK · v2
WiPay (AOA) + RedotPay (USD/Cripto)
O AcapaPay cobra em Kwanza (AOA) através da WiPay (checkout hospedado), e em Dólar (USD) através da RedotPay, que liquida em stablecoins e outras criptomoedas. Para integrações novas, estes são os dois únicos métodos que precisas de conhecer.
1. A regra que evita 90% dos enganos
A moeda escolhe-se na criação da fatura e não se muda depois. É ela que determina qual método usar:
| Moeda | Método | Gateway |
|---|---|---|
AOA (omissão) | WIP | WiPay (checkout hospedado) |
USD | RDP | RedotPay (criptomoeda) |
- Pedir
RDPnuma fatura emAOAdevolve 422. Não há conversão automática entre as duas moedas, nem forma de mudar a moeda de uma fatura já criada — cria uma fatura nova. - Qualquer outro código ISO (
EUR,BRL…) é rejeitado com 422 logo na criação: não existe gateway que o consiga liquidar. - Maiúsculas ou minúsculas é indiferente —
usdeUSDdão no mesmo.
2. WiPay (AOA) — o caminho mais curto
Se te basta redirecionar (ou embutir num iframe) o cliente para a página de pagamento, é a forma mais simples de integrar:
use AcapaPay\Laravel\Facades\AcapaPay;
$url = AcapaPay::createInvoice(
userId: auth()->id(),
amount: 15000,
description: 'Plano PRO — Mensal',
successUrl: url('/pagamento/sucesso'),
cancelUrl: url('/pagamento/cancelado'),
// sem currency/preferredMethod: assume WIP em AOA por omissão
);
return redirect($url);
O mesmo se aplica a checkoutSession(), para planos e subscrições — ver Checkout (v2). Um número de telemóvel é opcional em WIP; quando fornecido, pode ser usado pela WiPay para pré-preencher o ecrã de pagamento.
3. Configuração
Nenhuma variável nova é obrigatória: as credenciais da WiPay e da RedotPay vivem do lado do AcapaPay, não da tua app. Só precisas do que já tens configurado para o AcapaPay.
Se a tua app cobra sempre em dólar, podes definir isso por omissão:
ACAPAPAY_PREFERRED_CURRENCY=USD
ACAPAPAY_PREFERRED_METHOD=RDP
4. RedotPay (USD) — checkout hospedado
use AcapaPay\Laravel\Enums\Currency;
use AcapaPay\Laravel\Enums\PaymentMethod;
$url = AcapaPay::createInvoice(
userId: auth()->id(),
amount: 49.99,
description: 'Consultoria Premium — 1 hora',
currency: Currency::USD,
preferredMethod: PaymentMethod::RDP,
successUrl: url('/pagamento/sucesso'),
cancelUrl: url('/pagamento/cancelado'),
);
return redirect($url);
successUrl. É esse URL que a RedotPay usa para devolver o cliente depois de ele pagar; sem ele, o cliente fica na página de pagamento do AcapaPay em vez de voltar à tua aplicação.
5. Checkout próprio — a escolha é tua
Se preferes construir o teu próprio ecrã de pagamento, usa a API de pagamento direta (v2) através de AcapaPay::direct(). São dois passos:
use AcapaPay\Laravel\Facades\AcapaPay;
use AcapaPay\Laravel\Enums\Currency;
use AcapaPay\Laravel\Enums\PaymentMethod;
$fatura = AcapaPay::direct()->createInvoice([
'customer_name' => $cliente->nome,
'customer_email' => $cliente->email,
'currency' => Currency::USD,
'items' => [
['description' => 'Consultoria Premium', 'quantity' => 1, 'unit_price' => 49.99],
],
'app_reference' => $pedido->id,
'success_url' => url('/pagamento/sucesso'),
]);
$cobranca = AcapaPay::direct()->charge($fatura['invoice_id'], PaymentMethod::RDP);
Há um atalho para estes dois passos juntos:
$cobranca = AcapaPay::createCryptoCharge([
'customer_name' => $cliente->nome,
'items' => [['description' => 'Consultoria', 'quantity' => 1, 'unit_price' => 49.99]],
'success_url' => url('/pagamento/sucesso'),
]); // a moeda assume USD
A partir daqui, tu decides como levar o cliente a pagar (apenas para RDP — o WIP só tem o checkout hospedado). Nenhuma das duas formas é melhor que a outra — a escolha depende de quanto controlo visual queres e de quanto trabalho estás disposto a fazer.
Opção A — Redirecionar (o mais simples)
return redirect($cobranca->payUrl());
// ou, se estiveres a responder a um pedido AJAX do teu frontend:
// return response()->json(['pay_url' => $cobranca->payUrl()]);
O cliente chega a uma página hospedada pela RedotPay, com a lista de carteiras suportadas, cada uma já com o seu QR code desenhado. A RedotPay trata de tudo: gera as imagens dos QR, deteta se o visitante está em telemóvel ou computador, e abre o deep-link certo se a carteira estiver instalada.
A favor: zero código de UI, funciona logo em telemóvel e computador, e a RedotPay mantém aquela página actualizada (carteiras novas, correcções) sem tu mexeres em nada.
Contra: o cliente sai do teu domínio durante o pagamento.
Opção B — QR code dentro da tua página
Se queres que o cliente nunca saia da tua app — por exemplo, mostrar o QR dentro de um modal ao lado do teu logótipo — o ChargeResult dá-te as carteiras todas:
$carteiras = $cobranca->qrCodeUrls();
/*
[
'phantom' => ['name' => 'Phantom', 'logo' => 'https://.../phantom.svg',
'web' => 'https://phantom.app/ul/browse/...', 'h5' => '...'],
'metamask' => [...],
// tipicamente 12 a 13 carteiras — não assumas que são sempre as mesmas
]
*/
return view('checkout.cripto', ['carteiras' => $carteiras]);
webQrCode e h5QrCode que a RedotPay devolve não são imagens. São links (deep-links de carteira, do tipo https://phantom.app/ul/browse/…). Nem a RedotPay nem o SDK geram a imagem do QR code — quem a gera, a partir desse texto, és tu. Sem esse passo não tens QR nenhum para mostrar, só um link em bruto.
Gerar a imagem no servidor, com simplesoftwareio/simple-qrcode:
composer require simplesoftwareio/simple-qrcode
{{-- resources/views/checkout/cripto.blade.php --}}
@foreach ($carteiras as $id => $carteira)
@if ($carteira['web'])
<div class="carteira">
<img src="{{ $carteira['logo'] }}" alt="{{ $carteira['name'] }}">
<p>{{ $carteira['name'] }}</p>
{!! QrCode::size(220)->generate($carteira['web']) !!}
</div>
@endif
@endforeach
Ou no browser, se o teu checkout for uma SPA, com a biblioteca qrcode do npm.
Telemóvel não digitaliza o próprio ecrã
Se detectares que o visitante já está num telemóvel, não faz sentido pedir-lhe que digitalize um QR com o mesmo aparelho. Usa h5 em vez de web, ou nem mostres QR nenhum e dá-lhe antes um botão que abre a carteira directamente:
$link = $cobranca->appUrl('phantom'); // deep-link directo, sem QR
Qual escolher?
| WiPay | RedotPay — Opção A | RedotPay — Opção B | |
|---|---|---|---|
| Trabalho de implementação | Nenhum além de createInvoice() | Nenhum além de payUrl() | Integrar uma biblioteca de QR e decidir web vs. h5 por dispositivo |
| Onde o cliente paga | Página hospedada pela WiPay (ou iframe) | Página da RedotPay | Dentro da tua página |
| Manutenção | Nenhuma | Nenhuma | Acompanhar mudanças na lista de carteiras |
| Indicado para | Pagamentos em AOA | A maioria das apps em USD | Apps onde a marca no momento do pagamento é crítica |
6. Confirmar o pagamento
Para ambos os métodos, o evento invoice.paid chega ao teu webhook_url (ou ao Webhook URL de Faturação, se tiveres um dedicado), assinado em X-AcapaDev-Signature. Ver Webhooks.
use AcapaPay\Laravel\Events\AcapaPayPaymentReceived;
public function handle(AcapaPayPaymentReceived $event): void
{
// Dispara em qualquer fatura paga, com ou sem subscrição associada.
if ($event->currency === 'USD') {
// pagamento RedotPay
}
}
Para polling na tua UI enquanto o cliente paga:
$estado = AcapaPay::direct()->status($invoiceId);
// ['invoice_status' => 'paid', 'paid_at' => ..., 'transaction' => [...]]
7. Limites e prazos
| Item | WiPay (AOA) | RedotPay (USD) |
|---|---|---|
| Validade de uma cobrança | Sem expiração documentada pelo gateway | 1 hora a partir da criação |
| Valor máximo por fatura | Sem tecto documentado pela WiPay | 99 999,90 USD |
| Valor mínimo | Acima de 0 | Acima de 0 |
O tecto da RedotPay vem do contrato dela: cada linha de produto tem de valer menos de 10 000 e uma ordem tem no máximo 10 linhas. O AcapaPay reparte o total da tua fatura pelas linhas necessárias de forma automática; acima do tecto a cobrança falha com mensagem explícita, em vez de criar uma ordem com o valor errado.
8. Testar antes de ir para produção
Com ACAPAPAY_MODO=sandbox, as tuas faturas são marcadas como de teste e direct()->simulate($invoiceId) liquida-as na hora, disparando o invoice.paid real — é assim que validas o teu handler sem dinheiro envolvido.
RDP comporta-se de maneira diferente em sandbox. Devolve um link de pagamento genuíno do ambiente de sandbox da RedotPay, onde podes pagar com carteiras de teste, em vez de um mock nosso — a RedotPay tem o seu próprio ambiente de testes. O simulate() continua a funcionar em qualquer dos casos.
9. Lista de verificação
- Escolher a moeda:
AOA(omissão, WiPay) ouCurrency::USDcomPaymentMethod::RDP. - Nunca converter a moeda de uma fatura existente — cria uma nova.
- Enviar
success_url/successUrl, para o cliente voltar à tua app depois de pagar (essencial em RDP). - Para RDP: escolher entre redirecionar (
payUrl()) ou desenhar o teu QR (qrCodeUrls()). - Confirmar que o handler de
invoice.paidaguenta faturas sem subscrição — a maioria dos pagamentos avulsos usaAcapaPayPaymentReceived. - Testar em sandbox com
simulate()antes de passar alive.