
简介面向Android开发者的支付集成资料整合支付宝、微信、银联三种主流支付SDK覆盖开放平台注册、参数配置、接口调用、安全签名及异步回调等完整链路适合需要快速接入或深入理解移动支付原理的初中级开发者。压缩包共72个文件以Java源码、XML配置、Gradle脚本、JAR依赖及SO动态库为主辅以工程说明文档与签名文件整体仅1.6MB结构紧凑便于对照学习。目前已有708人学习下载。资源内提供完整的pay_sdk工程包含三种支付方式的分模块实现可查看支付入口、回调Activity、参数组装与验签逻辑等核心代码附带的Readme和目录组织能帮助开发者理清接入步骤遇到问题时也可参考其中JKS签名、ProGuard混淆规则等配置减少踩坑。1. 三个支付 SDK 同时维护账要对得上同事把 pay_sdk-master 工程丢过来时我第一反应是翻 libs 目录下有多少 jar 和 aar。这工程值钱的地方不是把支付宝、微信、银联三家的 SDK 堆在了一起而是把三套完全不同的初始化、回调协议和验签逻辑压在同一 Android 工程里做了归一化。支付宝走 payTask 唤起、微信要注册 IWXAPI、银联绕不开 UPPayPlugin——三者在 manifest、混淆和网络权限上的要求互不兼容能在一个工程里跑通说明作者把三份官方文档真正读过了一遍。对聚合支付开发而言这份工程的核心是三条完整链路而不是压缩包体积。2. 初始化差异libs 依赖、manifest 与混淆规则的拆解聚合支付 SDK 工程的第一步不是写代码而是先把三个渠道的初始化机制搞清楚。三者里只有支付宝的 SDK 是“拿来即用”的官方 jar 内部自带了支付能力不需要在应用启动阶段做任何注册动作微信必须先将IWXAPI注册到微信开放平台拿到校验后的IWXAPI实例才能发起支付银联则是通过UPPayAssistEx这个工具类动态反射调用依赖libs下的UPPayPluginExPro.jar和UPPayAssistEx.jar。这三套机制的并存决定了工程目录里paysdk、app、libs三个模块各自的职责边界。2.1 工程目录与 sourceSets 的关系解压后的pay_sdk-master目录里app模块是演示工程paysdk是被引用的封装库libs目录存放三家的终端 SDK 实体文件。以下是最常见的一种组织方式android { compileSdkVersion 28 sourceSets { main { jniLibs.srcDirs [libs] manifest.srcFile src/main/AndroidManifest.xml java.srcDirs [src/main/java] res.srcDirs [src/main/res] } } } dependencies { implementation fileTree(include: [*.jar, *.aar], dir: libs) implementation com.android.support:appcompat-v7:28.0.0 implementation com.squareup.okhttp3:okhttp:3.12.0 }这段配置的关键点是jniLibs.srcDirs [libs]。银联 SDK 的.so文件如果放在libs/armeabi、libs/armeabi-v7a下但没被声明为 jniLibs 目录运行时就会直接抛UnsatisfiedLinkError错误信息表现为UPPayAssistEx的静态方法调用失败。fileTree则保证libs下所有 jar 和 aar 都会被构入最终产物不需要逐个声明依赖。提示如果使用 Android Studio 4.0 以上版本compileSdkVersion建议不低于 30否则部分微信 SDK 的 targetSdkVersion 校验分支会走不通但具体的编译 SDK 版本以你工程实际为准。2.2 三套初始化 API 的对照三个渠道的初始化差异可以直接用一张表说清楚渠道初始化入口是否需要应用上下文失败表现支付宝无显式初始化PayTask构造时传入 Activity不需要但需要 Activity 实例唤起支付宝后立即返回 9000 以外的结果码微信IWXAPI.registerApp(appId)需要建议在Application.onCreate中执行onResp不回调或返回ERR_UNSUPPORTED银联UPPayAssistEx反射加载无需注册不需要返回UPPayPluginError常见为未安装控件微信的注册这一步最容易被忽略而它的坑也最隐蔽。registerApp必须在启动阶段完成不能在点击支付按钮时才执行因为 SDK 内部需要提前把 appId 和包名签名信息发送给微信客户端建立双向连接。如果注册成功微信客户端会调用应用里的WXPayEntryActivity注册失败则只有日志输出界面没有任何提示开发阶段很难定位。2.3 proguard 规则的三段式处理混淆规则是第三个需要单独处理的维度。三个渠道的 SDK 内部都有大量反射调用尤其银联的UPPayAssistEx是动态反射com.unionpay.UPPayPlugin混淆后类名和方法名一旦被改写运行期必然抛ClassNotFoundException。完整工程的proguard-rules.pro文件至少需要以下规则# 支付宝 -keep class com.alipay.sdk.** { *; } -dontwarn com.alipay.sdk.** # 微信 -keep class com.tencent.mm.opensdk.** { *; } -keep class com.tencent.wxop.** { *; } -dontwarn com.tencent.mm.opensdk.** # 银联 -keep class com.unionpay.** { *; } -keep class org.json.** { *; } -dontwarn com.unionpay.**dontwarn的作用是压制非关键警告因为微信旧版本 SDK 里存在引用android.support.annotation但未声明依赖的情况。org.json的 keep 规则是银联特有的老版本银联 SDK 直接引用了 org.json 内部类这部分如果不 keep混淆后会因为 JSON 处理反射失败而返回银联支付控件加载失败。压缩包里自带的proguard-rules.pro已经给出了参考实际接入时建议把这段规则直接抄进自己的主工程不要只依赖paysdk模块里的那份。3. 下单与唤起三个支付渠道的执行链路拆开看初始化完成之后三条支付链路的差异会集中体现在“下单参数组装”和“结果返回”这两个环节上。先说结论支付宝最独立全部参数由服务端签名后返回客户端客户端只做唤起微信要求客户端先调统一下单接口获得prepayId再交给IWXAPI发起二次请求银联的流程介于两者之间客户端拿到的tn交易流水号直接传给 SDK 即可。三者链路的共同点是客户端永远不要做 RSA 私钥签名签名必须在服务端完成。3.1 支付宝PayTask 的单线程约束支付宝的PayTask在com.alipay.sdk.app.PayTask下核心调用代码非常简单但有一个重要的线程约束——payV2方法必须在主线程调用并且调用成功后要把结果交由onActivityResult或回调统一处理private void startAlipay(Activity activity, final String orderInfo) { final PayTask payTask new PayTask(activity); Runnable payRunnable new Runnable() { Override public void run() { MapString, String result payTask.payV2(orderInfo, true); // payV2 是同步阻塞方法需要切回主线程处理 Message msg Message.obtain(); msg.what SDK_PAY_FLAG; msg.obj result; mHandler.sendMessage(msg); } }; new Thread(payRunnable).start(); }这里的orderInfo是服务端根据alipay.trade.page.pay接口生成的订单字符串包含了app_id、method、sign等参数客户端不能自行拼接。payV2的第二个布尔参数表示是否需要在唤起前弹出确认弹窗一般传true。支付宝的结果码需要特别留意9000 表示支付成功但 8000 表示正在处理中此时客户端不能直接把订单置为已支付必须等待服务端收到异步通知后做最终确认。3.2 微信WXPayEntryActivity 的声明与回调微信支付的回调入口不是通过接口拿返回值而是通过一个特定的 Activity。这个WXPayEntryActivity需要在 manifest 里显式声明并且包名路径必须与APP_ID的签名包名一致否则回调永远进不来public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler { private IWXAPI api; Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); api WXAPIFactory.createWXAPI(this, Constants.APP_ID); api.handleIntent(getIntent(), this); } Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); api.handleIntent(intent, this); } Override public void onResp(BaseResp baseResp) { if (baseResp.getType() ConstantsAPI.COMMAND_PAY_BY_WX) { int code baseResp.errCode; // 0 成功, -1 错误, -2 用户取消 } } }onNewIntent的重写很关键微信客户端在部分机型上是通过单实例模式拉起的 Activity如果只实现onCreate里的handleIntent第二次支付时回调可能丢失。baseResp.errCode只能作为参考0只是微信客户端“确认已支付”的回执不代表服务端一定收到了微信支付结果通知。实际项目里通常还要在onResp里取出returnKey与服务端对账防止中间层丢单。3.3 银联tn 流水号的边界情况银联的UPPayPluginExPro调用模式在三个渠道里最特殊它直接依赖厂商私有 APK 提供的插件控件。调用时只需要把服务端返回的tn字符串传入SDK 会自动完成后续流程UPPayAssistEx.startPay(this, null, null, tn, 00, new UPPayPluginCallBack() { Override public void onPayEnd(boolean result, String msg) { if (result) { // 支付成功回调仍需服务端二次确认 } } });00表示正式环境测试环境对应01。这里需要注意「控件安装」的判断逻辑老版本银联 SDK 在用户未安装银联控件时会自动跳转下载页但在部分国产 ROM 上跳转会被拦截表现为onPayEnd返回false且msg为 “控件加载失败”。处理方案是先调用UPPayAssistEx.checkServiceEnabled检查环境再决定是否弹出自定义的引导下载对话框。聚合支付场景下建议只在用户手动选择银联支付时才走这条链路不要把它设计成默认支付方式因为支付成功率受终端控件环境影响较大。4. 验签、证书与密钥管理的工程化落地三个渠道在支付结果通知上都采用了「客户端回调 服务端异步通知」的双通道模式。客户端回调是给用户看的决定 UI 跳转服务端异步通知是给账算的决定订单状态。后者必须做验签处理而验签算法在三个渠道之间并不统一——支付宝使用 RSA2SHA256withRSA微信使用 HMAC-SHA256 或 MD5银联则是基于数字证书的验签。pay_sdk-master工程根目录下的sinoli.jks文件也隐晦地提醒了另一件事签名证书的管理本身就是支付工程的一部分。4.1 服务端验签的核心逻辑以支付宝为例异步通知的验签参数是一个扁平的MapString, String开发者在服务端拿到后需要先剔除sign、sign_type两个字段把剩余参数按字典序拼接后再做 RSA 验证public static boolean rsa2CheckV2(MapString, String params, String publicKey) { String sign params.get(sign); String signType params.get(sign_type); params.remove(sign); params.remove(sign_type); ListString sortedKeys new ArrayList(params.keySet()); Collections.sort(sortedKeys); StringBuilder content new StringBuilder(); for (int i 0; i sortedKeys.size(); i) { String key sortedKeys.get(i); String value params.get(key); content.append(i 0 ? : ) .append(key) .append() .append(value); } try { Signature signature Signature.getInstance(SHA256withRSA); PublicKey pubKey getPublicKeyFromString(publicKey); signature.initVerify(pubKey); signature.update(content.toString().getBytes(UTF-8)); return signature.verify(Base64.decode(sign.getBytes(), Base64.NO_WRAP)); } catch (Exception e) { return false; } }参数排序是整个验签过程里最容易出错的地方。支付宝要求的是「字典序升序」微信支付要求的是「字典序升序后先编码再拼接」银联要求的是「证书私钥签名的原文与报文一致」。同一个聚合支付服务端如果为三个渠道各自实现验签逻辑代码量会成倍增长。常见做法是写一个统一接口传入渠道枚举内部用策略模式分发到各自的验签实现这样后续接京东白条或云闪付时只需要扩展新的实现类。提示判断商户通知是否可信还有一个额外条件——来源 IP。支付宝的异步通知来源 IP 固定范围会写在官方文档里微信也有固定的通知域名服务端验签通过后建议再校验一遍来源域名或 IP避免内网 DNS 被改写时验签被绕过。4.2 私钥与证书的存放边界支付宝和微信都只需要在服务端存私钥客户端存公钥或不需要存任何密钥。但银联的数字证书机制要求商户持有 pfx 证书文件这个文件不能放在 Android 的 assets 资源目录下否则反编译后外部可以直接拿到证书链。sinoli.jks是app模块演示用的签名证书它解决的问题是包名与微信开放平台上登记的签名一致性而不是银联的商户证书。两者如果混为一谈会在「打包上线后微信支付唤不起」和「银联验签一直失败」这两个问题上反复踩坑。实践上我的处理方式是微信和支付宝的公钥通过接口从服务端下发客户端不做静态存储银联证书路径写到buildConfigField中区分 debug 和 release 环境release 环境只保留证书指纹不在客户端持有完整的 pfx 文件。因为银联 SDK 本身支持服务端下发交易流水号后由服务端保存证书客户端实际上用不到 pfx。这一点很多集成文档没有写透导致不少团队在客户端包里塞了证书却不自知。4.3 回调幂等的数据库收口支付结果异步通知在生产环境是重复触发的支付宝会按一定时间策略重发 24 小时微信的通知重试策略也类似。客户端和服务端都必须满足幂等否则用户支付成功后订单被重复置为已支付后续发货逻辑就会执行两次。SQL 层面的收口方案一般是给订单表加唯一约束或在更新订单时带上前置状态条件UPDATE orders SET status PAID, paid_at NOW() WHERE order_id #{orderId} AND status UNPAID这里WHERE status UNPAID是关键。同一笔订单的通知除非极端异常否则只会成功执行一次更新第二次通知到达时status已经不是UNPAID更新行数为 0直接丢弃该通知。对于「通知到了但订单查不到」的场景需要先记录日志再返回渠道要求的成功标记避免渠道方误以为通知投递失败而反复重试最终形成告警风暴。5. 联调环境与回调防重入的验证技巧支付 SDK 的联调最恼人的不是代码写不对而是「真金白银地付了一分钱结果回调没进来」。针对这类问题我总结了一套基于日志埋点和状态机的验证方法尤其适合在接入pay_sdk-master这类聚合工程时使用。先做一层“三方状态机”日志埋点以orderId为追踪维度把所有渠道的支付结果统一转换成一组状态常量INIT、SERVER_SIGNED、CLIENT_CALLED、CHANNEL_RETURNED、SERVER_NOTIFIED、SETTLED。每笔订单在进入下一个状态时打印一条带orderId和渠道标识的日志。联调时只用一条 grep 命令就能看清问题出在哪个环节adb logcat -s PaySdk | grep orderId_20240601如果一个订单的日志停在CLIENT_CALLED再也没有推进说明微信的onResp或支付宝的onActivityResult没有触发先查 manifest 里的WXPayEntryActivity路径与签名是否匹配如果状态推进到了CHANNEL_RETURNED但服务端始终收不到SERVER_NOTIFIED那问题在服务端异步通知的验签上优先核对服务端的公钥和排序算法。第二层验证技巧利用微信支付和支付宝均支持的「预下单模式」做链路测试。服务端预下单接口分为“前台收银台下单”和“纯 API 下单”两种测试时选后者客户端拿到签名后的订单串但不唤起收银台而是把签名串直接丢给一个只做验签的 mock 接口验证签名数据的完整性与服务端是否一致。这个方法可以完全避免反复扫码测试效率提升非常明显。银联渠道因为没有可用于测试的客户端 mock 环境常见的做法是把tn参数复制到官方测试用例中手动跑一遍流程。银联测试环境通常允许任意金额下单实际支付时使用测试卡号即可不需要真实扣款。这里有一个容易被忽略的细节银联的tn有效期非常短如果服务端和客户端时间不一致tn很可能在到达客户端时已经过期。联调时建议把服务端下发tn的时间戳也打进日志配合adb shell date对比两边的时钟偏差确认过期问题的根因。最后一招是给回调处理逻辑加一个“安全网重复确认”机制。客户端收到回调后不直接修改 UI而是用orderId调一次服务端的订单查询接口以服务端的SETTLED状态为准刷新界面。这样即使三个渠道的回调都发生了丢单或者用户在支付成功后立刻杀掉了 App下次进入订单页面时依然能从服务端同步到正确状态。这个机制的成本极低却能规避掉绝大多数「用户已付款但页面还显示未支付」的客诉场景建议所有聚合支付工程都加上。本文还有配套的精品资源点击获取