Lifecycle booking
Status booking Termilo: held, confirmed, completed, cancelled, dan no_show — beserta transisi yang diizinkan, reschedule, kunci slot + idempotency saat membuat booking publik, dan manage token untuk customer.
held — slot ditahan
hold 10 menit
confirmed — booking sah
completed — sesi selesai
Status booking#
Field bookings.status hanya bisa bernilai salah satu dari lima status berikut. Tiga di antaranya terminal — sekali tercapai, booking tidak berpindah status lagi.
| Status | Label | Terminal | Keterangan |
|---|---|---|---|
held | Menunggu | tidak | Slot ditahan sementara saat booking baru dibuat. Kedaluwarsa otomatis jika tidak dikonfirmasi. |
confirmed | Terkonfirmasi | tidak | Booking sah dan slot terkunci. Dari sini bisa diselesaikan, ditandai tidak hadir, dibatalkan, atau dijadwal ulang. |
completed | Selesai | ya | Sesi selesai dijalankan. Status terminal. |
cancelled | Dibatalkan | ya | Booking dibatalkan oleh tenant atau customer. Status terminal. |
no_show | Tidak hadir | ya | Customer tidak hadir di sesi yang sudah terkonfirmasi. Status terminal. |
_at_ms — misalnya starts_at_ms, hold_expires_at_ms, confirmed_at_ms. Bukan epoch detik.Transisi yang diizinkan#
Perpindahan status diatur di modules/booking-lifecycle/policy.ts. Hanya transisi di bawah ini yang valid; transisi lain ditolak dengan kode error BOOKING_POLICY_BLOCKED.
starts_at_ms / ends_at_ms. Booking yang confirmed tetap confirmed setelah dijadwal ulang — slot baru tetap dikunci ulang dan diperiksa tumpang-tindih.Hold, slot-lock & idempotency#
Booking baru selalu lahir sebagai held dengan hold_expires_at_ms = now + 10 menit. Selama jendela itu, slot ditahan supaya dua orang tidak mengambil waktu yang sama. Saat create, slot dicek ulang di bawah Durable Object lock — jika sudah tidak tersedia, request gagal dengan BOOKING_SLOT_UNAVAILABLE (409).
finalAuthority: "booking_create_recheck_under_lock". Slot yang ditampilkan adalah kandidat, bukan otoritas final — kebenaran ditentukan saat create di bawah lock.Endpoint create publik mewajibkan header Idempotency-Key. Mengulang request dengan kunci yang sama mengembalikan booking yang sama (idempotentReplay: true, status 200) alih-alih membuat booking ganda — aman untuk retry jaringan.
Booking baru mengembalikan 201; replay idempoten mengembalikan 200 dengan booking yang sama plus manageToken dan manageUrl untuk self-service customer.
Endpoint lifecycle#
Create bersifat publik (tanpa auth, dilindungi kebijakan). Aksi lifecycle lainnya berjalan di dashboard dengan sesi login Google + header CSRF X-Termilo-CSRF dan tunduk pada RBAC.
| Method | Path | Catatan |
|---|---|---|
| POST | /book/:tenantSlug/bookings | Buat booking publik (header Idempotency-Key wajib). Status awal: held. |
| GET | /api/bookings | Daftar booking. Query: status, fromMs, toMs, limit (bawaan 50, maks 100). |
| GET | /api/bookings/:bookingId | Detail satu booking. |
| POST | /api/bookings/:bookingId/confirm | held → confirmed. |
| POST | /api/bookings/:bookingId/cancel | held | confirmed → cancelled (body: reason). |
| POST | /api/bookings/:bookingId/complete | confirmed → completed. |
| POST | /api/bookings/:bookingId/no-show | confirmed → no_show (body: reason). |
| POST | /api/bookings/:bookingId/reschedule | Pindahkan waktu (body: startsAtMs). Status tetap. |
Manage token customer#
Customer tidak punya akun. Setiap booking create mengembalikan manageToken bertanda tangan dan manageUrl yang membatasi self-service pada satu booking. Format tm_m1.<payload>.<hmac> (HMAC-SHA256, TTL 30 hari), terikat ke satu { tenantId, bookingId, customerId } dengan kumpulan aksi read | cancel | reschedule.
| Method | Path | Catatan |
|---|---|---|
| GET | /booking/manage/:token | Detail booking + actions: { canCancel, canReschedule }. |
| POST | /booking/manage/:token/cancel | Customer membatalkan bookingnya sendiri. |
| POST | /booking/manage/:token/reschedule | Pindahkan waktu (body: startsAtMs). Kunci ulang + cek tumpang-tindih. |
/api/* dan tidak membuat sesi. Responsnya selalu dikirim dengan cache-control: private, no-store.Mutasi lifecycle menulis baris ke tabel integration_outbox / notification_events, tetapi pengiriman webhook keluar (mis. booking.created, booking.cancelled) dikirim secara at-least-once dengan signature dan retry. Lihat halaman Webhooks untuk verifikasi signature serta panduan deduplikasi.