# Channex multi-property integration architecture
## Decision
Zapazime uses one Channex organisation and one API key with access to all properties. A Zapazime `Venue` is a Channex Property. Remote identifiers must be stored as mappings; they must never replace local primary keys.
The integration is a modular monolith inside Laravel. Queue workers provide isolation and throughput without the operational cost and distributed transactions of deploying separate microservices. Each Channex API has a small gateway service under `App\Services\Channex`; orchestration belongs in separate application services/jobs rather than in controllers or Eloquent observers.
## Service boundaries
| Channex API | Gateway | Responsibility |
|---|---|---|
| Properties | `PropertiesService` | Property CRUD and account-wide discovery |
| Room Types | `RoomTypesService` | Room-type CRUD scoped by remote property |
| Rate Plans | `RatePlansService` | Rate-plan CRUD scoped by remote property |
| Availability and Restrictions (ARI) | `AriService` | Separate availability and rate/restriction batches |
| Booking Revisions | `BookingRevisionsService` | Account-wide feed, revision lookup, acknowledgement |
| Webhooks | `WebhooksService` | Webhook registration and lifecycle |
| Channels, taxes, policies, photos, messages | Channex UI / later gateways | Channex-managed configuration; add gateways only when Zapazime becomes authoritative for these domains |
All gateways share `ChannexClient`, which owns the base URL, `user-api-key` header, JSON transport, timeouts, retry policy, pagination, and normalized exceptions.
## Local data model
Use explicit integration tables rather than columns scattered through core tables:
- `channex_property_mappings`: one record per `venue_id` and environment, remote `property_id`, lifecycle/approval status, sync timestamps, metadata, and last error.
- `channex_room_type_mappings`: one local `venue_object_type_id` per property/environment mapping and its remote room-type ID. `VenueObjectType` is venue-scoped and represents pooled sellable inventory; its physical rooms are the related `VenueObject` records.
- `channex_rate_plan_mappings`: one local `rate_plan_id` per property/environment mapping and its remote room-type/rate-plan IDs.
- `channex_booking_revisions`: remote revision ID (unique), booking ID, property ID, status, payload, attempts, processed/acknowledged timestamps and error.
- `channex_sync_outbox`: property, operation, aggregate, date range, payload hash, status, attempts and next retry time.
Every table must retain `venue_id` (and where applicable `company_id`) for authorization and diagnostics, even though the Channex credential itself is platform-wide.
## Workflows
### Onboard a property
1. Validate that the Venue is accommodation inventory and has address, ISO country, IANA timezone, currency, room grouping, and rate plans.
2. Create/update the Channex Property and persist its mapping.
3. Create room types and rate plans; persist each mapping.
4. Send a 500-day full ARI sync for certification.
5. Register and test the booking webhook.
6. Activate two-way synchronization only after mapping verification and a fresh full ARI sync. Production additionally requires recorded Channex approval.
Pausing stops outbound ARI but retains account-wide booking-feed processing. Safe disconnect sends zero availability and stop-sell for the full horizon before disabling the webhook. Removing remote entities is never an automated operation.
### Outbound ARI
Domain changes write an outbox record in the same database transaction. A queue worker coalesces changes per property for 30–60 seconds, sends availability separately from rates/restrictions, and treats warnings in successful HTTP 200 responses as partial failures requiring visibility. Send changes immediately under normal load and run a nightly full reconciliation per property. Keep each request below 10 MB.
### Inbound bookings
Use webhooks for latency and an account-wide Booking Revisions Feed pull every 15 minutes as recovery. The webhook returns HTTP 200 quickly after validating and recording the revision ID, then queues processing. Processing is idempotent by remote revision ID and executes in this order:
1. Resolve remote `property_id` to a Venue and reject unmapped data to a dead-letter state.
2. Lock the revision and relevant local inventory in a transaction.
3. Create, modify, or cancel the local Booking and its room assignments.
4. Commit locally.
5. Recalculate and enqueue availability for affected dates.
6. Acknowledge the Channex revision only after the local commit; retry acknowledgement independently.
Never log payment-card data. If card handling is required, design the PCI flow with Channex before enabling it.
## Reliability and observability
- Idempotency keys: booking revision ID inbound; payload hash plus aggregate/date range outbound.
- Retry only network failures, HTTP 429, and 5xx with exponential backoff and jitter. Do not retry validation failures without changing the payload.
- Per-property queue serialization prevents stale ARI writes; the booking feed itself remains one account-wide poll.
- Metrics: feed lag, unacknowledged count/age, webhook-to-booking latency, ARI queue depth, warning/error count, and last successful full sync per property.
- Structured logs contain local/remote property IDs, correlation ID, operation, task ID, and attempt—but no API key or guest payment data.
## Delivery sequence
1. **Foundation (implemented):** configuration, shared HTTP client, pagination/errors, and gateways for certification-critical APIs.
2. **Mappings (implemented):** constrained property, room-type and rate-plan mappings plus a tenant-scoped Company Admin readiness and property-sync UI.
3. **Outbound inventory (implemented):** adapters from Zapazime availability/rate models, transactional outbox, batching, workers, nightly 500-day reconciliation.
4. **Inbound bookings (implemented):** encrypted revision inbox, account-wide scheduled pull, authenticated webhook endpoint, idempotent booking importer, acknowledgement worker.
5. **Operations (implemented):** mapping UI, health counters and alerts, connection test, webhook registration, replay actions, environment-separated lifecycle controls, and safe disconnect.
6. **Certification tooling (implemented):** automated certification fixtures, staging evidence/task IDs, Booking.com lifecycle helper, and production approval gate.
7. **External completion:** execute certification with Channex, rotate credentials, obtain approval, and perform a controlled production pilot.
The canonical operator-facing document is [Channex integration guideline](CHANNEX_INTEGRATION_GUIDELINE.md).
## Room inventory identity
`VenueObjectType` is the stable, venue-scoped pooled inventory entity mapped to a Channex Room Type. `VenueObject.venue_object_type_id` is the authoritative relationship for physical rooms. The legacy `VenueObject.type` slug remains temporarily as a read-compatible projection and must not be used for Channex mappings.
Room-type identity and presentation are separate: the immutable-style `slug` is used by mappings and integrations, while `venue_object_type_translations` provides localized names and descriptions. Common global templates are seeded in English and Bulgarian Cyrillic and copy their translations when assigned to a venue.