# 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.