# Início rápido Aceite o seu primeiro pagamento FaciPay em sandbox em ~10 minutos: SDK, botão, backend e webhook. Este guia leva você de zero a um pagamento de teste funcional. Vai carregar a SDK, renderizar o botão e ligar um backend mínimo que cria a ordem e recebe o webhook. **Como obter as credenciais.** As chaves (`pk_…`, `clientId`, `clientSecret`, `webhookSecret`) são emitidas pela FaciPay após abrir uma **conta de negócio** e assinar contrato com a área comercial da **Faci-X**. Para começar, fale connosco: 📞 +244 923 168 670 · ✉️ [atendimento@faci-x.ao](mailto:atendimento@faci-x.ao). A usar IA para programar? Salte para [Construir com IA](/pt/get-started/build-with-ai) e copie um prompt pronto que faz toda esta integração por si. ## 1. Configure as variáveis de ambiente ```bash .env # Apenas backend — nunca exponha no bundle do frontend FACIPAY_CLIENT_ID=xxxxxxxx FACIPAY_CLIENT_SECRET=xxxxxxxx FACIPAY_WEBHOOK_SECRET=xxxxxxxx # Seguro expor no frontend FACIPAY_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxx # URL base: sandbox vs produção FACIPAY_API_URL=https://sandbox.api.faciconnect.com ``` `CLIENT_SECRET` e `WEBHOOK_SECRET` ficam **só no backend**. Apenas a `PUBLISHABLE_KEY` pode ir para o frontend. O `applicationUUID` usado na API é a `PUBLISHABLE_KEY` **sem** o prefixo `pk_test_`/`pk_live_`. ## 2. Carregue a SDK no frontend ```html
``` ## 3. Renderize o botão O container **tem** de existir no DOM antes do `.render()`. ```js const facipay = FaciPay(PUBLISHABLE_KEY); facipay.generateButton({ async createOrder() { const r = await fetch('/api/facipay/create-order', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items: cart }), }); if (!r.ok) throw new Error('Falha ao criar ordem'); const { referenceNumber } = await r.json(); return referenceNumber; // string não-vazia, obrigatório }, async onApprove(data, actions) { actions.onPopupWindowClosed(() => { window.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; window.location.href = `/pendente?ref=${ref}&entity=${entity}`; }); }, async onCancel() { window.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'); ``` ## 4. Crie a ordem no backend O frontend chama este endpoint dentro do `createOrder()`. **Recalcule o total no servidor.** ```js Node.js (Express) import express from 'express'; let cachedToken = null; // cache em memória até ~60s antes de expirar async function getAccessToken() { if (cachedToken && cachedToken.exp > Date.now()) return cachedToken.value; const basic = Buffer.from( `${process.env.FACIPAY_CLIENT_ID}:${process.env.FACIPAY_CLIENT_SECRET}` ).toString('base64'); const res = await fetch(`${process.env.FACIPAY_API_URL}/token`, { method: 'POST', headers: { Authorization: `Basic ${basic}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'grant_type=client_credentials&validity_period=3600', }); const json = await res.json(); cachedToken = { value: json.access_token, exp: Date.now() + 3540 * 1000 }; return cachedToken.value; } app.post('/api/facipay/create-order', express.json(), async (req, res) => { const amount = recalcTotalFromItems(req.body.items); // inteiro AOA, no servidor const externalTransactionId = 'order_' + crypto.randomUUID(); await db.orders.insert({ externalTransactionId, amount, status: 'PEN' }); const token = await getAccessToken(); const applicationUUID = process.env.FACIPAY_PUBLISHABLE_KEY.replace(/^pk_(test|live)_/, ''); const fp = await fetch(`${process.env.FACIPAY_API_URL}/facipaypartner/createPaymentOrder`, { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Accept-Language': 'pt', 'Content-Type': 'application/json', }, body: JSON.stringify({ externalTransactionId, clientId: req.user?.id ?? 'guest', applicationUUID, name: 'Compra', amount, quantity: 1, }), }); const { data } = await fp.json(); await db.orders.update(externalTransactionId, { referenceNumber: data.referenceNumber }); res.json({ referenceNumber: data.referenceNumber }); // string não-vazia }); ``` ## 5. Receba o webhook O webhook é a **fonte da verdade**. Valide o HMAC sobre o **body cru** antes de `JSON.parse`. ```js Node.js (Express) import crypto from 'node:crypto'; // Registe o parser raw SÓ nesta rota app.post('/api/facipay/webhook', express.raw({ type: 'application/json' }), (req, res) => { const raw = req.body; // Buffer const token = req.headers['x-facipay-content-token']; const expected = crypto .createHmac('sha256', process.env.FACIPAY_WEBHOOK_SECRET) .update(raw) .digest('hex'); const a = Buffer.from(String(token)); const b = Buffer.from(expected); if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) { return res.status(401).end(); // assinatura inválida } const payload = JSON.parse(raw.toString('utf8')); const { externalTransactionId, paymentStatus } = payload.data; // Idempotência: se já está em estado final, responde 200 e sai // Atualiza a ordem: CON = pago, CAN = cancelado, PEN = pendente updateOrder(externalTransactionId, paymentStatus); res.status(200).json({ received: true }); } ); ``` O webhook é **global / ao nível da conta**: forneça o seu URL de webhook público à equipa FaciPay para que o registe contra as suas credenciais (no onboarding ou via suporte). Não há campo `webhookUrl` na ordem — o mesmo endpoint recebe **todas** as suas notificações. Em desenvolvimento, exponha o seu webhook com um túnel (ngrok, Cloudflare Tunnel) e dê esse URL público à FaciPay para o registar. ## 6. Teste o fluxo Clique no botão → popup → confirme. Dispara `onApprove` e o webhook chega com `CON`. Escolha Referência → o popup mostra entidade + referência. Dispara `onPending`. Feche o popup sem pagar. Dispara `onCancel`. Chaves, ambientes, ciclo de vida da ordem e idempotência.