From 6587b8a998bc5e1b4cc5d0fca29922512b747b02 Mon Sep 17 00:00:00 2001 From: sumaisa-mou Date: Wed, 4 Mar 2026 13:33:41 +0600 Subject: [PATCH 01/10] WIP --- .../Core/Ecommerce/Platforms/WooCommerce.php | 108 ++++++++++++++++++ .../Ecommerce/WooCommerce/CartResource.php | 89 +++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php diff --git a/includes/Core/Ecommerce/Platforms/WooCommerce.php b/includes/Core/Ecommerce/Platforms/WooCommerce.php index 2ba8822..931e6de 100644 --- a/includes/Core/Ecommerce/Platforms/WooCommerce.php +++ b/includes/Core/Ecommerce/Platforms/WooCommerce.php @@ -4,6 +4,7 @@ use WeDevs\WeMail\Core\Ecommerce\Settings; use WeDevs\WeMail\Core\Sync\Ecommerce\RevenueTrack; +use WeDevs\WeMail\Rest\Resources\Ecommerce\WooCommerce\CartResource; use WeDevs\WeMail\Rest\Resources\Ecommerce\WooCommerce\CategoryResource; use WeDevs\WeMail\Rest\Resources\Ecommerce\WooCommerce\OrderResource; use WeDevs\WeMail\Rest\Resources\Ecommerce\WooCommerce\ProductResource; @@ -98,6 +99,12 @@ public function orders( array $args = array() ) { * Register post update hooks */ public function register_hooks() { + // Cart hooks + add_action( 'woocommerce_add_to_cart', array( $this, 'handle_add_to_cart' ), 10, 6 ); + add_action( 'woocommerce_cart_updated', array( $this, 'handle_cart_updated' ), 10, 0 ); + add_action( 'woocommerce_remove_cart_item', array( $this, 'handle_remove_cart_item' ), 10, 2 ); + add_action( 'woocommerce_cart_emptied', array( $this, 'handle_cart_emptied' ), 10, 0 ); + // New order created hook add_action( 'woocommerce_new_order', array( $this, 'handle_new_order' ), 10, 2 ); @@ -120,6 +127,107 @@ public function register_hooks() { add_action( 'delete_product_cat', array( $this, 'handle_category_delete' ), 10, 3 ); } + /** + * Handle add to cart event + * + * @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 + */ + public function handle_add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) { + if ( ! Settings::instance()->is_enabled() ) { + return; + } + + $this->send_cart_data( 'add_to_cart' ); + } + + /** + * Handle cart updated event + */ + public function handle_cart_updated() { + if ( ! Settings::instance()->is_enabled() ) { + return; + } + + $this->send_cart_data( 'cart_updated' ); + } + + /** + * Handle remove cart item event + * + * @param string $cart_item_key Cart item key + * @param \WC_Cart $cart Cart object + */ + public function handle_remove_cart_item( $cart_item_key, $cart ) { + if ( ! Settings::instance()->is_enabled() ) { + return; + } + + $this->send_cart_data( 'remove_cart_item' ); + } + + /** + * Handle cart emptied event + */ + public function handle_cart_emptied() { + if ( ! Settings::instance()->is_enabled() ) { + return; + } + + $this->send_cart_data( 'cart_emptied' ); + } + + /** + * Get or generate cart key + * + * @return string Cart key + */ + private function get_cart_key() { + $cart_key = WC()->session->get( 'wem_cart_key' ); + + if ( ! $cart_key ) { + $cart_key = wp_generate_uuid4(); + WC()->session->set( 'wem_cart_key', $cart_key ); + } + + return $cart_key; + } + + /** + * Send cart data to weMail API + * + * @param string $event Event type + */ + private function send_cart_data( $event ) { + $cart = WC()->cart; + + if ( ! $cart ) { + return; + } + + // Don't send if cart is empty (except for cart_emptied event) + if ( $event !== 'cart_emptied' && $cart->is_empty() ) { + return; + } + + $cart_key = $this->get_cart_key(); + $payload = CartResource::with_customer( $cart, $cart_key ); + $payload['event'] = $event; + $payload['session_id'] = WC()->session->get_customer_id(); + + error_log(print_r($payload, true)); + + wemail()->api + ->send_json() + ->ecommerce() + ->carts() + ->put( $payload ); + } + /** * Handle pending payment status * diff --git a/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php new file mode 100644 index 0000000..f162e99 --- /dev/null +++ b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php @@ -0,0 +1,89 @@ +get_cart(); + $items = array(); + + foreach ( $cart_items as $cart_item_key => $cart_item ) { + $product = $cart_item['data']; + $permalink = $product->get_permalink(); + $thumbnail = wp_get_attachment_image_url( $product->get_image_id() ); + + $items[] = array( + 'cart_item_key' => $cart_item_key, + 'id' => (string) $product->get_id(), + 'parent_id' => (string) $product->get_parent_id(), + 'name' => $product->get_name(), + 'quantity' => $cart_item['quantity'], + 'total' => floatval( $cart_item['line_total'] ), + 'thumbnail' => $thumbnail ? $thumbnail : null, + 'categories' => $product->get_category_ids(), + 'permalink' => $permalink ? $permalink : null, + ); + } + + return array( + 'items' => $items, + 'total' => floatval( $cart->get_total( 'raw' ) ), + 'subtotal' => floatval( $cart->get_subtotal() ), + 'currency' => get_woocommerce_currency(), + 'updated_at' => current_time( self::DATE_FORMAT, true ), + 'source' => 'woocommerce', + ); + } + + /** + * Get cart with customer data and cart key + * + * @param \WC_Cart $cart + * @param string $cart_key + * @return array + */ + public static function with_customer( $cart, $cart_key = null ) { + $data = self::single( $cart ); + $current_user = wp_get_current_user(); + + // Add cart key + $data['cart_key'] = $cart_key; + + if ( $current_user->exists() ) { + $data['customer'] = array( + 'id' => (string) $current_user->ID, + 'first_name' => $current_user->first_name, + 'last_name' => $current_user->last_name, + 'email' => $current_user->user_email, + ); + } else { + // Try to get guest checkout data from WooCommerce session + $customer = WC()->session->get( 'customer' ); + if ( $customer ) { + $data['customer'] = array( + 'first_name' => isset( $customer['first_name'] ) ? $customer['first_name'] : null, + 'last_name' => isset( $customer['last_name'] ) ? $customer['last_name'] : null, + 'email' => isset( $customer['email'] ) ? $customer['email'] : null, + ); + } + } + + return $data; + } +} From d2c9a0510469f26a918a8e45d3c086b1bc127124 Mon Sep 17 00:00:00 2001 From: sumaisa-mou Date: Thu, 12 Mar 2026 12:15:33 +0600 Subject: [PATCH 02/10] Add abandoned cart functionality and update cart data structure --- .../Core/AbandonedCarts/AbandonedCarts.php | 11 +++++ includes/Core/AbandonedCarts/Menu.php | 49 +++++++++++++++++++ .../Core/Ecommerce/Platforms/WooCommerce.php | 17 +++++++ includes/Core/Shortcode/Shortcode.php | 18 +++++++ .../Ecommerce/WooCommerce/CartResource.php | 13 ++--- 5 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 includes/Core/AbandonedCarts/AbandonedCarts.php create mode 100644 includes/Core/AbandonedCarts/Menu.php diff --git a/includes/Core/AbandonedCarts/AbandonedCarts.php b/includes/Core/AbandonedCarts/AbandonedCarts.php new file mode 100644 index 0000000..476262f --- /dev/null +++ b/includes/Core/AbandonedCarts/AbandonedCarts.php @@ -0,0 +1,11 @@ +add_filter( 'wemail_admin_submenu', 'register_submenu', $this->menu_priority, 2 ); + } + + /** + * Register submenu + * + * @since 1.0.0 + * + * @param array $menu_items + * @param string $capability + * + * @return array + */ + public function register_submenu( $menu_items, $capability ) { + if ( wemail()->user->can( 'view_wemail' ) ) { + $menu_items[] = array( __( 'Abandoned Carts', 'wemail' ), $capability, 'admin.php?page=wemail#/abandoned-carts' ); + } + + return $menu_items; + } +} + diff --git a/includes/Core/Ecommerce/Platforms/WooCommerce.php b/includes/Core/Ecommerce/Platforms/WooCommerce.php index 931e6de..4e83765 100644 --- a/includes/Core/Ecommerce/Platforms/WooCommerce.php +++ b/includes/Core/Ecommerce/Platforms/WooCommerce.php @@ -179,6 +179,8 @@ public function handle_cart_emptied() { } $this->send_cart_data( 'cart_emptied' ); + + $this->reset_cart_key(); } /** @@ -197,6 +199,17 @@ private function get_cart_key() { return $cart_key; } + /** + * Reset cart key for current session. + * + * @return void + */ + private function reset_cart_key() { + if ( function_exists( 'WC' ) && WC()->session ) { + WC()->session->__unset( 'wem_cart_key' ); + } + } + /** * Send cart data to weMail API * @@ -255,6 +268,8 @@ public function handle_pending_payment( $order_id ) { ->ecommerce() ->orders( $order_id ) ->put( $payload ); + + $this->reset_cart_key(); } /** @@ -311,6 +326,8 @@ private function process_order( $order_id, $order ) { ->ecommerce() ->orders( $order_id ) ->put( $payload ); + + $this->reset_cart_key(); } /** diff --git a/includes/Core/Shortcode/Shortcode.php b/includes/Core/Shortcode/Shortcode.php index b79dc51..5f0b0ce 100644 --- a/includes/Core/Shortcode/Shortcode.php +++ b/includes/Core/Shortcode/Shortcode.php @@ -110,6 +110,24 @@ public function get( $type = '' ) { ), ); + $shortcodes['abandoned_cart'] = array( + 'title' => __( 'Abandoned Cart', 'wemail' ), + 'codes' => array( + 'items' => array( + 'title' => __( 'Cart Items Table', 'wemail' ), + ), + 'total' => array( + 'title' => __( 'Cart Total', 'wemail' ), + ), + 'recover_url' => array( + 'title' => __( 'Cart Recovery Link', 'wemail' ), + ), + 'count' => array( + 'title' => __( 'Number of Items', 'wemail' ), + ), + ), + ); + if ( ! empty( $type ) && ! empty( $shortcodes[ $type ] ) ) { return $shortcodes[ $type ]; } diff --git a/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php index f162e99..bd683a4 100644 --- a/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php +++ b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php @@ -42,12 +42,13 @@ public function blueprint( $cart ) { } return array( - 'items' => $items, - 'total' => floatval( $cart->get_total( 'raw' ) ), - 'subtotal' => floatval( $cart->get_subtotal() ), - 'currency' => get_woocommerce_currency(), - 'updated_at' => current_time( self::DATE_FORMAT, true ), - 'source' => 'woocommerce', + 'items' => $items, + 'total' => floatval( $cart->get_total( 'raw' ) ), + 'subtotal' => floatval( $cart->get_subtotal() ), + 'currency' => get_woocommerce_currency(), + 'updated_at' => current_time( self::DATE_FORMAT, true ), + 'checkout_url' => wc_get_checkout_url(), + 'source' => 'woocommerce', ); } From 75fef7aea51e17c067f5ac00d34c3a0ff92f1662 Mon Sep 17 00:00:00 2001 From: sumaisa-mou Date: Fri, 13 Mar 2026 13:38:31 +0600 Subject: [PATCH 03/10] Implement cart recovery functionality for abandoned carts --- .../Core/Ecommerce/Platforms/WooCommerce.php | 53 ++++++++++++++++++- .../Ecommerce/WooCommerce/CartResource.php | 10 ++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/includes/Core/Ecommerce/Platforms/WooCommerce.php b/includes/Core/Ecommerce/Platforms/WooCommerce.php index 4e83765..1b417fa 100644 --- a/includes/Core/Ecommerce/Platforms/WooCommerce.php +++ b/includes/Core/Ecommerce/Platforms/WooCommerce.php @@ -99,6 +99,9 @@ public function orders( array $args = array() ) { * Register post update hooks */ public function register_hooks() { + // Cart recovery hook + add_action( 'template_redirect', array( $this, 'handle_cart_recovery' ) ); + // Cart hooks add_action( 'woocommerce_add_to_cart', array( $this, 'handle_add_to_cart' ), 10, 6 ); add_action( 'woocommerce_cart_updated', array( $this, 'handle_cart_updated' ), 10, 0 ); @@ -183,6 +186,54 @@ public function handle_cart_emptied() { $this->reset_cart_key(); } + /** + * Handle cart recovery from abandoned cart email link + */ + public function handle_cart_recovery() { + if ( ! isset( $_GET['wemail-recover-cart'] ) ) { + return; + } + + $token = sanitize_text_field( wp_unslash( $_GET['wemail-recover-cart'] ) ); + + if ( empty( $token ) ) { + return; + } + + if ( ! function_exists( 'WC' ) || ! WC()->cart ) { + return; + } + + $response = wemail()->api + ->abandoned_carts() + ->recover() + ->query( array( 'token' => $token ) ) + ->get(); + + if ( is_wp_error( $response ) || empty( $response['data']['items'] ) ) { + wp_safe_redirect( wc_get_checkout_url() ); + exit; + } + + WC()->cart->empty_cart(); + + $items = $response['data']['items']; + + foreach ( $items as $item ) { + $product_id = isset( $item['product_id'] ) ? absint( $item['product_id'] ) : 0; + $quantity = isset( $item['quantity'] ) ? absint( $item['quantity'] ) : 1; + $variation_id = isset( $item['variation_id'] ) ? absint( $item['variation_id'] ) : 0; + $variation_attr = isset( $item['variation'] ) ? (array) $item['variation'] : array(); + + if ( $product_id ) { + WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variation_attr ); + } + } + + wp_safe_redirect( wc_get_checkout_url() ); + exit; + } + /** * Get or generate cart key * @@ -232,8 +283,6 @@ private function send_cart_data( $event ) { $payload['event'] = $event; $payload['session_id'] = WC()->session->get_customer_id(); - error_log(print_r($payload, true)); - wemail()->api ->send_json() ->ecommerce() diff --git a/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php index bd683a4..80a2268 100644 --- a/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php +++ b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php @@ -24,14 +24,18 @@ public function blueprint( $cart ) { $items = array(); foreach ( $cart_items as $cart_item_key => $cart_item ) { - $product = $cart_item['data']; - $permalink = $product->get_permalink(); - $thumbnail = wp_get_attachment_image_url( $product->get_image_id() ); + $product = $cart_item['data']; + $permalink = $product->get_permalink(); + $thumbnail = wp_get_attachment_image_url( $product->get_image_id() ); + $product_id = isset( $cart_item['product_id'] ) ? $cart_item['product_id'] : $product->get_id(); + $variation_id = isset( $cart_item['variation_id'] ) ? $cart_item['variation_id'] : 0; $items[] = array( 'cart_item_key' => $cart_item_key, 'id' => (string) $product->get_id(), 'parent_id' => (string) $product->get_parent_id(), + 'product_id' => (string) $product_id, + 'variation_id' => (string) $variation_id, 'name' => $product->get_name(), 'quantity' => $cart_item['quantity'], 'total' => floatval( $cart_item['line_total'] ), From 4a665ad31fae25eb7b02319a16dbe29b1a45193f Mon Sep 17 00:00:00 2001 From: sumaisa-mou Date: Fri, 13 Mar 2026 14:52:44 +0600 Subject: [PATCH 04/10] Add product id and variant id --- includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php index 80a2268..f5b6b6a 100644 --- a/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php +++ b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php @@ -52,6 +52,7 @@ public function blueprint( $cart ) { 'currency' => get_woocommerce_currency(), 'updated_at' => current_time( self::DATE_FORMAT, true ), 'checkout_url' => wc_get_checkout_url(), + 'home_url' => home_url(), 'source' => 'woocommerce', ); } From 8dbb46cac37846a883d8651dcb5d7836359d64d4 Mon Sep 17 00:00:00 2001 From: sumaisa-mou Date: Mon, 16 Mar 2026 10:42:42 +0600 Subject: [PATCH 05/10] Add coupon creation functionality for cart recovery --- .../Core/Ecommerce/Platforms/WooCommerce.php | 37 +++++++++++++ includes/Core/Shortcode/Shortcode.php | 3 ++ includes/Rest/Ecommerce/Ecommerce.php | 53 +++++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/includes/Core/Ecommerce/Platforms/WooCommerce.php b/includes/Core/Ecommerce/Platforms/WooCommerce.php index 1b417fa..885eaf3 100644 --- a/includes/Core/Ecommerce/Platforms/WooCommerce.php +++ b/includes/Core/Ecommerce/Platforms/WooCommerce.php @@ -204,6 +204,8 @@ public function handle_cart_recovery() { return; } + $coupon_code = isset( $_GET['coupon'] ) ? sanitize_text_field( wp_unslash( $_GET['coupon'] ) ) : ''; + $response = wemail()->api ->abandoned_carts() ->recover() @@ -230,10 +232,45 @@ public function handle_cart_recovery() { } } + if ( ! empty( $coupon_code ) && ! empty( $response['data']['discount'] ) ) { + $this->create_recovery_coupon( $coupon_code, $response['data']['discount'] ); + WC()->cart->apply_coupon( $coupon_code ); + } + wp_safe_redirect( wc_get_checkout_url() ); exit; } + /** + * Create a WooCommerce coupon for cart recovery + * + * @param string $code Coupon code + * @param array $discount Discount settings from API response + */ + private function create_recovery_coupon( $code, $discount ) { + $existing = wc_get_coupon_id_by_code( $code ); + + if ( $existing ) { + return; + } + + $coupon = new \WC_Coupon(); + + $coupon->set_code( $code ); + $coupon->set_amount( floatval( $discount['amount'] ) ); + $coupon->set_usage_limit( 1 ); + $coupon->set_individual_use( true ); + + $discount_type = isset( $discount['type'] ) ? $discount['type'] : 'percent'; + $coupon->set_discount_type( 'percent' === $discount_type ? 'percent' : 'fixed_cart' ); + + if ( ! empty( $discount['expiry_date'] ) ) { + $coupon->set_date_expires( $discount['expiry_date'] ); + } + + $coupon->save(); + } + /** * Get or generate cart key * diff --git a/includes/Core/Shortcode/Shortcode.php b/includes/Core/Shortcode/Shortcode.php index 5f0b0ce..3ba83c2 100644 --- a/includes/Core/Shortcode/Shortcode.php +++ b/includes/Core/Shortcode/Shortcode.php @@ -125,6 +125,9 @@ public function get( $type = '' ) { 'count' => array( 'title' => __( 'Number of Items', 'wemail' ), ), + 'coupon_code' => array( + 'title' => __( 'Coupon Code', 'wemail' ), + ), ), ); diff --git a/includes/Rest/Ecommerce/Ecommerce.php b/includes/Rest/Ecommerce/Ecommerce.php index 43d59d2..0ec8aff 100644 --- a/includes/Rest/Ecommerce/Ecommerce.php +++ b/includes/Rest/Ecommerce/Ecommerce.php @@ -20,6 +20,7 @@ class Ecommerce extends RestController { public function register_routes() { $this->post( '/(?Pwoocommerce|edd)/settings', 'settings' ); $this->get( '/(?Pwoocommerce|edd)/(?Pproducts|orders|categories)', 'resource', 'permission' ); + $this->post( '/coupons', 'create_coupon', 'permission' ); $this->delete( '/disconnect', 'disconnect', 'permission' ); } @@ -107,6 +108,58 @@ public function disconnect() { return new \WP_REST_Response( $response ); } + /** + * Create a WooCommerce coupon + * + * @param \WP_REST_Request $request + * + * @return \WP_REST_Response|\WP_Error + */ + public function create_coupon( $request ) { + if ( ! function_exists( 'WC' ) ) { + return $this->respond_error( 'WooCommerce is not active.', 'woocommerce_inactive', self::HTTP_UNPROCESSABLE_ENTITY ); + } + + $data = $request->get_json_params(); + + if ( empty( $data['code'] ) ) { + return $this->respond_error( 'Coupon code is required.', 'missing_coupon_code', self::HTTP_UNPROCESSABLE_ENTITY ); + } + + $existing = wc_get_coupon_id_by_code( $data['code'] ); + + if ( $existing ) { + return $this->respond_error( 'Coupon code already exists.', 'coupon_exists', self::HTTP_CONFLICT ); + } + + $coupon = new \WC_Coupon(); + + $coupon->set_code( sanitize_text_field( $data['code'] ) ); + $coupon->set_discount_type( isset( $data['discount_type'] ) ? sanitize_text_field( $data['discount_type'] ) : 'percent' ); + $coupon->set_amount( isset( $data['amount'] ) ? floatval( $data['amount'] ) : 0 ); + $coupon->set_usage_limit( isset( $data['usage_limit'] ) ? absint( $data['usage_limit'] ) : 1 ); + $coupon->set_individual_use( ! empty( $data['individual_use'] ) ); + $coupon->set_free_shipping( ! empty( $data['free_shipping'] ) ); + + if ( ! empty( $data['date_expires'] ) ) { + $coupon->set_date_expires( $data['date_expires'] ); + } + + if ( ! empty( $data['email_restrictions'] ) && is_array( $data['email_restrictions'] ) ) { + $coupon->set_email_restrictions( array_map( 'sanitize_email', $data['email_restrictions'] ) ); + } + + $coupon->save(); + + return $this->respond( + array( + 'id' => $coupon->get_id(), + 'code' => $coupon->get_code(), + ), + self::HTTP_CREATED + ); + } + /** * Rest permission * From f5fe688b20a1f1b0ee091c78640514205eb4ad33 Mon Sep 17 00:00:00 2001 From: sumaisa-mou Date: Mon, 16 Mar 2026 14:36:18 +0600 Subject: [PATCH 06/10] Remove unused product and parent IDs from cart item data --- includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php index f5b6b6a..49ed0ec 100644 --- a/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php +++ b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php @@ -32,8 +32,6 @@ public function blueprint( $cart ) { $items[] = array( 'cart_item_key' => $cart_item_key, - 'id' => (string) $product->get_id(), - 'parent_id' => (string) $product->get_parent_id(), 'product_id' => (string) $product_id, 'variation_id' => (string) $variation_id, 'name' => $product->get_name(), From f2e4af3f59ff371da19d235c516bc10aca5c7d38 Mon Sep 17 00:00:00 2001 From: sumaisa-mou Date: Fri, 27 Mar 2026 14:12:16 +0600 Subject: [PATCH 07/10] Refactor --- includes/Core/Ecommerce/Platforms/WooCommerce.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/includes/Core/Ecommerce/Platforms/WooCommerce.php b/includes/Core/Ecommerce/Platforms/WooCommerce.php index 885eaf3..f8eeb14 100644 --- a/includes/Core/Ecommerce/Platforms/WooCommerce.php +++ b/includes/Core/Ecommerce/Platforms/WooCommerce.php @@ -207,6 +207,7 @@ public function handle_cart_recovery() { $coupon_code = isset( $_GET['coupon'] ) ? sanitize_text_field( wp_unslash( $_GET['coupon'] ) ) : ''; $response = wemail()->api + ->ecommerce() ->abandoned_carts() ->recover() ->query( array( 'token' => $token ) ) @@ -232,9 +233,11 @@ public function handle_cart_recovery() { } } - if ( ! empty( $coupon_code ) && ! empty( $response['data']['discount'] ) ) { + if ( ! empty( $coupon_code ) ) { $this->create_recovery_coupon( $coupon_code, $response['data']['discount'] ); WC()->cart->apply_coupon( $coupon_code ); + WC()->cart->calculate_totals(); + WC()->session->save_data(); } wp_safe_redirect( wc_get_checkout_url() ); From 833b71db46a2b53309af84734088873409fe0d75 Mon Sep 17 00:00:00 2001 From: sumaisa-mou Date: Fri, 27 Mar 2026 17:07:38 +0600 Subject: [PATCH 08/10] Remove coupon creation logic and ensure applied coupons are removed during cart recovery --- .../Core/Ecommerce/Platforms/WooCommerce.php | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/includes/Core/Ecommerce/Platforms/WooCommerce.php b/includes/Core/Ecommerce/Platforms/WooCommerce.php index f8eeb14..3eb4bbb 100644 --- a/includes/Core/Ecommerce/Platforms/WooCommerce.php +++ b/includes/Core/Ecommerce/Platforms/WooCommerce.php @@ -220,6 +220,10 @@ public function handle_cart_recovery() { WC()->cart->empty_cart(); + foreach ( WC()->cart->get_applied_coupons() as $coupon ) { + WC()->cart->remove_coupon( $coupon ); + } + $items = $response['data']['items']; foreach ( $items as $item ) { @@ -233,8 +237,7 @@ public function handle_cart_recovery() { } } - if ( ! empty( $coupon_code ) ) { - $this->create_recovery_coupon( $coupon_code, $response['data']['discount'] ); + if ( ! empty( $coupon_code ) && wc_get_coupon_id_by_code( $coupon_code ) ) { WC()->cart->apply_coupon( $coupon_code ); WC()->cart->calculate_totals(); WC()->session->save_data(); @@ -244,36 +247,6 @@ public function handle_cart_recovery() { exit; } - /** - * Create a WooCommerce coupon for cart recovery - * - * @param string $code Coupon code - * @param array $discount Discount settings from API response - */ - private function create_recovery_coupon( $code, $discount ) { - $existing = wc_get_coupon_id_by_code( $code ); - - if ( $existing ) { - return; - } - - $coupon = new \WC_Coupon(); - - $coupon->set_code( $code ); - $coupon->set_amount( floatval( $discount['amount'] ) ); - $coupon->set_usage_limit( 1 ); - $coupon->set_individual_use( true ); - - $discount_type = isset( $discount['type'] ) ? $discount['type'] : 'percent'; - $coupon->set_discount_type( 'percent' === $discount_type ? 'percent' : 'fixed_cart' ); - - if ( ! empty( $discount['expiry_date'] ) ) { - $coupon->set_date_expires( $discount['expiry_date'] ); - } - - $coupon->save(); - } - /** * Get or generate cart key * From 9470cd9d9199397f2588f391c2fbfb24847c8d84 Mon Sep 17 00:00:00 2001 From: sumaisa-mou Date: Fri, 27 Mar 2026 17:12:29 +0600 Subject: [PATCH 09/10] Fix phpcs --- includes/Core/AbandonedCarts/AbandonedCarts.php | 1 - includes/Core/AbandonedCarts/Menu.php | 1 - 2 files changed, 2 deletions(-) diff --git a/includes/Core/AbandonedCarts/AbandonedCarts.php b/includes/Core/AbandonedCarts/AbandonedCarts.php index 476262f..61ed2db 100644 --- a/includes/Core/AbandonedCarts/AbandonedCarts.php +++ b/includes/Core/AbandonedCarts/AbandonedCarts.php @@ -8,4 +8,3 @@ class AbandonedCarts { use Core; } - diff --git a/includes/Core/AbandonedCarts/Menu.php b/includes/Core/AbandonedCarts/Menu.php index cd36b06..06e5281 100644 --- a/includes/Core/AbandonedCarts/Menu.php +++ b/includes/Core/AbandonedCarts/Menu.php @@ -46,4 +46,3 @@ public function register_submenu( $menu_items, $capability ) { return $menu_items; } } - From f8489d9ddec71571f3b3fc0eb60032ea341456bc Mon Sep 17 00:00:00 2001 From: sumaisa-mou Date: Sat, 28 Mar 2026 13:38:14 +0600 Subject: [PATCH 10/10] Fix critical issue. --- .../Core/Ecommerce/Platforms/WooCommerce.php | 37 +++++++++++++------ .../Ecommerce/WooCommerce/CartResource.php | 3 +- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/includes/Core/Ecommerce/Platforms/WooCommerce.php b/includes/Core/Ecommerce/Platforms/WooCommerce.php index 3eb4bbb..3c1294c 100644 --- a/includes/Core/Ecommerce/Platforms/WooCommerce.php +++ b/includes/Core/Ecommerce/Platforms/WooCommerce.php @@ -14,6 +14,13 @@ class WooCommerce extends AbstractPlatform { use Singleton; + /** + * Flag to prevent recursive hook firing during cart recovery + * + * @var bool + */ + private $is_recovering = false; + /** * Get currency * @@ -141,7 +148,7 @@ public function register_hooks() { * @param array $cart_item_data Cart item data */ public function handle_add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) { - if ( ! Settings::instance()->is_enabled() ) { + if ( $this->is_recovering || ! Settings::instance()->is_enabled() ) { return; } @@ -152,7 +159,7 @@ public function handle_add_to_cart( $cart_item_key, $product_id, $quantity, $var * Handle cart updated event */ public function handle_cart_updated() { - if ( ! Settings::instance()->is_enabled() ) { + if ( $this->is_recovering || ! Settings::instance()->is_enabled() ) { return; } @@ -166,7 +173,7 @@ public function handle_cart_updated() { * @param \WC_Cart $cart Cart object */ public function handle_remove_cart_item( $cart_item_key, $cart ) { - if ( ! Settings::instance()->is_enabled() ) { + if ( $this->is_recovering || ! Settings::instance()->is_enabled() ) { return; } @@ -177,7 +184,7 @@ public function handle_remove_cart_item( $cart_item_key, $cart ) { * Handle cart emptied event */ public function handle_cart_emptied() { - if ( ! Settings::instance()->is_enabled() ) { + if ( $this->is_recovering || ! Settings::instance()->is_enabled() ) { return; } @@ -218,11 +225,9 @@ public function handle_cart_recovery() { exit; } - WC()->cart->empty_cart(); + $this->is_recovering = true; - foreach ( WC()->cart->get_applied_coupons() as $coupon ) { - WC()->cart->remove_coupon( $coupon ); - } + WC()->cart->empty_cart(); $items = $response['data']['items']; @@ -233,16 +238,22 @@ public function handle_cart_recovery() { $variation_attr = isset( $item['variation'] ) ? (array) $item['variation'] : array(); if ( $product_id ) { - WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variation_attr ); + try { + WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variation_attr ); + } catch ( \Exception $e ) { + error_log( 'weMail cart recovery: failed to add product ' . $product_id . ' - ' . $e->getMessage() ); + } } } if ( ! empty( $coupon_code ) && wc_get_coupon_id_by_code( $coupon_code ) ) { WC()->cart->apply_coupon( $coupon_code ); - WC()->cart->calculate_totals(); - WC()->session->save_data(); } + WC()->cart->calculate_totals(); + + $this->is_recovering = false; + wp_safe_redirect( wc_get_checkout_url() ); exit; } @@ -280,6 +291,10 @@ private function reset_cart_key() { * @param string $event Event type */ private function send_cart_data( $event ) { + if ( ! WC()->session || ! WC()->session->get_customer_id() ) { + return; + } + $cart = WC()->cart; if ( ! $cart ) { diff --git a/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php index 49ed0ec..45ea90d 100644 --- a/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php +++ b/includes/Rest/Resources/Ecommerce/WooCommerce/CartResource.php @@ -2,7 +2,6 @@ namespace WeDevs\WeMail\Rest\Resources\Ecommerce\WooCommerce; -use DateTimeZone; use WeDevs\WeMail\Rest\Resources\JsonResource; class CartResource extends JsonResource { @@ -10,7 +9,7 @@ class CartResource extends JsonResource { /** * Date format */ - const DATE_FORMAT = 'Y-m-d h:i:s'; + const DATE_FORMAT = 'Y-m-d H:i:s'; /** * Structure of cart