PAYATHON 2026

如何接入信用租线上免密支付?

支付老李

结论

接入信用租线上免密支付,不能只调用普通支付接口。商户要先申请并开通支付宝的信用租和免密代扣产品。用户明确授权后,商户需要保存支付宝返回的协议号。消费结束后,商户服务端按实际应付金额发起代扣。款项结清后,再调用产品提供的协议解约能力,或通过信用租订单完结流程触发解约。

完整流程通常包括:

  1. 创建信用租订单。
  2. 引导用户确认信用服务并授权免密代扣。
  3. 保存业务订单号、支付宝订单号和协议号。
  4. 消费结束后计算最终金额。
  5. 由服务端发起免密扣款。
  6. 接收并验签异步通知。
  7. 确认款项结清后完结订单。
  8. 解约代扣协议,并处理失败补偿。

具体可用的接口名称、请求参数和自动解约规则,取决于商户实际签约的支付宝产品方案。开发时应以开放平台向该应用展示的产品文档为准。

为什么需要先签约

免密支付本质上是基于用户授权协议进行代扣。商户不能只凭支付宝用户标识扣款,也不能在用户尚未确认授权范围、扣款规则和协议内容时发起扣款。

授权完成后,支付宝通常会返回用于标识协议的信息。后续扣款时,商户需要关联:

  • 商户业务订单号;
  • 用户的信用租订单;
  • 有效的免密代扣协议;
  • 本次实际消费金额;
  • 商品或服务说明;
  • 请求时间和幂等标识。

签约、扣款、订单完结和解约需要关联到同一条业务链路,不能只在前端记录授权成功状态。

接入步骤

1. 开通对应产品能力

先在支付宝开放平台创建应用,申请信用租和免密代扣相关产品。完成应用签约、密钥、网关和回调地址配置后,再开始开发。

需要确认:

  • 应用是否有创建信用租订单的权限;
  • 免密代扣是否需要单独签约;
  • 产品支持的行业、金额和扣款次数限制;
  • 用户授权协议的有效期;
  • 订单完结后会自动解约,还是需要商户主动解约;
  • 沙箱环境是否支持完整的信用租和代扣流程。

如果开放平台控制台没有授予对应的接口权限,即使请求参数和签名都正确,调用仍可能被拒绝。

2. 创建商户侧订单

先在商户系统中创建业务订单,并生成全局唯一的订单号。订单初始状态可以设为 CREATED,用户完成授权前不要将订单标记为可扣款。

建议至少保存以下字段:

merchant_order_no
alipay_order_no
agreement_no
user_id
order_status
agreement_status
consumed_amount
paid_amount
created_at
finished_at

金额应使用整数分或精确十进制类型保存,不要使用浮点数。

3. 引导用户授权

商户服务端按照已开通产品的要求生成签约或信用租确认请求,前端只负责跳转或唤起支付宝。

用户操作完成后,不能只根据前端返回页面判断签约是否成功。服务端应通过支付宝同步响应、异步通知或协议查询能力确认最终状态,并保存支付宝返回的协议号。

状态可以按以下顺序变化:

CREATED
  -> SIGNING
  -> ACTIVE
  -> CONSUMING
  -> DEDUCTING
  -> PAID
  -> FINISHED

如果签约失败或用户取消,应将订单转入单独的失败状态,不能继续发起扣款。

4. 计算最终消费金额

消费结束后,商户服务端根据租期、商品价格、优惠、赔付和已支付金额计算最终应扣金额。

发起扣款前,需要再次检查:

协议有效
订单未完结
订单未取消
应扣金额大于 0
累计扣款未超过订单应付金额
相同扣款请求没有成功执行过

如果实际应付金额为零,应直接进入结算或完结流程,不要提交零金额代扣请求。

5. 服务端发起扣款

扣款必须由可信的服务端发起。不同信用租产品开放的扣款接口和字段可能不同,下面的业务层伪代码只用于说明处理结构,不代表支付宝官方接口定义:

public DeductResult deduct(String orderNo) {
    RentalOrder order = orderRepository.findForUpdate(orderNo);

    if (order == null) {
        throw new IllegalArgumentException("order not found");
    }
    if (!order.getAgreementStatus().equals("ACTIVE")) {
        throw new IllegalStateException("agreement is not active");
    }
    if (order.getStatus().equals("PAID")
            || order.getStatus().equals("FINISHED")) {
        return DeductResult.alreadyProcessed();
    }

    long amountInCent = billingService.calculateAmount(order);

    if (amountInCent <= 0) {
        order.markPaid();
        orderRepository.save(order);
        return DeductResult.noPaymentRequired();
    }

    String requestNo = "DEDUCT_" + order.getMerchantOrderNo();

    PaymentRequest request = new PaymentRequest();
    request.setRequestNo(requestNo);
    request.setMerchantOrderNo(order.getMerchantOrderNo());
    request.setAgreementNo(order.getAgreementNo());
    request.setAmountInCent(amountInCent);
    request.setSubject("信用租订单结算");

    order.markDeducting(requestNo, amountInCent);
    orderRepository.save(order);

    /*
     * 此处调用商户实际获权的支付宝免密扣款接口。
     * 接口名称和字段应以开放平台当前产品文档为准。
     */
    return paymentGateway.deduct(request);
}

requestNo 必须保持幂等。遇到网络超时,不要立刻生成新单号并再次扣款。应先查询原请求的处理结果,避免重复收费。

6. 处理异步通知

异步通知是确认最终支付状态的重要依据。处理通知时,必须先验签,再核对应用、商户、订单、金额和币种等信息。

public String handlePaymentNotify(
        Map<String, String> parameters,
        String rawBody) {

    if (!alipaySignatureVerifier.verify(parameters, rawBody)) {
        return "failure";
    }

    String orderNo = parameters.get("out_trade_no");
    String tradeStatus = parameters.get("trade_status");

    RentalOrder order = orderRepository.findForUpdate(orderNo);
    if (order == null) {
        return "failure";
    }

    amountValidator.verify(parameters, order);

    if (isSuccessfulStatus(tradeStatus)) {
        if (!order.isPaid()) {
            order.markPaid();
            orderRepository.save(order);
        }
        return "success";
    }

    return "success";
}

示例中的通知字段只用于说明处理方式。实际字段名称、成功状态值和应答内容,必须以所接产品的通知文档为准。

同一通知可能重复发送,因此回调处理需要保证幂等。完成业务处理和数据落库后再返回成功应答。如果先应答再落库,可能出现支付宝停止重试,但商户状态没有更新的情况。

7. 完结订单并解约

只有满足以下全部条件,才能完结订单:

  • 消费已经结束;
  • 应付款项已经结清,或已确认无需付款;
  • 没有正在处理的扣款;
  • 退款、赔付等业务已经确认;
  • 商户订单与支付宝侧状态一致。

如果产品支持“订单完结自动解约”,调用订单完结能力后仍应查询协议状态,确认协议是否已解约。不能只根据“自动解约”的产品描述,直接将本地协议标记为失效。

如果产品要求商户主动解约,应在订单完结成功后调用对应的协议解约接口。解约失败时,需要创建待重试任务:

public void finishOrder(String orderNo) {
    RentalOrder order = orderRepository.findForUpdate(orderNo);

    if (!order.canFinish()) {
        throw new IllegalStateException("order cannot be finished");
    }

    creditRentalGateway.finish(order.getAlipayOrderNo());
    order.markFinished();
    orderRepository.save(order);

    if (productConfiguration.requiresManualUnsign()) {
        try {
            agreementGateway.unsign(order.getAgreementNo());
            order.markAgreementTerminated();
            orderRepository.save(order);
        } catch (RemoteException ex) {
            retryTaskRepository.createUnsignTask(
                order.getMerchantOrderNo(),
                order.getAgreementNo()
            );
        }
    }
}

这段代码同样是业务伪代码,finish 和 unsign 并不表示具体的支付宝 API 名称。

关键注意事项

不要依赖前端结果

前端跳转成功只能说明用户完成了页面操作,不能证明签约或扣款最终成功。最终状态应由服务端根据通知或主动查询结果确认。

签约成功不等于可以无限扣款

扣款必须符合用户授权时展示的用途、金额、期限和次数限制。超出授权范围的扣款可能被拒绝,也可能带来资金和合规风险。

做好幂等控制

创建订单、处理签约回调、扣款和解约时,都可能遇到超时或重复通知。每个环节都应设置独立的业务请求号,并使用数据库唯一约束或状态锁防止重复执行。

不能把扣款失败当作订单完结

余额不足、协议失效、风控拦截和网络异常都可能造成扣款失败。系统应区分明确失败、处理中和结果未知。遇到结果未知的情况,应先查询状态,不能直接重新扣款。

解约前确认没有未结款项

协议解约后,通常无法继续基于原协议扣款。如果还有未结费用,应先完成结算或转入人工处理流程。

保护敏感信息

支付宝私钥只能保存在服务端,不能放入网页、小程序或 App。日志中也不能完整记录私钥、签名原文、用户身份信息和协议敏感字段。

建立对账与补偿机制

建议定时检查:

  • 长时间停留在 SIGNING 或 DEDUCTING 状态的订单;
  • 已支付但尚未完结的订单;
  • 已完结但协议仍然有效的订单;
  • 支付宝账单与商户账单金额不一致的订单。

自动解约失败后,应进行有限次数的重试并触发告警,不能无限循环调用接口。

备注:内容仅供参考。