# Construir com IA Copie um prompt pronto e integre a FaciPay com o seu assistente de IA — Cursor, Claude, v0 e mais. 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](/pt/android/overview) e [iOS](/pt/ios/overview); para WooCommerce, vê o [plugin](/pt/woocommerce/overview). ## 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. ````markdown Português 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 Accept-Language: pt { "externalTransactionId": "order_", "clientId": "", "applicationUUID": "", "name": "Compra", "amount": , "quantity": 1, "additionalInfo": "" } 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: 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. ```` ````markdown English Act as a senior payments integration engineer. You are going to integrate the FaciPay payment system (Angola, currency AOA) into my project — frontend + backend. Execute it in four phases, in the order shown. Do not write any code before Phase 1 has been answered. This brief is the complete specification and the single source of truth for FaciPay: use ONLY the endpoints, headers, fields and SDK callbacks defined here; do not invent or assume others (there is no FaciPay npm package). If something is not covered here, say so and ask instead of guessing. ## PHASE 1 — Discovery Ask all the questions below in a single message and wait for my answers. (If you can read the project's code — IDE agent or app builder — inspect it first, answer what you can yourself, and ask me only what remains.) 1. Stack — frontend (Next.js, React+Vite, Vue/Nuxt, SvelteKit, plain HTML…), backend (Next.js API routes, Express, NestJS, Fastify, Laravel…) and DB/ORM. 2. Authentication — how do you identify the user on the server (cookie, JWT, session)? 3. Product — where does the payment button go (product, cart, dedicated checkout)? 4. Methods — Multicaixa Express (FPMCXEXPRSS), EMIS Reference (FPSOLPGEXT), direct FaciPay (FPSOLPG), or several? (Direct FaciPay is always the default and cannot be disabled.) 5. Return pages — paths for success, pending, canceled. 6. Webhook — what public endpoint URL will receive notifications? (Give this URL to the FaciPay team to register against your account credentials.) For dev, which tunnel (ngrok, Cloudflare Tunnel)? 7. Environment — sandbox (pk_test_…) or production (pk_live_…)? ## PHASE 2 — Credentials and environment I will paste: FACIPAY_PUBLISHABLE_KEY = pk_test_XXXX FACIPAY_CLIENT_ID = XXXX FACIPAY_CLIENT_SECRET = XXXX FACIPAY_WEBHOOK_SECRET = XXXX Rules: - Until I paste the real values, use placeholders. Never invent credentials, never echo them back, never hardcode them in source files or commit them. - Store the variables using the platform's secrets mechanism: .env plus a .env.example with the same keys and empty values, or the platform's secrets manager (Replit Secrets, Lovable/Supabase secrets, Vercel env vars…). - CLIENT_SECRET and WEBHOOK_SECRET stay on the backend only. PUBLISHABLE_KEY can go to the frontend. - applicationUUID = PUBLISHABLE_KEY without the pk_test_/pk_live_ prefix. - Base URL: sandbox → https://sandbox.api.faciconnect.com · production → https://api.faciconnect.com ## PHASE 3 — Implementation Adapt routes, file layout and idioms to the stack from Phase 1; keep the HTTP contracts exactly as specified. ### Backend Endpoint A — POST /api/facipay/create-order (called by the frontend before the popup): 1. Validate items and RECALCULATE the total on the server (never trust the client). 2. Create a record in `orders` with status 'PEN' and external_transaction_id = "order_" + UUID. 3. Obtain an access token (cache in memory until ~60s before it expires): 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. Create the order: POST {baseUrl}/facipaypartner/createPaymentOrder Authorization: Bearer Accept-Language: pt { "externalTransactionId": "order_", "clientId": "", "applicationUUID": "", "name": "Compra", "amount": , "quantity": 1, "additionalInfo": "" } Note: the webhook is GLOBAL, registered once by the FaciPay team against your account credentials — you give them your webhook endpoint URL during onboarding/support; there is no per-order or self-service setup. additionalInfo is informative and is NOT used to configure the webhook or any return/cancel URLs. Return/cancel navigation is handled client-side by the SDK callbacks (onApprove/onPending/onCancel), not by additionalInfo. 5. Persist response.data.referenceNumber and return it to the frontend (non-empty string). Endpoint B — POST /api/facipay/webhook (source of truth): 1. Read the RAW body before any JSON parser (Express: express.raw() on this route only; Next.js: await req.text(); Laravel: $request->getContent()). 2. Compute HMAC SHA-256 of the raw body with WEBHOOK_SECRET; compare with the header x-facipay-content-token using timingSafeEqual. Invalid → 401 and stop. 3. Only now JSON.parse(raw). 4. Idempotency by externalTransactionId (if already CON/CAN, respond 200 without reprocessing). 5. Update orders.status according to data.paymentStatus: CON=paid, CAN=canceled, PEN=pending. 6. Respond 200 in < 5s (heavy tasks go to a queue/background). Endpoint C — GET /api/facipay/order/:externalTransactionId/status (fallback, safety net only): query GET {baseUrl}/facipaypartner/paymentByExternalTransaction with query params externalTransactionId and applicationUUID, same Bearer token and Accept-Language as Endpoint A. ### Frontend The SDK is loaded ONLY via this script tag (there is no npm package) and exposes the global window.FaciPay: In React/Vue/Svelte, inject the tag once (app HTML or a script loader in an effect) and guard against loading it twice. The FaciPay-hosted popup handles the entire payment UI — never collect payment details yourself. The container must exist in the DOM before .render(). Initialization: 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' }, // local button style only ('rect' | 'pill') config: { lang: 'pt', showAmount: true }, // lang reaches the checkout; showAmount is local paymentConfig: { // everything here reaches the hosted checkout theme: 'light', allowedPaymentMethods: ['FPMCXEXPRSS', 'FPSOLPGEXT', 'FPSOLPG'], showUIOfProcessingInfo: true, referencePaymentLifeSpan: 1440 } } }).render('#facipay-button-container'); ## PHASE 4 — Verification Go through the checklist and report each item as pass or fail: 1. SDK loads without errors and the button renders. 2. Multicaixa Express → onApprove → /sucesso. 3. EMIS Reference → onPending → /pendente with ref and entity. 4. Cancel → onCancel → /cancelado. 5. Webhook with a valid signature → status changes in the DB. 6. Webhook with an invalid signature → 401, DB does not change. 7. Duplicate webhook → 200, without reprocessing. 8. Endpoint C returns the same DB status after the webhook. ## Critical rules - Currency AOA, amount integer with no decimals. Total recalculated on the server. - The HMAC is validated over the raw body, before JSON.parse. The webhook is the source of truth and is idempotent by externalTransactionId. - createOrder() returns a non-empty string. The container exists in the DOM before .render(). - Secrets never reach frontend code, logs or version control. - Test everything with pk_test_ before pk_live_. - Stay within this specification; when in doubt, ask instead of improvising. Start with Phase 1. Ask the questions. Wait. ```` Passe primeiro pelo [Início rápido](/pt/get-started/quickstart) 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.