Prompt de integração (web)
Copia tudo:Vais integrar o sistema de pagamentos FaciPay (Angola, moeda AOA) no meu projecto —
frontend + backend. Executa em quatro fases, pela ordem indicada. Não escrevas código antes
da Fase 1 estar respondida.
## FASE 1 — Descoberta
Faz todas as perguntas abaixo numa só mensagem e espera as minhas respostas.
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 identificas 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 directo
(FPSOLPG), ou vários? (O FaciPay directo é sempre o padrão e não se desativa.)
5. Páginas de retorno — paths para sucesso, pendente, cancelado.
6. Webhook — tens domínio público? Se não, qual o túnel de dev (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:
- 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
- Acrescenta as variáveis ao .env e versões vazias ao .env.example.
## FASE 3 — Implementação
### 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": "{\"returnUrl\":\"...\",\"cancelUrl\":\"...\",\"webhookUrl\":\"...\"}"
}
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.
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.
### Frontend
Carregar a SDK uma vez:
<script src="https://cdn.faciconnect.com/sdks/v1/facipay.min.js"></script>
Container 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' },
config: { lang: 'pt', showAmount: true },
paymentConfig: {
theme: 'light',
allowedPaymentMethods: ['FPMCXEXPRSS', 'FPSOLPGEXT', 'FPSOLPG'],
showUIOfProcessingInfo: true,
referencePaymentLifeSpan: 1440
}
}
}).render('#facipay-button-container');
## FASE 4 — Verificação
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().
- Testar tudo com pk_test_ antes de pk_live_.
Começa pela Fase 1. Faz as perguntas. Espera.
Passa primeiro pelo Quickstart se quiseres perceber o fluxo
antes de deixar a IA escrever o código.