PAYATHON 2026

使用 RESTClient 请求 PaymentSpring 时返回 Not Found

支付阿杰

结论

Not Found 表示服务器返回了 HTTP 404。最可能的原因是 path: '/charge' 以 / 开头,RESTClient 因而覆盖了基础地址中的 /api/v1,实际请求的是:

https://api.paymentspring.com/charge

预期地址则是:

https://api.paymentspring.com/api/v1/charge

解决方法有两种:在请求中使用完整的 API 路径,或者让基础地址以 / 结尾,同时使用不以 / 开头的相对路径。

推荐修改

可以只将域名设为基础地址,并在请求中写明完整路径:

@Grab(
    group = 'org.codehaus.groovy.modules.httpbuilder',
    module = 'http-builder',
    version = '0.7'
)

import groovyx.net.http.HttpResponseException
import groovyx.net.http.RESTClient

import static groovyx.net.http.ContentType.URLENC

def paymentSpring() {
    def username = 'test_XXXXXXXXXXXXXXXXXXXX'

    def https = new RESTClient('https://api.paymentspring.com')
    https.auth.basic(username, '')

    def charge = [
        card_owner_name: 'test test',
        card_number    : '345829002709133',
        card_exp_month : '12',
        card_exp_year  : '2015',
        csc            : '999',
        amount         : '100.00',
        email_address  : 'ujdieu@yahoo.com',
        send_receipt   : true
    ]

    try {
        def resp = https.post(
            path: '/api/v1/charge',
            requestContentType: URLENC,
            body: charge
        )

        println "HTTP ${resp.status}"
        println resp.data
    } catch (HttpResponseException ex) {
        println "HTTP ${ex.statusCode}: ${ex.message}"
        println ex.response?.data
    }
}

paymentSpring()

也可以保留原来的基础地址,但需要将它写成目录形式,并传入相对路径:

def https = new RESTClient('https://api.paymentspring.com/api/v1/')
def resp = https.post(
    path: 'charge',
    requestContentType: URLENC,
    body: charge
)

这里应当写:

path: 'charge'

不要写成:

path: '/charge'

为什么会出现 404

按照 URL 解析规则,以 / 开头的路径通常会被视为从域名根目录开始的绝对路径。例如:

基础地址:https://api.paymentspring.com/api/v1
请求路径:/charge

两者组合后可能得到的不是 /api/v1/charge,而是 /charge。服务器虽然收到了请求,但这个地址没有对应的 API 资源,因此返回 Not Found。

这通常不是身份认证或表单参数造成的。认证失败更常返回 401 Unauthorized 或 403 Forbidden,字段错误一般会返回 400、422 等验证错误,而路径不存在才会返回 404。

改进异常输出

原代码只打印了异常对象,之后又打印了尚未赋值的 resp,所以输出会是:

groovyx.net.http.HttpResponseException: Not Found
null

调试时可以改为输出状态码、最终请求地址和响应正文:

try {
    def resp = https.post(
        path: '/api/v1/charge',
        requestContentType: URLENC,
        body: charge
    )

    println resp.status
    println resp.data
} catch (HttpResponseException ex) {
    println "Status: ${ex.statusCode}"
    println "URI: ${ex.response?.context?.getAttribute('http.target_host')}"
    println "Response: ${ex.response?.data}"
}

不同版本的 HTTPBuilder 暴露的上下文字段可能不同,因此不一定能取得 URI 调试信息。必要时,可以启用 HTTPBuilder 或 Apache HttpClient 的请求日志,确认最终发送的 URL。

还需要检查的事项

修正路径后,如果接口返回参数验证错误,还需要检查:

  • card_exp_year: '2015' 已经过期,应替换为接口测试环境允许的有效测试数据。
  • 测试环境必须使用 PaymentSpring 提供的测试 API Key 和测试卡号,不要提交真实银行卡信息。
  • amount 的格式和单位应以当前 PaymentSpring API 文档为准。有些支付接口使用货币小单位,有些则接受带小数的金额字符串。
  • Content-Type 已通过 requestContentType: URLENC 指定,通常不用再手动添加同名请求头。
  • 如果当前 PaymentSpring 文档中的 endpoint 已不再是 /api/v1/charge,应以账号对应的环境和当前官方文档列出的路径为准。

先修复 URL 拼接问题。确认请求确实发送到了 /api/v1/charge 后,再检查认证信息和业务参数。

备注:内容仅供参考。