PAYATHON 2026

安卓 app 如何接入支付宝sdk实现免密支付流程?

支付小周

结论

安卓 App 可以通过支付宝 App 支付 SDK 拉起签约页面,但免密支付无法只靠客户端完成。完整流程需要 App、业务服务端和支付宝共同配合:

  1. App 向业务服务端申请签约参数。
  2. 服务端调用支付宝签约接口,生成 SDK 所需的 orderStr。
  3. App 使用支付宝 SDK 拉起签约页面,服务端根据异步通知确认签约结果。
  4. 后续扣款由服务端使用签约号调用支付宝扣款接口,App 不能在本地直接发起免密扣款。

应用私钥、签名生成、签约状态确认、扣款和异步通知验签都必须放在服务端。客户端返回的 resultStatus 只能用于界面提示,不能作为签约或扣款成功的最终依据。

一、接入前需要准备什么

先在支付宝开放平台创建应用并完成产品签约。通常需要准备:

  • 支付宝开放平台应用的 app_id
  • 应用私钥
  • 应用公钥证书或支付宝公钥
  • 支付宝公钥证书、根证书等证书文件,具体取决于所选签名模式
  • 签约异步通知地址
  • 支付异步通知地址
  • 业务系统中的用户、协议和订单数据
  • 已申请并获准使用的免密支付或周期扣款产品

免密支付不是普通 App 支付中的一个客户端开关。商户必须先取得相应的产品权限。可用的 product_code、personal_product_code、签约规则和扣款接口,应以该应用在支付宝开放平台实际开通的产品文档为准。

二、Android 接入支付宝 SDK

在应用模块中添加支付宝 SDK 依赖。SDK 版本号和仓库配置可能发生调整,应从支付宝开放平台当前的 Android 接入文档或下载页面获取,不建议直接复制旧版本号。

如果使用本地 AAR,可以按照 Android Gradle 的方式引入:

dependencies {
    implementation files('libs/alipaysdk.aar')
}

如果官方当前提供 Maven 坐标,优先使用官方文档中的依赖声明。

还要检查应用是否声明了网络权限:

<uses-permission android:name="android.permission.INTERNET" />

支付宝 SDK 可能还要求配置特定组件或混淆规则,不同版本之间会有差异。Activity、Service 或 Provider 等配置应以当前 SDK 的官方接入说明为准,不要自行猜测。

三、整体签约和扣款流程

签约和扣款可以按下面的流程处理:

用户点击开通免密支付
        ↓
App 请求业务服务端创建签约
        ↓
服务端调用支付宝签约接口并生成 orderStr
        ↓
App 使用支付宝 SDK 拉起支付宝签约页面
        ↓
支付宝返回客户端结果,同时通知业务服务端
        ↓
服务端验签并保存 agreement_no
        ↓
需要扣款时,由业务服务端创建扣款订单
        ↓
服务端携带 agreement_no 调用支付宝扣款接口
        ↓
服务端接收支付通知或主动查询支付结果

流程中有两个重要的业务标识:

  • external_agreement_no:商户生成的外部协议号,必须唯一。
  • agreement_no:支付宝在签约成功后返回的协议号,后续扣款和解约通常都需要使用。

建议为签约信息建立独立的记录表,至少保存以下字段:

user_id
external_agreement_no
agreement_no
sign_status
product_code
sign_time
invalid_time
last_notify_time

四、服务端生成签约参数

签约通常使用 alipay.user.agreement.page.sign 接口。服务端通过支付宝服务端 SDK 构造请求,再调用 sdkExecute,生成交给 Android SDK 的 orderStr。

下面是 Java 服务端示例。代码只展示典型字段,实际必填项取决于商户开通的免密产品。

AlipayClient alipayClient = new DefaultAlipayClient(
        "https://openapi.alipay.com/gateway.do",
        APP_ID,
        APP_PRIVATE_KEY,
        "json",
        "UTF-8",
        ALIPAY_PUBLIC_KEY,
        "RSA2"
);

AlipayUserAgreementPageSignRequest request =
        new AlipayUserAgreementPageSignRequest();

request.setNotifyUrl("https://api.example.com/alipay/agreement/notify");
request.setReturnUrl("https://www.example.com/alipay/agreement/result");

JSONObject bizContent = new JSONObject();
bizContent.put("external_agreement_no", externalAgreementNo);
bizContent.put("personal_product_code", "以实际开通产品为准");
bizContent.put("sign_scene", "以实际业务场景为准");

JSONObject accessParams = new JSONObject();
accessParams.put("channel", "ALIPAYAPP");
bizContent.put("access_params", accessParams);

request.setBizContent(bizContent.toJSONString());

AlipayUserAgreementPageSignResponse response =
        alipayClient.sdkExecute(request);

if (!response.isSuccess()) {
    throw new IllegalStateException(
            response.getCode() + ": " + response.getSubMsg()
    );
}

String orderStr = response.getBody();

服务端生成参数后,将 orderStr 返回给 App,例如:

{
  "code": 0,
  "data": {
    "orderStr": "app_id=...&method=alipay.user.agreement.page.sign&..."
  }
}

不要让 Android 客户端自行拼接请求参数,也不要在客户端使用应用私钥签名。否则,私钥很容易因 APK 被反编译而泄露。

如果开通的是周期扣款产品,签约请求可能还要包含周期规则、单次金额限制等参数。这些字段因产品而异,必须按照支付宝为对应产品提供的接口文档填写。

五、Android 拉起支付宝签约

App 从服务端取得完整的 orderStr 后,在工作线程中调用支付宝 SDK。

ExecutorService executor = Executors.newSingleThreadExecutor();

executor.execute(() -> {
    PayTask payTask = new PayTask(activity);

    Map<String, String> result =
            payTask.payV2(orderStr, true);

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

        if ("9000".equals(resultStatus)) {
            // 这里只表示客户端流程看起来已完成。
            // 仍然必须向业务服务端查询最终签约状态。
            queryAgreementStatus();
        } else if ("6001".equals(resultStatus)) {
            showMessage("用户取消了签约");
        } else {
            showMessage(
                    memo == null ? "签约未完成" : memo
            );
        }
    });
});

Kotlin 写法如下:

lifecycleScope.launch(Dispatchers.IO) {
    val payTask = PayTask(this@AgreementActivity)
    val result = payTask.payV2(orderStr, true)

    withContext(Dispatchers.Main) {
        when (result["resultStatus"]) {
            "9000" -> queryAgreementStatus()
            "6001" -> showMessage("用户取消了签约")
            else -> showMessage(result["memo"] ?: "签约未完成")
        }
    }
}

部分支付宝产品或 SDK 版本可能采用不同的客户端拉起方式。如果当前产品文档明确要求使用授权任务、URL Scheme 或其他方式,应按对应文档接入,不能直接套用普通 App 支付示例中的 API。

六、以异步通知确认签约结果

签约完成后,支付宝会向签约请求中的 notify_url 发送通知。服务端收到通知后,应完成以下检查和处理:

  1. 使用支付宝公钥或证书验签。
  2. 检查通知中的 app_id 是否属于当前应用。
  3. 检查 external_agreement_no 是否存在,以及是否对应当前用户。
  4. 检查协议状态和产品类型。
  5. 保存支付宝返回的 agreement_no。
  6. 以幂等方式更新签约状态。
  7. 按照支付宝的要求返回成功响应。

验签示例:

Map<String, String> params = new HashMap<>();

request.getParameterMap().forEach((key, values) -> {
    if (values != null && values.length > 0) {
        params.put(key, values[0]);
    }
});

boolean verified = AlipaySignature.rsaCheckV1(
        params,
        ALIPAY_PUBLIC_KEY,
        "UTF-8",
        "RSA2"
);

if (!verified) {
    response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    return;
}

如果使用证书模式,应调用对应的证书验签方法,不能混用支付宝公钥模式和证书模式。

支付宝可能重复发送同一通知,因此更新逻辑必须具备幂等性。例如,可以为 external_agreement_no 和 agreement_no 建立唯一索引,避免重复创建协议记录。

即使客户端显示签约成功,只要服务端还没有确认,就不能立即允许免密扣款。App 可以调用业务接口查询签约状态,必要时再由业务服务端向支付宝查询协议状态。

七、服务端发起免密扣款

签约成功后,服务端保存 agreement_no。需要扣款时,由服务端创建商户订单,并调用支付宝提供的签约扣款接口。

典型调用可能基于 alipay.trade.pay,同时在请求中传入协议参数。下面只展示常见结构:

AlipayTradePayRequest request = new AlipayTradePayRequest();
request.setNotifyUrl("https://api.example.com/alipay/payment/notify");

JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo);
bizContent.put("total_amount", "19.90");
bizContent.put("subject", "会员服务费");
bizContent.put("product_code", "以实际开通产品为准");

JSONObject agreementParams = new JSONObject();
agreementParams.put("agreement_no", agreementNo);
bizContent.put("agreement_params", agreementParams);

request.setBizContent(bizContent.toJSONString());

AlipayTradePayResponse payResponse =
        alipayClient.execute(request);

if (!payResponse.isSuccess()) {
    // 记录 code、sub_code、sub_msg,按错误类型决定查询、
    // 重试、关闭订单或提示用户重新签约。
    throw new IllegalStateException(
            payResponse.getCode() + ": " +
            payResponse.getSubCode() + ": " +
            payResponse.getSubMsg()
    );
}

具体使用哪个接口,以及 product_code 和 agreement_params 中需要哪些字段,应以商户实际开通的产品为准。周期扣款和一般代扣的准入条件、扣款时间、金额限制及接口参数可能不同。

同步响应也不能单独用来判断扣款是否成功。服务端需要结合以下信息确认最终结果:

  • 支付宝异步支付通知
  • 主动订单查询
  • 商户订单状态
  • 支付宝交易号

八、扣款通知和订单幂等

处理支付通知时,至少要校验:

  • 通知签名
  • app_id
  • out_trade_no
  • trade_no
  • total_amount
  • seller_id
  • 交易状态
  • 订单当前状态

金额校验应使用 BigDecimal,不要使用 double:

BigDecimal notifiedAmount =
        new BigDecimal(params.get("total_amount"));

BigDecimal orderAmount =
        order.getAmount();

if (notifiedAmount.compareTo(orderAmount) != 0) {
    throw new IllegalStateException("支付金额不一致");
}

同一条通知可能会被多次投递。更新订单状态时,建议使用数据库条件更新,只允许以下状态转换:

WAIT_PAY -> SUCCESS
WAIT_PAY -> CLOSED

已经成功的订单再次收到成功通知时,应直接返回成功,不能重复发货、充值或增加会员时长。

九、解约和失效处理

业务系统还需要提供关闭免密支付的入口。解约通常由服务端调用支付宝的协议解约接口,并通过 agreement_no 或商户协议号定位协议。

完整流程包括:

  1. 用户在 App 中申请关闭免密支付。
  2. 服务端查询本地协议状态。
  3. 服务端调用支付宝解约接口。
  4. 根据同步结果、异步通知或主动查询确认解约结果。
  5. 将本地协议状态更新为已解约。
  6. 后续扣款前再次检查协议状态。

用户也可能直接在支付宝客户端解约。因此,本地保存了 agreement_no 并不表示协议会一直有效。扣款失败时,应识别协议失效类错误并停止自动重试,再提示用户重新签约。

十、常见错误

在 Android 中保存应用私钥

这是严重的安全问题。APK 可以被反编译,私钥泄露后,攻击者可能伪造商户请求。所有签名操作都应由业务服务端完成。

把 resultStatus=9000 当成最终签约成功

客户端结果可能受到网络中断、进程终止或回调数据异常的影响。最终状态必须由服务端通过异步通知或主动查询确认。

签约成功后由客户端直接扣款

免密扣款涉及商户私钥、协议号、订单金额和交易状态控制,必须在可信的服务端执行。

重复使用外部协议号或订单号

external_agreement_no 和 out_trade_no 都应在业务范围内保持唯一。重复使用可能引发请求冲突或状态混乱。

对所有失败请求直接重试

免密扣款失败可能由余额不足、协议失效、额度限制、风控或系统繁忙引起。只有明确可以重试的系统类错误,才适合进行有限次数的重试。遇到协议失效或业务拒绝时,不应自动连续扣款。

忽略用户授权范围

实际扣款必须符合签约时向用户展示的金额、周期、服务内容和解约规则。签约后不能任意改变扣款用途或频率。

十一、推荐的服务端接口设计

Android App 不必直接处理支付宝的全部参数,只需对接业务服务端:

POST /api/payment/agreement/create
GET  /api/payment/agreement/status
POST /api/payment/agreement/terminate

服务端内部处理以下回调:

POST /callbacks/alipay/agreement
POST /callbacks/alipay/payment

扣款接口通常只供可信的业务服务调用,不建议直接暴露给移动客户端:

POST /internal/payment/alipay/deduct

采用这种设计后,支付宝参数、签名逻辑、订单幂等和风控都集中在服务端。Android 端只负责展示签约入口、拉起支付宝并查询最终状态。

备注:内容仅供参考。