🔬 Análise competitiva & prevenção
Guia Completo de
Prevenção de Erros
Os erros que derrubaram o checkout do teu concorrente — diagnosticados, explicados e resolvidos. Mais 60 vetores de falha adicionais para a Opsellio ser a plataforma #1 de Moçambique.
M-Pesa
e-Mola
Tela em branco
Dados incorretos
Erro ao comprar
60 vetores de falha
Gateway & API
Segurança
Infraestrutura
Índice do documento
01Os 3 erros reportados pelos clientes do concorrente — causas raiz e prevençãoUrgente
↳Erro 1 — Tela em branco após clicar Pagar
↳Erro 2 — "Dados incorretos" com dados certos
↳Erro 3 — Erro genérico ao tentar comprar
0260 erros possíveis organizados em 9 categorias — checklist completo60 itens
01Erros do Concorrente
Os 3 erros que derrubaram o concorrente
Clientes do teu concorrente reportaram três falhas específicas no checkout M-Pesa e e-Mola. Estes erros têm causas técnicas bem definidas. Aqui estão as causas raiz e como prevenir cada um na Opsellio.
O padrão destes três erros juntos aponta para um problema de arquitetura comum: o concorrente provavelmente trata o pagamento M-Pesa/e-Mola de forma síncrona — o servidor fica à espera da confirmação do USSD antes de responder ao browser. Isso é a raiz de quase tudo. A confirmação USSD pode demorar 30–120 segundos, tempo durante o qual qualquer timeout, redirect falhado ou erro de rede aparece como bug para o utilizador.
Erro 1 — Tela em branco após clicar Pagar
O que o cliente vê: Clica em "Pagar", a página recarrega ou muda de URL, mas o ecrã fica completamente vazio ou branco. Não há mensagem de erro. Não há indicação do que fazer a seguir. O utilizador não sabe se foi cobrado.
Causas mais prováveis — M-Pesa & e-Mola
1
Redirect para URL de confirmação que não existe ou expirou
O gateway redireciona o utilizador para uma URL de callback após confirmação USSD. Se essa URL não estiver configurada ou der 404, o browser fica em branco. Causa #1 deste problema.
2
Sessão de pagamento expirou antes do redirect
O utilizador demorou a confirmar no USSD (mais de 120s). O token de sessão expirou no backend. O redirect acontece mas não há estado para restaurar — página em branco.
3
Erro JavaScript silencioso que limpa o DOM
Um erro não tratado no JS do checkout apaga o conteúdo do ecrã mas não mostra nada ao utilizador. Comum quando o gateway retorna um formato de resposta inesperado que quebra o parser.
4
WebView do Instagram/Facebook bloqueia o redirect
O gateway M-Pesa/e-Mola redireciona para confirmação numa URL diferente. WebViews de apps sociais bloqueiam certos redirects — resultado: tela branca no WebView, que é exactamente onde os Meta Ads mandam os utilizadores.
5
Content Security Policy (CSP) a bloquear scripts do gateway
Se o site tem CSP configurado, pode bloquear scripts externos do SDK do gateway. O SDK não carrega, o checkout não renderiza, tela em branco sem qualquer mensagem de erro visível.
Como prevenir — implementações concretas
Página de loading dedicada durante o processamento
Crítico
Nunca redirecionar para uma página que depende de estado externo. Mostrar uma página de "A processar o teu pagamento..." com polling ativo até ter confirmação. Se expirar, mostrar opções — nunca branco.
// Em vez de redirect imediato, mostrar página de polling
async function pollPaymentStatus(orderId, maxAttempts = 24) {
for (let i = 0; i < maxAttempts; i++) {
await sleep(5000); // verificar a cada 5s
const status = await fetch(`/api/orders/${orderId}/status`);
const data = await status.json();
if (data.status === 'paid') return redirectToSuccess(orderId);
if (data.status === 'failed') return showErrorWithOptions(data.error);
if (data.status === 'expired') return showExpiredWithRetry();
// se 'pending' — continuar polling
}
// após 2min sem resposta — mostrar estado ambíguo
showAmbiguousState(orderId);
}
Try/catch global em todo o código de checkout
Crítico
Envolver cada chamada ao gateway num try/catch. Nunca deixar um erro JS chegar ao utilizador como tela branca — mostrar sempre uma mensagem de fallback com opções claras e o próximo passo.
async function initiatePayment(method, data) {
try {
const result = await gatewaySDK.pay(method, data);
return handleSuccess(result);
} catch (err) {
// NUNCA deixar propagar — sempre capturar
logError('payment_failed', { method, err: err.message });
showFallbackError(method); // mensagem humana + alternativas
}
}
// Error boundary global — último recurso
window.addEventListener('error', (e) => {
if (isCheckoutActive()) showCheckoutFallback();
});
Estado de sessão persistido no servidor, não só no browser
Crítico
Guardar o estado do checkout (produto, email, método) no backend assim que o utilizador avança. Se a página recarregar ou o redirect falhar, restaurar o estado do servidor — nunca branco.
Detetar WebView e desativar redirects problemáticos
Alto
Em WebViews do Instagram e Facebook, usar apenas métodos de pagamento que não dependem de redirect externo. Mostrar banner "Para melhor experiência, abre no Chrome ou Safari" quando WebView for detetado.
// Deteção de WebView
const isWebView = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i
.test(navigator.userAgent)
|| /FB_IAB|FB4A|FBAN/i.test(navigator.userAgent);
if (isWebView) {
hideRedirectBasedMethods();
showOpenInBrowserBanner();
}
Testes obrigatórios antes do lançamento — Tela em branco
Erro 2 — "Dados incorretos" com dados certos
O que o cliente vê: Insere o número M-Pesa ou e-Mola corretamente, clica em Pagar, e recebe "Número inválido", "Dados incorretos" ou "Utilizador não encontrado" — mesmo que o número seja válido e ativo. O utilizador pensa que está a fazer algo mal quando o erro é da plataforma.
Causas mais prováveis — M-Pesa & e-Mola
1
Validação de formato demasiado rígida no frontend
O código aceita apenas um formato exacto (ex: "258841234567") mas o utilizador escreve "+258 84 123 4567" ou "841234567". A validação falha antes de sequer chegar ao gateway. Causa #1 deste erro.
2
Prefixo do país enviado em duplicado para o gateway
O utilizador escreve "+258841234567". O backend adiciona "258" automaticamente por cima. O gateway recebe "258258841234567" — número inválido. Erro clássico de concatenação de prefixo.
3
Confusão entre M-Pesa (84/85) e e-Mola (87/86)
O utilizador escolhe "M-Pesa" mas tem número e-Mola (87xxx). O sistema envia para a API errada. A API M-Pesa responde "número não encontrado" — que é verdade, mas a mensagem parece erro do utilizador.
4
Espaços, hífens ou caracteres invisíveis no campo
Utilizadores que colam o número de outro app (WhatsApp, contactos) trazem espaços ou hífens. O campo aceita visualmente mas envia o número sujo para o gateway — que o rejeita.
5
Erro do gateway mapeado incorretamente como "dados inválidos"
O gateway retorna um erro de rede, timeout ou manutenção (código 500/503), mas o código trata qualquer erro non-200 como "dados incorretos" — enganando o utilizador com uma mensagem que culpa os dados dele.
Como prevenir — normalização e deteção inteligente
Normalizar o número antes de qualquer validação
Crítico
Aceitar todos os formatos possíveis, limpar e normalizar no backend antes de enviar para o gateway. Nunca rejeitar pelo formato — rejeitar apenas pelo conteúdo depois de limpar.
function normalizePhone(raw) {
// remover tudo excepto dígitos
let digits = raw.replace(/\D/g, '');
// remover prefixo duplicado
if (digits.startsWith('258258')) digits = digits.slice(3);
if (digits.startsWith('258')) digits = digits.slice(3);
if (digits.startsWith('00258')) digits = digits.slice(5);
// deve ter 9 dígitos agora
if (digits.length !== 9) throw new Error('Comprimento inválido');
return '258' + digits; // formato final para o gateway
}
// Testar: todos devem retornar '258841234567'
normalizePhone('+258 84 123 4567') // ✓
normalizePhone('258841234567') // ✓
normalizePhone('841234567') // ✓
normalizePhone('+258841234567') // ✓
normalizePhone('84 123-4567') // ✓
Detetar automaticamente M-Pesa vs e-Mola pelo prefixo
Crítico
Ao utilizador inserir o número, detetar automaticamente o operador correto — sem depender do que ele seleccionou manualmente. Sugerir a correção se detectar mismatch entre o operador selecionado e o número.
function detectOperator(digits9) {
const prefix = digits9.slice(0, 2);
const mpesa = ['84', '85'];
const emola = ['86', '87'];
if (mpesa.includes(prefix)) return 'mpesa';
if (emola.includes(prefix)) return 'emola';
return null; // número não reconhecido
}
// No frontend — sugerir correção automaticamente
phoneInput.addEventListener('input', (e) => {
const digits = normalize(e.target.value);
const detected = detectOperator(digits);
if (detected && detected !== selectedMethod) {
showSuggestion(`Número ${detected.toUpperCase()} detetado — mudar método?`);
}
});
Mapear cada código de erro do gateway para mensagem específica
Crítico
Nunca mostrar "dados incorretos" para erros que não são culpa do utilizador. Mapear cada código de erro do gateway para uma mensagem honesta em português que explica o que aconteceu e o que fazer.
const ERROR_MESSAGES = {
'INVALID_MSISDN': 'Número de telefone inválido — verifica os dígitos',
'SUBSCRIBER_NOT_FOUND': 'Número não encontrado no M-Pesa — é número e-Mola?',
'INSUFFICIENT_FUNDS': 'Saldo insuficiente na tua conta M-Pesa',
'SYSTEM_ERROR': 'Problema técnico do M-Pesa — tenta novamente em 2 min',
'SERVICE_UNAVAILABLE': 'M-Pesa em manutenção — tenta por e-Mola ou cartão',
'TRANSACTION_LIMIT': 'Limite de transação M-Pesa atingido para hoje',
'TIMEOUT': 'Sem resposta do M-Pesa — não foste cobrado, tenta de novo',
};
Testes obrigatórios antes do lançamento — Dados incorretos
Erro 3 — Erro genérico ao tentar comprar
O que o cliente vê: Clica em Pagar, fica a aguardar, e recebe uma mensagem de erro genérica — "Erro ao processar", "Algo correu mal", ou um código de erro técnico sem contexto. Não sabe se foi cobrado. Não sabe o que fazer a seguir. Frequentemente não tenta de novo.
Causas mais prováveis — M-Pesa & e-Mola
1
Timeout do gateway sem tratamento assíncrono
A confirmação USSD demora 30–120s. O backend faz um request síncrono e aguarda resposta. Ao fim de 30s o servidor dá timeout e retorna erro — mas o utilizador pode ter confirmado. Causa mais comum de erro falso em M-Pesa e e-Mola.
2
Credenciais do gateway de staging em produção
As credenciais de API (API key, merchant ID) do ambiente de sandbox foram para produção por engano — ou vice-versa. O gateway rejeita todas as transações reais com erro de autenticação.
3
Certificado SSL do gateway a expirar ou inválido
A API do gateway usa HTTPS. Se o certificado expirou ou a chain de certificados está incompleta, o servidor da plataforma rejeita a ligação e retorna erro — o utilizador vê "erro ao comprar" sem qualquer contexto.
4
Rate limiting — demasiados requests do mesmo IP
Em períodos de alto tráfego (campanhas, lançamentos), o gateway pode rate-limitar o IP do servidor da plataforma. Todos os utilizadores nesse momento recebem erro — aparece como falha massiva simultânea.
5
Montante em formato errado (centavos vs inteiros)
Alguns gateways esperam o valor em centavos (1500 = 15.00 MT), outros em valor inteiro (15). Se o formato estiver trocado, o gateway rejeita ou processa o valor errado — causando erro ou cobrança incorreta.
6
Webhook de confirmação não configurado ou URL errado
O gateway tenta enviar a confirmação para o webhook URL e falha. Sem confirmação, o backend considera o pagamento como falhado e mostra erro — mesmo que o utilizador tenha sido cobrado.
Como prevenir — arquitetura assíncrona e monitoring
Arquitetura assíncrona — nunca aguardar confirmação de forma síncrona
Crítico
Ao iniciar pagamento M-Pesa/e-Mola, retornar imediatamente um order_id e mostrar página de polling. O webhook do gateway confirma o resultado. Nunca bloquear a thread a aguardar confirmação USSD.
// ERRADO — síncrono, vai dar timeout
async function badPay(phone, amount) {
const result = await mpesa.charge(phone, amount); // bloqueia 30-120s!
return result; // timeout antes disto
}
// CORRETO — assíncrono com polling
async function goodPay(phone, amount) {
// 1. iniciar a transação (resposta rápida — milissegundos)
const { transaction_id } = await mpesa.initiateCharge(phone, amount);
// 2. guardar no DB como 'pending'
await db.orders.update(orderId, { transaction_id, status: 'pending' });
// 3. retornar imediatamente — o webhook trata do resto
return { order_id: orderId, status: 'pending' };
// o webhook /mpesa/callback vai atualizar quando o USSD for confirmado
}
Variáveis de ambiente separadas por ambiente com validação no startup
Crítico
Credenciais do gateway em variáveis de ambiente — nunca hardcoded. Staging usa um conjunto, produção usa outro. Verificação automática ao arrancar o servidor que falha imediatamente se detectar configuração errada.
// Verificar no arranque — falhar rápido se configuração errada
function validateGatewayConfig() {
const required = [
'MPESA_API_KEY', 'MPESA_API_SECRET',
'MPESA_MERCHANT_ID', 'MPESA_WEBHOOK_SECRET',
'EMOLA_API_KEY', 'EMOLA_MERCHANT_CODE'
];
const missing = required.filter(k => !process.env[k]);
if (missing.length > 0) {
throw new Error(`Config em falta: ${missing.join(', ')}`);
}
// confirmar que não estão a usar sandbox em prod
if (process.env.NODE_ENV === 'production') {
if (process.env.MPESA_ENV !== 'live') {
throw new Error('MPESA_ENV deve ser "live" em produção!');
}
}
}
Health check dos gateways antes de aceitar pagamentos
Alto
Verificar a cada 5 minutos se os gateways M-Pesa e e-Mola estão a responder. Se um gateway estiver em baixo, esconder esse método no checkout e mostrar alternativas — sem que o utilizador encontre o erro.
// Verificar saúde dos gateways a cada 5 minutos
async function checkGatewayHealth() {
const gateways = { mpesa: false, emola: false };
try {
await mpesaSDK.ping(); // chamada de teste leve
gateways.mpesa = true;
} catch { gateways.mpesa = false; }
try {
await emolaSDK.ping();
gateways.emola = true;
} catch { gateways.emola = false; }
// atualizar cache — o checkout lê este estado
await cache.set('gateway_health', gateways, 300);
return gateways;
}
// No checkout: só mostrar métodos com gateway saudável
Alertas em tempo real para taxas de erro acima de 5%
Alto
Monitorar a taxa de falhas por método de pagamento. Se M-Pesa falhar mais de 5% das tentativas numa janela de 10 minutos, alertar a equipa técnica imediatamente — antes que os utilizadores inundem o suporte com reclamações.
Testes obrigatórios antes do lançamento — Erro ao comprar
0260 Vetores de Falha
Todos os erros possíveis — as 9 categorias
Para ser a plataforma #1 do país, o que separa as que ficam das que crescem de verdade não é só não ter bugs — é ter sistemas que se recuperam sozinhos quando algo corre mal. São 60 vetores de falha organizados em 9 categorias de risco.
Ordem de ataque recomendada: primeiro Segurança e Gateway (os erros que matam imediatamente), depois Async e Infra (os que escalam mal), depois Email e Notificações (os que afastam clientes silenciosamente), e por último Legal e Negócio (os que aparecem com o crescimento).
Opsellio
Guia de Prevenção de Erros — Referência para programadores · v2025-05
3 erros diagnósticados · 60 vetores de falha · 9 categorias · M-Pesa & e-Mola