目录
1、核心概念
2、两种计费模式(生产环境都存在)
模式 A:实时分片计费(线上真实充电桩主流)
模式 B:后处理全量切片(老设备 / 上报粒度粗)
3、各个金额的计算时机【重点】
4、完整业务时序(从启充到出账单)
5、常见坑点(线上生产问题)
6、简单伪代码示意
7、补充:什么场景优惠会实时预览?
业务背景:充电桩设备上报充电度数,计价服务做计费;电费分峰 / 平 / 谷电价(来自电网),服务费是运营商收益,优惠是营销抵扣,要区分:什么时候取电价、什么时候算服务费、什么时候扣优惠、什么时候生成账单。
1、核心概念
注意:电价不是固定写死代码,是配置在计价系统,支持后台修改,同步生效;部分场站还有 “尖峰” 档位。
- 峰时段:电价最高
- 平时段:中间
- 谷时段:电价最低(夜间)
关键点:充电可能跨时段!比如 22:40 开始充电,00:30 结束,横跨平段→谷段。不能简单拿充电开始时间直接套一个电价,要按时间切片分段算电量。
服务费 充电桩运营商收取,一般按 元 / 度,也可以按小时;服务费可以不分峰谷,也可以配置峰谷不同服务费。 总单价 = 分时电费单价 + 分时服务费单价。
优惠 优惠券、折扣、立减、会员折扣、活动补贴;分为:按度优惠、按总金额优惠、固定立减、折扣比例。 优惠是最后抵扣,不是先扣优惠再算电费。
2、两种计费模式(生产环境都存在)
模式 A:实时分片计费(线上真实充电桩主流)
充电过程中,设备每隔 30s~5min 上报充电增量度数,计价服务按当前所在的时段实时分片计算。
适合:直流快充,充电时间短,但可能跨峰谷。
流程:
为什么优惠不实时扣:优惠券过期、券被收回、活动下线、多券叠加规则复杂,实时扣会出现回滚、金额错乱,业务上统一在充电结束做优惠计算。
模式 B:后处理全量切片(老设备 / 上报粒度粗)
设备只上报开始时间、结束时间、总充电度数,没有中间增量。 计价服务拿到:startTime、endTime、totalKwh。 系统把【开始~结束】整个时间区间,切割成若干峰 / 平 / 谷时间片,按时间占比分摊总电量到各个时段,分别计算每一片电费 + 服务费,求和得到原始总金额,结束后再应用优惠。
举例子: 充电 22:30 ~ 00:30,总充 20 度
- 22:30‑23:00:平段,30 分钟
- 23:00‑00:30:谷段,90 分钟 总时长 120 分钟 平段分摊电量 = 20 × (30/120) =5 度 谷段分摊电量 =20 × (90/120)=15 度 分别用平电价、谷电价算电费,叠加服务费。
这个是时间占比分摊,前提是假设充电功率稳定;现实快充功率会变化,所以高精度场景优先用设备上报的增量电量分片。
3、各个金额的计算时机【重点】
| 峰谷电费 | 充电过程分片计算(实时) / 充电结束后切片分摊 | 按每一段充电所处的时间,不是开始时间 |
| 服务费 | 和电费同步分片计算 | 服务费也可以配置分时,峰期服务费更高 |
| 原始总金额(电费 + 服务费) | 充电结束前就累计完成,订单充电中就有预估金额 | 用户 APP 看到 “预估费用” 就是这个值 |
| 优惠抵扣(券、立减、折扣、会员) | 充电停止,拿到最终充电数据之后才计算 | 充电过程中不扣优惠;避免中途券失效带来金额回滚 |
| 实付金额 | 优惠计算完成后得出 | 实付 = 原始总金额‑优惠总额,最低不能小于 0 |
边界:优惠大于原始总金额,实付 = 0,不出现负数。
4、完整业务时序(从启充到出账单)
- 查询用户可用优惠券、会员权益、活动规则;
- 校验券有效期、场站限制、车型限制;
- 按优先级叠加 / 互斥规则计算优惠总抵扣;
5、常见坑点(线上生产问题)
❌错误:只拿充电开始时间选峰谷电价,充电跨时段但全程用一个电价。 ✅正确:按时间切片,分段算。
❌错误:充电中途就扣优惠券,充电过程中券过期,金额异常。 ✅正确:优惠统一放在充电结束后计算。
❌错误:服务端分片累加度数和设备硬件总度数不一致,出现金额偏差。 ✅正确:充电结束,以设备上报总度数为准,全量重算,修正累计误差。
❌错误:服务费跟随优惠一起抵扣,混淆电网电费和运营商服务费。
电费是交给电网,服务费是运营商收入;优惠可以抵扣全部,也可以配置只抵扣服务费,不能抵扣电费,看业务规则。
6、简单伪代码示意
java
// 1.充电结束,拿到设备真实数据
LocalDateTime startTime = chargeOrder.getStartTime();
LocalDateTime endTime = chargeOrder.getEndTime();
BigDecimal totalKwh = deviceReport.getTotalKwh(); //设备上报最终度数
//2.时间切片:把[startTime,endTime]切分成多个时段片,每个片段包含:开始、结束、电价、服务费
List<TimeSlice> slices = peakValleyService.splitTimeSlice(startTime, endTime);
//3.按时间占比分摊总电量到各个时间片(如果没有中间增量上报)
BigDecimal originTotal = BigDecimal.ZERO;
for(TimeSlice slice : slices){
//分摊该片段电量
BigDecimal sliceKwh = calcShareKwh(slice, startTime, endTime, totalKwh);
//片段金额 = 电量*(分时电费+分时服务费)
BigDecimal sliceAmount = sliceKwh.multiply(slice.electricPrice.add(slice.serviceFee));
originTotal = originTotal.add(sliceAmount);
}
//4.充电结束后,执行优惠引擎
BigDecimal discount = discountEngine.calcDiscount(userId, orderId, originTotal);
BigDecimal realPay = originTotal.subtract(discount);
if(realPay.compareTo(BigDecimal.ZERO) <0){
realPay = BigDecimal.ZERO;
}
//写入订单、账单
chargeOrder.setOriginAmount(originTotal);
chargeOrder.setDiscountAmount(discount);
chargeOrder.setRealPayAmount(realPay);
7、补充:什么场景优惠会实时预览?
APP 上充电中展示 “预计优惠后 xx 元”,这只是前端预估算,给用户看的参考值,不是真实扣减。后端真实抵扣一定等充电结束。





