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 格式。
备注:内容仅供参考。
