大陆支付宝SDK与香港支付宝SDK如何在同一APP中集成?
结论
同一个 Android APP 可以同时支持大陆支付宝和香港支付宝,前提是两套支付能力能够在客户端共存。
如果 AlipayHK 的本地 JAR 与大陆支付宝 SDK 使用了相同的包名或类名,就不能直接把两个依赖放进同一个项目。exclude、调整依赖顺序或只保留其中一套重复类,通常无法可靠解决这类冲突,因为两个 SDK 可能依赖不同版本的内部实现。
建议按以下顺序处理:
- 向大陆支付宝和 AlipayHK 的技术支持确认,是否有支持共存的新版 SDK、统一 SDK,或已经更换包名的兼容版本。
- 如果没有可共存的版本,可以让一个渠道使用客户端 SDK,另一个渠道使用官方支持的 H5、网页收银台或其他不会引起依赖冲突的接入方式。
- 只有在官方明确允许,而且 SDK 不涉及复杂的反射、JNI、资源绑定等机制时,才考虑对 JAR 进行包名重定位。自行修改 SDK 风险很高,不适合作为正式项目的首选方案。
productFlavors只能生成分别支持不同支付宝的多个 APK,不能让同一个安装包同时支持两种支付。
大陆支付宝 APP SDK 通常不能直接拉起 AlipayHK APP。最终拉起哪个钱包,取决于接入的支付产品、客户端 SDK、订单类型和钱包侧协议,不能通过修改订单参数或包名来切换。如果需要接入 AlipayHK 支付,应采用 AlipayHK 或相应跨境支付产品提供的正式方案。
为什么会发生冲突
Android 构建 APK 时,同一个完整类名只能存在一份。例如,两个依赖都包含:
com.alipay.sdk.app.PayTask
构建时可能出现类似错误:
Duplicate class com.alipay.sdk.app.PayTask found in modules ...
即使构建工具没有立即报错,应用运行时也可能加载错误的版本,进而出现以下问题:
NoSuchMethodErrorClassNotFoundException- 支付页面无法拉起
- 支付结果解析异常
- 回调失败
- 混淆后出现兼容性问题
这不是普通的 Gradle 版本仲裁问题。对于包含相同类名的本地 JAR,Gradle 无法像处理常规 Maven 版本冲突那样,自动选出一套同时兼容两个支付渠道的实现。
推荐的集成步骤
1. 先确认接入产品,而不只是 SDK 名称
需要分别向两个支付渠道确认:
- 当前商户签约的是大陆支付宝、AlipayHK,还是某种跨境或聚合支付产品;
- Android 端推荐使用哪个 SDK;
- 最新 SDK 是否支持与另一套支付宝 SDK 共存;
- 是否提供 Maven 坐标,以免继续使用过期的本地 JAR;
- 是否支持 H5 收银台;
- SDK 实际会拉起哪个钱包 APP;
- 回跳协议、异步通知和订单查询分别使用什么接口。
不同商户产品的接入方式可能并不相同,不能因为名称中都有“支付宝”,就认为两边的订单或 SDK 可以通用。
2. 检查重复类
将两个 SDK 放入测试分支后,可以使用 Android Studio 的依赖分析功能查看依赖来源,也可以直接检查 JAR 中的类列表。例如:
jar tf alipayhk-sdk.jar
检查时需要注意两套依赖中是否存在相同的完整类名、资源名,以及重复的 provider、activity 或 service 声明。
如果已经出现 Duplicate class,不要通过删除不确定用途的类文件来强行通过编译。应先向官方索取兼容版本。
3. 在业务层隔离两个支付渠道
无论最终采用两套 SDK,还是“一套 SDK + 一套 H5”,业务代码都不应到处直接调用具体 SDK。可以先定义统一的支付接口:
enum class PaymentChannel {
ALIPAY_CN,
ALIPAY_HK
}
data class PaymentRequest(
val channel: PaymentChannel,
val orderInfo: String,
val cashierUrl: String? = null
)
sealed class PaymentResult {
data class Success(val rawResult: String) : PaymentResult()
data class Pending(val rawResult: String) : PaymentResult()
data class Failure(
val code: String?,
val message: String?,
val rawResult: String?
) : PaymentResult()
}
interface PaymentAdapter {
suspend fun pay(request: PaymentRequest): PaymentResult
}
大陆支付宝适配器可以封装官方 PayTask。具体调用方式应以项目所用版本的官方文档为准:
class AlipayCnAdapter(
private val activity: Activity
) : PaymentAdapter {
override suspend fun pay(request: PaymentRequest): PaymentResult {
require(request.channel == PaymentChannel.ALIPAY_CN)
return withContext(Dispatchers.IO) {
val payTask = PayTask(activity)
val result = payTask.payV2(request.orderInfo, true)
PaymentResult.Pending(
rawResult = result.toString()
)
}
}
}
这里返回 Pending,是因为客户端返回值不能作为最终入账依据。实际项目可以继续解析 resultStatus 来更新页面状态,但最终支付结果仍应由服务端确认。
香港支付宝适配器需要根据最终获批的接入方式实现。如果采用 H5,也可以继续使用同一套接口:
class AlipayHkH5Adapter(
private val activity: Activity
) : PaymentAdapter {
override suspend fun pay(request: PaymentRequest): PaymentResult {
require(request.channel == PaymentChannel.ALIPAY_HK)
val url = requireNotNull(request.cashierUrl) {
"AlipayHK cashierUrl is required"
}
withContext(Dispatchers.Main) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
activity.startActivity(intent)
}
return PaymentResult.Pending(rawResult = "H5 cashier opened")
}
}
这段代码只用于说明客户端架构和渠道隔离方式。H5 是否允许在外部浏览器中打开、是否必须使用 WebView,以及回跳 URL 的具体格式,都应以相应支付产品的正式文档为准。
4. 后端分别创建订单
客户端不应自行拼接支付参数或完成签名。服务端应根据支付渠道调用对应的接口:
{
"orderId": "ORDER_202609090001",
"channel": "ALIPAY_HK",
"orderInfo": null,
"cashierUrl": "https://example.invalid/cashier/session-id"
}
客户端再根据 channel 选择适配器:
suspend fun startPayment(request: PaymentRequest): PaymentResult {
val adapter = when (request.channel) {
PaymentChannel.ALIPAY_CN -> alipayCnAdapter
PaymentChannel.ALIPAY_HK -> alipayHkAdapter
}
return adapter.pay(request)
}
两种支付渠道需要分别维护:
- 商户号及应用身份;
- 私钥、公钥或证书;
- 签名算法及验签逻辑;
- 下单接口;
- 异步通知地址;
- 退款和查询接口;
- 订单号与渠道交易号;
- 支付状态映射。
不要把大陆支付宝生成的 orderInfo 直接传给香港支付宝 SDK,也不要假设双方的签名方式、订单字段和返回码完全相同。
SDK 确实冲突时的处理方案
方案一:使用官方共存版本
这是风险最低的方案。共存版本通常会通过更换包名、统一底层组件或合并接口来解决重复类问题。
替换后需要覆盖以下测试场景:
- 仅安装大陆支付宝 APP;
- 仅安装 AlipayHK APP;
- 两个钱包都安装;
- 两个钱包都未安装;
- 支付成功、取消和失败;
- APP 被系统回收后的回跳;
- 从支付完成页返回 APP;
- 服务端异步通知晚于客户端回调;
- 混淆后的正式包。
方案二:一个渠道使用 SDK,另一个渠道使用 H5
如果官方没有提供可共存的 SDK,通常可以让一个渠道使用 SDK,另一个渠道使用 H5。第二套冲突 JAR 不会被打入 APK,因此重复类问题也就不存在了。
采用这种方式前,需要确认对应产品正式支持 H5,并处理好以下事项:
- 浏览器或 WebView 的使用限制;
- 自定义 Scheme、App Link 或 Universal Link 回跳;
- 用户取消支付;
- 页面关闭后主动查询订单;
- 防止伪造回跳参数;
- 服务端异步通知验签。
方案三:对 JAR 做包名重定位
理论上,可以使用构建工具整体改写其中一套 SDK 的包名,例如将:
com.alipay.sdk.*
迁移为:
com.example.shaded.alipayhk.*
但只有同时满足以下条件,这种做法才可能可行:
- SDK 授权条款允许修改或重新打包;
- SDK 不依赖固定类名进行反射;
- 不包含无法同步修改的 JNI 类名;
- 不依赖固定包名的组件声明;
- 不存在资源名称或
ContentProvider冲突; - 官方愿意支持这种部署方式。
支付 SDK 涉及签名、风控和安全校验。自行重打包可能引起功能异常,也可能导致项目失去官方技术支持。没有支付方的书面确认,不建议在生产环境中使用这种方案。
不建议采用的做法
不要依赖下面这种方式解决冲突:
implementation(files("libs/alipayhk-sdk.jar")) {
exclude(group = "com.alipay", module = "alipaySdk")
}
exclude 主要用于排除传递依赖,不能删除本地 JAR 中的部分重复类。
也不要简单保留“版本较新”的重复类。即使类名相同,两个支付渠道所需的方法、协议或内部实现也可能不同。项目能够编译,不代表运行时可靠。
把两套支付功能放进不同的 Android 进程同样无效。重复类问题发生在 APK 构建阶段,这些类仍需被打包到同一个应用中。
支付结果处理注意事项
客户端显示“支付成功”,并不表示资金已经可靠入账。正确流程如下:
- 客户端向业务服务端申请支付订单;
- 服务端调用对应渠道创建订单;
- 客户端通过对应 SDK 或收银台完成支付;
- 服务端接收并验证支付渠道的异步通知;
- 服务端主动查询订单,在必要时核对状态;
- 客户端向业务服务端查询最终订单状态。
客户端回调、H5 回跳参数和 SDK 返回字符串只能用来改善交互,不能作为发货或开通服务的唯一依据。
最终方案不应让大陆支付宝 SDK 去“兼容”或“冒充”AlipayHK SDK,而应将两个渠道作为独立的支付能力处理。优先获取官方可共存版本;如果没有,就采用官方支持的替代收银台方案,并在业务层和服务端分别隔离订单、签名、回调和状态处理。
备注:内容仅供参考。
