Acapadev
⚠️
Esta é a documentação v1 (REF/GPO/EKZ/RDP). Para integrações novas, vê a v2 (WiPay + RedotPay).

AcapaPay · Laravel SDK

Pagamentos em USD e Criptomoeda (RedotPay)

O AcapaPay cobra em Kwanza (AOA) através da Multicaixa e do E-Kwanza, e em Dólar (USD) através da RedotPay, que liquida em stablecoins e outras criptomoedas. O cliente paga com a carteira que já usa — MetaMask, Phantom, Trust Wallet, TronLink, Coinbase, entre outras — e tu recebes em dólar.

✅
Não mexe em nada do que já tens. O dólar é uma moeda adicional, não uma substituição: as tuas cobranças em REF, GPO e EKZ continuam exactamente como estão, com o mesmo código, os mesmos webhooks e o mesmo comportamento. Se não criares faturas em USD, nada nesta página te afecta. A versão mínima do pacote é a 1.3.0.

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 quais os métodos possíveis:

MoedaMétodos disponíveisGateway
AOA (omissão)REF, GPO, EKZMulticaixa (AppyPay/Pay4All) e E-Kwanza
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. Configuração

Nenhuma variável nova é obrigatória: as credenciais 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 defini-lo por omissão e deixar de o repetir em cada chamada:

ACAPAPAY_PREFERRED_CURRENCY=USD
ACAPAPAY_PREFERRED_METHOD=RDP

3. Caminho mais curto — checkout hospedado

Se te basta redirecionar o cliente para a página de pagamento do AcapaPay, é uma linha de diferença em relação ao que já fazes: passas currency e preferredMethod.

use AcapaPay\Laravel\Facades\AcapaPay;
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);

O mesmo se aplica a checkoutSession(), para planos e subscrições:

$url = AcapaPay::checkoutSession(
    userId: auth()->id(),
    planReference: 'PRO_YEARLY',
    successUrl: url('/pagamento/sucesso'),
    cancelUrl: url('/pagamento/cancelado'),
    currency: Currency::USD,
    preferredMethod: PaymentMethod::RDP,
);
⚠️
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.

4. Checkout próprio — a escolha é tua

Se preferes construir o teu próprio ecrã de pagamento, usa a API de pagamento direta 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. 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?

Opção A — RedirecionarOpção B — QR embutido
Trabalho de implementaçãoNenhum além de payUrl()Integrar uma biblioteca de QR e decidir web vs. h5 por dispositivo
Onde o cliente vê o QRPágina da RedotPayDentro da tua página
ManutençãoNenhumaAcompanhar mudanças na lista de carteiras
Indicado paraA maioria das appsApps onde a marca no momento do pagamento é crítica

5. Confirmar o pagamento

Exactamente como nos métodos em kwanza: 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 —
    // é o evento certo para pagamentos avulsos, como a maioria dos de cripto.
    if ($event->currency === 'USD') {
        // ...
    }
}

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 da RedotPay falhe, a reconciliação do AcapaPay apanha o pagamento na mesma, só com mais alguns minutos de atraso.

6. Limites e prazos

ItemValor
Validade de uma cobrança RDP1 hora a partir da criação
Valor máximo por fatura99 999,90 USD
Valor mínimoAcima de 0

O tecto vem do contrato da RedotPay: 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. Se precisares de cobrar mais, divide por várias faturas.

7. 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 dos outros métodos em sandbox. Enquanto REF, GPO e EKZ devolvem sempre um mock, o RDP devolve um link de pagamento genuíno do ambiente de sandbox da RedotPay, onde podes pagar com carteiras de teste. A razão é simples: a RedotPay tem o seu próprio ambiente de testes, e um mock nosso ensinaria pouco sobre como a página deles se comporta. O simulate() continua a funcionar em qualquer dos casos.

8. Lista de verificação

  1. Actualizar para devkussema/acapapay-laravel 1.3.0 ou superior.
  2. Criar a fatura com currency: Currency::USD — nunca converter uma existente.
  3. Enviar success_url, para o cliente voltar à tua app depois de pagar.
  4. 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 em cripto é avulsa. Usar AcapaPayPaymentReceived.
  6. Testar em sandbox com simulate() antes de passar a live.
Enter para selecionar ESC para sair