1. 支付宝SDK快速集成与沙箱支付

支付宝SDK快速集成与沙箱支付 学习卡片

支付宝 App 支付的最小链路

支付宝 App 支付的核心是「客户端发起 → 服务端签名 → 客户端调起 SDK → 支付宝处理 → 同步/异步回调」这条链路。客户端不持有私钥,订单参数必须由服务端用应用私钥签名后返回,客户端只负责把签名后的订单串交给 SDK 调起收银台。

沙箱环境与生产环境的差异只在网关地址和密钥体系:沙箱使用独立的沙箱 AppID、沙箱买家账号和沙箱网关,签名算法与生产完全一致(推荐 RSA2)。因此沙箱跑通后,切换到生产只需替换 AppID、私钥和网关。

Android 客户端 PayTask.payV2() orderInfo 订单串 PayResult 回调 商户服务端 AlipayClient RSA2 签名 alipay.trade.app.pay 支付宝网关 沙箱网关 openapi.alipaydev .com ① 请求下单 ② 返回签名订单串 ③ payV2 调起 支付宝收银台 沙箱买家账号登录 确认付款 ④ 支付请求(含签名) 验签 + 扣款 返回 trade_no resultStatus=9000 ⑤ 同步返回 PayResult ⑥ 异步通知 notify_url 服务端验签 RSA2 verify 更新订单状态 关键约束: 客户端不持有私钥 · 订单串由服务端签名 · 同步回调仅作 UI 提示 · 最终状态以异步通知验签结果为准

集成步骤与关键代码

在 app/build.gradle 中添加支付宝 SDK 依赖,并在 AndroidManifest.xml 中声明网络权限。SDK 通过 AAR 方式集成,无需额外配置混淆规则(SDK 自带 consumer-rules)。

// app/build.gradle
dependencies {
    implementation 'com.alipay.sdk:alipaysdk-android:15.8.17'
}

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

服务端使用支付宝 Java SDK 生成签名订单串。沙箱环境需将网关切换为 https://openapi.alipaydev.com/gateway.do,并使用沙箱 AppID 与应用私钥。

// 服务端:生成 app_pay 订单串
AlipayClient client = new DefaultAlipayClient(
    "https://openapi.alipaydev.com/gateway.do",  // 沙箱网关
    SANDBOX_APP_ID,
    APP_PRIVATE_KEY,          // 应用私钥(PKCS8)
    "json", "UTF-8",
    ALIPAY_PUBLIC_KEY,        // 支付宝公钥,用于验签
    "RSA2"
);

AlipayTradeAppPayRequest req = new AlipayTradeAppPayRequest();
req.setNotifyUrl("https://your-domain.com/alipay/notify");

JSONObject biz = new JSONObject();
biz.put("out_trade_no", "ORDER_" + System.currentTimeMillis());
biz.put("total_amount", "0.01");
biz.put("subject", "沙箱测试订单");
biz.put("product_code", "QUICK_MSECURITY_PAY");
req.setBizContent(biz.toJsonString());

// sdkExecute 返回已签名的 orderInfo 字符串
String orderInfo = client.sdkExecute(req).getBody();

客户端拿到 orderInfo 后,通过 PayTask.payV2() 调起收银台。注意 payV2 必须在子线程执行,结果通过 Message 回传主线程。

// 客户端:调起支付宝
public void pay(String orderInfo) {
    Runnable payRunnable = () -> {
        PayTask alipay = new PayTask(activity);
        Map<String, String> result = alipay.payV2(orderInfo, true);

        Message msg = new Message();
        msg.what = SDK_PAY_FLAG;
        msg.obj = result;
        mHandler.sendMessage(msg);
    };
    new Thread(payRunnable).start();
}

private final Handler mHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        if (msg.what == SDK_PAY_FLAG) {
            PayResult payResult = new PayResult((Map<String, String>) msg.obj);
            String status = payResult.getResultStatus();
            // 9000 支付成功;6001 用户取消;8000/6004 处理中
            if ("9000".equals(status)) {
                // 仅作 UI 提示,最终以服务端异步通知为准
                showToast("支付成功,等待服务端确认");
            } else if ("6001".equals(status)) {
                showToast("用户取消支付");
            } else {
                showToast("支付结果待确认:" + status);
            }
        }
    }
};

同步验签与结果判读

同步回调中的 result 字段是支付宝返回的签名数据,客户端可用支付宝公钥做本地验签,确认数据未被篡改。但同步回调不可作为发货依据——用户可能在回调前杀进程,或网络中断导致回调丢失。

// 客户端同步验签(可选,用于增强可信度)
public boolean verifySyncResult(String result) {
    try {
        return AlipaySignature.rsaCheckV1(
            parseResultToMap(result),
            ALIPAY_PUBLIC_KEY,
            "UTF-8",
            "RSA2"
        );
    } catch (AlipayApiException e) {
        return false;
    }
}
resultStatus含义处理建议
9000支付成功提示用户,等待服务端异步通知确认
6001用户中途取消恢复订单状态,允许重新支付
8000 / 6004正在处理中轮询服务端订单状态,不可重复发起
4000 / 5000 / 6002失败 / 网络错误提示重试,检查网络与订单参数
沙箱环境必须使用沙箱买家账号登录支付宝 App 才能完成付款。若使用真实账号,会提示「订单不存在」或「商户未开通」。沙箱买家账号在支付宝开放平台沙箱应用中查看。

服务端异步通知验签

异步通知是最终支付结果的权威来源。服务端收到 notify_url 的 POST 请求后,必须用支付宝公钥验签,并校验 out_trade_no、total_amount、app_id 与本地订单一致,再更新订单状态并返回 success。

// 服务端:异步通知验签
@PostMapping("/alipay/notify")
public String notify(HttpServletRequest request) throws Exception {
    Map<String, String> params = new HashMap<>();
    request.getParameterMap().forEach((k, v) -> params.put(k, v[0]));

    // 1. 验签
    boolean signOk = AlipaySignature.rsaCheckV1(
        params, ALIPAY_PUBLIC_KEY, "UTF-8", "RSA2");
    if (!signOk) return "failure";

    // 2. 校验业务参数
    String tradeStatus = params.get("trade_status");
    String outTradeNo = params.get("out_trade_no");
    String totalAmount = params.get("total_amount");

    if ("TRADE_SUCCESS".equals(tradeStatus)) {
        Order order = orderService.findByNo(outTradeNo);
        if (order != null
            && order.getAmount().compareTo(new BigDecimal(totalAmount)) == 0) {
            orderService.markPaid(outTradeNo, params.get("trade_no"));
        }
    }
    return "success";  // 必须返回 success,否则支付宝会重试
}
沙箱调试时,notify_url 必须是公网可访问地址。本地开发可用内网穿透工具映射到本地端口,或在沙箱控制台配置可访问的回调地址。异步通知的验签逻辑与生产完全一致,沙箱跑通即可直接复用。

最小验证清单

  • 依赖集成后能编译通过,PayTask 类可正常引用。
  • 服务端 sdkExecute 返回的 orderInfo 非空且包含 sign 字段。
  • 客户端调起后能打开支付宝沙箱收银台,显示订单金额与商品名。
  • 使用沙箱买家账号付款后,resultStatus 返回 9000。
  • 服务端收到异步通知,验签通过,订单状态更新为已支付。
  • 重复收到同一 out_trade_no 的通知时,订单状态不重复变更(幂等)。