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.
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:
| Moeda | Métodos disponíveis | Gateway |
|---|---|---|
AOA (omissão) | REF, GPO, EKZ | Multicaixa (AppyPay/Pay4All) e E-Kwanza |
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. 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,
);
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]);
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 — Redirecionar | Opção B — QR embutido | |
|---|---|---|
| Trabalho de implementação | Nenhum além de payUrl() | Integrar uma biblioteca de QR e decidir web vs. h5 por dispositivo |
| Onde o cliente vê o QR | Página da RedotPay | Dentro da tua página |
| Manutenção | Nenhuma | Acompanhar mudanças na lista de carteiras |
| Indicado para | A maioria das apps | Apps 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' => [...]]
6. Limites e prazos
| Item | Valor |
|---|---|
Validade de uma cobrança RDP | 1 hora a partir da criação |
| Valor máximo por fatura | 99 999,90 USD |
| Valor mínimo | Acima 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.
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
- Actualizar para
devkussema/acapapay-laravel1.3.0 ou superior. - Criar a fatura com
currency: Currency::USD— nunca converter uma existente. - Enviar
success_url, para o cliente voltar à tua app depois de pagar. - 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 em cripto é avulsa. UsarAcapaPayPaymentReceived. - Testar em sandbox com
simulate()antes de passar alive.