如何为 Instant Payment Notification 返回 HTTP 200
结论
在 Rails 控制器中,可以用 head :ok 返回只有 HTTP 状态码、没有响应正文的 200 OK:
def success
# 处理 IPN 消息
head :ok
end
不要只写:
render status: 200
如果没有指定响应内容,Rails 在某些版本或调用方式下仍可能查找与 action 对应的模板,进而触发 MissingTemplate。
推荐写法
现有 action 可以改成:
def success
transaction = Transaction.find_by_merchant_reference(params[:external_ref])
if transaction && transaction.status != "Completed"
date_time = params[:date_time]
amount = params[:amount]
narrative = params[:narrative]
network_ref = params[:network_ref]
external_ref = params[:external_ref]
msisdn = params[:msisdn]
signature = params[:signature]
if verfiy_signiture(
date_time,
amount,
narrative,
network_ref,
external_ref,
msisdn
) == signature
user = User.find(transaction.user_id)
user.balance += transaction.amount * 0.975
user.save!
transaction.update!(status: "Completed")
TopUp.account_topup(
user.name,
user.email,
user.currency,
transaction.amount,
transaction.merchant_reference
).deliver
end
end
head :ok
end
head :ok 生成的响应类似:
HTTP/1.1 200 OK
Content-Length: 0
这种响应适用于不需要返回 HTML、JSON 或跳转页面的 IPN webhook。
为什么会出现 MissingTemplate
Rails action 执行完毕后必须完成一次响应。如果没有明确调用 render、redirect_to 或 head,Rails 通常会尝试隐式渲染与 action 同名的模板,例如:
app/views/<controller_name>/success.html.erb
IPN 请求一般不需要页面模板,因此应该明确调用:
head :ok
如果需要返回简单文本,也可以写成:
render plain: "OK", status: :ok
如果对方只要求 HTTP 200,不要求响应正文,使用 head :ok 更直接。
不要使用路径辅助方法代替响应
原代码中的:
user_home_path(@current_user)
只会生成一个 URL 字符串,既不会执行跳转,也不会结束请求。如果确实需要让浏览器跳转,应使用:
redirect_to user_home_path(@current_user)
不过,IPN 是服务器之间的通知请求,通常不需要跳转。这里应删除该调用,并在最后执行 head :ok。
同样,flash[:notice] 主要用于下一次浏览器页面请求。它对 webhook/IPN 通常没有意义,也不能代替 HTTP 响应。
确保所有分支都返回 200
如果接口要求无论消息是否得到处理都返回 HTTP 200,应将 head :ok 放在正常执行路径的末尾,而不是只放在签名验证成功的分支里:
def success
process_ipn_if_applicable
head :ok
end
如果查询、保存或发送邮件时抛出异常,程序会提前退出,末尾的 head :ok 无法执行,Rails 通常会返回 500。如果协议要求任何处理结果都返回 200,就需要明确处理预期异常:
def success
begin
process_ipn
rescue StandardError => e
Rails.logger.error("IPN processing failed: #{e.class}: #{e.message}")
end
head :ok
end
不建议在不了解故障类型时长期吞掉所有异常。更稳妥的做法是只捕获可预期的异常,记录足够的信息,并将耗时或容易失败的业务处理交给后台任务。
还应注意
- 验证签名后,再修改余额、更新交易状态或发送邮件。
- IPN 可能重复发送,因此处理逻辑必须保持幂等。只检查状态可能产生并发竞争,关键更新最好放入数据库事务,并配合锁或唯一约束。
- 应检查
user.save和交易更新是否成功。使用save!、update!可以避免保存失败后继续执行。 - 不要在日志中记录完整签名、密钥或其他敏感支付数据。
- 如果该 endpoint 受 Rails CSRF 校验保护,而外部支付平台无法提供 Rails CSRF token,需要仅针对该 webhook action 配置适当的 CSRF 处理,同时继续使用 IPN 签名验证身份。
备注:内容仅供参考。
