Documentação Técnica
Documentação Técnica
Gateway de pagamento Angolano por validação de comprovativos
Simples de integrar
3 endpoints principais. Criar, consultar e receber webhook.
Anti-Fraude nativo
HMAC-SHA256, validação IBAN stricta, deduplicação de transações.
AI-Ready
Schema JSON de tools disponível em GET /api/agent-tools.
https://backend-phi-seven-54.vercel.app|Content-Type: application/jsonAutenticação
Endpoints protegidos requerem o header X-API-Key. Obtenha a sua chave em Configurações → API & Webhooks. Apenas o endpoint de consulta de pagamento (GET /api/payments/{id}) é público.
# Todos os requests autenticados necessitam deste header:X-API-Key: spk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Exemplo cURL:curl "https://backend-phi-seven-54.vercel.app/api/payments" \ -H "X-API-Key: spk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Segurança: Nunca exponha a X-API-Key no código de frontend (browser). Todas as cobranças devem ser criadas pelo seu servidor backend.
Fluxo de Pagamento
O sPay valida comprovativos PDF de transferências bancárias enviados por email. O fluxo completo envolve o seu servidor, o cliente e o sPay.
Criar Cobrança
O seu servidor chama POST /api/payments com o montante. Recebe payment_id e pay_url.
Redirecionar Cliente
Envie o cliente para pay_url. A página hosted mostra o email temporário e os dados de transferência.
Cliente Efectua Transferência
O cliente transfere via BAI Directo, EMIS/Multicaixa Express e envia o PDF comprovativo para o email mostrado.
sPay Valida o PDF
O sPay recebe o email, extrai o PDF, valida IBAN/telefone, montante e unicidade da transação.
Webhook Disparado
Se válido, o sPay envia POST ao seu webhook com event: 'payment.paid' assinado com HMAC-SHA256.
Entregar Produto/Serviço
O seu servidor verifica a assinatura e activa o produto ou serviço para o cliente.
SDKs Oficiais
Integre o sPay rapidamente usando as nossas bibliotecas oficiais para Python e JavaScript/TypeScript. Ambas estão configuradas por padrão para comunicar com o ambiente de produção.
SDK para Python
Biblioteca oficial para integrações em ambientes Django, Flask, FastAPI ou scripts locais.
Instalação
pip install ./sdk/pythonUso Básico
from spay_sdk import SPayClient # Inicializa o cliente (URL de produção por defeito)client = SPayClient(api_key="spay_live_xxxxxx") # Criar cobrançapayment = client.create_payment(amount=3000, description="Compra #1")print(payment["pay_url"]) # Listar pagamentos pagospayments = client.list_payments(status="paid", limit=10)SDK para JS / TS
Biblioteca compilada e tipada para projetos Node.js, Express, NestJS e frameworks modernos.
Instalação & Build
cd sdk/javascript && npm install && npm run buildUso Básico
import { SPayClient } from './sdk/javascript'; const client = new SPayClient('spay_live_xxxxxx'); async function main() { // Criar cobrança const payment = await client.createPayment(3000, { description: "Compra #1" }); console.log(payment.pay_url); // Listar pagamentos const payments = await client.listPayments("paid", 10);}Criar Cobrança
POST/api/paymentsCria uma nova cobrança e retorna um email temporário único para onde o cliente deve enviar o comprovativo PDF. Requer autenticação via X-API-Key.
Parâmetros do Body
| Campo | Tipo | Req. | Descrição |
|---|---|---|---|
| amount | number | ✓ | Montante em Kz (Kwanzas). Deve ser maior que 0. |
| expires_in_minutes | integer | ✓ | Tempo de expiração em minutos. Intervalo aceite: 5 a 1440 (24h). |
| description | string | — | Descrição opcional da encomenda, exibida na página de checkout. |
const createPayment = async () => { const response = await fetch("https://backend-phi-seven-54.vercel.app/api/payments", { method: "POST", headers: { "Content-Type": "application/json", "X-API-Key": "spk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, body: JSON.stringify({ amount: 3000, expires_in_minutes: 15, description: "Compra #1234 - Produto XYZ" }) }); const payment = await response.json(); // Redirecionar cliente para o checkout window.location.href = payment.pay_url; // Ou guardar o ID para rastrear console.log("Payment ID:", payment.payment_id); // payment.email = "pay-pay-x8f92k@mailtm.com" // payment.expires_at = "2026-06-16T21:15:00Z"};Resposta 201 Created
{ "payment_id": "pay_x8f92k", "email": "pay-pay-x8f92k@domain.mailtm.com", "pay_url": "https://spayment.vercel.app/pay/pay_x8f92k", "expires_at": "2026-06-16T21:15:00.000Z", "amount": 3000, "currency": "Kz", "status": "pending"}Campos da Resposta
| Campo | Tipo | Req. | Descrição |
|---|---|---|---|
| payment_id | string | ✓ | Identificador único. Guarde para rastrear o pagamento. |
| string | ✓ | Email temporário. O cliente envia o PDF comprovativo para este endereço. | |
| pay_url | string | ✓ | URL pública da página de checkout hospedada pelo sPay. Redirecione o cliente aqui. |
| expires_at | string (ISO 8601) | ✓ | Data e hora de expiração da cobrança. |
| status | "pending" | ✓ | Estado inicial da cobrança. |
Consultar Pagamento
GET/api/payments/{payment_id}Consulta o estado actual de uma cobrança. Não requer autenticação — pode ser chamado directamente do frontend para mostrar o estado ao cliente.
const checkStatus = async (paymentId) => { // Não requer autenticação const response = await fetch( `https://backend-phi-seven-54.vercel.app/api/payments/${paymentId}` ); const details = await response.json(); // status: "pending" | "paid" | "expired" console.log("Estado:", details.status); if (details.status === "paid") { console.log("Transação:", details.transaction_id); console.log("Pago em:", details.paid_at); }};Resposta 200 OK
{ "payment_id": "pay_x8f92k", "status": "paid", "amount": 3000.0, "currency": "Kz", "description": "Compra #1234", "created_at": "2026-06-16T21:00:00", "expires_at": "2026-06-16T21:15:00", "paid_at": "2026-06-16T21:08:42", "transaction_id": "10963242"}| Status | Descrição |
|---|---|
| pending | Aguarda o envio e validação do comprovativo. |
| paid | Comprovativo validado com sucesso. Pagamento confirmado. |
| expired | O tempo limite foi ultrapassado sem confirmação. |
Listar Pagamentos
GET/api/paymentsLista todos os pagamentos do comerciante autenticado. Suporta filtragem por estado e paginação.
Query Parameters
| Campo | Tipo | Req. | Descrição |
|---|---|---|---|
| status | string | — | Filtrar por estado: "pending", "paid" ou "expired". |
| limit | integer | — | Máximo de resultados a retornar. Default: 50. |
| offset | integer | — | Offset de paginação. Default: 0. |
const listPayments = async () => { const params = new URLSearchParams({ status: "paid", limit: "20", offset: "0" }); const response = await fetch( `https://backend-phi-seven-54.vercel.app/api/payments?${params}`, { headers: { "X-API-Key": "spk_xxxxxxxxxxxxxxxxxxxxxxxx" } } ); const payments = await response.json(); console.log(`Total recebidos: ${payments.length}`);};Configurar Webhook
Configure a URL do seu servidor para receber notificações em tempo real quando um pagamento é confirmado. Esta é a forma preferida e mais fiável de rastrear pagamentos, em vez de polling.
/api/webhook/configConfigura ou actualiza a URL do webhook.
curl -X POST "https://backend-phi-seven-54.vercel.app/api/webhook/config" \ -H "Authorization: Bearer SEU_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url": "https://seu-servidor.com/webhooks/spay"}'/api/webhook/testEnvia um evento de teste para a URL configurada.
curl -X POST "https://backend-phi-seven-54.vercel.app/api/webhook/test" \ -H "Authorization: Bearer SEU_JWT_TOKEN"Verificação de Assinatura HMAC-SHA256
const crypto = require("crypto"); // No seu servidor (ex: Express)app.post("/webhooks/spay", (req, res) => { const signature = req.headers["x-spay-signature"]; const secret = "whsec_xxxxxxxxxxxxxxxxxxxxxxxx"; // 1. Serializar o payload com chaves ordenadas const payloadStr = JSON.stringify( req.body, Object.keys(req.body).sort() ); // 2. Calcular HMAC-SHA256 const computed = crypto .createHmac("sha256", secret) .update(payloadStr) .digest("hex"); // 3. Comparar de forma segura if (computed !== signature) { return res.status(401).send("Assinatura inválida"); } // 4. Processar o evento const { event, data } = req.body; if (event === "payment.paid") { console.log(`Pagamento ${data.payment_id} confirmado!`); console.log(`Valor: ${data.amount} Kz`); // → Activar o produto/serviço para o cliente } res.status(200).send("OK");});Eventos de Webhook
O sPay envia um HTTP POST para o seu endpoint configurado quando ocorre um evento relevante.
payment.paid— Disparado quando o comprovativo é validado com sucesso// Header recebido no seu servidor:// X-SPay-Signature: e3b0c44298fc1c149afbf4c... { "event": "payment.paid", "data": { "payment_id": "pay_x8f92k", "status": "paid", "amount": 3000.0, "currency": "Kz", "transaction_id": "10963242", "recipient_phone": "922599463", "paid_at": "2026-06-07T12:04:05" }}Fontes de Pagamento Suportadas
| Fonte | Remetente do Email | Método de Validação |
|---|---|---|
| BAI Directo (via telefone) | baidirecto@bancobai.ao | Últimos 9 dígitos do telemóvel |
| BAI Directo (via IBAN) | baidirecto@bancobai.ao | IBAN completo — correspondência exacta |
| EMIS / Multicaixa Express | noreply@emis.co.ao | Telemóvel (últimos 9 dígitos) ou IBAN completo |
Comprovativos BAI Directo de remetentes diferentes de baidirecto@bancobai.ao e comprovativos EMIS / Multicaixa Express de remetentes diferentes de noreply@emis.co.ao são rejeitados como fraudulentos.
Referência de Erros
Todas as respostas de erro seguem o formato: {"detail": "Descrição do erro em Português."}
| Código HTTP | Quando ocorre | Acção recomendada |
|---|---|---|
| 400Bad Request | Body malformado ou campos inválidos. | Verificar a estrutura do JSON enviado. |
| 401Unauthorized | X-API-Key ausente ou inválida. | Confirmar a chave em Configurações → API & Webhooks. |
| 403Forbidden | Saldo de faturação insuficiente ou limite do plano atingido. | Recarregar o saldo em Faturação & Planos. |
| 404Not Found | payment_id não encontrado. | Verificar se o ID está correcto. |
| 409Conflict | Transação duplicada — o mesmo PDF já foi usado. | Solicitar um novo comprovativo ao cliente. |
| 422Validation Error | amount ≤ 0 ou expires_in_minutes fora do intervalo 5–1440. | Corrigir os valores dos parâmetros. |
| 500Internal Error | Erro interno do servidor. | Tentar novamente. Contactar suporte se persistir. |
Limites & Planos
O sPay opera num modelo pré-pago. Cada transação confirmada debita 25 Kz do saldo de faturação. O plano define também o número máximo de contas bancárias de receção.
| Plano | Preço | Validações | Contas Bancárias | Custo por Transação |
|---|---|---|---|---|
| Starter | 1.000 Kz | 40 transações | 1 conta | 25 Kz / transação |
| Boost | 5.000 Kz | 210 transações | 2 contas | 25 Kz / transação |
| Growth | 10.000 Kz | 450 transações | 4 contas | 25 Kz / transação |
| Scale | 25.000 Kz | 1.200 transações | 10 contas | 25 Kz / transação |
Guia para Agentes de IA
O sPay foi desenhado para ser facilmente integrado por agentes de IA (LLMs, AutoGPT, CrewAI, LangChain, etc.). O endpoint GET /api/agent-tools retorna o schema JSON de ferramentas no formato compatível com OpenAI Function Calling.
skill.md
Documentação completa em Markdown para treino de agentes
agent-tools JSON
Schema de tools no formato OpenAI Function Calling
Schema JSON de Ferramentas
// GET https://backend-phi-seven-54.vercel.app/api/agent-tools// Retorna o schema JSON completo para uso em AI Agents { "tools": [ { "name": "create_payment", "description": "Cria uma cobrança sPay. Retorna payment_id e pay_url.", "parameters": { "type": "object", "properties": { "amount": { "type": "number" }, "expires_in_minutes": { "type": "integer", "default": 15 }, "description": { "type": "string" } }, "required": ["amount", "expires_in_minutes"] }, "http": { "method": "POST", "path": "/api/payments" } }, { "name": "get_payment_status", "description": "Consulta o status de um pagamento.", "parameters": { "type": "object", "properties": { "payment_id": { "type": "string" } }, "required": ["payment_id"] }, "http": { "method": "GET", "path": "/api/payments/{payment_id}" } } ]}Árvore de Decisão para Agentes
# Lógica recomendada para um AI Agent processar pagamento: 1. CRIAR COBRANÇA POST /api/payments Body: { amount, expires_in_minutes: 15, description } → Guardar: payment_id, pay_url 2. APRESENTAR AO UTILIZADOR "Aceda a {pay_url} para efectuar o pagamento. Envie o PDF comprovativo para o email mostrado na página." 3. MONITORIZAR STATUS (a cada 30 segundos) GET /api/payments/{payment_id} → status == "paid" → Confirmar e entregar produto/serviço → status == "expired" → Informar utilizador, criar nova cobrança → status == "pending" → Continuar a monitorizar 4. ALTERNATIVA PREFERIDA: Webhook Configurar POST /api/webhook/config com a sua URL Ao receber event "payment.paid" → Confirmar imediatamenteTemplate de Prompt para Agentes
Você é um assistente de pagamento integrado com sPay. - API Key: {API_KEY} - Base URL: {BASE_URL} Quando solicitado a cobrar um cliente: 1. Chame POST /api/payments com o montante e 15 minutos de expiração. 2. Retorne o pay_url ao utilizador. 3. Rastreie o payment_id e reporte o status quando consultado. 4. Ao receber webhook event "payment.paid", confirme a transação.
Exemplos de Código Completo
Exemplos de integração completa prontos a usar — criar cobrança + receber e verificar webhook.
const express = require("express");const crypto = require("crypto"); const API_KEY = "spk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";const BASE_URL = "https://backend-phi-seven-54.vercel.app";const WEBHOOK_SECRET = "whsec_xxxxxxxxxxxxxxxxxxxxxxxx"; const app = express();app.use(express.json()); // 1. Criar cobrança no checkout da lojaapp.post("/checkout", async (req, res) => { const { amount, description } = req.body; const spay = await fetch(`${BASE_URL}/api/payments`, { method: "POST", headers: { "Content-Type": "application/json", "X-API-Key": API_KEY }, body: JSON.stringify({ amount, expires_in_minutes: 15, description }), }); if (!spay.ok) return res.status(400).json({ error: "Erro ao criar cobrança" }); const payment = await spay.json(); // Guardar payment_id na base de dados da sua loja await db.orders.update({ orderId: req.body.orderId }, { paymentId: payment.payment_id, paymentStatus: "pending" }); res.json({ pay_url: payment.pay_url });}); // 2. Receber confirmação via Webhookapp.post("/webhooks/spay", (req, res) => { const signature = req.headers["x-spay-signature"]; const payloadStr = JSON.stringify(req.body, Object.keys(req.body).sort()); const computed = crypto.createHmac("sha256", WEBHOOK_SECRET) .update(payloadStr).digest("hex"); if (computed !== signature) return res.status(401).send("Inválido"); const { event, data } = req.body; if (event === "payment.paid") { // Activar o produto/serviço para o cliente console.log(`Pagamento ${data.payment_id} de ${data.amount} Kz confirmado!`); db.orders.update({ paymentId: data.payment_id }, { status: "paid" }); } res.sendStatus(200);}); app.listen(3001, () => console.log("Servidor sPay integration a correr na porta 3001"));Nota de Segurança:
Nunca exponha a X-API-Key no código de frontend (HTML/React no browser). Todas as chamadas para criar cobranças devem ser originadas do seu servidor seguro (Backend) para prevenir que terceiros gerem faturas não autorizadas em seu nome.