支付宝手机网站支付接口出现跨域提示
结论
跨域报错通常不是支付宝接口引起的,而是前端页面与自有服务端接口使用了不同的协议、域名或端口,导致浏览器拦截前端发出的 AJAX 请求。
这个问题需要从两方面处理:
- 在自有服务端或网关配置 CORS,允许前端域名访问接口。也可以使用反向代理,让前后端保持同源。
- 支付宝手机网站支付通常返回一段用于跳转到支付页面的 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'。这种模式不能真正解决跨域问题,而且前端无法正常读取响应内容。
备注:内容仅供参考。
