PAYATHON 2026

多租户自助售卖机如何支持刷脸支付、付款码支付与分账

支付老李

明确结论

可以实现,但不能把”直付通”简单替换为另一个支付接口。更合适的是采用支付机构或银行收单机构提供的”服务商/平台型收单”模式:

  • 平台以服务商或平台方身份接入支付渠道;
  • 每个租户独立完成商户进件,取得自己的子商户号;
  • 售卖机发起交易时,系统根据设备所属租户指定对应的子商户;
  • 刷脸支付和付款码支付均由该子商户收款;
  • 支付成功后,通过渠道分账、交易结算分润或手续费返佣,将平台佣金划给平台;
  • 其余资金直接结算到租户绑定的银行账户。

各租户可以共用一个小程序和同一套售卖机程序。资金最终归属于谁,不由小程序 appid 决定,而取决于每笔支付请求中的商户、子商户、门店、设备及订单归属信息。

不同支付机构的产品名称、接口和准入条件并不相同。刷脸支付、付款码支付和分账能力能否同时开通,通常还取决于行业、设备认证、租户资质和服务商权限。因此,技术开发开始前,应先取得渠道方的书面确认。

推荐的整体架构

系统可以分为三个层次:

统一小程序 / 售卖机客户端
          │
          ▼
平台交易系统
  ├─ 识别设备所属租户
  ├─ 创建平台订单
  ├─ 选择支付通道
  ├─ 计算佣金与租户应得金额
  └─ 保存支付、分账和退款状态
          │
          ▼
服务商型支付通道
  ├─ 平台服务商号
  ├─ 租户 A 子商户号 → 租户 A 结算账户
  ├─ 租户 B 子商户号 → 租户 B 结算账户
  └─ 平台分账接收方 → 平台佣金账户

例如,一笔订单的支付金额为 100 元,平台佣金为 5 元:

消费者支付:100 元
平台佣金:    5 元
租户所得:   95 元

资金可以采用以下两种处理方式:

  1. 消费者向租户子商户支付 100 元,支付成功后再将其中的 5 元分给平台;
  2. 渠道在清算时直接计算平台服务费,将 95 元结算给租户,5 元结算给平台。

并非所有渠道都提供第二种方式。如果渠道只支持交易后分账,就采用第一种方式。

为什么不能只用一个普通商户号收款

如果所有订单都先进入平台自己的普通商户号,再由平台定期向租户转账,技术实现看起来更简单,但会产生一系列问题:

  • 平台可能被认定为代收代付;
  • 租户资金会先进入平台账户,无法称为”直接结算”;
  • 退款、投诉、税务和交易对账都会落到平台主体上;
  • 银行流水中的交易商户与实际经营者不一致;
  • 可能触发支付机构的风控、限额,甚至导致账户被冻结;
  • 平台自行保管和划拨商户资金,可能涉及支付合规问题。

因此,每个租户都应作为支付渠道认可的商户或子商户完成实名进件。平台负责技术服务、订单路由和合规分润,不自行沉淀租户资金。

各租户共用一个小程序如何区分收款主体

小程序可以保持统一,但平台必须建立可靠的租户路由关系:

device_id → tenant_id → sub_merchant_id → settlement_account

售卖机只提交设备编号、商品和支付凭证,不能自行指定子商户号。后端根据已经绑定的设备信息确定收款商户,防止客户端篡改资金流向。

示例数据结构:

CREATE TABLE tenant_payment_account (
    tenant_id           BIGINT PRIMARY KEY,
    channel             VARCHAR(32) NOT NULL,
    sub_merchant_id     VARCHAR(64) NOT NULL,
    account_status      VARCHAR(32) NOT NULL,
    profit_share_status VARCHAR(32) NOT NULL
);

CREATE TABLE vending_device (
    device_id VARCHAR(64) PRIMARY KEY,
    tenant_id BIGINT NOT NULL,
    store_id  VARCHAR(64) NOT NULL
);

创建订单时,由服务端完成路由:

public PaymentOrder createOrder(CreateOrderRequest request) {
    Device device = deviceRepository.findEnabled(request.deviceId());
    TenantPaymentAccount account =
        paymentAccountRepository.findAvailable(device.tenantId());

    Money totalAmount = pricingService.calculate(
        device.tenantId(),
        request.items()
    );

    Money commission = commissionService.calculate(
        device.tenantId(),
        totalAmount
    );

    return orderRepository.create(
        device.tenantId(),
        device.deviceId(),
        account.subMerchantId(),
        totalAmount,
        commission
    );
}

sub_merchant_id 必须由后端查询,不能信任客户端上传的值。

付款码支付的实现方式

付款码支付通常采用售卖机扫描消费者付款码的模式:

  1. 消费者在支付 App 中展示付款码;
  2. 售卖机扫码器读取付款码;
  3. 售卖机将付款码和平台订单号提交到平台后端;
  4. 后端根据设备查找租户子商户号;
  5. 后端以该子商户身份调用付款码支付接口;
  6. 支付成功后出货;
  7. 平台异步执行或确认分账。

以下示例只用于说明业务结构,具体字段和接口名称应以所接支付渠道的当前文档为准:

{
  "order_no": "VM202609100001",
  "device_id": "DEVICE-10086",
  "auth_code": "消费者付款码",
  "amount": 1000,
  "currency": "CNY"
}

服务端组装渠道请求:

CodePayRequest payRequest = new CodePayRequest();
payRequest.setOutTradeNo(order.getOrderNo());
payRequest.setSubMerchantId(order.getSubMerchantId());
payRequest.setAmount(order.getTotalAmount().toCent());
payRequest.setAuthCode(request.authCode());
payRequest.setDeviceId(order.getDeviceId());
payRequest.setDescription(order.getDescription());

CodePayResult result = paymentChannel.codePay(payRequest);

付款码是高敏感、短时有效的支付凭证,处理时应满足以下要求:

  • 全程使用 TLS;
  • 不写入业务日志;
  • 不在客户端持久化;
  • 不保存完整付款码;
  • 严格限制接口调用来源;
  • 对设备进行身份认证并校验请求签名;
  • 防止同一付款码被重复提交。

付款码交易还可能返回”处理中”状态。遇到这种情况,不能立即判定交易失败,也不能重复创建新订单,而应查询原订单的最终状态。

刷脸支付的实现方式

刷脸支付通常不能通过一个普通 HTTP 接口直接完成。人脸采集和身份核验需要使用支付渠道认可的设备、刷脸组件或 SDK。

典型流程如下:

  1. 售卖机使用渠道认证的刷脸设备和 SDK;
  2. SDK 完成人脸识别,返回一次性支付凭证;
  3. 售卖机将凭证提交到平台后端;
  4. 后端根据 device_id 获取对应租户的子商户号;
  5. 后端以该子商户身份发起刷脸支付;
  6. 确认支付结果后出货;
  7. 平台按照订单执行分账。

业务请求可以抽象为:

{
  "order_no": "VM202609100002",
  "device_id": "DEVICE-10086",
  "face_token": "一次性刷脸支付凭证",
  "amount": 1000
}

后端处理示例:

public PayResult facePay(FacePayCommand command) {
    PaymentOrder order = orderRepository.find(command.orderNo());

    deviceSecurityService.verifyBinding(
        command.deviceId(),
        order.getTenantId()
    );

    FacePayRequest request = new FacePayRequest();
    request.setOutTradeNo(order.getOrderNo());
    request.setSubMerchantId(order.getSubMerchantId());
    request.setFaceToken(command.faceToken());
    request.setAmount(order.getTotalAmount().toCent());
    request.setDeviceId(command.deviceId());

    return paymentChannel.facePay(request);
}

face_token 只是一个抽象名称。不同渠道使用的凭证名称、SDK 流程和加密方式可能完全不同,在确认渠道文档前不应写死。

平台也不能将人脸照片或视频上传到自己的后端进行识别,再把识别结果作为支付授权。支付身份核验应由合规支付渠道及其认证组件完成,平台不应保存消费者的人脸原始图像或生物特征模板。

分账与佣金抽取

支付成功后,需要为订单生成不可变的分账快照:

{
  "order_no": "VM202609100001",
  "paid_amount": 10000,
  "platform_commission": 500,
  "tenant_amount": 9500,
  "currency": "CNY"
}

金额应统一使用最小货币单位,例如人民币的”分”,不要使用浮点数:

long paidAmount = 10000L;
long platformCommission = 500L;
long tenantAmount = Math.subtractExact(
    paidAmount,
    platformCommission
);

如果渠道采用”租户先收款、平台后分账”的模式,处理逻辑可以写成:

public void handlePaymentSucceeded(PaymentOrder order) {
    if (!order.tryMarkPaid()) {
        return;
    }

    ProfitShareRequest request = new ProfitShareRequest();
    request.setTransactionId(order.getChannelTransactionId());
    request.setOutOrderNo("PS-" + order.getOrderNo());
    request.setSubMerchantId(order.getSubMerchantId());

    request.addReceiver(
        platformPaymentAccount.getReceiverId(),
        order.getCommissionAmount().toCent(),
        "平台技术服务费"
    );

    profitShareService.submit(request);
}

分账请求必须支持幂等。可以使用平台分账单号配合数据库唯一索引,确保同一订单不会被重复分账:

CREATE UNIQUE INDEX uk_profit_share_order
ON profit_share_order(channel, out_order_no);

平台还应分别保存以下信息:

  • 平台订单号;
  • 渠道交易号;
  • 租户子商户号;
  • 实付金额;
  • 平台佣金;
  • 分账单号;
  • 支付状态;
  • 分账状态;
  • 退款状态;
  • 渠道通知原文摘要及验签结果。

可选择的接入路径

支付机构官方服务商模式

分别申请微信支付、支付宝等渠道的服务商能力,并为每个租户创建或绑定子商户。

这种方式的交易链路直接,各项能力也比较明确。不过,平台需要分别处理进件、支付、分账、退款和对账,刷脸设备还可能需要针对不同渠道进行适配。

银行或持牌收单机构的平台收单方案

由银行或持牌收单机构提供聚合收单、子商户进件、设备管理及分润服务。

这类方案更有可能同时覆盖刷脸支付、付款码支付和多渠道收单,但接入前必须确认:

  • 是否真正按照子商户结算;
  • 是否支持平台分润;
  • 是否支持交易级分账;
  • 刷脸设备是否已经获得支付渠道认证;
  • 是否允许一个统一应用服务多个子商户;
  • 退款后能否自动退回已经分账的佣金;
  • 平台是否需要承担垫资或资金清算责任。

聚合支付服务商

平台也可以接入具备相应资质和渠道授权的聚合支付服务商,减少多渠道开发工作。

不能只依据”支持刷脸”和”支持分账”等宣传信息作出判断。需要确认这两项能力能否在同一产品、同一交易链路及同一子商户模型中组合使用。

小程序支付与售卖机支付应分开建模

统一小程序可以用于选品、登录、领取优惠券和查看订单,但支付场景需要按照实际渠道区分:

小程序内支付    → 小程序支付接口
售卖机扫付款码  → 付款码支付接口
售卖机刷脸      → 刷脸设备 SDK + 刷脸支付接口

这三种支付方式可以共用一套平台订单体系,但不能假设它们使用相同的支付 API。

可以定义统一的内部支付接口:

public interface PaymentChannel {
    PayResult miniProgramPay(MiniProgramPayRequest request);

    PayResult codePay(CodePayRequest request);

    PayResult facePay(FacePayRequest request);

    ProfitShareResult profitShare(ProfitShareRequest request);

    RefundResult refund(RefundRequest request);

    PaymentQueryResult query(String outTradeNo);
}

租户路由、佣金计算和订单状态管理由平台层统一处理,各渠道的字段差异则封装在对应的适配器中。

退款必须和分账一起设计

支付和分账完成后,如果订单发生退款,除了退还消费者款项,还要处理平台已经取得的佣金。

以支付金额 100 元、平台佣金 5 元的订单为例:

  • 全额退款时,平台通常需要退回 5 元佣金;
  • 部分退款时,应按照合同规则计算平台需要退回的佣金;
  • 如果平台分账余额不足,退款可能失败,也可能需要平台补足资金;
  • 渠道是否自动回退分账,需要根据实际产品确认。

佣金规则应在创建订单时固化,避免退款时使用已经变更的新费率:

long calculateCommissionRefund(
    long originalCommission,
    long originalPaidAmount,
    long refundAmount
) {
    if (refundAmount == originalPaidAmount) {
        return originalCommission;
    }

    return BigDecimal.valueOf(originalCommission)
        .multiply(BigDecimal.valueOf(refundAmount))
        .divide(
            BigDecimal.valueOf(originalPaidAmount),
            0,
            RoundingMode.DOWN
        )
        .longValueExact();
}

具体的取整方式应写入租户协议,并与支付渠道允许的分账及退款规则保持一致。

上线前需要向渠道确认的问题

确定替代方案前,应把完整业务链路交给支付渠道或收单机构确认,而不是分开询问单个功能:

  1. 平台是否可以作为服务商接入多个租户?
  2. 每个租户是否拥有独立的子商户号和结算账户?
  3. 同一个小程序或应用是否可以服务这些子商户?
  4. 子商户是否同时支持付款码支付和刷脸支付?
  5. 刷脸支付支持哪些设备型号和 SDK?
  6. 平台佣金采用实时分账、延迟分账还是清算分润?
  7. 平台可分账比例是否有限制?
  8. 租户和平台分别需要签署哪些协议?
  9. 退款、撤销和投诉发生时,分账如何回退?
  10. 交易账单、分账账单和结算账单是否可以分别下载?
  11. 租户主体、门店、设备和子商户号是否必须一一绑定?
  12. 平台是否会接触或沉淀租户资金?

只有确认这些问题,才能判断某个方案能否真正替代直付通。

实施建议

较为稳妥的落地方式是:

  1. 选择支持服务商型收单的支付渠道或持牌收单机构;
  2. 让所有租户独立完成子商户进件;
  3. 将每台售卖机绑定到唯一的租户和门店;
  4. 由后端根据设备确定子商户,禁止客户端指定收款方;
  5. 分别接入付款码支付和经过认证的刷脸支付能力;
  6. 支付成功后,通过渠道执行分账或清算分润;
  7. 建立支付、分账、退款和结算四套对账机制;
  8. 正式开发前,使用真实子商户完成小额支付、分账、部分退款和全额退款的闭环验证。

替代直付通的重点并不是寻找一个完全相同的接口,而是找到同时支持”子商户收单、刷脸/付款码支付、平台分润、租户独立结算”的服务商型支付产品。只要每笔支付请求都能路由到正确的子商户,共用一个小程序就不会影响多租户收款和平台佣金分账。

备注:内容仅供参考。