PAYATHON 2026

支付宝手机网站支付接口出现跨域提示

支付阿杰

结论

跨域报错通常不是支付宝接口引起的,而是前端页面与自有服务端接口使用了不同的协议、域名或端口,导致浏览器拦截前端发出的 AJAX 请求。

这个问题需要从两方面处理:

  1. 在自有服务端或网关配置 CORS,允许前端域名访问接口。也可以使用反向代理,让前后端保持同源。
  2. 支付宝手机网站支付通常返回一段用于跳转到支付页面的 HTML 表单,不建议通过 AJAX 获取后直接渲染。可以让浏览器跳转到服务端支付入口,或者由前端提交服务端返回的支付表单。

原因

浏览器的同源策略要求双方的协议、域名和端口完全一致。例如:

前端:https://www.example.com
接口:https://api.example.com

这两个地址的域名不同,属于跨域请求。浏览器可能会先发送 OPTIONS 预检请求。如果服务端没有返回正确的 CORS 响应头,前端就会出现跨域错误。

服务端调用支付宝是服务器之间的通信,不受浏览器同源策略限制。所以,前端访问自有接口时遇到的跨域问题,不应在支付宝侧处理。

解决方式一:在服务端配置 CORS

以 Spring Boot 为例,可以将支付接口限制为只允许指定的前端域名访问:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/pay/**")
                .allowedOrigins("https://www.example.com")
                .allowedMethods("GET", "POST", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

如果项目使用 Spring Security,还要在安全配置中启用 CORS,并确保 OPTIONS 请求不会被登录校验或鉴权过滤器拦截。具体配置方式取决于项目使用的 Spring Security 版本。

如果不需要携带 Cookie,可以关闭凭证:

.allowCredentials(false)

生产环境中不要直接允许所有来源。特别是在启用凭证的情况下,Access-Control-Allow-Origin 不能设置为 *。

解决方式二:通过 Nginx 保持同源

可以让浏览器统一访问前端域名,再由 Nginx 把接口请求代理到后端:

server {
    listen 443 ssl;
    server_name www.example.com;

    location /api/ {
        proxy_pass http://backend-service/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

前端使用相对路径发起请求:

fetch('/api/pay/create', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    orderNo: 'ORDER_123'
  })
});

此时,浏览器看到的页面地址和接口地址属于同一个源,一般不会再触发 CORS 检查。配置 proxy_pass 时要留意路径末尾的 /,否则实际转发路径可能发生变化。

正确发起手机网站支付

如果服务端调用支付宝 SDK 后得到自动提交的 HTML 表单,建议让浏览器直接访问支付入口,由服务端输出该表单:

@PostMapping(value = "/pay/wap", produces = "text/html;charset=UTF-8")
@ResponseBody
public String wapPay() throws Exception {
    // 此处省略订单校验及支付宝请求参数构造
    String form = alipayClient.pageExecute(request).getBody();
    return form;
}

前端可以使用普通表单或页面跳转访问这个接口:

<form method="post" action="/api/pay/wap">
  <input type="hidden" name="orderNo" value="ORDER_123">
  <button type="submit">支付宝支付</button>
</form>

如果前后端必须通过 AJAX 交互,也可以让服务端返回支付表单字符串,再将其写入新页面并触发表单提交:

const response = await fetch('/api/pay/wap', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    orderNo: 'ORDER_123'
  })
});

if (!response.ok) {
  throw new Error(`创建支付请求失败:${response.status}`);
}

const formHtml = await response.text();
const payWindow = window.open('', '_self');
payWindow.document.open();
payWindow.document.write(formHtml);
payWindow.document.close();

这种方式可能受到浏览器弹窗策略、页面安全策略和前端框架渲染机制的影响。普通表单提交或顶层页面跳转通常更稳妥。

注意事项

  • 不要在前端 JavaScript 中直接调用需要商户私钥签名的支付宝接口。私钥只能保存在服务端。
  • return_url 只负责用户完成支付后的页面跳转,不能作为判断支付成功的唯一依据。
  • 订单状态应以服务端异步通知和主动查询结果为准,同时验证签名、商户身份、订单号、金额、应用标识等重要数据。
  • 后端异常、重定向、网关鉴权失败,或者 OPTIONS 请求返回 401、403、404,也可能在前端表现为跨域错误。应先打开浏览器开发者工具,在 Network 面板中查看实际失败的请求和响应。
  • 如果接口返回 302 并跳转到支付宝,AJAX 并不等同于浏览器的顶层页面跳转,仍可能受到跨域限制。因此,不要依赖 fetch 自动跟随重定向来打开支付页面。
  • CORS 响应头应由自有服务端或网关设置,不要在前端使用 mode: 'no-cors'。这种模式不能真正解决跨域问题,而且前端无法正常读取响应内容。

备注:内容仅供参考。