豆包打车的支付链路在设计上采用渠道适配层与结算规则层分离的方式,因此新增支付方式不会影响计价逻辑。用户一键下单后,系统会根据行程类型、风控结果和用户签约状态选择即时支付或先乘后付,行程结束后由计价中心生成账单,再根据支付方式路由到对应渠道完成扣款。整个过程中涉及支付方式选择、免密代扣、金额计算、优惠分摊、退款与对账等环节。

一、豆包打车支持的支付方式与可用范围
豆包打车目前接入的支付方式包括微信支付、支付宝、抖音支付、云闪付以及企业月结。个人用户最常用的是免密代扣,签约后可以在行程结束自动扣款,避免下车时手动支付。未开通免密签约的用户,一键下单后仍然会在订单完成时收到支付提醒,需要在收银台主动完成付款。企业月结面向开通企业账户的组织,由平台按账期合并开票,单笔行程不再实时扣款。
不同支付方式对应不同的渠道能力。微信支付和支付宝支持小额免密、退款原路退回以及账单穿透;抖音支付在字节生态内体验更顺滑,但外部用户覆盖面相对有限;云闪付适合银行卡直接扣款场景;企业月结则完全绕过实时支付,依赖账期结算和授信额度。支付方式在用户端是互斥选择,后台通过支付方式编码与渠道路由表决定请求发往哪家网关。渠道不具备余额支付能力时,不能向用户下发余额支付选项,否则会触发支付失败并增加客诉。
public enum PayChannel {
WECHAT("wx", true, true),
ALIPAY("ali", true, true),
DOUYIN("dy", true, false),
UNIONPAY("union", false, true),
ENTERPRISE("ent", false, false);
private final String code;
private final boolean supportSign;
private final boolean supportRefundOriginal;
PayChannel(String code, boolean supportSign, boolean supportRefundOriginal) {
this.code = code;
this.supportSign = supportSign;
this.supportRefundOriginal = supportRefundOriginal;
}
public String getCode() {
return code;
}
public boolean isSupportSign() {
return supportSign;
}
public boolean isSupportRefundOriginal() {
return supportRefundOriginal;
}
}
渠道路由模块并不直接依赖具体支付渠道,而是根据渠道编码查找对应的适配器。适配器内部封装签名、下单、回调验签和查单能力。这样做的目的是当新增支付渠道或调整签约策略时,只需要增加适配器并修改路由配置,订单域和计价域无需改动。
二、一键下单的支付时序与状态流转
一键下单并不等于立即扣款。订单创建后先进入待服务状态,行程开始时记录计费快照,行程结束后由计价中心推送费用明细。支付状态从待支付流转到支付中,再到支付成功或支付失败。免密代扣场景下,系统会对已签约用户发起自动扣款请求,如果渠道返回余额不足,状态进入支付失败并触发重试策略。重试次数通常是三次,每次间隔递增,超过次数后转人工催缴。
为了防止重复扣款,每次支付请求都会携带业务侧生成的支付单号,该单号全局唯一。支付回调到达时,先按支付单号做幂等校验,已经处理过的回调直接返回成功,不重复变更订单状态。订单状态和支付状态必须分离:订单状态描述行程生命周期,支付状态描述资金生命周期。即使订单已经完成,支付状态仍可能处于支付中或支付失败,二者不能混为一谈。
public enum OrderStatus {
CREATED,
DISPATCHED,
IN_SERVICE,
FINISHED,
CLOSED
}
public enum PayStatus {
UNPAID,
PAYING,
SUCCESS,
FAILED,
REFUNDING
}
public void onPayCallback(PayCallback callback) {
if (payOrderService.exists(callback.getPayNo())) {
return;
}
PayOrder payOrder = payOrderService.getByPayNo(callback.getPayNo());
if (callback.isSuccess()) {
payOrder.setPayStatus(PayStatus.SUCCESS);
orderService.updateOrderPayStatus(payOrder.getOrderId(), PayStatus.SUCCESS);
} else {
payOrder.setPayStatus(PayStatus.FAILED);
}
payOrderService.update(payOrder);
}
支付中的订单如果长时间没有收到回调,不能简单判定为失败,而需要主动向渠道发起查单。后台任务扫描支付中状态超过指定时间的订单,逐笔调用渠道查询接口,根据查询结果更新支付状态。这样可以消除渠道回调丢失带来的资金不确定性。
三、费用结算的规则引擎与金额计算
费用结算由计价中心完成,输入包括行程轨迹、时长、里程、时段和城市规则。典型费用项有起步价、里程费、时长费、远途费、夜间服务费、动态加价。高速费和停车费属于代收项目,不参与平台抽佣。优惠券、平台立减和积分抵扣在总金额之后按顺序扣减,最后得到用户实付金额。金额计算统一使用分为单位,避免浮点数误差。
计价规则不是直接写在订单服务里的,而是拆成独立的规则引擎。每个城市、每种车型维护一套计价版本。行程结束后,系统读取行程开始时刻的规则版本,而不是最新版本,避免规则变更影响历史订单。规则引擎按费用项逐项计算,每项费用都可以独立溯源,方便向用户展示账单明细。
public FareResult calculate(Trip trip, FareRule rule) {
long baseFee = rule.getBaseFee();
long distanceFee = trip.getDistanceMeters() / 1000 * rule.getPerKmFee();
long durationFee = trip.getDurationSeconds() / 60 * rule.getPerMinuteFee();
long nightFee = trip.isNight() ? rule.getNightServiceFee() : 0;
long longDistanceFee = trip.getDistanceMeters() > rule.getLongDistanceThreshold()
? (trip.getDistanceMeters() - rule.getLongDistanceThreshold()) / 1000 * rule.getLongDistancePerKmFee()
: 0;
long total = baseFee + distanceFee + durationFee + nightFee + longDistanceFee;
long discount = couponService.calculateDiscount(trip.getUserId(), total);
long payable = Math.max(0, total - discount);
return new FareResult(total, discount, payable);
}
每一笔费用项在账单表中都有独立记录,优惠券按平台券、商户券和积分抵扣分别存储。退款时根据分摊后的金额原路退回,而不是简单退回整单金额。代收项目如高速费需要单独标记,退款时一并原路退回,但不参与优惠分摊。
四、异常场景与对账补偿
支付链路最常出现的异常是渠道超时、回调丢失和重复通知。渠道超时不能直接判定失败,需要主动查询渠道订单结果。回调丢失由定时任务扫描支付中状态超过一定时间的订单,逐笔向渠道发起查询。重复通知则依赖支付单号幂等,重复通知到达后直接返回成功,不改变订单状态。幂等处理必须放在数据库事务中,防止并发回调穿透。
对账在次日生成渠道账单文件,与平台支付流水按金额、单号、状态核对。金额不一致时挂入差错表,由人工或自动补偿处理。退款场景通常只允许原路退回,企业月结还款可冲抵下一账期账单。涉及跨渠道的优惠券分摊,必须记录分账明细,否则退款时无法准确计算应退金额。每日对账结果会生成差异报告,供运营和财务复核。
SELECT order_no, amount, pay_status FROM payment_flow WHERE pay_status = 'PAYING' AND update_time < NOW() - INTERVAL 10 MINUTE ORDER BY update_time ASC LIMIT 500;
支付和计价两个域通过领域事件协同,订单侧不直接保存计价公式,支付侧不关心费用明细。这种解耦方式让支付方式调整和计价规则调整可以独立上线。对账补偿的核心是保证资金流水与渠道账单最终一致,而不是追求强一致,因此所有异常处理最终都要落到可审计的流水记录上。
整体来看,豆包打车的支付结算设计重点在于支付幂等、金额单位统一、状态分离和规则版本化。接入类似系统时,优先保证每笔支付请求都有唯一支付单号,所有金额使用分存储,订单状态与支付状态分开管理,计价规则按版本快照结算。完成这些基础后,再逐步补充分账、退款和对账能力,才能支撑一键下单后的自动扣款与费用清算。