PAYATHON 2026

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 '失败';
}

这段代码的执行过程如下:

  1. execute() 向支付宝网关发送请求。
  2. 支付宝返回响应对象。
  3. $responseNode 根据接口名称计算响应节点名称。
  4. 程序从该响应节点中读取 code。
  5. 当 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。

备注:内容仅供参考。