bKash payment API integration 无法访问 App URL
结论
这通常不是 Laravel 的 CORS 问题。更可能的情况是,App URL 作为普通页面或图片可以访问,但无法被 bKash 页面通过 iframe 或 popup 流程加载。
先检查这些响应头:
X-Frame-Options: SAMEORIGIN或DENYContent-Security-Policy中的frame-ancestors- 页面加载 bKash 内容时,
frame-src、connect-src和script-src是否允许 bKash 域名
普通链接和 <img> 能打开,只能说明 URL 基本可达,不能说明它支持跨域嵌入、脚本调用或支付重定向。CORS 主要控制 fetch、XHR 等跨源请求,不能解除浏览器对 iframe 嵌入的限制。
先确认失败发生在哪一层
需要向 bKash 确认“无法访问”具体指什么:
- bKash 的服务器请求 App URL 时失败;
- bKash 页面尝试在
iframe中嵌入 App URL,但被浏览器阻止; - App URL 可以打开,但支付 popup 中的脚本、Cookie 或 API 请求失败;
- URL 重定向后进入了登录页、错误页或其他域名;
- bKash 的自动检查程序发送了
HEAD请求,而应用只处理GET。
如果能拿到 bKash 返回的 HTTP 状态码、浏览器 Console 错误或失败请求的响应头,通常很快就能缩小排查范围。
检查 iframe 安全策略
如果响应中包含以下任一配置,bKash 的跨域页面可能无法嵌入 App URL:
X-Frame-Options: SAMEORIGIN
X-Frame-Options: DENY
或者:
Content-Security-Policy: frame-ancestors 'self'
浏览器控制台通常会显示类似信息:
Refused to display the page in a frame because it set
'X-Frame-Options' to 'sameorigin'.
如果 bKash 确实需要嵌入该页面,应通过 CSP 精确放行 bKash 提供的正式域名。域名必须以 bKash 的接入文档或技术人员提供的信息为准,不要根据名称猜测。
例如:
Content-Security-Policy: frame-ancestors 'self' https://<official-bkash-domain>;
不要使用这种过于宽泛的配置:
Content-Security-Policy: frame-ancestors *;
它会允许任意网站嵌入支付页面,增加点击劫持风险。
多数现代浏览器已经弃用 X-Frame-Options: ALLOW-FROM ...,不应依赖它。需要按域名放行时,应使用 CSP 的 frame-ancestors。
Laravel 中设置响应头
可以只为 bKash 支付入口配置中间件,以免放宽整个应用的安全策略。
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class BkashFramePolicy
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// 这里必须替换为 bKash 明确提供的实际域名。
$response->headers->set(
'Content-Security-Policy',
"frame-ancestors 'self' https://<official-bkash-domain>"
);
// 避免上游中间件继续发送 SAMEORIGIN 或 DENY。
$response->headers->remove('X-Frame-Options');
return $response;
}
}
然后将中间件应用到有限的支付路由:
Route::get('/payment/bkash', [BkashController::class, 'show'])
->middleware('bkash.frame');
具体如何注册取决于 Laravel 版本和项目结构。没有确认版本时,不要直接照搬某个固定注册文件的写法。
还要检查 Nginx、Apache、CDN、负载均衡器和安全插件。即使 Laravel 删除了响应头,上游服务器仍可能重新添加:
add_header X-Frame-Options SAMEORIGIN;
如果存在这项配置,应针对支付路径调整,不能只修改 Laravel。
检查页面加载 bKash 资源的 CSP
如果是你的页面负责加载 bKash popup,问题也可能不在 frame-ancestors,而是 CSP 阻止了页面连接 bKash。
例如,下面的策略可能过于严格:
Content-Security-Policy: default-src 'self'; frame-src 'self'; connect-src 'self';
此时需要根据实际集成方式,放行 bKash 官方提供的资源域名:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://<official-bkash-script-domain>;
connect-src 'self' https://<official-bkash-api-domain>;
frame-src 'self' https://<official-bkash-frame-domain>;
frame-ancestors 'self' https://<official-bkash-parent-domain>;
这些占位域名不能直接用于生产环境。sandbox 和 production 域名需要分别核对,因为测试环境可以正常工作,并不代表生产审核系统使用相同的域名。
从外部验证 HTTP 响应
不要只在已经登录或连接公司网络的浏览器中测试。可以从外部网络检查:
curl -I https://example.com/payment/bkash
进一步查看重定向过程和最终响应:
curl -L -v https://example.com/payment/bkash
模拟跨站来源:
curl -I \
-H 'Origin: https://<official-bkash-domain>' \
-H 'Referer: https://<official-bkash-domain>/' \
https://example.com/payment/bkash
重点检查:
- 是否返回
200; - 是否先返回
301、302、401、403、419或500; - 最终 URL 是否仍为 HTTPS;
- 是否被重定向到登录页面;
- 是否存在
X-Frame-Options; - CSP 是否阻止 bKash;
- 更换请求方法或
User-Agent后,响应是否发生变化。
还需要测试 HEAD:
curl -I -X HEAD https://example.com/payment/bkash
有些自动审核系统会先发送 HEAD 请求。如果返回 405 Method Not Allowed,可以让服务器正确响应 HEAD,或者请 bKash 确认其验证方式。
排查网络、DNS 和 TLS
如果 bKash 的服务器根本无法建立连接,常见原因包括以下几类。
DNS 配置异常
检查:
- 公网 DNS 是否已经生效;
- 是否错误配置了
AAAA记录; - 是否存在 IPv4 可用但 IPv6 不可用的情况;
- bKash 所在网络是否解析到了旧地址或内网地址。
一种常见情况是,域名同时配置了 A 和 AAAA 记录,但服务器没有正确提供 IPv6 服务。部分客户端会优先使用 IPv6,结果就会出现“自己能访问,第三方不能访问”。
TLS 证书链不完整
浏览器可能会缓存或补全中间证书,但服务器端程序一般会严格校验证书链。需要检查:
- 证书是否过期;
- 域名是否与证书匹配;
- 中间证书链是否完整;
- 是否只支持过旧的 TLS 或加密套件;
- 是否存在 SNI 配置错误。
防火墙或 WAF 拦截
Cloudflare、服务器防火墙或主机商的安全策略可能根据以下条件拒绝请求:
- 来源 IP;
- 国家或地区;
User-Agent;- 请求频率;
- 请求是否携带 Cookie;
- 是否来自数据中心网络或自动化客户端;
- 是否为
HEAD请求; - 是否触发 Bot 防护或 JavaScript Challenge。
查看 Web 服务器、CDN 和 WAF 日志,确认其中是否有 bKash 的请求。如果请求已经到达并返回 403,应让 bKash 提供出口 IP,然后采用范围尽可能小的放行策略。如果日志里完全没有请求,则继续检查 DNS、TLS 和上游网络。
检查认证、Cookie 和 CSRF
App URL 应该能在新的无痕会话中直接访问,不能依赖以下条件:
- 用户已经登录;
- 浏览器中已经保存 session;
- 特定 Cookie 已经存在;
- 请求来自站内页面;
- 本地缓存中已经有 CSRF token。
如果页面被跨站嵌入,Cookie 还会受到 SameSite 限制。如果确实需要在跨站 iframe 中发送 session Cookie,通常需要:
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=none
对应的 Cookie 必须同时包含:
SameSite=None; Secure
即使这样,第三方 Cookie 仍可能被浏览器或隐私策略拦截。因此,支付流程不应把跨站 iframe Cookie 作为唯一的状态依据。更可靠的方式是使用短期、一次性的服务端支付状态标识,并在回调时重新校验交易。
对于 bKash 发起的服务端回调,不要简单关闭整个站点的 CSRF。应当:
- 只为指定回调路由配置有限例外;
- 校验 bKash 要求的签名、token 或交易标识;
- 校验订单金额、币种和订单状态;
- 保证回调处理具有幂等性。
检查 URL 和重定向
App URL 最好满足以下条件:
- 使用标准的公网 HTTPS 地址;
- 不需要 Basic Authentication;
- 不包含仅在本地有效的域名;
- 不重定向到
localhost、内网 IP 或其他端口; - 不出现 HTTP 与 HTTPS 循环跳转;
- 不依赖浏览器自动补全路径;
- URL 大小写处理一致;
- 可以在新的无痕窗口中直接打开。
如果 Laravel 位于反向代理之后,还要确认受信任代理配置正确。否则,应用可能把 HTTPS 请求识别成 HTTP,进而生成错误的回调地址或导致循环重定向。
建议的排查顺序
- 使用浏览器无痕模式直接访问 App URL,确认页面不依赖登录状态和已有 Cookie。
- 在开发者工具的 Network 和 Console 中查看 iframe、CSP、Cookie 以及重定向错误。
- 检查最终响应中的
X-Frame-Options和Content-Security-Policy。 - 检查 Nginx、Apache、CDN 和 WAF 是否覆盖了 Laravel 设置的响应头。
- 验证
GET和HEAD是否都能得到合理响应。 - 检查公网 DNS、IPv6、TLS 证书链和非标准端口。
- 查看服务器访问日志,确认 bKash 请求是否到达,以及服务器返回了什么状态码。
- 向 bKash 索取失败时间、来源 IP、目标 URL、HTTP 状态码和正式环境域名。
- 只为支付入口放行必要的 bKash 域名,然后重新测试。
缺少实际报错截图、HTTP 状态码和响应头时,还无法确定唯一原因。但如果“链接能打开,放进 <img> 也能访问,第三方页面却无法加载”,应该先检查 X-Frame-Options、CSP 和跨站 Cookie,而不是继续扩大 CORS 权限。
备注:内容仅供参考。
