PAYATHON 2026

Rails 集成 HiPay Payment API 时出现 SocketError

支付老李

结论

问题出在 Net::HTTP.new 的参数格式,与 XML 或 Rails controller 无关。

Net::HTTP.new 接收主机名和端口,不能把完整 URL "https://test-www.hipay.com/" 作为 host 传入。否则,系统会尝试解析包含 https:// 和 / 的字符串,最终触发:

getaddrinfo: nodename nor servname provided, or not known

发送 HTTPS 请求时,还要使用 443 端口并启用 TLS。

推荐写法

用 URI 拆分 URL,不必手动处理 host、port 和 HTTPS:

require "net/http"
require "uri"

uri = URI("https://test-www.hipay.com/order")

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == "https"
http.open_timeout = 10
http.read_timeout = 30

request = Net::HTTP::Post.new(uri.request_uri)
request["Content-Type"] = "application/xml"
request["Accept"] = "application/xml"
request.body = xml.target!

response = http.request(request)

case response
when Net::HTTPSuccess
  @response = response.body
else
  Rails.logger.error(
    "HiPay request failed: status=#{response.code}, body=#{response.body}"
  )
  raise "HiPay API returned HTTP #{response.code}"
end

此时,uri.host 的值是:

test-www.hipay.com

不是:

https://test-www.hipay.com/

正确生成 XML 请求体

Builder::XmlMarkup 是 XML 构建器。发送请求前,调用 target! 获取最终字符串:

xml = Builder::XmlMarkup.new(indent: 2)

xml.instruct!(:xml, version: "1.0", encoding: "UTF-8")

xml.HIPAY_MAPI_SimplePayment do
  xml.HIPAY_MAPI_PaymentParams do
    xml.login(ENV.fetch("HIPAY_LOGIN"))
    xml.password(ENV.fetch("HIPAY_PASSWORD"))
    xml.itemAccount(ENV.fetch("HIPAY_ITEM_ACCOUNT"))
    xml.taxAccount(ENV.fetch("HIPAY_TAX_ACCOUNT"))
    xml.insuranceAccount(ENV.fetch("HIPAY_INSURANCE_ACCOUNT"))

    # 继续填写 HiPay 要求的其他字段
  end
end

xml_body = xml.target!

发送时写成:

request.body = xml_body

不要让 Net::HTTP 隐式转换 Builder::XmlMarkup 对象。

更简洁的完整示例

require "net/http"
require "uri"

xml = Builder::XmlMarkup.new
xml.instruct!(:xml, version: "1.0", encoding: "UTF-8")

xml.HIPAY_MAPI_SimplePayment do
  xml.HIPAY_MAPI_PaymentParams do
    xml.login(ENV.fetch("HIPAY_LOGIN"))
    xml.password(ENV.fetch("HIPAY_PASSWORD"))
    xml.itemAccount(ENV.fetch("HIPAY_ITEM_ACCOUNT"))
    xml.taxAccount(ENV.fetch("HIPAY_TAX_ACCOUNT"))
    xml.insuranceAccount(ENV.fetch("HIPAY_INSURANCE_ACCOUNT"))
  end
end

uri = URI("https://test-www.hipay.com/order")

response = Net::HTTP.start(
  uri.host,
  uri.port,
  use_ssl: true,
  open_timeout: 10,
  read_timeout: 30
) do |http|
  request = Net::HTTP::Post.new(uri.request_uri)
  request["Content-Type"] = "application/xml; charset=UTF-8"
  request["Accept"] = "application/xml"
  request.body = xml.target!

  http.request(request)
end

unless response.is_a?(Net::HTTPSuccess)
  raise "HiPay request failed with HTTP #{response.code}: #{response.body}"
end

@response = response.body

如果直接使用 Net::HTTP.new

最小改动如下:

http = Net::HTTP.new("test-www.hipay.com", 443)
http.use_ssl = true

@response = http.post(
  "/order",
  xml.target!,
  "Content-Type" => "application/xml; charset=UTF-8"
)

host 只能填写主机名,不能包含协议或路径。

还需要核对的 HiPay 配置

修复 SocketError 只能说明请求可以继续进入 DNS 解析和网络连接阶段,并不代表 Payment API 会接受请求。还要根据所用的 HiPay API 版本检查:

  • 测试环境的准确域名和请求路径;
  • /order 是否是完整 endpoint;
  • API 要求的 Content-Type;
  • XML 是否直接作为 HTTP body 发送;
  • XML 根节点、字段名称、字段顺序和字符编码;
  • 认证信息应放在 XML 中,还是通过其他认证机制提交;
  • API 是否要求额外的签名、金额、币种、回调地址等字段。

不同 HiPay 产品或 API 版本可能使用不同的 endpoint 和请求格式,应以当前接入文档为准。如果接口接收的是表单参数,而不是原始 XML,可以改成:

request.set_form_data("xml" => xml.target!)

此时不要再手动把 Content-Type 设置为 application/xml,因为 set_form_data 会使用表单编码。

注意事项

不要在 controller 中硬编码登录名、密码或账户编号。可以通过 Rails credentials 或环境变量读取:

login = Rails.application.credentials.dig(:hipay, :login)

也不要用下面的设置绕过证书验证:

http.verify_mode = OpenSSL::SSL::VERIFY_NONE

生产环境应正常校验 TLS 证书。修正 host 后,如果仍然出现 getaddrinfo,应继续检查服务器的 DNS、代理、防火墙和网络出口。如果错误变为 HTTP 4xx 或 HiPay 返回的业务错误,说明网络连接已经建立,接下来需要检查 endpoint、认证信息和 XML 格式。

备注:内容仅供参考。