Usa IA para programar (Claude Code, Cursor, ChatGPT, Codex, Lovable, Bolt.new, v0, Replit…)? Copie o prompt abaixo e cole-o no seu assistente. Ele conduz a integração da FaciPay em quatro fases — descoberta, credenciais, implementação (frontend + backend + webhook) e verificação.
Âmbito: este prompt cobre aplicações web (frontend num browser + backend HTTP). Não cobre apps mobile nativas nem plugins de CMS (WordPress/WooCommerce, Shopify). Para mobile, vê as SDKs Android e iOS; para WooCommerce, vê o plugin.

Prompt de integração (web)

Escolha a língua do prompt e copie tudo. Sugerimos o inglês — os assistentes de IA tendem a seguir prompts em inglês com mais fiabilidade.
Aja como um engenheiro sénior de integrações de pagamentos. Vai integrar o sistema de
pagamentos FaciPay (Angola, moeda AOA) no meu projeto — frontend + backend. Execute em
quatro fases, pela ordem indicada. Não escreva código antes da Fase 1 estar respondida.

Este briefing é a especificação completa e a única fonte de verdade sobre a FaciPay: use
APENAS os endpoints, headers, campos e callbacks da SDK aqui definidos; não invente nem
assuma outros (não existe pacote npm da FaciPay). Se algo não estiver coberto aqui,
diga-o e pergunte em vez de adivinhar.

## FASE 1 — Descoberta
Faça todas as perguntas abaixo numa só mensagem e espere as minhas respostas. (Se
conseguir ler o código do projeto — agente de IDE ou app builder — inspecione-o primeiro,
responda você mesmo ao que conseguir e pergunte-me apenas o que faltar.)
1. Stack — frontend (Next.js, React+Vite, Vue/Nuxt, SvelteKit, HTML puro…), backend
   (Next.js API routes, Express, NestJS, Fastify, Laravel…) e BD/ORM.
2. Autenticação — como identifica o utilizador no servidor (cookie, JWT, sessão)?
3. Produto — onde fica o botão de pagamento (produto, carrinho, checkout dedicado)?
4. Métodos — Multicaixa Express (FPMCXEXPRSS), Referência EMIS (FPSOLPGEXT), FaciPay direto
   (FPSOLPG), ou vários? (O FaciPay direto é sempre o padrão e não se desativa.)
5. Páginas de retorno — paths para sucesso, pendente, cancelado.
6. Webhook — que URL de endpoint público vai receber as notificações? (Forneça esta URL à equipa
   FaciPay para a registar contra as credenciais da sua conta.) Em dev, qual o túnel (ngrok,
   Cloudflare Tunnel)?
7. Ambiente — sandbox (pk_test_…) ou produção (pk_live_…)?

## FASE 2 — Credenciais e ambiente
Vou colar:
  FACIPAY_PUBLISHABLE_KEY = pk_test_XXXX
  FACIPAY_CLIENT_ID       = XXXX
  FACIPAY_CLIENT_SECRET   = XXXX
  FACIPAY_WEBHOOK_SECRET  = XXXX
Regras:
- Até eu colar os valores reais, use placeholders. Nunca invente credenciais, nunca as
  mostre de volta, nunca as hardcode no código-fonte nem as faça commit.
- Guarde as variáveis no mecanismo de segredos da plataforma: .env mais um .env.example
  com as mesmas chaves e valores vazios, ou o gestor de segredos da plataforma (Replit
  Secrets, segredos Lovable/Supabase, env vars da Vercel…).
- CLIENT_SECRET e WEBHOOK_SECRET ficam só no backend. PUBLISHABLE_KEY pode ir ao frontend.
- applicationUUID = PUBLISHABLE_KEY sem o prefixo pk_test_/pk_live_.
- Base URL: sandbox → https://sandbox.api.faciconnect.com · produção → https://api.faciconnect.com

## FASE 3 — Implementação
Adapte rotas, estrutura de ficheiros e convenções à stack da Fase 1; mantenha os
contratos HTTP exatamente como especificados.

### Backend
Endpoint A — POST /api/facipay/create-order (chamado pelo frontend antes do popup):
1. Validar itens e RECALCULAR o total no servidor (nunca confiar no cliente).
2. Criar registo em `orders` com status 'PEN' e external_transaction_id = "order_" + UUID.
3. Obter access token (cache em memória até ~60s antes de expirar):
   POST {baseUrl}/token
   Authorization: Basic base64(CLIENT_ID:CLIENT_SECRET)
   Content-Type: application/x-www-form-urlencoded
   grant_type=client_credentials&validity_period=3600
4. Criar a ordem:
   POST {baseUrl}/facipaypartner/createPaymentOrder
   Authorization: Bearer <token>
   Accept-Language: pt
   {
     "externalTransactionId": "order_<UUID>",
     "clientId": "<id estável do utilizador>",
     "applicationUUID": "<PUBLISHABLE_KEY sem prefixo>",
     "name": "Compra",
     "amount": <inteiro AOA, sem decimais>,
     "quantity": 1,
     "additionalInfo": "<texto livre opcional; apenas informativo>"
   }
   Nota: o webhook é GLOBAL, registado uma única vez pela equipa FaciPay contra as credenciais
   da sua conta — você fornece-lhes a URL do seu endpoint de webhook no onboarding/suporte; não
   há configuração por ordem nem self-service. additionalInfo é informativo e NÃO é usado para
   configurar o webhook nem quaisquer URLs de retorno/cancelamento. A navegação de
   retorno/cancelamento é feita no cliente pelos callbacks da SDK
   (onApprove/onPending/onCancel), não pelo additionalInfo.
5. Persistir response.data.referenceNumber e devolvê-lo ao frontend (string não-vazia).

Endpoint B — POST /api/facipay/webhook (fonte da verdade):
1. Ler o body CRU antes de qualquer parser JSON (Express: express.raw() só nesta rota;
   Next.js: await req.text(); Laravel: $request->getContent()).
2. Calcular HMAC SHA-256 do body cru com WEBHOOK_SECRET; comparar com o header
   x-facipay-content-token usando timingSafeEqual. Inválido → 401 e parar.
3. Só agora JSON.parse(raw).
4. Idempotência por externalTransactionId (se já CON/CAN, responder 200 sem reprocessar).
5. Atualizar orders.status segundo data.paymentStatus: CON=pago, CAN=cancelado, PEN=pendente.
6. Responder 200 em < 5s (tarefas pesadas vão para fila/background).

Endpoint C — GET /api/facipay/order/:externalTransactionId/status (fallback, só rede de
segurança): consulta GET {baseUrl}/facipaypartner/paymentByExternalTransaction com query
params externalTransactionId e applicationUUID, mesmo Bearer token e Accept-Language do
Endpoint A.

### Frontend
A SDK carrega-se APENAS por esta tag de script (não existe pacote npm) e expõe o global
window.FaciPay:
  <script src="https://cdn.faciconnect.com/sdks/v1/facipay.min.js"></script>
Em React/Vue/Svelte, injete a tag uma única vez (HTML da app ou um loader num effect) e
proteja contra carregamento duplo. O popup alojado pela FaciPay trata de toda a UI de
pagamento — nunca recolha dados de pagamento você mesmo. O container tem de existir no
DOM antes do .render(). Inicialização:
  const facipay = FaciPay(PUBLISHABLE_KEY);
  facipay.generateButton({
    async createOrder() { /* POST /api/facipay/create-order → referenceNumber */ },
    async onApprove(data, actions) {
      actions.onPopupWindowClosed(() => location.href = `/sucesso?orderId=${data.payment.orderId}`);
    },
    async onPending(data, actions) {
      actions.onPopupWindowClosed(() => {
        const ref = data.payment.data?.paymentReference;
        const entity = data.payment.data?.entity?.number;
        location.href = `/pendente?ref=${ref}&entity=${entity}`;
      });
    },
    async onCancel() { location.href = '/cancelado'; },
    async onError(e) { console.error('FaciPay error:', e); },
    options: {
      style: { width: '100%', shape: 'pill' },   // só estilo local do botão ('rect' | 'pill')
      config: { lang: 'pt', showAmount: true },  // lang chega ao checkout; showAmount é local
      paymentConfig: {                           // tudo aqui chega ao checkout alojado
        theme: 'light',
        allowedPaymentMethods: ['FPMCXEXPRSS', 'FPSOLPGEXT', 'FPSOLPG'],
        showUIOfProcessingInfo: true,
        referencePaymentLifeSpan: 1440
      }
    }
  }).render('#facipay-button-container');

## FASE 4 — Verificação
Percorra a checklist e reporte cada item como passou/falhou:
1. SDK carrega sem erros e o botão renderiza.
2. Multicaixa Express → onApprove → /sucesso.
3. Referência EMIS → onPending → /pendente com ref e entity.
4. Cancelar → onCancel → /cancelado.
5. Webhook com assinatura válida → estado muda na BD.
6. Webhook com assinatura inválida → 401, BD não muda.
7. Webhook duplicado → 200, sem reprocessar.
8. Endpoint C devolve o mesmo estado da BD após o webhook.

## Regras críticas
- Moeda AOA, amount inteiro sem decimais. Total recalculado no servidor.
- HMAC valida-se sobre o body cru, antes de JSON.parse. Webhook é a fonte da verdade e é
  idempotente por externalTransactionId.
- createOrder() devolve string não-vazia. Container existe no DOM antes de .render().
- Segredos nunca chegam ao código do frontend, a logs nem ao controlo de versões.
- Testar tudo com pk_test_ antes de pk_live_.
- Mantenha-se dentro desta especificação; na dúvida, pergunte em vez de improvisar.

Comece pela Fase 1. Faça as perguntas. Espere.
Passe primeiro pelo Início rápido se quiser perceber o fluxo antes de deixar a IA escrever o código.

Em breve

Estamos a preparar uma tela interativa para personalizar este prompt — escolher os métodos de pagamento padrão, o comportamento do botão e a sua stack, e gerar um prompt à medida do seu projeto.