PAYATHON 2026

Android 智能手表如何通过纯 App 绑定支付宝并展示付款码?

支付老李

结论

普通 Android App 不能仅凭支付宝账号、用户 ID 或 OAuth 授权,自行生成并展示可用的支付宝付款码。

如果要在智能手表上完成绑定支付宝账号、展示付款码并由商户扫码扣款,必须接入支付宝面向智能穿戴设备提供的官方产品、SDK 或合作方案,同时通过设备、安全、支付场景和商业资质审核。是否可以接入、采用什么接口、能否离线付款,都要以支付宝开放平台的说明或商务对接结果为准。

暂时无法获得穿戴设备付款能力时,可以考虑以下替代方案:

  • 从手表唤起已安装的支付宝客户端,由支付宝完成身份验证并展示付款码。
  • 如果用途是商户收款,可以在手表上展示商户收款码。商户收款码和用户付款码是两种不同的能力,不能互相替代。

为什么常规 App 做不到

支付宝付款码并不是把账号信息转换成二维码或条形码。它通常具有动态变化、短时有效、防重放、设备绑定和风险控制等安全属性。

下面这些做法都无法获得付款码能力:

  • 通过支付宝 OAuth 获取用户身份后,在本地生成条码。
  • 把用户 ID、手机号、登录令牌或付款 URL 编码成二维码。
  • 抓取支付宝 App 的页面、网络请求或缓存数据。
  • 在 WebView 中模拟支付宝登录并提取付款码。
  • 保存手机上的付款码截图,再同步到手表。

支付宝 OAuth 用于用户身份授权。即使应用取得了 user_id 或其他授权信息,也不等于获得了代用户付款或生成付款码的权限。

可行方案一:接入支付宝穿戴设备付款能力

独立手表付款需要通过支付宝的正规穿戴设备付款方案实现。典型架构如下:

手表 App
   │
   │ 设备注册、绑定状态查询、付款凭证请求
   ▼
业务服务端
   │
   │ 使用支付宝分配的应用身份、证书或密钥调用签约接口
   ▼
支付宝穿戴设备支付服务

具体产品名称、申请入口和 API 可能因合作模式而异,不能认为普通开放平台应用默认拥有这些接口。实施前需要向支付宝确认:

  • 是否接受目标设备类型和业务场景。
  • 是否要求设备厂商、品牌方或支付机构资质。
  • 手表是否必须具备安全芯片、TEE、可信设备标识等能力。
  • 支持在线付款码还是离线付款码。
  • 付款限额、免密规则、风控和用户挂失流程。
  • SDK 支持哪些 Android 版本、CPU 架构和设备形态。
  • App、服务端和设备固件各自承担哪些安全职责。

建议的绑定流程

正式接口以支付宝提供的文档为准。业务流程通常可以设计为:

  1. 手表 App 向业务服务端申请一次性绑定会话。
  2. 服务端通过获准使用的官方接口创建绑定请求。
  3. 手表展示绑定二维码,或者提示用户前往手机支付宝确认。
  4. 用户在支付宝可信页面完成登录、协议确认和必要的身份验证。
  5. 支付宝通过服务端通知或状态查询返回绑定结果。
  6. 服务端只保存业务所需的绑定标识和状态,不保存支付宝密码。
  7. 手表取得经过签名的设备绑定结果,并将其安全写入本地。
  8. 用户打开付款页面时,由官方 SDK 或获准接口生成或下发付款凭证。

不要让用户在手表 App 自建的页面中直接输入支付宝账号和密码。

可行方案二:通过手机支付宝完成授权或绑定确认

如果手表需要与手机配套使用,可以由手表发起绑定,再到手机支付宝中完成实际授权。

一种常见的交互流程是:

手表发起绑定
    ↓
手机配套 App 接收请求
    ↓
唤起支付宝完成授权或确认
    ↓
业务服务端接收结果
    ↓
手机将绑定状态同步给手表

这样可以避免用户在手表的小屏幕上登录,但手机端的 OAuth 授权仍然无法生成付款码。最终要取得付款码能力,依然需要支付宝官方穿戴设备支付产品的授权。

手机端需要唤起支付宝时,应使用支付宝文档明确允许的 SDK、App Link 或 URL Scheme。不要使用通过逆向获取的内部页面地址。这类地址可能随客户端升级失效,也可能带来安全风险。

可行方案三:手表只唤起支付宝客户端

如果手表运行完整的 Android 系统,而且能够安装支付宝客户端,第三方 App 可以尝试跳转到支付宝公开支持的页面。

这种方案有明确的边界:

  • 付款码由支付宝客户端展示。
  • 用户身份验证由支付宝客户端完成。
  • 第三方 App 不读取或截取付款码内容。
  • 能否直接进入付款码页面,取决于支付宝是否公开并长期支持相应入口。

如果没有公开接口提供保证,只能启动支付宝应用:

fun launchAlipay(context: Context): Boolean {
    val intent = context.packageManager
        .getLaunchIntentForPackage("com.eg.android.AlipayGphone")
        ?: return false

    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    context.startActivity(intent)
    return true
}

这段代码只能启动支付宝,不能保证直接打开付款码页面,也不表示支付宝会为该手表系统提供兼容性支持。应用还需要处理支付宝未安装、系统禁止启动和版本不兼容等情况。

可行方案四:展示商户收款码

如果实际需求是让别人扫码向手表持有者付款,可以展示商户收款码或经过批准的收款链接。

val qrContent = response.approvedCollectionUrl
qrView.setContent(qrContent)

其中,approvedCollectionUrl 必须来自合法的支付宝商户产品或服务端接口,不能由应用自行拼接。商户收款码属于收款能力,用户付款码则是付款凭证。两者的签约条件、资金流向和风险控制完全不同。

参考实现:绑定状态管理

下面的代码只用于演示客户端状态机和网络调用结构。接口路径和字段均为业务侧占位定义,不是支付宝官方 API。

sealed interface BindState {
    data object Unbound : BindState
    data object Binding : BindState
    data class Bound(val deviceBindingId: String) : BindState
    data class Failed(val message: String) : BindState
}

data class CreateBindSessionResponse(
    val sessionId: String,
    val confirmationUrl: String,
    val expiresAt: Long
)

data class BindStatusResponse(
    val status: String,
    val deviceBindingId: String?
)

interface WatchPaymentApi {
    @POST("/watch/bind-sessions")
    suspend fun createBindSession(): CreateBindSessionResponse

    @GET("/watch/bind-sessions/{sessionId}")
    suspend fun queryBindStatus(
        @Path("sessionId") sessionId: String
    ): BindStatusResponse
}

轮询绑定状态时需要设置超时时间,并在用户退出页面后停止任务:

suspend fun waitForBindResult(
    api: WatchPaymentApi,
    sessionId: String
): BindState {
    repeat(30) {
        when (val result = api.queryBindStatus(sessionId)) {
            is BindStatusResponse -> {
                when (result.status) {
                    "BOUND" -> {
                        val bindingId = result.deviceBindingId
                            ?: return BindState.Failed("绑定结果缺少设备标识")
                        return BindState.Bound(bindingId)
                    }
                    "REJECTED", "EXPIRED" -> {
                        return BindState.Failed("绑定未完成或已过期")
                    }
                }
            }
        }
        delay(2_000)
    }

    return BindState.Failed("绑定确认超时")
}

生产环境更适合采用推送、长连接或带退避策略的状态查询,减少高频轮询。

参考实现:安全展示官方付款凭证

只有在应用已经获得官方能力,并且接口明确允许 App 获取和展示付款凭证时,才能实现下面的界面逻辑。字段名称仍然只是示例,不代表真实的支付宝协议。

data class PaymentCredential(
    val payload: String,
    val format: String,
    val expiresAt: Long
)

fun showCredential(
    activity: Activity,
    credential: PaymentCredential
) {
    require(System.currentTimeMillis() < credential.expiresAt) {
        "付款凭证已过期"
    }

    activity.window.addFlags(
        WindowManager.LayoutParams.FLAG_SECURE
    )

    when (credential.format) {
        "QR_CODE" -> renderQrCode(credential.payload)
        "CODE_128" -> renderBarcode(credential.payload)
        else -> error("不支持的付款凭证格式")
    }
}

renderQrCode 或 renderBarcode 只能把官方下发的短期凭证渲染成图形,不能推导或生成支付凭证。条码类型、纠错级别、刷新频率、背景色和留白等参数也要遵守官方规范。

服务端必须承担的职责

手表不应直接携带应用私钥调用敏感接口。服务端至少需要负责:

  • 保管应用私钥、证书和平台密钥。
  • 验证支付宝回调签名。
  • 核对通知中的应用、商户、设备和绑定标识。
  • 保证回调处理的幂等性,避免重复处理。
  • 管理绑定、解绑、挂失和设备迁移状态。
  • 对付款凭证请求执行鉴权、限流和风险检查。
  • 记录必要的安全审计日志。
  • 避免把完整支付凭证写入普通日志或埋点系统。

手表与服务端之间还要使用 HTTPS 和短期访问令牌。对于高风险场景,可以增加设备证明、请求签名、时间戳和随机数,降低令牌被复制或请求被重放的风险。

手表端的安全要求

付款码在有效期内通常相当于支付凭证,不能按普通二维码处理。开发时需要注意:

  • 使用 Android Keystore 保存设备密钥和绑定令牌。
  • 不要在 SharedPreferences、SQLite 明文字段或日志中保存付款凭证。
  • 禁止在付款页面截图和录屏。
  • 用户退出页面、设备息屏或页面超时后,立即清除付款码。
  • 每次展示前检查凭证有效期和设备绑定状态。
  • 不要长期缓存付款码图片。
  • 检测设备时间异常、Root、调试和完整性风险,具体处置方式遵循官方要求。
  • 为设备丢失提供远程解绑、冻结或挂失入口。
  • 明确处理无网络、手机不在线和绑定失效等状态。
  • 不在界面中展示完整的支付宝账号、手机号等非必要信息。

不能通过延长在线付款码的有效期来模拟离线付款。离线付款通常需要预置受控凭证、安全存储、次数或金额限制,以及联网后的状态同步。只有官方方案明确支持时才能实现。

上线前的确认清单

投入开发前,建议先确认以下事项:

  1. 支付宝是否向当前公司和设备开放穿戴设备付款能力。
  2. 目标设备是否符合硬件安全和系统完整性要求。
  3. 绑定、解绑、付款码刷新和挂失分别由哪些接口完成。
  4. 是否必须配套手机 App,以及手表能否独立使用。
  5. 是否支持离线支付,离线额度和有效期有哪些规则。
  6. 付款码是否允许在第三方界面中展示,还是必须由支付宝组件展示。
  7. 如何申请测试环境、生产密钥,以及验收和上线审核如何进行。
  8. 用户协议、隐私政策、支付风险提示和个人信息处理是否合规。

可落地的方案不是寻找一个把支付宝账号转换成付款码的通用 SDK,而是先取得支付宝穿戴设备支付能力,再按照官方协议完成设备绑定、凭证管理和安全展示。如果拿不到这项能力,第三方纯 App 最多只能安全地唤起支付宝,不能自行提供等价的付款码功能。

备注:内容仅供参考。