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 是否随请求提交,以及公网网络链路是否正常。如果回调已有访问记录,订单却没有更新,则应检查验签、状态判断、金额核对和数据库事务。
可以按照以下顺序排查:
- 在
preCreate()前显式调用asyncNotify()。 - 确认最终请求中包含正确的
notify_url。 - 确认回调地址可以接收来自公网的
POST请求。 - 查看 Web 服务器、网关和应用日志。
- 检查验签结果和异常日志。
- 确认处理成功后返回纯文本
success。 - 使用订单查询接口核对实际交易状态,并为漏单准备主动补偿机制。
备注:内容仅供参考。
