企业 网站预约

Odoo 企业版网站付费预约为什么不是“付款成功就锁时段”:booking 暂存、invoice token 与 return token 边界讲透

appointment_account_payment 与 website_appointment_account_payment 的关键,不是给预约页加一个支付按钮,而是先把 booking 暂存下来,再用 booking_token 与 invoice access token 串起支付后的回跳和最终成会。

企业 网站
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 4 阅读

多数字面理解都会把“付费预约”想成一个很直的流程:访客选时段、点支付、付成功、日历里马上生成事件。企业版源码故意没有这么做。它先把信息落在 calendar.booking,让时段、答案、参与人和收费信息暂存在 booking 里;只有付款真的过账,或者满足最终确认条件后,才把 booking 转成正式的 calendar.event

这不是多绕一步,而是在日历资源占用、支付结果不确定、门户回跳安全三件事之间做隔离。

主要参考的企业版源码入口:

  • enterprise/appointment_account_payment/controllers/appointment.py
  • enterprise/appointment_account_payment/controllers/payment.py
  • enterprise/appointment_account_payment/models/calendar_booking.py
  • enterprise/website_appointment_account_payment/views/appointment_templates_payment.xml

一、真正的跨模块链路是什么

这里至少串了四层:

  1. Website / Appointment 表单层:访客在网站选时段、填问题、带着 invite 参数进入支付页。
  2. Booking 暂存层_handle_appointment_form_submission() 不直接建 calendar.event,而是创建 calendar.booking
  3. Accounting / Payment 层_make_invoice_from_booking() 先生成发票,支付页根据发票 access token 进入支付流程。
  4. Calendar 最终确认层_make_event_from_paid_booking() 只在真正满足“已支付/可成会”后才生成事件,并再次过滤不可用 booking。

所以用户看到的是一个预约页,但系统内部其实是“网站表单 -> booking 暂存 -> 发票/支付 -> 日历成会”的四段式链路。

二、为什么必须先存 booking,不能直接占用日历

_handle_appointment_form_submission() 的注释说得很明确:这样做是为了避免把未确认、未支付的预约直接同步到日历。如果用户支付中断、支付失败、浏览器关掉,系统里留下来的应该是可回收的 booking,而不是已经污染员工日历或资源容量的正式事件。

calendar.booking 里保留了:

  • 预约问题答案;
  • 资源或员工占用草稿;
  • booking_token
  • 发票关联;
  • 目标产品与容量信息。

也就是说,booking 是跨 Appointment、Accounting、Calendar 的“中间态合同”。

三、支付完成后,为什么有时回摘要页,有时直接回事件页

回跳不是固定 URL,而是由 token 和状态共同决定:

  • appointment_post_payment() 先通过发票 access_token 找到 booking;
  • 如果 booking 已经生成了 calendar_event_id,就跳到 /calendar/view/<event_token>
  • 如果支付失败、待处理,或事件还没生成,就跳回 /calendar_booking/<booking_token>/view 摘要页。

这就是本文最重要的边界:

  • invoice token 负责把支付结果安全地带回正确 booking;
  • booking token 负责让访客回看自己的暂存预约摘要;
  • event token 只在正式成会后才对外暴露。

三个 token 对应三种对象,不能混成一个“支付成功页链接”。

四、为什么支付成功也不等于一定能成会

_make_event_from_paid_booking() 在转事件前还会调用 _filter_unavailable_bookings()。这意味着即使用户付过钱,系统仍会重新看资源/员工在该时间窗内是否可用。

这一步解决的是并发边界:

  • 访客提交 booking 到付款成功之间有时间差;
  • 同一资源可能被别的流程先确认;
  • 站点展示的“看起来可约”不等于最终“可成会”。

也因此文章标题里说“不是付款成功就锁时段”。支付是必要条件,但不是越过 availability 复核的许可证。

五、落地时最容易踩的坑

最常见的误判有四个:

  • 误把 calendar.booking 当成正式预约,脚本里直接拿它做运营统计;
  • 以为支付成功页一定能拿到最终事件链接;
  • 忽略了 booking 的自动清理,导致把过期 booking 误当活数据;
  • 把回跳做成单一 URL,结果 pending / failed payment 无法回到正确摘要页。

_gc_calendar_booking() 的存在也说明 booking 本来就是一个可清理的中间对象,而不是永久业务主表。

六、结论

企业版网站付费预约真正解决的不是“在线支付预约”这四个字,而是未确认预约如何安全跨过网站、会计和日历三个子系统calendar.booking 把中间态隔离出来,invoice token、booking token 和 event token 又把支付后回跳分成三层身份,最终保证:

  • 未支付时不会污染正式日历;
  • 已支付时能精确回到正确对象;
  • 并发冲突时仍保留最后一道 availability 复核。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。