Architecture¶
Module overview¶
src/
├── Dixa.php # Plugin bootstrap — registers REST routes, boots modules
├── DixaDataGenerator.php # Builds the customer payload for the REST response
├── API/
│ ├── API.php # Façade — instantiates and exposes resource clients
│ ├── APIClient.php # Guzzle client preconfigured for https://dev.dixa.io/v1/
│ ├── Requests/Models/ # Request models (EndUser, AnonymizationModel, …)
│ └── Resources/
│ ├── Clients/ # EndUsers, Anonymization, CustomerServiceStatus
│ └── Schemas/ # Response schemas (EndUser, ErrorSchema, …)
├── CLI/
│ ├── CLI.php # Registers WP-CLI command groups
│ └── Commands/ # Anonymization, Users
├── Data/
│ ├── DixaExporter.php # create_or_patch helper; tracks export state via user meta
│ ├── DixaCustomer.php # Customer data mapper
│ ├── DixaOrder.php # Order data mapper
│ └── …
├── Frontend/
│ └── CustomerServiceStatusShortCode.php # [dixa-status] shortcode
└── Modules/
├── Jobs/CustomerServiceStatus.php # Action Scheduler job — polls Dixa every 30 min
├── Watchers/UserWatcher.php # WooCommerce hooks → async sync via Action Scheduler
└── OrderStatus/ # Order status REST endpoint for Dixa MIM Tools
├── REST/RouteController.php # Registers POST /dixa/v1/order-status
├── Auth/BasicAuthGuard.php # Basic Auth against .env credentials + rate limiting
├── RateLimiter.php # Transient-backed attempt counter
├── OrderLookupService.php # order_number + email/phone verification
├── EstimatedDelivery.php # Default per-carrier business-day estimate + `dixa_order_status_estimated_delivery` filter
└── Data/OrderStatusData.php # Response DTO, `dixa_order_status_data` filter
Data flow — customer sync¶
sequenceDiagram
participant WC as WooCommerce
participant Watcher as UserWatcher
participant AS as Action Scheduler
participant Exporter as DixaExporter
participant Dixa as Dixa API
WC->>Watcher: woocommerce_created_customer
Watcher->>AS: as_enqueue_async_action(dixa/scheduler/create-customer)
AS->>Exporter: export_customer($customer_id)
Exporter->>Dixa: POST /endusers (create)
alt EmailExists / PhoneNumberExists
Dixa-->>Exporter: error
Exporter->>Dixa: GET /endusers?email= (lookup)
Exporter->>Dixa: PATCH /endusers/{id}
end
Exporter->>WC: update_user_meta(exported_to_dixa, 1) Customer service status flow¶
The CustomerServiceStatus job runs every 30 minutes via Action Scheduler. It calls the Dixa business-hours API and writes the result to the dixa_customer_service_status option, which the [dixa-status] shortcode reads synchronously.
REST API endpoints¶
Dixa.phpregisters a read-only customer lookup endpoint underwc/v3/dixa/v1/list, gated by WooCommerce's own REST auth/capabilities.Modules/OrderStatus/REST/RouteController.phpregistersPOST /dixa/v1/order-statusfor Dixa MIM Tools, deliberately outside thewc/v3namespace so it isn't intercepted by WooCommerce's REST auth — it authenticates and rate-limits itself instead (see flow below). It also hooksrest_authentication_errorsatPHP_INT_MAX, clearing any conflicting global Basic Auth rejection (e.g. from Application Passwords or a security plugin also readingPHP_AUTH_USER) for its own path only, so its ownBasicAuthGuardalways gets the final say regardless of what else is installed on the site.
See api.md for both endpoints' request/response reference.
Order status lookup flow¶
sequenceDiagram
participant Mim as Dixa MIM Tools
participant Route as OrderStatus\REST\RouteController
participant Auth as BasicAuthGuard
participant Limiter as RateLimiter
participant Lookup as OrderLookupService
participant WC as WooCommerce
Mim->>Route: POST /dixa/v1/order-status
Route->>Auth: authorize(request)
Auth->>Limiter: is_locked_out(identity)?
alt locked out
Auth-->>Mim: 429 rate_limited
else credentials invalid
Auth->>Limiter: register_failure(identity)
Auth-->>Mim: 401 unauthorized
else authorized
Route->>Lookup: find(order_number, email, phone)
Lookup->>WC: wc_get_order(order_number)
alt order missing or email/phone mismatch
Lookup-->>Route: EntityNotFoundException
Route->>Limiter: register_failure(identity)
Route-->>Mim: 404 order_not_found
else match
Route-->>Mim: 200 OrderStatusData (incl. estimated_delivery filter result)
end
end Every failure path — unknown order, wrong email, wrong phone — returns the same generic 404 order_not_found, on purpose: a caller must never be able to tell "no such order" apart from "right order, wrong contact details".