Skip to content

故障排查 ​

收不到 Webhook ​

诊断 ​

  1. 控制台 → Webhooks → 日志:列表中有这条请求吗?
    • 没有 → 事件未触发。请确认交易已到达 success/failed/cancelled/expired。可通过 POST/v1/payments/{id}/verify 强制查询。
    • 有,但 http_status 不是 2xx → 您的服务器拒绝了请求。参见 “我的端点返回 4xx/5xx”。
    • 有,且 error_message: "timeout" → 您的服务器响应超过 15 秒。参见 “超时”。

我的端点返回 4xx/5xx ​

http_status常见原因
400请求体解析:您的服务器在验证签名前读取了 payload
401 / 403认证中间件拦截了外部请求:请将 /webhooks/soamtar 排除
404路由错误:请在控制台中核对准确的 URL
422框架校验:请按原样接受 payload
500应用错误:请查看您的日志
502 / 503反向代理宕机或应用未启动

解决方法:将您的 Webhook 路由从 CSRF 保护(例如 Laravel 中的 withoutMiddleware([VerifyCsrfToken::class]))和认证中间件中排除。

超时 ​

您的处理程序必须在 15 秒内返回 2xx。如果处理耗时较长:

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
}

我的服务器收到同一个 Webhook 5 次 ​

这是重试,属于正常现象。原因:

  1. 您返回了 2xx,但耗时超过 15 秒 → SOAMTAR 已记录超时
  2. 您返回了非 2xx 状态码
  3. 您返回了 2xx,但 SOAMTAR 未收到响应(网络问题)

解决方法:在您这一端按 (event, data.id) 组合去重。

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

Webhook 签名无效 ​

症状 ​

尽管使用了正确的密钥,您的 HMAC 验证仍然失败。

原因 1:验证前请求体已被修改 ​

Express、Symfony 和 Laravel 会自动解析 JSON。您必须在任何解析之前,对原始请求体验证签名。

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())
})

原因 2:HMAC 编码 ​

两端都应比较小写十六进制(PHP 的 hash_hmac 返回小写;请在 Node 中确认)。使用 hash_equals / crypto.timingSafeEqual,而不是 ===。

原因 3:时间戳过期 ​

|now - t| > 300s 会被拒绝。请检查服务器时钟(NTP)。不要为了处理旧的投递而放宽容差:请在 Dashboard → Webhook logs → Resend 中重新发送(每次尝试都会用新的时间戳重新签名),或改用 verify 查询。

Idempotency-Key 冲突 ​

症状 ​

重新发送请求时返回 409 idempotency_conflict。

诊断 ​

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

解决方法:

  • 重试:使用相同的密钥和完全相同的请求体。
  • 新请求:使用新的密钥。

最佳实践 ​

在客户点击“支付”时生成密钥,并持久保存(本地存储或数据库),直到得到最终结果。不要在每次 HTTP 重试时重新生成。

交易卡在 pending ​

诊断 ​

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

verify 查询会向 Orange Money 询问并同步状态。如果 Orange Money 确认,交易会继续推进,并向您发送 Webhook。

如果没有任何变化 ​

  • 客户尚未用 PIN 确认 Orange Money 请求(该请求约保持 5 分钟):请客户检查手机,或发起新的付款。
  • Orange Money 出现故障:请咨询客服。
  • 交易会在 expires_at 自动过期(默认 2 小时)。
  • 仍然卡住:请携带 SOAMTAR 参考号(TXN-...)发送邮件至 support@so-amtar.com。

为 Orange Money 付款退款 ​

使用 POST/v1/refunds(密钥需具备 can_refund scope):

  1. 传入付款的 transaction_id;不传 amount 则退还剩余全部金额,传入则为部分退款。
  2. 款项会退回到付款人的 Orange Money 号码并从您的余额中扣除。退款在 Orange 确认前保持 processing 状态;结果通过 refund.success / refund.failed 通知。
  3. 如果收到 refund_payer_phone_missing,说明该付款没有付款人号码:请联系支持团队。

参见退款(英文)。

显示的余额与结算不一致 ​

组成部分是否计入显示余额?
尚未结算的 success是
pending否
已结算的 success否
已发出的退款或出款是(扣减)
未结的拒付冻结在单独的余额中

“可用”余额不同于“账面”余额;控制台会同时显示两者。

live 密钥找不到我的对象 ​

您在用 live 密钥访问沙盒中创建的对象(或反之)。请使用对应模式的密钥:id 严格按模式区分。

API 连接问题(SSL、超时等) ​

SSL 错误:SSL certificate problem: unable to get local issuer certificate ​

通常出现在 Windows 服务器或缺少最新 CA 证书包的旧版 PHP/cURL 安装中。

解决方法:

  1. 从 curl.se/ca/cacert.pem 下载官方 cacert.pem。
  2. 在 php.ini 中指向它:
    ini
    curl.cainfo = "C:/chemin/vers/votre/cacert.pem"
    openssl.cafile = "C:/chemin/vers/votre/cacert.pem"
  3. 重启 Web 服务器。

错误:Connection Timeout ​

如果您的服务器无法访问 app.so-amtar.com:

  1. 防火墙:您的服务器必须允许在 443 端口(HTTPS) 发起出站请求。
  2. DNS:确认服务器能解析 app.so-amtar.com。
  3. 代理:如果服务器使用代理,请相应配置 SDK(或 HTTP_PROXY 环境变量)。

错误:401 Unauthorized(密钥无效) ​

  1. 后端调用请使用私钥(sk_...),而不是公钥(pk_...)。
  2. 检查模式:sk_test_ 密钥只能访问沙盒对象。
  3. 确认复制密钥时没有带上空格或换行。

了解更多 ​

  • 客服:support@so-amtar.com

SOAMTAR Payment API Documentation.