title: 'Ciclo de Vida del Pedido, FSM de Estados y Action Scheduler Asíncrono' description: >- Diseño de un flujo robusto de procesamiento de pedidos en WooCommerce 9.x (WordPress 7.0+, PHP 8.4+): máquina de estados finitos (FSM), prevención de Race Conditions entre webhook IPN y cliente, bloqueos de idempotencia (Idempotency Locks) y colas asíncronas con Action Scheduler. module: woocommerce order: 3 difficulty: advanced estimatedTime: 40 min topics:
En el comercio electrónico, el instante en que el comprador pulsa «Confirmar pedido» desencadena una compleja transacción distribuida. El pedido debe descontar existencias en almacén, consolidar la autorización de la pasarela de pago, generar la factura electrónica fiscal, sincronizar el registro en el CRM, emitir la etiqueta en la empresa de transporte y notificar al ERP de almacén.
Ejecutar estas integraciones de manera síncrona dentro del ciclo de vida HTTP convencional del checkout condena a la plataforma a severos fallos operativos:
Thank You). Ambos procesos PHP paralelos intentan promover el estado del pedido a processing y enviar el email de confirmación, generando duplicidad de correos al cliente e inserciones dobles en el sistema de gestión de almacén.El estándar arquitectónico moderno en WooCommerce 9.x+ resuelve este dilema segregando la operativa en una Máquina de Estados Finitos Estricta (Order Finite State Machine — FSM) y una Cola de Tareas Asíncronas en Segundo Plano (Action Scheduler). Cualquier interacción con servicios externos se encola mediante as_enqueue_async_action() y es resuelta por procesos en segundo plano con garantías de idempotencia y reintentos automáticos programados (Retry with Exponential Backoff).
Visualice el flujo de procesamiento de pedidos en WooCommerce como las operaciones de control de vuelos en un aeropuerto internacional:
pending) a «Aterrizado y desembarcado» (completed) obviando «Autorización de despegue y ruta» (processing). Cada transición la ratifica la torre de control de forma atómica.[Cliente pulsa «Pagar»] ──► Pedido creado (pending) ──► Redirección a pasarela
│
┌──────────────────────────────────────────────┴───────────────────────────┐
▼ ▼
[Navegador vuelve a /thank-you] [Pasarela envía POST /wc-api/...]
│ │
└─────────────────────────────┬────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ VERIFICACIÓN ATÓMICA DE ESTADO: $order->payment_complete│
│ • Idempotency Lock: _paid_lock │
│ • Validación transición FSM: pending -> processing │
└─────────────────────────────┬────────────────────────────┘
│ Transición exitosa
▼
┌──────────────────────────────────────────────────────────┐
│ ACTION SCHEDULER: as_enqueue_async_action() │
│ • En segundo plano: Facturación electrónica fiscal │
│ • En segundo plano: Exportación de lead a CRM / ERP │
│ • En segundo plano: Generación de etiqueta de envío │
└──────────────────────────────────────────────────────────┘La entidad del pedido se persiste en HPOS con estado inicial 'pending' (pendiente de pago). Se reservan existencias en inventario.
Al registrarse el pago, $order->payment_complete() verifica atómicamente el estado en base de datos mediante transacción InnoDB, descartando webhooks concurrentes.
Se activa la máquina de estados: se disparan el hook dinámico woocommerce_order_status_processing y las notificaciones globales de cambio de estado.
Los suscriptores no ejecutan peticiones HTTP externas síncronas, sino que programan acciones en las tablas wp_actionscheduler_actions.
Los workers de Action Scheduler procesan tareas por lotes, añaden notas de auditoría a Order Notes y reprograman reintentos con Exponential Backoff ante errores de red.
En los entornos de producción de Fullstack Agentic Lab, las arquitecturas FSM y Action Scheduler se aplican para:
< 150ms) con entrega garantizada en segundo plano ante caídas temporales del CRM.payment_complete() blindada contra facturación duplicada.processing y notificación de expedición al conmutar a completed.hash_equals() y cerrojo de estado.Comparemos la ejecución síncrona tradicional frente al paradigma asíncrono robusto de WooCommerce 9.x:
A continuación se expone un artefacto de producción empresarial para WooCommerce 9.x (PHP 8.4+). Implementa un Resilient Order Event Subscriber:
_agentic_crm_lock).Order Notes.<?php
/**
* Plugin Name: Fullstack Agentic Lab — Resilient Order Event Pipeline & Action Scheduler
* Description: Canal de eventos de pedidos idempotente y colas en segundo plano con Action Scheduler.
* Version: 3.2.0
* Requires at least: 6.8
* Requires PHP: 8.4
* Author: Artur Yusupov (Fullstack Agentic Lab)
*/
declare(strict_types=1);
namespace AgenticLab\Commerce\Orders;
use Automattic\WooCommerce\Utilities\FeaturesUtil;
use WC_Order;
use WP_Error;
defined('ABSPATH') || exit;
final readonly class OrderLifecycleOrchestrator
{
private const string ACTION_SYNC_CRM = 'agentic_lab_async_sync_order_to_crm';
private const string META_LOCK_KEY = '_agentic_crm_sync_status';
public function registerHooks(): void
{
// 1. Declarar compatibilidad con HPOS
add_action('before_woocommerce_init', static function (): void {
if (class_exists(FeaturesUtil::class)) {
FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true);
}
});
// 2. Escuchar transición a estado "processing" (pago verificado)
add_action('woocommerce_order_status_processing', $this->onOrderProcessing(...), 10, 2);
// 3. Registrar el handler del worker en Action Scheduler
add_action(self::ACTION_SYNC_CRM, $this->executeCrmSyncWorker(...), 10, 2);
}
/**
* Se ejecuta al transicionar el pedido a processing.
* ¡JAMÁS DISPARA PETICIONES HTTP SÍNCRONAS! Encola la tarea asíncrona únicamente.
*/
public function onOrderProcessing(int $orderId, WC_Order $order): void
{
// Comprobación de idempotencia: si ya está en cola o procesada, salimos
$currentLock = (string) $order->get_meta(self::META_LOCK_KEY, true);
if ($currentLock === 'queued' || $currentLock === 'completed') {
return;
}
// Bloqueo atómico del estado del pedido
$order->update_meta_data(self::META_LOCK_KEY, 'queued');
$order->add_order_note('[Pipeline] Pedido programado en Action Scheduler para sincronización asíncrona con ERP.');
$order->save();
// Encolar acción en Action Scheduler (ejecución desatendida en segundos)
if (function_exists('as_enqueue_async_action')) {
as_enqueue_async_action(self::ACTION_SYNC_CRM, [
'orderId' => $orderId,
'attemptCount'=> 1,
], 'agentic-commerce-integrations');
}
}
/**
* Worker en segundo plano de Action Scheduler. No bloquea al cliente.
*/
public function executeCrmSyncWorker(int $orderId, int $attemptCount = 1): void
{
$order = wc_get_order($orderId);
if (!$order instanceof WC_Order) {
return;
}
// Cerciorarse de que otro proceso simultáneo no haya consolidado la sincronización
if ($order->get_meta(self::META_LOCK_KEY, true) === 'completed') {
return;
}
// Preparar lote normalizado para el CRM externo
$payload = [
'orderNumber' => $order->get_order_number(),
'totalAmount' => (float) $order->get_total(),
'currency' => $order->get_currency(),
'customerEmail' => $order->get_billing_email(),
'customerPhone' => $order->get_billing_phone(),
'items' => [],
];
foreach ($order->get_items() as $item) {
$product = $item->get_product();
$payload['items'][] = [
'sku' => $product?->get_sku() ?? 'N/A',
'name' => $item->get_name(),
'quantity' => $item->get_quantity(),
'total' => (float) $item->get_total(),
];
}
// Ejecutar petición HTTP con control estricto de timeout
$result = $this->dispatchToExternalCrm($payload);
if ($result instanceof WP_Error) {
$errorMessage = $result->get_error_message();
$order->add_order_note("[CRM Pipeline] Fallo de sincronización (Intento #{$attemptCount}): {$errorMessage}");
$order->save();
// Programar reintento con retroceso exponencial si no se superó el límite (3)
if ($attemptCount < 3 && function_exists('as_schedule_single_action')) {
$retryDelaySeconds = match ($attemptCount) {
1 => 60, // Reintento en 1 minuto
2 => 300, // Reintento en 5 minutos
default => 900,
};
as_schedule_single_action(
time() + $retryDelaySeconds,
self::ACTION_SYNC_CRM,
['orderId' => $orderId, 'attemptCount' => $attemptCount + 1],
'agentic-commerce-integrations'
);
} else {
$order->update_meta_data(self::META_LOCK_KEY, 'failed');
$order->add_order_note('[CRM Pipeline] CRÍTICO: Agotados los 3 intentos en segundo plano. Requiere inspección manual.');
$order->save();
}
return;
}
// Registro de finalización satisfactoria
$order->update_meta_data(self::META_LOCK_KEY, 'completed');
$order->update_meta_data('_agentic_external_lead_id', sanitize_text_field((string) $result['leadId']));
$order->add_order_note("[CRM Pipeline] Pedido sincronizado con éxito en ERP. ID externo: {$result['leadId']}");
$order->save();
}
/**
* Emulación de envío de datos a endpoint REST exterior.
*
* @param array<string, mixed> $payload
* @return array<string, mixed>|WP_Error
*/
private function dispatchToExternalCrm(array $payload): array|WP_Error
{
// En producción: wp_remote_post('https://api.crm.internal/leads', ...)
return [
'success' => true,
'leadId' => 'LEAD-' . strtoupper(bin2hex(random_bytes(4))),
];
}
}
(new OrderLifecycleOrchestrator())->registerHooks();
Identifique el evento de negocio: confirmación de pago (woocommerce_payment_complete), orden de empaquetado (status_processing) o entrega efectiva (status_completed).
Valide los metadatos: si la tarea figura como encolada o completada, aborte la ejecución de inmediato para evitar redundancias.
Invoque as_enqueue_async_action() con el identificador del pedido, garantizando tiempos de respuesta ultrarrápidos (< 150ms) en la interfaz.
Desarrolle el worker de segundo plano de modo que ante caídas de red se reprograme tras 1, 5 y 15 minutos, manteniendo un registro auditable en Order Notes.