PAYATHON 2026

Alipay EasySDK调用FaceToFace preCreate异步通知不生效

支付阿杰

结论

FaceToFace::preCreate() 支付成功后没有收到异步通知,通常不是支付流程出了问题,而是支付宝受理 alipay.trade.precreate 请求时没有获取到有效的 notify_url,或者无法访问该地址。

排查时,可以先在当前调用中通过 asyncNotify() 明确指定通知地址,避免只依赖全局配置。然后再检查地址的公网可达性、应用日志、签名验证和接口返回内容。

显式设置异步通知地址

全局配置可以填写 notifyUrl,但需要确认属性名、初始化顺序和当前 EasySDK 版本是否正确:

<?php

use Alipay\EasySDK\Kernel\Config;
use Alipay\EasySDK\Kernel\Factory;

$config = new Config();
$config->protocol = 'https';
$config->gatewayHost = 'openapi.alipay.com';
$config->signType = 'RSA2';
$config->appId = '你的应用 AppId';
$config->merchantPrivateKey = '你的应用私钥';
$config->alipayPublicKey = '支付宝公钥';
$config->notifyUrl = 'https://pay.example.com/alipay/notify';

Factory::setOptions($config);

为了确认全局配置是否传递到了具体接口,可以在调用 preCreate() 时显式设置通知地址:

<?php

$result = Factory::payment()
    ->faceToFace()
    ->asyncNotify('https://pay.example.com/alipay/notify')
    ->preCreate(
        '订单标题',
        'ORDER_202609090001',
        '0.01'
    );

需要注意:

  • 配置属性通常是 notifyUrl,不是 notify_url。
  • 必须先执行 Factory::setOptions($config),再调用支付接口。
  • asyncNotify() 要放在 preCreate() 之前。
  • 如果当前安装版本没有 asyncNotify(),不能直接照搬这段链式调用。请先查看该版本的接口定义,再通过升级 EasySDK 或使用底层通用请求传入 notify_url。

检查实际请求是否包含 notify_url

配置文件中写有通知地址,不代表它一定随请求提交给了支付宝。开启应用侧请求日志,确认最终发出的 alipay.trade.precreate 请求确实包含:

notify_url=https://pay.example.com/alipay/notify

同时查看接口返回结果,至少确认以下几点:

  • code 表明请求已受理成功。
  • out_trade_no 与本地订单一致。
  • 返回了可用的 qr_code。
  • 业务代码没有重新初始化 Factory,覆盖之前的配置。

如果项目运行在常驻进程、队列消费者或 PHP-FPM 以外的环境中,还需要确认每个进程都执行了 EasySDK 初始化。

检查通知地址是否可被支付宝访问

异步通知由支付宝服务器主动向 notify_url 发起 HTTP 请求,与用户浏览器是否跳转无关。通知地址需要满足以下条件:

  • 可以从公网访问,不能使用 localhost、127.0.0.1 或内网 IP。
  • 域名解析正确。
  • HTTPS 证书有效,证书链完整。
  • 访问时不需要登录、Cookie、验证码或人工操作。
  • 请求不会被防火墙、WAF、网关鉴权、IP 白名单或反向代理拦截。
  • 不会发生异常重定向,例如从 HTTPS 跳转到无法访问的 HTTP 地址。
  • 路由允许 POST 请求。

可以从另一台公网服务器进行测试:

curl -i -X POST 'https://pay.example.com/alipay/notify' \
  -d 'out_trade_no=TEST_ORDER' \
  -d 'trade_status=TRADE_SUCCESS'

这项测试只能检查网络和路由是否可达,无法模拟签名有效的支付宝通知。

正确处理回调

通知处理程序应读取支付宝提交的表单参数,完成验签后,再根据订单号、金额和交易状态更新订单。处理成功后,响应正文必须是纯文本:

success

示例:

<?php

use Alipay\EasySDK\Kernel\Factory;

$params = $_POST;

try {
    $verified = Factory::payment()
        ->common()
        ->verifyNotify($params);

    if (!$verified) {
        http_response_code(400);
        echo 'failure';
        exit;
    }

    $outTradeNo = $params['out_trade_no'] ?? '';
    $tradeNo = $params['trade_no'] ?? '';
    $tradeStatus = $params['trade_status'] ?? '';
    $totalAmount = $params['total_amount'] ?? '';

    // 还应从数据库读取本地订单,并核对:
    // 1. out_trade_no 是否存在
    // 2. app_id、seller_id 是否符合预期
    // 3. total_amount 是否与订单金额一致
    // 4. 订单是否已经处理,避免重复入账
    //
    // 仅在 TRADE_SUCCESS 或业务允许的其他状态下更新订单。

    if ($tradeStatus === 'TRADE_SUCCESS') {
        // 使用数据库事务更新订单。
        // 该操作必须支持幂等处理。
    }

    echo 'success';
} catch (\Throwable $e) {
    error_log('Alipay notify error: ' . $e->getMessage());

    http_response_code(500);
    echo 'failure';
}

不同 EasySDK 版本中,verifyNotify() 的返回形式可能不同,请以项目实际安装版本的方法签名为准。

验签前不要修改通知参数,包括字符集转换、删除空字段或重新拼装参数。这些操作可能导致验签失败。

容易被误判为“没有通知”的情况

有时支付宝已经发出通知,只是应用没有留下相应日志。常见原因有:

  • 回调入口被框架的 CSRF 校验拒绝。
  • 路由只接受 JSON,而支付宝发送的是表单数据。
  • 程序只读取 JSON 请求体,没有读取 $_POST。
  • 验签异常被全局异常处理器吞掉。
  • 接口返回了 HTML、JSON 或带空格的内容,而不是纯文本 success。
  • 回调执行时间过长,或者返回了 4xx、5xx。
  • 日志被记录在容器、网关或另一台服务器中。
  • 订单更新不支持幂等处理,重复通知引发数据库异常。

可以在验签前记录请求时间、来源地址、请求头和脱敏后的原始参数,以便判断支付宝根本没有发起请求,还是请求到达后处理失败。

进一步确认交易状态

异步通知不应是确认订单状态的唯一方式。生成二维码后,可以通过订单查询接口核对支付宝侧的交易状态:

<?php

$result = Factory::payment()
    ->common()
    ->query('ORDER_202609090001');

如果查询结果显示支付成功,但回调入口没有任何访问记录,应重点检查 notify_url 是否随请求提交,以及公网网络链路是否正常。如果回调已有访问记录,订单却没有更新,则应检查验签、状态判断、金额核对和数据库事务。

可以按照以下顺序排查:

  1. 在 preCreate() 前显式调用 asyncNotify()。
  2. 确认最终请求中包含正确的 notify_url。
  3. 确认回调地址可以接收来自公网的 POST 请求。
  4. 查看 Web 服务器、网关和应用日志。
  5. 检查验签结果和异常日志。
  6. 确认处理成功后返回纯文本 success。
  7. 使用订单查询接口核对实际交易状态,并为漏单准备主动补偿机制。

备注:内容仅供参考。