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

Agentes IA · Servidor MCP

Integrar Pagamentos de Ponta a Ponta

Esta é a razão de ser do servidor MCP. A API Direta de Pagamentos permite-te construir o teu próprio checkout, sem iframe — e o MCP dá ao assistente tudo o que é preciso para a integrar sozinho: descobrir a app, ler a arquitetura, configurar o webhook, escrever o código e testá-lo.

A sequência

Basta uma instrução em linguagem natural. O que se segue é o que o assistente executa por baixo:

#PassoFerramenta
1Deduzir a app a partir do domínio do projeto localfind_app_by_url
2Ler o contrato completo: chaves, métodos, assinatura, endpointsget_payment_api_config
3Garantir que existe um segredo de assinaturarotate_webhook_secret
4Definir o webhook de faturação e subscrever invoice.paidupdate_payment_config
5Escrever o handler e o cliente M2M no teu projeto— (edição de ficheiros)
6Sincronizar o catálogo de planos, se houver assinaturassync_plans
7Auto-validar contra o código acabado de escrevertrigger_test_payment_webhook
8Percorrer o ciclo real ponta a pontacreate_invoice → create_test_charge → simulate_invoice_payment
9Diagnosticar o que falhou, se falhoulist_webhook_deliveries

Passo 1 — Descoberta automática

O assistente lê o APP_URL do .env do teu projeto e resolve-o contra as tuas apps registadas, comparando o domínio com o redirect_url, a homepage, as origens permitidas e os webhooks de cada uma. Não te pergunta qual é o App ID porque não precisa.

// find_app_by_url { "url": "https://minhaloja.ao" }
{
  "status": "success",
  "data": {
    "host": "minhaloja.ao",
    "match_count": 1,
    "apps": [{
      "id": 7,
      "name": "Minha Loja",
      "environment": "test",
      "client_id": "9d2f...",
      "billing_webhook_url": null
    }]
  }
}

Se match_count for 0, o assistente lista as tuas apps ou cria uma nova com create_oauth_app.

Passo 2 — Ler o contrato, não adivinhar

O get_payment_api_config é o coração da autonomia. Devolve num único pedido tudo o que uma IA teria de perguntar: as chaves, se o gateway está em modo simulado, os métodos de pagamento com os respetivos prazos de validade, o esquema exato de assinatura, e a lista completa de endpoints — tanto os M2M de produção como os de desenvolvimento.

{
  "environment": "test",
  "uses_mock_gateway": true,
  "webhook_secret_configured": true,
  "invoice_paid_subscribed": false,
  "payment_methods": {
    "REF": { "label": "Referência Multicaixa (AppyPay/Pay4All)", "requires_phone_number": false, "validity": "3 dias a partir da criação", "currency": "AOA" },
    "GPO": { "label": "Multicaixa Express", "requires_phone_number": true,  "validity": "60 segundos a partir da criação", "currency": "AOA" },
    "EKZ": { "label": "E-Kwanza", "requires_phone_number": true,  "validity": "5 minutos a partir da criação", "currency": "AOA" },
    "RDP": { "label": "RedotPay (criptomoeda/stablecoins)", "requires_phone_number": false, "validity": "1 hora a partir da criação", "currency": "USD" },
    "WIP": { "label": "WiPay (hospedado)", "requires_phone_number": false, "validity": "sem expiração documentada", "currency": "AOA" }
  },
  "default_currency": "AOA",
  "supported_currencies": ["AOA", "USD"],
  "currency_methods": { "AOA": ["REF", "GPO", "EKZ", "WIP"], "USD": ["RDP"] },
  "webhook_signature": {
    "header": "X-AcapaDev-Signature",
    "algorithm": "HMAC-SHA256",
    "formula": "hash_hmac('sha256', , webhook_secret)"
  },
  "endpoints": { "m2m": { "...": "..." }, "developer": { "...": "..." } }
}

Passo 4 — Configurar o webhook

Se não indicares um URL, o assistente deriva-o do domínio do teu projeto. O subscribe_invoice_paid é essencial: sem ele o evento nunca é enviado, por muito bem configurado que esteja o URL.

// update_payment_config
{
  "app_id": 7,
  "billing_webhook_url": "https://minhaloja.ao/api/webhooks/billing",
  "subscribe_invoice_paid": true
}

Se definires um billing_webhook_url, só o invoice.paid passa a ir para lá; os eventos de identidade (user.logout, user.updated) continuam no webhook_url geral. O segredo de assinatura é o mesmo para ambos. Em desenvolvimento local precisas de um túnel público (ngrok, Expose) — o SSO tem de conseguir alcançar o URL.

Passo 5 — O código que a IA escreve

O handler verifica a assinatura sobre o corpo bruto do pedido, antes de fazer parse do JSON, e com comparação em tempo constante:

Route::post('/api/webhooks/billing', function (Request $request) {
    $raw = $request->getContent();
    $expected = hash_hmac('sha256', $raw, config('services.acapadev.webhook_secret'));

    if (!hash_equals($expected, (string) $request->header('X-AcapaDev-Signature'))) {
        abort(401);
    }

    $payload = json_decode($raw, true);

    if (($payload['event'] ?? null) !== 'invoice.paid') {
        return response()->noContent();
    }

    // Idempotência: o mesmo invoice.paid pode chegar mais do que uma vez.
    $invoiceId = $payload['data']['invoice_id'];

    if (ProcessedPayment::where('invoice_id', $invoiceId)->exists()) {
        return response()->noContent();
    }

    ProcessedPayment::create(['invoice_id' => $invoiceId]);

    // app_reference é a tua referência interna, devolvida tal como a enviaste.
    ActivateOrder::dispatch($payload['data']['app_reference']);

    return response()->noContent();
})->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);

Idempotência não é opcional. O mesmo invoice.paid pode chegar duas vezes — por reenvio manual, por retry de rede, ou porque o polling e o webhook do gateway confirmaram em paralelo. Sem esta guarda, creditas o cliente a dobrar.

Passo 7 — O loop de auto-validação

É aqui que o MCP se distingue de uma API normal. O trigger_test_payment_webhook envia um invoice.paid assinado para o teu endpoint e devolve, de forma síncrona, o código HTTP e o corpo com que o teu código respondeu:

{
  "status": "success",
  "data": {
    "target_url": "https://minhaloja.ao/api/webhooks/billing",
    "signature_header": "X-AcapaDev-Signature",
    "payload_sent": { "event": "invoice.paid", "...": "..." },
    "delivery": {
      "status": "failed",
      "response_status": 500,
      "response_body": "Undefined array key \"app_reference\""
    }
  }
}

O assistente lê aquele response_body, percebe que o próprio código dele rebentou, corrige o ficheiro e volta a disparar — sem te fazer perguntas e sem te pedir para testares nada. A ferramenta não cria faturas, por isso pode ser repetida à vontade.

Passo 8 — O ciclo real

Depois do handler responder 2xx, o assistente percorre o fluxo completo tal como acontecerá em produção:

create_invoice          → devolve invoice_id (UUID) e pay_url
create_test_charge      → REF | GPO | EKZ | RDP | WIP, devolve is_mock: true
simulate_invoice_payment → liquida a fatura e dispara o invoice.paid real
list_webhook_deliveries  → confirma que o teu endpoint respondeu 200

O create_test_charge é o que te dá a forma exata dos dados que a tua UI de checkout vai ter de apresentar — a referência e a entidade para REF, o prompt do Multicaixa Express para GPO, o ticket para EKZ, e o pay_url mais a lista de carteiras para RDP. O campo expires_at vem sempre normalizado em ISO8601, seja qual for o método, para saberes quando deixar de mostrar a referência.

Pagamentos em dólar (RedotPay)

A moeda escolhe-se na criação da fatura (currency) e não se muda depois — é ela que determina os métodos possíveis. AOA dá acesso a REF, GPO e EKZ; USD dá acesso a RDP (criptomoeda via RedotPay). Pedir RDP numa fatura em AOA devolve 422, e não há conversão automática entre as duas.

create_invoice(currency: "USD", success_url: "https://a-tua-app/pagamento/ok")
create_test_charge(payment_method: "RDP")
    → data.pay_url        página de checkout da RedotPay (envia o utilizador para lá)
    → data.paymentMethods carteiras suportadas, cada uma com logo e links
    → expires_at          1 hora

Duas coisas que poupam tempo: o success_url da fatura é o URL para onde a RedotPay devolve o cliente depois de pagar — sem ele, ele fica na página de pagamento do AcapaPay; e os campos webQrCode/h5QrCode de cada carteira são links, não imagens de QR code, por isso quem quiser mostrar o QR dentro da própria página tem de gerar a imagem a partir do link.

Diagnóstico

Sintoma em list_webhook_deliveriesO que significa
Nenhuma entrega registadaO evento não está subscrito, ou não há webhook configurado. Verifica invoice_paid_subscribed.
response_status: 401A verificação de assinatura falhou. Quase sempre por se estar a assinar o JSON reserializado em vez do corpo bruto, ou por o segredo no .env estar desatualizado após uma rotação.
response_status: 404A rota não existe no URL configurado, ou o túnel local caiu.
response_status: 419Proteção CSRF ativa na rota. Exclui-a.
response_status: 500Exceção no handler. O response_body traz a mensagem.
status: failed sem response_statusO SSO não conseguiu sequer alcançar o URL — DNS, certificado ou túnel em baixo.
Enter para selecionar ESC para sair