PAYATHON 2026

Laravel localhost 支付 callback 端口丢失导致 404

支付阿杰

结论

可以在本机测试支付流程,但需要满足两个条件:

  1. 支付平台接受 localhost 和非标准端口作为回调地址。
  2. 回调由浏览器跳转触发,而不是由支付平台的服务器主动请求。

当前代码不应把完整 URL 传给 url()。建议正确配置 APP_URL,为回调路由命名,再用 route() 生成包含 :8000 的完整地址。

推荐配置

先在 .env 中设置应用地址:

APP_URL=http://localhost:8000

如果修改后没有生效,请清理 Laravel 的配置缓存:

php artisan optimize:clear

然后定义命名路由:

use Illuminate\Support\Facades\Route;

Route::get('/buy', function () {
    try {
        $gateway = \Gateway::zarinpal();

        $gateway->setCallback(route('payment.callback'));
        $gateway->price(1000)->ready();

        $refId = $gateway->refId();
        $transID = $gateway->transactionId();

        // 保存 $refId、$transID、金额和订单编号

        return $gateway->redirect();
    } catch (\Exception $e) {
        abort(500, $e->getMessage());
    }
});

Route::match(['get', 'post'], '/payment/callback', function () {
    try {
        $gateway = \Gateway::verify();

        $trackingCode = $gateway->trackingCode();
        $refId = $gateway->refId();
        $cardNumber = $gateway->cardNumber();

        return response()->json([
            'card_number' => $cardNumber,
            'tracking_code' => $trackingCode,
            'reference_id' => $refId,
        ]);
    } catch (\RetryException $e) {
        abort(409, $e->getMessage());
    } catch (\PortNotFoundException $e) {
        abort(404, $e->getMessage());
    } catch (\InvalidRequestException $e) {
        abort(400, $e->getMessage());
    } catch (\NotFoundTransactionException $e) {
        abort(404, $e->getMessage());
    } catch (\Exception $e) {
        abort(500, $e->getMessage());
    }
})->name('payment.callback');

生成的回调地址应该是:

http://localhost:8000/payment/callback

可以暂时用下面的代码检查实际提交给支付组件的地址:

dd(route('payment.callback'));

不要这样调用 url()

原代码写成了:

$gateway->setCallback(
    url('http://localhost:8000/PaymentSuccessfulController')
);

url() 通常用于把应用内路径转换为完整 URL,例如:

url('/payment/callback')

如果已经有完整地址,可以直接传入:

$gateway->setCallback(
    'http://localhost:8000/payment/callback'
);

不过,命名路由更方便维护。部署到测试环境或生产环境后,也不必在业务代码中修改域名。

为什么地址会变成 http://localhost/...

浏览器访问:

http://localhost/PaymentSuccessfulController

相当于访问本机 HTTP 服务的默认端口 80。而 php artisan serve 通常监听 8000 端口,所以这个请求不会进入当前的 Laravel 应用,最终会返回 404。

如果 route('payment.callback') 已经生成了带 :8000 的地址,但从支付页面返回时端口仍然消失,问题通常出在以下某个环节:

  • 支付 SDK 错误地解析或规范化了 callback URL。
  • SDK 从自己的配置文件中读取了另一个 callback 地址。
  • 支付平台不接受 localhost 或非标准端口。
  • 支付平台后台设置的返回地址覆盖了代码提交的地址。
  • 发起支付请求时,实际传给平台的 callback 参数已经丢失了端口。

这时需要检查 SDK 最终发出的请求参数或日志,确认提交给支付平台的 callback 究竟是哪一个地址。

支付平台无法访问 localhost 时怎么办

如果回调只是支付结束后的浏览器重定向,而且支付操作和 Laravel 服务都在同一台电脑上运行,那么 localhost:8000 有可能正常工作。

如果支付平台会从自己的服务器请求 callback,localhost 就一定不可用。因为支付平台所访问的 localhost 指向它自己的服务器,而不是开发者的电脑。此时需要使用公网可访问的 HTTPS 地址,例如测试服务器或本地隧道,并将 APP_URL 改为该地址:

APP_URL=https://your-test-domain.example

支付平台是否接受 localhost、HTTP 地址或自定义端口,应以该平台当前的回调地址规则为准。

还需要注意的事项

如果支付平台通过 POST 请求 web.php 中的回调路由,Laravel 的 CSRF 中间件可能会拒绝该请求。应根据所用的 Laravel 版本,只为支付回调路径配置 CSRF 排除,不要关闭整个站点的 CSRF 防护。

另外,成功进入 callback 不代表订单已经支付成功。处理回调时还需要:

  • 使用 transaction_id 或平台返回的交易标识进行服务端验证。
  • 核对订单金额、订单号和支付状态。
  • 防止同一笔交易被重复处理。
  • 在数据库事务中更新订单。
  • 不要只根据地址栏参数将订单标记为已支付。

先用 route('payment.callback') 确认 Laravel 生成的地址包含端口。如果提交时地址正确,返回时端口仍被删除,就需要检查支付 SDK 或平台对 callback URL 的限制,并改用公网测试地址。

备注:内容仅供参考。