PAYATHON 2026

Flutter 使用 alipay_kit 调起支付提示系统繁忙

支付小周

结论

能够跳转到支付宝,说明 alipay_kit 的客户端接入和应用唤起流程基本正常。支付宝提示“系统繁忙”,通常不是 Flutter 插件本身的问题,而是传入的支付订单字符串不合法。常见情况包括签名错误、参数被二次编码、缺少必填字段,或客户端传入的内容并不是完整的 orderInfo。

仅凭“系统繁忙”无法判断具体哪个字段有误。还需要查看插件返回的 resultStatus、memo,并结合服务端生成订单时的日志排查。

检查服务端返回值

支付宝 App 支付要求服务端生成并签名完整的订单参数。客户端收到的内容通常类似:

app_id=2021xxxxxxxxxxxx
&biz_content=%7B%22subject%22%3A%22...%22%2C%22out_trade_no%22%3A%22...%22%2C%22total_amount%22%3A%220.01%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%7D
&charset=utf-8
&method=alipay.trade.app.pay
&sign_type=RSA2
&timestamp=2026-09-09%2012%3A00%3A00
&version=1.0
&sign=...

实际返回的内容一般是一整行。客户端不能只传下面这样的值:

2026090900000000000000

这类支付宝交易号或商户订单号不是 orderInfo,不能直接用来调起支付。

常见原因

1. 签名后修改了参数

服务端完成 RSA2 签名后,如果客户端或网关又修改了金额、订单号、空格、字符编码或参数顺序,支付宝验签就会失败。

正确的处理流程如下:

  1. 服务端组装业务参数。
  2. 服务端生成待签名字符串。
  3. 使用应用私钥完成 RSA2 签名。
  4. 按照支付宝的要求,对签名值及相关参数进行编码。
  5. 将完整的订单字符串原样返回客户端。
  6. Flutter 不再拼接或修改该字符串,直接将其传给插件。

2. 二次 URL 编码或错误解码

如果服务端已经对 sign 和 biz_content 做过编码,Flutter 又执行:

Uri.encodeComponent(orderInfo)

字符串中的 % 会被再次编码为 %25,支付宝因此无法正确解析参数。

也不要随意执行:

Uri.decodeComponent(orderInfo)

解码后,签名中的 + 可能被错误地处理成空格,导致验签失败。

3. 返回值带有多余的 JSON 包装

假设接口响应如下:

{
  "code": 0,
  "data": {
    "orderInfo": "app_id=...&biz_content=...&sign=..."
  }
}

传给插件的应当是 orderInfo 字段,而不是整个 JSON:

final orderInfo = response.data['data']['orderInfo'] as String;

同时检查字符串两端是否有额外的引号、换行或转义字符,例如:

"app_id=...&sign=..."

最外层的双引号不属于订单字符串,不能一并传入。

4. 应用配置与签名密钥不匹配

下面这些配置必须属于同一个支付宝开放平台应用:

  • app_id
  • 应用私钥
  • 配置到支付宝开放平台的应用公钥或支付宝公钥证书
  • sign_type
  • 网关环境

如果服务端用 A 应用的私钥签名,却在参数中填写 B 应用的 app_id,客户端仍有可能打开支付宝,但支付请求无法通过校验。

5. 混用沙箱与正式环境

沙箱应用、沙箱账号、沙箱网关不能与正式环境混用。服务端使用沙箱配置时,客户端测试环境也必须与其对应。

6. App 支付参数不完整

biz_content 中通常需要正确提供以下参数:

{
  "subject": "订单标题",
  "out_trade_no": "唯一商户订单号",
  "total_amount": "0.01",
  "product_code": "QUICK_MSECURITY_PAY"
}

服务端需要检查金额格式、订单号唯一性和 product_code。具体哪些参数必填,应以当前的支付宝 App 支付接口文档为准。

Flutter 调用示例

不同版本的 alipay_kit API 可能略有差异。下面以常见的平台接口形式为例:

Future<void> startAlipay(String orderInfo) async {
  if (orderInfo.trim().isEmpty) {
    throw ArgumentError('orderInfo 不能为空');
  }

  final result = await AlipayKitPlatform.instance.pay(
    orderInfo: orderInfo,
  );

  debugPrint('resultStatus: ${result.resultStatus}');
  debugPrint('memo: ${result.memo}');
  debugPrint('result: ${result.result}');
}

客户端应直接传入服务端返回的完整字符串:

final orderInfo = response.data['data']['orderInfo'] as String;

await AlipayKitPlatform.instance.pay(
  orderInfo: orderInfo,
);

不要再对它进行编码、解码或重新拼接:

// 不建议
final encoded = Uri.encodeComponent(orderInfo);

// 不建议
final decoded = Uri.decodeComponent(orderInfo);

// 不建议
final modified = '$orderInfo&extra=value';

排查步骤

  1. 打印插件返回的 resultStatus 和 memo,不要只看支付宝页面上的“系统繁忙”。
  2. 确认传入的是完整的 App 支付订单字符串,而不是订单号、预下单编号或整个接口响应。
  3. 对比服务端最终生成的字符串和 Flutter 实际收到的字符串,确认两者完全一致。
  4. 检查字符串中是否有 %25、多余引号、换行、反斜杠,或 + 被替换为空格等情况。
  5. 在服务端核对 RSA2 签名过程,确认签名所用的原始参数与最终发送的参数一致。
  6. 检查 app_id、密钥、证书以及沙箱或正式环境是否匹配。
  7. 查看支付宝服务端接口返回的错误码和日志,不要只根据客户端的通用提示判断问题。

注意事项

应用私钥必须保存在服务端,不能放进 Flutter 工程。订单金额、订单号和签名也应由服务端生成。

客户端返回支付成功,只能用于界面提示。最终支付状态应由服务端通过支付宝异步通知或主动查询接口确认,不能仅凭客户端结果执行发货、充值到账等业务操作。

备注:内容仅供参考。