Skip to content

Resolução de problemas ​

O meu webhook não chega ​

Diagnóstico ​

  1. Painel → Webhooks → Registos: o pedido aparece na lista?
    • Não → o evento não foi gerado. Confirme que a transação chegou a success/failed/cancelled/expired. Force uma consulta com POST/v1/payments/{id}/verify.
    • Sim, com um http_status diferente de 2xx → o seu servidor recusou-o. Veja «O meu endpoint responde 4xx/5xx».
    • Sim, com error_message: "timeout" → o seu servidor demorou mais de 15 segundos. Veja «Tempo de espera esgotado».

O meu endpoint responde 4xx/5xx ​

http_statusCausa habitual
400Leitura do corpo: o seu servidor lê o payload antes de verificar a assinatura
401 / 403Um middleware de autenticação bloqueia os pedidos externos: exclua /webhooks/soamtar
404Rota errada: confirme o URL exato no painel
422Validação do framework: aceite o payload tal como está
500Erro da aplicação: consulte os seus registos
502 / 503Proxy inverso em baixo ou aplicação não iniciada

Solução: exclua a sua rota de webhook da proteção CSRF (por ex. withoutMiddleware([VerifyCsrfToken::class]) em Laravel) e do seu middleware de autenticação.

Tempo de espera esgotado ​

O seu handler deve responder 2xx em menos de 15 segundos. Para um processamento longo:

php
// Bad
public function webhook(Request $request) {
    $this->processOrder($request);     // 30 seconds
    $this->sendEmail($request);
    return response('ok');
}

// Good
public function webhook(Request $request) {
    ProcessSoamtarWebhookJob::dispatch($request->all());
    return response('ok');             // immediate
}

O meu servidor recebe o webhook 5 vezes ​

É uma repetição, o que é esperado. Causas:

  1. Responde 2xx mas após mais de 15 s → a SOAMTAR já registou um tempo esgotado
  2. Responde com um código diferente de 2xx
  3. Responde 2xx mas a SOAMTAR nunca recebe a resposta (rede)

Solução: elimine duplicados do seu lado segundo o par (event, data.id).

sql
CREATE UNIQUE INDEX uq_inbound_event ON your_webhook_log(event, transaction_id);

Assinatura de webhook inválida ​

Sintoma ​

A sua verificação HMAC falha apesar de usar o segredo correto.

Causa 1: corpo alterado antes da verificação ​

Express, Symfony e Laravel interpretam o JSON automaticamente. Tem de verificar a assinatura sobre o corpo em bruto, antes de qualquer interpretação.

js
// Bad
app.use(express.json())   // parses JSON
app.post('/webhook', (req, res) => {
  verify(req.body)        // req.body is already an object, not the raw body
})

// Good
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  verify(req.body.toString())   // raw
  const event = JSON.parse(req.body.toString())
})

Causa 2: codificação do HMAC ​

Compare hexadecimal em minúsculas dos dois lados (o hash_hmac do PHP devolve minúsculas; verifique no Node). Use hash_equals / crypto.timingSafeEqual, não ===.

Causa 3: carimbo temporal expirado ​

|now - t| > 300s é recusado. Verifique o relógio do seu servidor (NTP). Não alargue a tolerância para processar entregas antigas: reenvie-as a partir de Dashboard → Webhook logs → Resend (cada tentativa é assinada de novo com um carimbo temporal recente) ou use a consulta verify.

Conflito de Idempotency-Key ​

Sintoma ​

409 idempotency_conflict ao reenviar o pedido.

Diagnóstico ​

bash
curl -X POST .../v1/payments \
  -H "Idempotency-Key: abc-123" \
  -d '{"merchant_ref":"ORDER-7","amount":"150.00","currency":"SLE"}'
# 201

curl -X POST .../v1/payments \
  -H "Idempotency-Key: abc-123" \
  -d '{"merchant_ref":"ORDER-7","amount":"150.00","currency":"SLE","description":"oops"}'
# 409 — different body

Solução:

  • Para repetir: mesma chave e exatamente o mesmo corpo.
  • Para um novo pedido: uma nova chave.

Boa prática ​

Gere a chave quando o cliente toca em «Pagar» e guarde-a (armazenamento local ou base de dados) até ao resultado final. Não a gere de novo a cada repetição HTTP.

Transação bloqueada em pending ​

Diagnóstico ​

bash
curl -X POST https://app.so-amtar.com/v1/payments/{id}/verify \
  -H "Authorization: Bearer sk_test_xxx"

A consulta verify pergunta ao Orange Money e sincroniza o estado. Se o Orange Money confirmar, a transação avança e é-lhe enviado um webhook.

Se nada mudar ​

  • O cliente não confirmou o pedido do Orange Money com o seu PIN (o pedido fica aberto cerca de 5 minutos): peça-lhe que verifique o telemóvel ou inicie um novo pagamento.
  • O Orange Money tem um incidente: consulte o suporte.
  • A transação expira automaticamente em expires_at (2 horas por predefinição).
  • Se continuar bloqueada: escreva para support@so-amtar.com com a referência SOAMTAR (TXN-...).

Reembolsar um pagamento Orange Money ​

Use POST/v1/refunds (chave com o scope can_refund):

  1. Envie o transaction_id do pagamento; omita amount para reembolsar o montante restante ou indique-o para um reembolso parcial.
  2. O montante é creditado no número Orange Money do pagador e debitado do seu saldo. O reembolso fica em processing até à confirmação da Orange; o resultado chega por refund.success / refund.failed.
  3. Se receber refund_payer_phone_missing, o pagamento não tem número de pagador: contacte o suporte.

Ver Reembolsos (em inglês).

O saldo mostrado não corresponde às minhas liquidações ​

ComponenteIncluído no saldo mostrado?
success ainda não liquidadoSim
pendingNão
success já liquidadoNão
Reembolso ou pagamento de saída emitidoSim (deduzido)
Estorno em abertoReservado num saldo à parte

O saldo «disponível» é diferente do saldo «contabilístico»; o painel mostra ambos.

Uma chave live não encontra o meu objeto ​

Está a usar uma chave live num objeto criado no sandbox (ou o inverso). Use a chave do modo correspondente: os identificadores dependem estritamente do modo.

Problemas de ligação à API (SSL, tempo esgotado…) ​

Erro SSL: SSL certificate problem: unable to get local issuer certificate ​

Surge normalmente em servidores Windows ou instalações antigas de PHP/cURL sem um pacote de certificados CA atualizado.

Solução:

  1. Descarregue o cacert.pem oficial em curl.se/ca/cacert.pem.
  2. Aponte o php.ini para ele:
    ini
    curl.cainfo = "C:/chemin/vers/votre/cacert.pem"
    openssl.cafile = "C:/chemin/vers/votre/cacert.pem"
  3. Reinicie o seu servidor web.

Erro: Connection Timeout ​

Se o seu servidor não consegue chegar a app.so-amtar.com:

  1. Firewall: o seu servidor tem de poder fazer pedidos de saída na porta 443 (HTTPS).
  2. DNS: confirme que app.so-amtar.com é resolvido a partir do seu servidor.
  3. Proxy: se o seu servidor usa um proxy, configure o SDK em conformidade (ou as variáveis de ambiente HTTP_PROXY).

Erro: 401 Unauthorized (chave inválida) ​

  1. Use a chave secreta (sk_...) nas chamadas a partir do backend, não a chave pública (pk_...).
  2. Confirme o modo: uma chave sk_test_ só funciona com objetos do sandbox.
  3. Garanta que não copiou espaços nem quebras de linha com a chave.

Saber mais ​

  • Suporte: support@so-amtar.com

SOAMTAR Payment API Documentation.