dispatcher = $dispatcher; $this->abandoned_cart_service = $abandoned_cart_service; } /** * Register hooks. * * @return void */ public function register(): void { $triggers = WorkflowTriggers::getTriggers(true); foreach ( $triggers as $trigger => $data ) { $callback = $this->getTriggerHookCallback( $trigger ); if ( $callback && is_callable( $callback ) ) { $wrapped_callback = function( ...$args ) use ( $callback, $trigger ) { return call_user_func_array( $callback, $args ); }; // Use the wrapped callback instead of the original one. add_action( "woocommerce_{$trigger}", $wrapped_callback, 10, $data['accepted_args'] ); } //if ( $callback && is_callable( $callback ) ) { // add_action( "woocommerce_{$trigger}", $callback, 10, $data['accepted_args'] ); //} } } protected function getTriggerHookCallback( string $trigger ) { $parts = explode( '_', $trigger ); $method_name = 'on' . implode( '', array_map( 'ucfirst', $parts ) ); if ( method_exists( $this, $method_name ) ) { return array( $this, $method_name ); } return null; } // ------------------------------------------------------------------------- // 1. Checkout / Order Creation Hooks // ------------------------------------------------------------------------- /** * Dispatch OrderCreatedEvent when order is created during checkout. * * @param \WC_Order $order Order object. * @return void */ public function onCheckoutOrderCreated( $order ): void { if ( ! $order instanceof \WC_Order ) { return; } $order_id = $order->get_id(); if ( $order_id <= 0 ) { return; } $this->dispatcher->dispatch( new OrderCreatedEvent( $order_id ) ); // Mark abandoned cart as recovered if customer completed checkout. if ( $this->abandoned_cart_service ) { $this->abandoned_cart_service->markRecovered( $order ); } } /** * Dispatch OrderCreatedEvent when a new order is created (checkout, admin, API). * * @param int $order_id Order ID. * @param \WC_Order $order Order object. * @return void */ public function onNewOrder( int $order_id, $order = null ): void { $order_id = (int) $order_id; if ( $order_id <= 0 ) { return; } $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'new_order', array( 'order_id' => $order_id ) ) ); } // ------------------------------------------------------------------------- // 2. Order Status Change Hooks // ------------------------------------------------------------------------- /** * Dispatch when checkout order meta is updated. * * @param int $order_id Order ID. * @param array $data Posted checkout data. * @return void */ public function onCheckoutUpdateOrderMeta( int $order_id, array $data ): void { $order_id = (int) $order_id; if ( $order_id <= 0 ) { return; } $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'checkout_update_order_meta', array( 'order_id' => $order_id, 'data' => $data, ) ) ); } /** * Dispatch OrderStatusChangedEvent when order status changes. * * @param int $order_id Order ID. * @param string $old_status Old status. * @param string $new_status New status. * @param \WC_Order $order Order object. * @return void */ public function onOrderStatusChanged( int $order_id, string $old_status, string $new_status, $order ): void { $order_id = (int) $order_id; if ( $order_id <= 0 ) { return; } $order = wc_get_order( $order_id ); if ( ! $order ) { return; } // Check if a more specific hook was already processed by the plugin (which means all specific hooks should add this meta_data to the order) $meta_key = '_afromewo_order_status_changed_already'; if ( $order->get_meta( $meta_key, true ) ) { $order->delete_meta_data( $meta_key ); $order->save(); return; } $this->dispatcher->dispatch( new OrderStatusChangedEvent( $order_id, $old_status, $new_status ) ); } /** * Dispatch when order enters processing status. * * @param int $order_id Order ID. * @param \WC_Order $order Order object. * @return void */ public function onOrderStatusProcessing( int $order_id, $order ): void { $this->dispatchStatusEvent( 'order_status_processing', $order_id, $order ); } /** * Dispatch when order enters completed status. * * @param int $order_id Order ID. * @param \WC_Order $order Order object. * @return void */ public function onOrderStatusCompleted( int $order_id, $order ): void { $this->dispatchStatusEvent( 'order_status_completed', $order_id, $order ); } /** * Dispatch when order is cancelled. * * @param int $order_id Order ID. * @param \WC_Order $order Order object. * @return void */ public function onOrderStatusCancelled( int $order_id, $order ): void { $this->dispatchStatusEvent( 'order_status_cancelled', $order_id, $order ); } /** * Dispatch when order is refunded. * * @param int $order_id Order ID. * @param \WC_Order $order Order object. * @return void */ public function onOrderStatusRefunded( int $order_id, $order ): void { $this->dispatchStatusEvent( 'order_status_refunded', $order_id, $order ); } /** * Dispatch when order enters pending status. * * @param int $order_id Order ID. * @param \WC_Order $order Order object. * @return void */ public function onOrderStatusPending( int $order_id, $order ): void { $this->dispatchStatusEvent( 'order_status_pending', $order_id, $order ); } /** * Dispatch when checkout order is fully processed (classic checkout). * * @param int $order_id Order ID. * @param array $posted_data Posted checkout data. * @param \WC_Order $order Order object. * @return void */ public function onCheckoutOrderProcessed( int $order_id, array $posted_data, $order ): void { $order_id = (int) $order_id; if ( $order_id <= 0 ) { return; } $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'checkout_order_processed', array( 'order_id' => $order_id, 'posted_data' => $posted_data, ) ) ); } /** * Dispatch when block checkout order is processed. * * @param \WC_Order $order Order object. * @return void */ public function onStoreApiCheckoutOrderProcessed( $order ): void { if ( ! $order instanceof \WC_Order ) { return; } $order_id = $order->get_id(); if ( $order_id <= 0 ) { return; } $this->dispatcher->dispatch( new OrderCreatedEvent( $order_id ) ); if ( $this->abandoned_cart_service ) { $this->abandoned_cart_service->markRecovered( $order ); } $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'checkout_order_processed', array( 'order_id' => $order_id ) ) ); } // ------------------------------------------------------------------------- // 3. Payment Hooks // ------------------------------------------------------------------------- /** * Dispatch when order transitions from pending to cancelled. * * @param int $order_id Order ID. * @param \WC_Order $order Order object. * @return void */ public function onOrderStatusPendingToCancelled( int $order_id, $order ): void { $order_id = (int) $order_id; if ( $order_id <= 0 ) { return; } $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'order_status_pending_to_cancelled', array( 'order_id' => $order_id ) ) ); } /** * Dispatch when order enters failed status. * * @param int $order_id Order ID. * @param \WC_Order $order Order object. * @return void */ public function onOrderStatusFailed( int $order_id, $order ): void { $this->dispatchStatusEvent( 'order_status_failed', $order_id, $order ); } /** * Dispatch PaymentCompletedEvent when payment is complete. * * @param int $order_id Order ID. * @return void */ public function onPaymentComplete( int $order_id): void { $order_id = (int) $order_id; if ( $order_id <= 0 ) { return; } $this->dispatcher->dispatch( new PaymentCompletedEvent( $order_id ) ); } // ------------------------------------------------------------------------- // 4. Cart Hooks // ------------------------------------------------------------------------- /** * Dispatch when item is added to cart. * * @param string $cart_item_key Cart item key. * @param int $product_id Product ID. * @param int $quantity Quantity. * @param int $variation_id Variation ID. * @param array $variation Variation data. * @param array $cart_item_data Cart item data. * @return void */ public function onAddToCart( string $cart_item_key, int $product_id, int $quantity, int $variation_id, array $variation, array $cart_item_data ): void { $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'add_to_cart', array( 'cart_item_key' => $cart_item_key, 'product_id' => $product_id, 'quantity' => $quantity, 'variation_id' => $variation_id, 'variation' => $variation, 'cart_item_data' => $cart_item_data, ) ) ); } /** * Dispatch when cart is updated. * * @return void */ public function onCartUpdated(): void { // @TODO: $cart_total = WC()->cart->get_total(); $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'cart_updated', array() ) ); } // ------------------------------------------------------------------------- // 5. User Registration Hooks // ------------------------------------------------------------------------- /** * Dispatch when a new customer is created. * * @param int $customer_id Customer user ID. * @param array $new_customer_data New customer data. * @param bool $password_generated Whether password was generated. * @return void */ public function onCreatedCustomer( int $customer_id, array $new_customer_data, bool $password_generated ): void { $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'created_customer', array( 'customer_id' => $customer_id, 'new_customer_data' => $new_customer_data, 'password_generated' => $password_generated, ) ) ); } // ------------------------------------------------------------------------- // 6. Coupon Hooks // ------------------------------------------------------------------------- /** * Dispatch when a coupon is applied. * * @param string $coupon_code Coupon code. * @return void */ public function onAppliedCoupon( string $coupon_code ): void { $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'applied_coupon', array( 'coupon_code' => $coupon_code, ) ) ); } // ------------------------------------------------------------------------- // 7. Product Purchase Hooks // ------------------------------------------------------------------------- /** * Dispatch when an order item is added. * * @param int $item_id Order item ID. * @param object $item Order item object. * @param int $order_id Order ID. * @return void */ public function onOrderItemAdded( int $item_id, $item, int $order_id ): void { $order_id = (int) $order_id; if ( $order_id <= 0 ) { return; } $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'order_item_added', array( 'item_id' => $item_id, 'order_id' => $order_id, ) ) ); } /** * Fire event when thank you page is loaded. * * @param int $order_id Order ID. * @return void */ public function onThankYouPage( $order_id ): void { $order_id = (int) $order_id; if ( $order_id <= 0 ) { return; } $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'thankyou', array( 'order_id' => $order_id ) ) ); } public function onCartAbandoned(): void { $this->dispatcher->dispatch( new WorkflowTriggerEvent( 'cart_abandoned', array() ) ); } // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- /** * Dispatch a status-specific workflow trigger event. * * @param string $trigger_name Trigger name. * @param int $order_id Order ID. * @param \WC_Order $order Order object. * @return void */ private function dispatchStatusEvent( string $trigger_name, int $order_id, $order ): void { $order_id = (int) $order_id; if ( $order_id <= 0 ) { return; } $payload = array( 'order_id' => $order_id, ); $this->dispatcher->dispatch( new WorkflowTriggerEvent( $trigger_name, $payload ) ); } }