PAYATHON 2026

第三方 App 唤起支付宝支付时如何校验双方账号为同一人?

支付阿杰

结论

仅仅“唤起支付宝支付”,无法在付款前确认第三方 App 当前登录用户与支付宝付款账号是否属于同一人。

支付宝收银台允许用户选择或切换支付宝账号。第三方 App 无法读取支付宝账号绑定的手机号,也不能直接获取姓名、身份证号进行比对。客户端返回的支付结果也不能用于身份校验。

“身份绑定”和“支付”应分开处理:

  1. 第三方 App 先完成自身的实名认证。
  2. 通过已经签约并获得用户授权的支付宝身份认证或账号授权能力,确认当前支付宝用户与该实名身份一致。
  3. 保存支付宝返回的稳定用户标识,如 user_id、open_id。具体字段取决于已接入的产品和接口版本。
  4. 支付完成后,由服务端验签并获取实际付款人的支付宝用户标识。
  5. 将实际付款人标识与此前绑定的标识进行比较。如果不一致,不得将这笔支付记入当前用户。

如果业务只需要确认“钱已经付了”,通常不必校验付款人是否为同一人。如果涉及实名账户充值、会员权益、金融服务或监管要求,则应采用上述绑定方案。

为什么不能直接比较手机号

第三方 App 登记的手机号与支付宝登录账号之间没有可靠的一一对应关系,原因包括:

  • 支付宝登录账号不一定是手机号。
  • 用户可能更换绑定手机号。
  • 同一手机号在不同系统中的实名认证状态不能相互证明。
  • 用户可以在支付过程中切换支付宝账号。
  • 受隐私和安全限制,支付接口通常不会向商户返回完整手机号、姓名或身份证号。

因此,不能使用 buyer_logon_id、脱敏手机号或客户端展示的支付宝账号判断是否为同一人。即使两个手机号看起来相同,也不能代替实名认证结果。

推荐实现方案

1. 保存第三方 App 的实名身份

实名认证信息应以服务端记录为准,至少保存内部用户 ID、实名状态和身份信息的安全索引。

身份证号、姓名等敏感数据需要加密保存,并严格限制访问权限。如果需要查重,可以另行保存经过加盐或带密钥散列后的值。日志中不得输出敏感信息明文。

2. 单独完成支付宝身份绑定

如果需要验证“操作支付宝的用户就是第三方 App 已实名的用户”,应接入支付宝开放平台提供的身份认证能力。具体产品名称、准入条件和可用接口会因行业、商户资质及签约情况而有所不同,应以商户后台实际能够申请的产品和当前官方文档为准。

常见流程如下:

  1. 服务端读取当前 App 用户已经确认的姓名、证件类型和证件号码。
  2. 服务端创建支付宝身份认证任务。
  3. App 跳转到支付宝完成认证。
  4. 服务端查询认证结果,不能只信任客户端的回跳参数。
  5. 认证通过后,保存与本次认证关联的支付宝用户标识。
  6. 建立第三方用户 ID 与支付宝用户标识之间的绑定关系。

认证请求必须由服务端生成。客户端只负责跳转,不能自行提交“认证成功”状态。

如果业务只接入支付宝账号授权,通常只能确定当前操作的是哪个支付宝用户,不能据此证明该用户与第三方 App 中登记的姓名、身份证号一致。账号授权和实名同人认证是两个不同的概念。

3. 创建订单时记录预期付款人

第三方 App 创建支付订单时,应在服务端保存订单所属用户及其绑定的支付宝用户标识,例如:

out_trade_no -> app_user_id -> bound_alipay_user_id

不要直接信任客户端传入的 app_user_id 或支付宝用户标识。服务端应根据登录会话确定当前用户。

4. 在支付通知中确认实际付款人

支付完成后,应以支付宝服务端异步通知或主动查询结果为准:

  1. 验证支付宝签名。
  2. 校验 app_id、商户身份、订单号、金额、币种和交易状态。
  3. 根据 out_trade_no 查询本地订单。
  4. 读取支付结果中的付款人用户标识。
  5. 将其与订单中保存的绑定标识进行比较。
  6. 确认一致后,再更新业务权益。

不同接口版本可能使用 buyer_id、buyer_user_id、buyer_open_id 或其他字段。应以当前支付产品文档实际返回的字段为准,并确保绑定阶段和支付阶段使用的是同一标识体系。不同应用、不同商户主体或不同标识类型下的值不能直接比较。

服务端处理示例

下面的 Java 示例经过简化,主要用于说明校验顺序。字段名需要根据实际使用的支付宝 SDK 和接口响应进行调整:

public void handleAlipayNotify(Map<String, String> params) {
    boolean signatureValid = AlipaySignature.rsaCheckV1(
        params,
        alipayPublicKey,
        "UTF-8",
        "RSA2"
    );

    if (!signatureValid) {
        throw new SecurityException("Invalid Alipay signature");
    }

    String appId = params.get("app_id");
    String outTradeNo = params.get("out_trade_no");
    String tradeStatus = params.get("trade_status");
    String totalAmount = params.get("total_amount");

    if (!configuredAppId.equals(appId)) {
        throw new SecurityException("Unexpected app_id");
    }

    Order order = orderRepository.findByOutTradeNo(outTradeNo);
    if (order == null) {
        throw new IllegalStateException("Order not found");
    }

    if (!order.getTotalAmount().toPlainString().equals(totalAmount)) {
        throw new SecurityException("Amount mismatch");
    }

    if (!"TRADE_SUCCESS".equals(tradeStatus)
            && !"TRADE_FINISHED".equals(tradeStatus)) {
        return;
    }

    // 示例字段。应按实际接口文档选择 buyer_id、buyer_open_id 等。
    String actualAlipayUserId = params.get("buyer_id");
    String expectedAlipayUserId = order.getBoundAlipayUserId();

    if (actualAlipayUserId == null || expectedAlipayUserId == null) {
        orderRepository.markForManualReview(outTradeNo);
        return;
    }

    if (!expectedAlipayUserId.equals(actualAlipayUserId)) {
        orderRepository.markPayerMismatch(outTradeNo, actualAlipayUserId);
        return;
    }

    // 必须保证幂等,避免重复通知导致重复入账。
    orderRepository.markPaidIfUnpaid(
        outTradeNo,
        params.get("trade_no"),
        actualAlipayUserId
    );
}

实际项目中,建议使用 BigDecimal 按业务币种规则比较金额,不要使用 double:

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

if (order.getTotalAmount().compareTo(notifiedAmount) != 0) {
    throw new SecurityException("Amount mismatch");
}

只能在支付后校验怎么办

如果现有支付接口可以返回可靠的付款人用户标识,但业务还没有接入身份认证,可以在支付完成后检查付款账号是否与已经绑定的支付宝账号一致。

这种方式只能证明“本次付款账号与历史绑定账号相同”,不能证明支付宝账号的实名身份与第三方 App 中的实名身份一致。首次绑定仍需可靠的身份认证依据。

如果发现账号不一致,不建议简单地把订单标记为未支付,因为支付宝侧可能已经完成扣款。可以将订单设为“付款人不一致”状态,暂停发放实名权益,再根据业务规则退款或转入人工处理。

注意事项

  • 不要使用支付宝客户端回传的 resultStatus 判断最终支付结果。
  • 不要通过抓取支付宝页面、读取本机账号信息或比较脱敏手机号来校验身份。
  • 不要将姓名和身份证号直接拼接后进行普通 MD5 计算。敏感信息应加密保存,或使用带密钥的安全摘要。
  • 支付回调必须验签,同时校验订单号、金额、商户身份和应用身份。
  • 回调处理必须保证幂等。
  • 支付阶段获取的用户标识不等于实名认证信息。
  • 不同应用或商户主体下的 open_id 可能无法直接比较,必须确认标识的作用域。
  • 用户解除授权、更换支付宝账号或实名信息发生变化后,应重新认证并绑定。
  • 支付宝认证通过后,也不能自动允许用户修改第三方 App 中已经登记的实名身份。实名变更应经过独立的高风险审核流程。
  • 身份认证涉及个人敏感信息处理,需要满足最小必要、明确授权、加密存储、访问审计和数据保留期限等要求。

备注:内容仅供参考。