Acapadev

AcapaPay · Laravel SDK · v2

WiPay (AOA) + RedotPay (USD/Cripto)

ℹ️
Esta é a documentação v2: os dois métodos recomendados para integrações novas são WIP (WiPay, hospedado, AOA) e RDP (RedotPay, USD/cripto). REF, GPO e EKZ continuam a funcionar para apps registadas antes de 30/09/2026, mas já não fazem parte desta documentação — consulta a v1 se a tua app ainda os usa.

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:

MoedaMétodoGateway
AOA (omissão)WIPWiPay (checkout hospedado)
USDRDPRedotPay (criptomoeda)
  • Pedir RDP numa fatura em AOA devolve 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 — usd e USD dã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);
⚠️
Envia sempre 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]);
⚠️
Lê isto com atenção antes de escolher a opção B: os campos 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?

WiPayRedotPay — Opção ARedotPay — Opção B
Trabalho de implementaçãoNenhum além de createInvoice()Nenhum além de payUrl()Integrar uma biblioteca de QR e decidir web vs. h5 por dispositivo
Onde o cliente pagaPágina hospedada pela WiPay (ou iframe)Página da RedotPayDentro da tua página
ManutençãoNenhumaNenhumaAcompanhar mudanças na lista de carteiras
Indicado paraPagamentos em AOAA maioria das apps em USDApps 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' => [...]]
ℹ️
Trata o webhook como fonte de verdade e o polling como conveniência de interface. Um pagamento pode confirmar-se pelo webhook quando o cliente já fechou o separador — e, mesmo que o callback do gateway falhe, a reconciliação do AcapaPay apanha o pagamento na mesma, só com mais alguns minutos de atraso.

7. Limites e prazos

ItemWiPay (AOA)RedotPay (USD)
Validade de uma cobrançaSem expiração documentada pelo gateway1 hora a partir da criação
Valor máximo por faturaSem tecto documentado pela WiPay99 999,90 USD
Valor mínimoAcima de 0Acima 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.

ℹ️
O 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

  1. Escolher a moeda: AOA (omissão, WiPay) ou Currency::USD com PaymentMethod::RDP.
  2. Nunca converter a moeda de uma fatura existente — cria uma nova.
  3. Enviar success_url/successUrl, para o cliente voltar à tua app depois de pagar (essencial em RDP).
  4. Para RDP: escolher entre redirecionar (payUrl()) ou desenhar o teu QR (qrCodeUrls()).
  5. Confirmar que o handler de invoice.paid aguenta faturas sem subscrição — a maioria dos pagamentos avulsos usa AcapaPayPaymentReceived.
  6. Testar em sandbox com simulate() antes de passar a live.
Enter para selecionar ESC para sair