PAYATHON 2026

如何调试app支付接口并通过Json拉起支付宝支付页面?

支付老李

结论

支付接口返回的 JSON 一般只是业务响应,不能直接传给支付宝 SDK。App 需要先解析 JSON,取出服务端生成并签名的 orderString。这个字段也可能叫 orderInfo 或 payInfo。取出后,将字符串原样传给支付宝 App 支付 SDK。

完整流程如下:

App 创建订单
    → 业务服务端调用支付宝 App 支付接口并签名
    → 服务端返回 orderString
    → App 调用支付宝 SDK 拉起支付页面
    → SDK 返回同步结果
    → 支付宝向服务端发送异步通知
    → 服务端验签并更新订单
    → App 查询服务端确认最终状态

控制台可以用来检查接口参数、签名和响应,但不能模拟 App 拉起支付宝。要快速跑通流程,仍然需要一个最小化服务端接口,以及一个已经接入支付宝 SDK 的测试 App。

JSON 应该如何使用

假设服务端返回:

{
  "code": 0,
  "message": "success",
  "data": {
    "orderNo": "202609130001",
    "orderString": "alipay_sdk=...&app_id=...&biz_content=...&charset=utf-8&method=alipay.trade.app.pay&sign=...&sign_type=RSA2&timestamp=..."
  }
}

App 应按以下方式处理:

  1. 判断业务接口是否调用成功。
  2. 读取 data.orderString。
  3. 不拆分参数,不调整顺序,也不重新签名。
  4. 将 orderString 原样传给支付宝 SDK。

不要把整个 JSON 交给支付宝 SDK:

错误:SDK.pay(JSON.stringify(response))
正确:SDK.pay(response.data.orderString)

如果接口只返回 biz_content、订单号或一组尚未签名的参数,App 仍然不能直接拉起支付。服务端需要按照支付宝接口要求组装参数并完成 RSA2 签名,生成最终的订单字符串。

服务端生成支付参数

下面的示例只保留了主要结构。不同版本的服务端 SDK 可能使用不同的网关地址、初始化方式和方法签名,请以项目实际采用的支付宝开放平台 SDK 文档为准。

Java 示例

AlipayClient alipayClient = new DefaultAlipayClient(
    gatewayUrl,
    appId,
    merchantPrivateKey,
    "json",
    "UTF-8",
    alipayPublicKey,
    "RSA2"
);

AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();

AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo("202609130001");
model.setSubject("测试商品");
model.setTotalAmount("0.01");
model.setProductCode("QUICK_MSECURITY_PAY");

request.setBizModel(model);
request.setNotifyUrl("https://example.com/api/alipay/notify");

AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);

if (!response.isSuccess()) {
    throw new IllegalStateException(
        "创建支付参数失败:" + response.getSubCode() + " " + response.getSubMsg()
    );
}

// body 即应返回给 App 的订单字符串
String orderString = response.getBody();

业务接口再将结果包装成 JSON:

Map<String, Object> data = new HashMap<>();
data.put("orderNo", "202609130001");
data.put("orderString", orderString);

return ApiResponse.success(data);

商户私钥只能保存在服务端,不能写入 Android 或 iOS 客户端。否则攻击者可能提取私钥,伪造支付请求。

Android 拉起支付宝

支付宝 Android SDK 常见的调用方式如下:

String orderString = response.getData().getOrderString();

Runnable payRunnable = () -> {
    PayTask alipay = new PayTask(this);
    Map<String, String> result = alipay.payV2(orderString, true);

    runOnUiThread(() -> {
        String resultStatus = result.get("resultStatus");
        String memo = result.get("memo");

        // 此处只适合更新界面状态
        // 最终支付结果应向业务服务端查询确认
        handlePayResult(resultStatus, memo);
    });
};

Thread payThread = new Thread(payRunnable);
payThread.start();

Kotlin 写法如下:

val orderString = response.data.orderString

Thread {
    val payTask = PayTask(this)
    val result = payTask.payV2(orderString, true)

    runOnUiThread {
        val resultStatus = result["resultStatus"]
        val memo = result["memo"]

        // 不要仅凭客户端结果发货
        queryOrderStatusFromServer()
    }
}.start()

不要在主线程中执行 PayTask.payV2()。线程限制和 SDK 接入方式应以当前集成版本的说明为准。

iOS 拉起支付宝

iOS 端需要配置 URL Scheme,以便支付完成后从支付宝返回 App。示例:

NSString *orderString = response[@"data"][@"orderString"];

[[AlipaySDK defaultService] payOrder:orderString
                         fromScheme:@"your_app_scheme"
                           callback:^(NSDictionary *resultDic) {
    NSLog(@"支付宝返回结果:%@", resultDic);

    // 回调仅用于界面提示
    // 应继续请求业务服务端查询订单状态
    [self queryOrderStatusFromServer];
}];

Swift 项目可以调用 SDK 暴露的 Objective-C 接口:

let orderString = response.data.orderString

AlipaySDK.defaultService().payOrder(
    orderString,
    fromScheme: "your_app_scheme"
) { result in
    print("支付宝返回结果:\(String(describing: result))")
    self.queryOrderStatusFromServer()
}

如果支付宝可以正常打开,却无法返回 App,先检查以下几项:

  • fromScheme 是否与项目注册的 URL Scheme 完全一致。
  • Scheme 的拼写和大小写是否正确。
  • App 生命周期回调是否正确交给支付宝 SDK 处理。
  • 使用多个 Scene 的项目是否遗漏了相应的 URL 回调入口。

具体回调代码取决于工程是否使用 SceneDelegate,请按照当前支付宝 iOS SDK 的接入说明进行配置。

如何快速走通完整流程

1. 先打通服务端下单接口

准备一个最小接口,例如:

POST /api/pay/alipay/create
Content-Type: application/json

请求:

{
  "productId": "TEST_001"
}

订单号和金额由服务端生成,响应如下:

{
  "code": 0,
  "data": {
    "orderNo": "202609130001",
    "orderString": "..."
  }
}

不要直接信任 App 上传的金额。正式业务应根据 productId 在服务端查询实际价格。

2. 在控制台或 HTTP 工具中检查返回值

主要检查:

  • orderString 是否为空。
  • method 是否对应 App 支付接口。
  • app_id 是否与测试环境的配置一致。
  • sign_type、字符集和签名配置是否一致。
  • biz_content 是否包含唯一的 out_trade_no。
  • total_amount 和 product_code 是否符合接口要求。
  • 支付宝服务器能否通过公网访问 notify_url。

控制台调用成功,只能说明服务端的请求参数和签名基本可用。客户端 SDK、URL Scheme 和异步通知等环节仍需单独验证。

3. 在测试 App 中只保留必要流程

测试页面可以只放一个按钮:

点击按钮
→ 请求 /api/pay/alipay/create
→ 取得 orderString
→ 调用支付宝 SDK
→ 支付结束后查询订单状态

暂时不要加入优惠券、复杂订单合并和页面跳转等业务逻辑。等最小流程跑通后,再逐步恢复正式逻辑,这样更容易找到问题所在。

4. 使用匹配的测试环境

使用支付宝沙箱时,通常需要保证以下配置来自同一套沙箱环境:

  • 沙箱应用的 app_id
  • 沙箱对应的密钥
  • 沙箱网关
  • 沙箱买家账号
  • 符合沙箱要求的客户端或测试方式

沙箱参数不能与正式环境参数混用。沙箱工具和支持方式可能调整,具体配置应以当前支付宝开放平台控制台显示的内容为准。

5. 验证异步通知

支付完成后,支付宝会请求服务端的 notify_url。服务端需要:

  1. 读取支付宝发送的通知参数。
  2. 使用支付宝公钥验签。
  3. 校验 app_id、商户身份、订单号和金额。
  4. 查询本地订单,确认该订单尚未处理。
  5. 在事务中更新支付状态。
  6. 按接口要求返回成功响应。

伪代码如下:

if (!verifyAlipaySignature(requestParams)) {
    return "failure";
}

Order order = orderService.findByOrderNo(outTradeNo);

if (order == null) {
    return "failure";
}

if (!order.getAmount().equals(notifyAmount)) {
    return "failure";
}

if (!expectedAppId.equals(notifyAppId)) {
    return "failure";
}

orderService.markPaidIdempotently(order, tradeNo);

return "success";

支付宝可能重复发送异步通知,因此订单更新必须具备幂等性。同一个支付宝交易号或商户订单号不能重复入账或重复发货。

常见问题排查

SDK 提示参数错误

先确认传给 SDK 的内容确实是完整的 orderString,而不是以下内容:

  • 整个 JSON 响应;
  • 单独的 biz_content;
  • 被截断的字符串;
  • App 自行重新拼接的参数;
  • 经过错误 URL 解码或二次编码的字符串。

网络库在解析 JSON 时通常会正确还原转义字符,业务代码不要再随意调用 URLDecoder、decodeURIComponent,也不要重新编码签名字段。

能拉起支付宝,但支付失败

优先核对:

  • 应用 ID、商户密钥和支付宝公钥是否配套。
  • 当前使用的是正式网关还是沙箱网关。
  • 订单号是否重复。
  • 金额格式是否正确。
  • 签名算法是否与配置一致。
  • 应用是否拥有对应的产品权限。
  • 商户或应用当前是否允许交易。

服务端日志应完整记录支付宝返回的错误码和子错误码,不能只留下“支付失败”四个字。

App 显示成功,但服务端订单未支付

客户端 SDK 返回的结果不能作为发货依据。常见原因包括:

  • notify_url 无法从公网访问;
  • HTTPS 证书或网络策略异常;
  • 服务端验签时使用了错误的公钥;
  • 通知参数在验签前已被修改;
  • 金额或应用 ID 校验失败;
  • 数据库更新异常;
  • App 查询订单状态的时间过早。

支付回跳后,客户端可以短暂轮询业务服务端:

async function waitForPayment(orderNo) {
  for (let i = 0; i < 5; i++) {
    const result = await queryOrder(orderNo);

    if (result.status === "PAID") {
      return result;
    }

    await new Promise(resolve => setTimeout(resolve, 1500));
  }

  return { status: "PROCESSING" };
}

轮询次数和间隔要根据实际业务设置,不能无限请求。

注意事项

  • 商户私钥和签名逻辑必须放在服务端。
  • App 只接收和使用服务端生成的订单字符串。
  • 不要记录完整私钥、身份证号、手机号等敏感信息。
  • 测试日志可以记录订单号、支付宝交易号、错误码和主要处理阶段。
  • 每次调试都使用新的 out_trade_no,避免重复订单干扰判断。
  • 客户端同步结果只用于界面提示。订单处理应以服务端异步通知和主动查询结果为依据。
  • 收到支付成功通知后,仍要校验订单号、金额、应用和商户身份。
  • 正式上线前,需要测试支付成功、用户取消、支付处理中、重复通知、网络中断和回跳失败等情况。

备注:内容仅供参考。