PHP App支付无法调起及$result字符串校验问题
结论
$result 应当是服务端通过支付宝 PHP SDK 的 sdkExecute() 生成的完整订单字符串。参数完整、签名可以验证,并且字符串没有被二次编码或修改,就可以交给 App 端的支付 SDK。
alipay_sdk=alipay-sdk-php-20200415 是 PHP SDK 自动添加的版本标识,属于正常的公共参数,不会妨碍 App 调起支付宝。生成签名后,不要单独删除或修改这个参数,否则会导致验签失败。
官方 demo 中的 $resultCode 通常用于判断支付宝接口的同步响应码。例如,10000 表示接口调用成功。这种判断适用于通过 execute() 发起的服务端接口请求,不适用于仅生成 App 支付订单字符串的 sdkExecute()。如果在 App 支付场景中混用这两套代码,很容易误以为 $resultCode 不应该为空。
正确生成 App 支付订单字符串
PHP 服务端应使用 AlipayTradeAppPayRequest 和 sdkExecute():
<?php
$aop = new AopClient();
$aop->gatewayUrl = $gatewayUrl;
$aop->appId = $appId;
$aop->rsaPrivateKey = $appPrivateKey;
$aop->alipayrsaPublicKey = $alipayPublicKey;
$aop->apiVersion = '1.0';
$aop->signType = 'RSA2';
$aop->postCharset = 'UTF-8';
$aop->format = 'json';
$bizContent = [
'out_trade_no' => 'ORDER_' . date('YmdHis'),
'total_amount' => '0.01',
'subject' => '测试商品',
'product_code' => 'QUICK_MSECURITY_PAY',
];
$request = new AlipayTradeAppPayRequest();
$request->setNotifyUrl('https://example.com/alipay/notify');
$request->setBizContent(
json_encode(
$bizContent,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
)
);
// 生成交给 App 支付 SDK 的订单字符串,不是支付结果
$result = $aop->sdkExecute($request);
echo $result;
这里的 $result 只是已签名的支付请求,不代表交易已经成功。支付结果需要结合以下信息判断:
- App 支付 SDK 返回的
resultStatus - 支付宝异步通知
- 服务端主动查询到的订单结果
最终是否支付成功,应以服务端收到的异步通知或订单查询结果为准,不能只依赖客户端返回值。
如何检查 $result 是否正确
正常的订单字符串一般包含以下参数:
app_id
method=alipay.trade.app.pay
format=json
charset=UTF-8
sign_type=RSA2
timestamp
version=1.0
biz_content
sign
还可能包含:
notify_url
alipay_sdk
检查时应重点查看 biz_content 解码后的内容,至少确认以下字段:
{
"out_trade_no": "唯一订单号",
"total_amount": "0.01",
"subject": "商品标题",
"product_code": "QUICK_MSECURITY_PAY"
}
在测试环境中,可以临时解析 $result:
$params = [];
parse_str($result, $params);
echo '<pre>';
print_r($params);
if (isset($params['biz_content'])) {
print_r(json_decode($params['biz_content'], true));
}
echo '</pre>';
不要把私钥、完整签名或用户交易信息写入公开日志。
本地验证 RSA2 签名
还可以使用应用公钥验证订单字符串中的签名:
function verifyAppPayOrderString(string $orderString, string $appPublicKey): bool
{
$params = [];
parse_str($orderString, $params);
if (empty($params['sign'])) {
return false;
}
$signature = base64_decode($params['sign'], true);
if ($signature === false) {
return false;
}
unset($params['sign'], $params['sign_type']);
ksort($params);
$pairs = [];
foreach ($params as $key => $value) {
if ($value === '' || $value === null) {
continue;
}
$pairs[] = $key . '=' . $value;
}
$contentToVerify = implode('&', $pairs);
$publicKey = trim($appPublicKey);
if (strpos($publicKey, 'BEGIN PUBLIC KEY') === false) {
$publicKey = "-----BEGIN PUBLIC KEY-----\n"
. chunk_split($publicKey, 64, "\n")
. "-----END PUBLIC KEY-----";
}
return openssl_verify(
$contentToVerify,
$signature,
$publicKey,
OPENSSL_ALGO_SHA256
) === 1;
}
调用方式:
$isValid = verifyAppPayOrderString($result, $appPublicKey);
var_dump($isValid);
这里必须使用与签名私钥配对的应用公钥,而不是支付宝公钥。支付宝公钥用于验证支付宝返回的数据和异步通知。
实际项目应优先使用 SDK 自带的签名和验签能力。上面的代码更适合排查签名数据是否在传输过程中被修改。
alipay_sdk 参数有什么作用
示例:
alipay_sdk=alipay-sdk-php-20200415
这个参数用于标识生成请求的 SDK 及其版本,通常由 SDK 自动添加。
处理时遵循以下原则:
- 不需要手动添加。
- 不要为了精简参数而删除它。
- 生成签名后不要修改它。
- 它本身一般不会导致支付宝 App 无法调起。
- SDK 升级后,该字段的值可能发生变化,这是正常现象。
判断订单字符串是否正确,不能只看是否存在 alipay_sdk,还要检查公共参数、biz_content 和签名。
$resultCode 的执行逻辑
PHP 官方示例中经常出现类似代码:
$result = $aop->execute($request);
$responseNode = str_replace(
'.',
'_',
$request->getApiMethodName()
) . '_response';
$resultCode = $result->$responseNode->code ?? null;
if (!empty($resultCode) && $resultCode == 10000) {
echo '成功';
} else {
echo '失败';
}
这段代码的执行过程如下:
execute()向支付宝网关发送请求。- 支付宝返回响应对象。
$responseNode根据接口名称计算响应节点名称。- 程序从该响应节点中读取
code。 - 当
code为10000时,表示该接口请求处理成功。
某个接口返回的数据可能类似:
{
"alipay_xxx_response": {
"code": "10000",
"msg": "Success"
},
"sign": "..."
}
此时 $resultCode 的值是:
10000
App 支付预下单通常使用:
$result = $aop->sdkExecute($request);
sdkExecute() 在本地组装参数并生成签名字符串,不会像 execute() 一样返回包含 code 的支付宝响应对象。因此,下面的写法不适用于 $result:
$resultCode = $result->alipay_trade_app_pay_response->code;
此时 $result 是字符串,不是支付宝响应对象。
ALI38173/AE050001205 的排查方法
排查这类报错时,需要依次检查订单字符串的生成、传输和 App 调用,不能只看客户端。
1. 检查必传业务参数
先确认:
$productCode = 'QUICK_MSECURITY_PAY';
同时检查以下内容:
out_trade_no不为空,并且在商户系统中唯一。total_amount是合法金额,例如"0.01"。subject不为空。biz_content是合法 JSON。- 参数名称的大小写和拼写准确。
- 不要随意把空字符串、
null或无关字段放入请求。
2. 检查公共参数
至少确认以下参数存在:
app_id
method=alipay.trade.app.pay
charset=UTF-8
sign_type=RSA2
timestamp
version=1.0
sign
biz_content
常见错误包括:
- 使用了错误的
app_id。 - 混用了沙箱应用配置和正式环境配置。
- 私钥不属于当前应用。
sign_type填写的是RSA2,实际却使用了其他算法。- 字符集没有配置为 UTF-8。
timestamp格式不正确。- 接口名称写错。
3. 检查密钥对应关系
生成签名时使用:
应用私钥
验证支付宝异步通知时使用:
支付宝公钥
不要把支付宝公钥当作应用公钥,也不能使用支付宝公钥生成订单签名。
如果应用重新生成或更换过密钥,还要确认开放平台上配置的应用公钥与当前服务端私钥确实配对。
4. 检查沙箱与正式环境是否混用
沙箱应用、沙箱网关、沙箱账号和对应密钥必须属于同一套环境。
尤其要避免以下情况:
- 沙箱
app_id配合正式环境密钥。 - 正式应用
app_id请求沙箱网关。 - 使用普通支付宝账号测试仅支持沙箱账号的流程。
- 客户端测试环境与服务端网关环境不一致。
具体的沙箱网关和测试客户端要求可能会随平台调整,应以当前开放平台控制台及对应版本的文档为准。
5. 不要对 $result 二次编码
sdkExecute() 返回的内容已经是可以交给 App 支付 SDK 的订单字符串,客户端应原样使用。
错误示例:
$result = urlencode($aop->sdkExecute($request));
或者:
$result = base64_encode($aop->sdkExecute($request));
除非客户端和服务端已经明确约定解码流程,否则不要这样处理。
通过 JSON 接口返回时,可以直接把字符串放入字段中:
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'code' => 0,
'order_string' => $result,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
客户端取得 order_string 后,应直接传给支付宝支付 SDK,不能再次拼接、拆分、排序或编码。
6. 对比服务端与客户端收到的字符串
服务端可以分别记录:
hash('sha256', $result);
strlen($result);
客户端也计算收到字符串的 SHA-256 和长度。如果两端的结果不同,说明订单字符串在接口序列化、网络传输或客户端处理过程中发生了变化。
重点检查以下字符:
+
/
=
%
&
"
签名中的 + 被转换成空格,是很常见的传输问题。
7. 检查客户端调用参数类型
客户端支付方法需要接收完整的订单字符串,而不是解析后的 JSON 对象,也不是单独的 biz_content。
以下做法都不正确:
- 只把
biz_content传给支付 SDK。 - 只传
sign。 - 把订单字符串拆成字典后重新拼接。
- 对整个订单字符串再次进行 URL 编码。
- 把服务端接口的外层 JSON 当作订单字符串传入。
正确流程如下:
PHP sdkExecute()
→ 完整订单字符串
→ JSON 接口返回给 App
→ App 取出 order_string
→ 原样传给支付宝支付 SDK
建议的定位顺序
先打印并解析 $result,确认 method、app_id、sign_type、biz_content 和 sign 都存在。然后验证签名,并比较服务端与客户端收到的字符串长度和 SHA-256。
如果这些内容完全一致,再检查沙箱环境、应用配置和客户端支付 SDK。服务端生成的字符串缺少必传参数时,应修改 PHP 请求的构造逻辑。服务端生成正确,但客户端收到的字符串发生变化时,则要检查接口序列化和客户端的二次处理。
需要明确区分以下两个方法:
$aop->sdkExecute($request);
它返回供 App 调起支付的订单字符串。
$aop->execute($request);
它返回支付宝服务端接口的响应对象,该对象才可能包含 $resultCode。
备注:内容仅供参考。
