Appearance
概述
续租是租赁订单在租期到期前(或逾期后)由用户端(小程序/H5/App)或管理后台发起的延长租期操作。续租会在原订单上追加新的分期账单(eb_store_order_installment),更新订单租赁信息(总天数/总期数/结束时间/总租金),并同步支付宝侧(交易组件 / 租赁行业交易)的租期计划。
取消续租仅管理后台支持,用户端不允许取消续租(route/api.php 无取消续租接口)。
功能入口
| 入口 | 端 | 位置 | 说明 |
|---|---|---|---|
| 发起续租 | 用户端 | view/uniapp/pages/goods/lease_order_xuzu/index.vue | 订单详情/列表页「续租」按钮进入,选择天数后刷脸(e签宝)或静默签署(爱签) |
| 发起续租 | 管理后台 | view/admin/src/pages/order/leaseOrderList/components/rentDetailModal.vue(租金账单页右上「续租」按钮) | 输入续租天数(最多补足 90 天)与续租金额,无需人脸与合同 |
| 取消续租 | 管理后台 | 同 rentDetailModal.vue 租金账单操作栏「取消续租」(仅续租账期 & 未支付 & 最新续租批次) | 选择取消原因后提交 |
接口清单
用户端(route/api.php)
| 方法 | 路径 | 控制器 | 说明 |
|---|---|---|---|
| POST | order/relet_lease/:id | app\controller\api\v1\order\StoreOrder::reletLease | 发起续租($uid>0,需人脸/合同) |
| POST | order/relet_price/:id | app\controller\api\v1\order\StoreOrder::getReletPrice | 查询续租价格 |
前端封装:view/uniapp/api/leaseOrder.js → reletLease() / getReletMoney()
reletLease 参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| days | int | 是 | 续租天数 |
| provider | string | 否 | 人脸认证 provider,默认 mpalipay |
| product_id | int | 否 | 商品信息ID(自定义租期走斜率公式计价时必传) |
管理后台(route/admin.php)
| 方法 | 路径 | 控制器 | 说明 |
|---|---|---|---|
| POST | order/relet_order | v1.order.StoreOrder/reletOrder(app\common\controller\Lease) | 后台续租($uid=0,无合同) |
| POST | order/cancel_relet_append | v1.order.StoreOrder/cancelReletAppend(app\common\controller\Lease) | 取消续租 |
前端封装:view/admin/src/api/leaseOrder.js → xuzuRent() / cancelReletAppend()
reletOrder 参数: oid(订单ID)、days(续租天数)、relet_money(续租金额,可选,不传按日租金计算)
cancelReletAppend 参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| oid | int | 是 | 订单ID |
| num | int | 是 | 取消起始期数(该期及以后同批次续租分期一并删除) |
| reason_code | string | 是 | 取消原因代码(固定枚举,见下) |
| reason_desc | string | 否 | 取消原因描述,不传则取代码对应文案 |
reason_code 枚举(app/services/alipay/CommerceRentServices.php::$cancelReasonCode):
| 代码 | 文案 |
|---|---|
USER_PLAN_CHANGE | 用户租赁计划有变 |
USER_OVERDUE_PAYMENT | 用户超期支付自动取消 |
OUT_OF_STOCK | 无库存取消 |
OTHER | 其他 |
核心流程
用户端续租
用户端「续租」页
├─ 查询续租价格 getReletPrice
│ ├─ 斜率公式计价(product_id):QuickOrderServices::caculatePriceBySlopeFormula
│ ├─ 租期折扣:StoreProductLeaseDiscountAuxServices::getBestDiscount
│ ├─ SVIP 订单叠加计算
│ └─ 返回 origin_price / discount_price / promotions_price
│
└─ 发起续租 reletLease → StoreOrderServices::reletLeaseOrder($oid, $uid>0, ...)
├─ 1. 参数与状态校验(见「校验规则」)
├─ 2. 计算续租天数→期数(≤30天=1期,>30天=每30天1期)
├─ 3. 续租后总天数 > 90 天:抛「后台操作暂不支持续长租」(长租续租暂禁用,
│ longReletLeaseOrder 生成 type=13 新订单的代码保留但不可达)
├─ 4. 分支:
│ ├─ 爱签(contract_sign=2):
│ │ ├─ 缓存续租参数 reletLeaseOrder_{oid}_{uid}(60分钟)
│ │ ├─ StoreOrderInstallmentServices::createReletData 直接生成续租账单
│ │ └─ SystemContractServices::asignReletFlow 静默签署续租合同(复用首次人脸)
│ └─ e签宝:
│ ├─ EsignServices::persons_verify 发起人脸认证(返回 apply.originalUrl/faceToken)
│ └─ 缓存续租参数 reletLeaseOrder_{oid}_{uid}(60分钟),待认证回调
└─ 5. e签宝认证回调链路:
verify_callback(app/controller/api/v2/user/Esign.php)
→ 记录 face_auth_detail.relet_{n},face_auth_status=2
→ OrderCreateAfterJob::afterEsignPersonsVerify($oid, $relet_times)
→ createReletData 生成续租账单
→ generateEsignContract 创建填充续租合同
→ esign3PersonSign 用户静默签署管理后台续租
管理后台租金账单页「续租」→ xuzuRent({oid, days, relet_money})
→ StoreOrderServices::reletLeaseOrder($oid, $uid=0, ...)
├─ 同样的状态校验(无用户归属校验)
├─ 缓存续租参数 reletLeaseOrder_{oid}_{uid}(仅5秒,防并发)
└─ 直接 createReletData:无需人脸与合同,追加租期取消续租(仅管理后台)
管理后台租金账单操作栏「取消续租」(row.relet_times>0 && row.status==0 && 最新续租批次)
→ cancelReletAppend({oid, num, reason_code, reason_desc})
→ StoreOrderServices::cancelReletAppend
├─ 1. 校验:订单存在 / 有续租记录 / 仅允许取消最新续租批次(该期 relet_times == 订单 relet_times)
├─ 2. 事务内:
│ ├─ 取出 relet_times 相同且 num >= 传入 num 的所有分期,逐一校验 status==0(未支付)
│ ├─ 物理删除这些分期
│ ├─ 回滚订单:total_price 减、lease_days/end_time/lease_num 回退
│ └─ CommerceRentServices::cancelReletAppend
│ → rentplaninfoAppend(..., 'CANCEL_APPEND') 同步支付宝取消追加租期计划
└─ 3. OrderstatusJob 记录订单状态日志(msg: 取消续租: 第X期,原因:...)核心代码位置
| 职责 | 文件 | 方法 |
|---|---|---|
| 续租主入口(校验/分支) | app/services/order/StoreOrderServices.php | reletLeaseOrder() |
| 长租续租(新订单,暂不可达) | 同上 | longReletLeaseOrder() |
| 续租价格查询 | app/services/order/StoreOrderInstallmentServices.php | getReletPrice() |
| 生成续租分期账单 | 同上 | createReletData() |
| 取消续租(回滚+同步支付宝) | app/services/order/StoreOrderServices.php | cancelReletAppend() |
| 支付宝侧续租追加/取消 | app/services/alipay/CommerceRentServices.php | reletAppend() / cancelReletAppend() |
| 爱签续租合同静默签署 | app/services/other/SystemContractServices.php | asignReletFlow() |
| e签宝人脸认证回调 | app/controller/api/v2/user/Esign.php | verify_callback() |
| 人脸认证后生成账单/合同 | app/jobs/order/OrderCreateAfterJob.php | afterEsignPersonsVerify() / esign3PersonSign() |
| 用户端控制器 | app/controller/api/v1/order/StoreOrder.php | reletLease() / getReletPrice() |
| 后台控制器 | app/common/controller/Lease.php | reletOrder() / cancelReletAppend() |
| 后台前端 | view/admin/src/pages/order/leaseOrderList/components/rentDetailModal.vue | 续租/取消续租弹窗 |
| 用户端前端 | view/uniapp/pages/goods/lease_order_xuzu/index.vue | 续租页 |
校验规则(reletLeaseOrder)
以下任一不满足即抛异常:
- 订单存在且未被取消
- 用户端:
uid > 0时校验订单.uid == uid(订单归属) - 订单租赁信息
is_relet == 1(商品支持续租) - 订单
status >= 2(已确认收货)、status != 6(未完结) lease_status∈ [1 租用中, 2 逾期中, 5 待归还, 6 待接收]- 无未完成的买断支付订单
- 非子订单(
pid == 0) - 无未完结租期(
lease_num_paid == lease_num) order_lease.relet_times字段存在- 用户端逾期订单(end_time < 当前时间)放行(校验已注释)
- 续租金额 ≤ 剩余预授权金额(
rest_amount) - 非蚂蚁森林订单(
active_tag != 'mysl') days > 0check_type∈ [1 芝麻免押, 3 新租赁行业交易](代扣方式不支持续租)- 0元租包:续租到期不超过会员有效期;SVIP:会员到期校验
- 续租后总天数 ≤ 90 天(长租续租暂禁用)
数据表字段
eb_store_order_lease(订单租赁信息)
| 字段 | 说明 |
|---|---|
is_relet | 订单是否允许续租(下单时快照商品配置,1=是) |
relet_times | 已续租次数(每次续租 +1) |
relet_days | 续租累计总天数 |
lease_days | 租赁总天数(原 + 续租) |
lease_num | 租赁总期数(原 + 续租新增期数) |
end_time | 租期结束时间(每次续租向后顺延) |
eb_store_order_installment(分期账单)
| 字段 | 说明 |
|---|---|
relet_times | 该期所属续租批次(0=原始租期,1=第一次续租,以此类推);管理后台据此识别续租账期并展示「续租」标签 |
eb_store_product(商品)
| 字段 | 说明 |
|---|---|
is_relet | 商品是否支持续租(1=是) |
day_price | 每日租金(续租默认计价基准) |
续租账单生成规则(createReletData)
- 从 Redis 缓存
reletLeaseOrder_{oid}_{uid}读取续租参数(天数/日租金/总租金)- 用户端 e签宝/爱签:缓存 60 分钟(等待人脸/签署完成)
- 后台:仅缓存 5 秒(直接使用)
- 续租新增期数:
lease_num + 1起,relet_times标记为本批次 - 每期金额 = 续租总额 / 期数(余数计入首期);续租折扣(
getBestDiscount)与 SVIP 差价逻辑与价格查询一致 - 同步更新:
total_price(订单总租金累加)、lease_status=1、lease_days、lease_num、end_time、relet_days - 同步支付宝:
check_type=1(芝麻免押):TradeComponentServices::orderModify交易组件追加租期check_type=3(租赁行业交易):CommerceRentServices::reletAppend追加分期计划
- 推送订阅消息
order_relet - 记录订单状态日志(
OrderstatusJob,msg:用户/后台申请短租续租: X天)
注意事项与限制
- 取消续租仅后台:用户端无取消续租接口;管理后台仅允许取消最新续租批次(
relet_times == 订单.order_lease.relet_times)中未支付(status==0)的分期,且从所选期数起该批次后续分期一并删除。 - 长租续租暂禁用:续租后总租期超过 90 天直接抛异常;
longReletLeaseOrder(生成type=13续租新订单)代码保留但当前不可达。 - 后台续租免合同:
$uid=0走「无需人脸及合同,直接追加租期」分支;用户端必须完成人脸认证(e签宝)或静默签署(爱签)后才生成账单。 - 并发保护:续租入口用
CacheService::lock(3秒)防重复提交;createReletData依赖 Redis 缓存中的续租参数,缓存缺失时抛「续租操作失败,请重试」。 - 取消续租为物理删除:续租分期记录直接从
eb_store_order_installment删除(非状态标记),同时回滚订单租赁数据,并同步支付宝CANCEL_APPEND。