From 04ba84ee32fe3dc13fad201dc93c992af222a194 Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Mon, 23 Dec 2024 15:28:41 +0600 Subject: [PATCH 01/12] feat: added coupon info into meta data when order create --- includes/Vendor/Coupon.php | 205 +++++++++++++++++++++++++++++++++++++ includes/Vendor/Hooks.php | 3 + 2 files changed, 208 insertions(+) create mode 100644 includes/Vendor/Coupon.php diff --git a/includes/Vendor/Coupon.php b/includes/Vendor/Coupon.php new file mode 100644 index 0000000000..101a9a053b --- /dev/null +++ b/includes/Vendor/Coupon.php @@ -0,0 +1,205 @@ +apply_coupon( $coupon ); + + do_action( 'dokan_wc_coupon_applied', $coupon, $discounts, $item, $apply_quantity, $this ); + + add_filter( 'woocommerce_coupon_get_apply_quantity', [ $this, 'intercept_wc_coupon' ], 15, 4 ); + + return $apply_quantity; + } + + /* + * Apply line item coupon discount + * + * @param WC_Coupon $coupon + * @param WC_Discounts $discounts + * @param object $item + * + * @return void + */ + public function save_item_coupon_discount( $coupon, $discounts, $item ) { + $object_to_apply_coupon = $discounts->get_object(); + + if ( $object_to_apply_coupon instanceof WC_Cart ) { + $this->save_coupon_data_to_cart_item( $coupon, $discounts, $item ); + } + + if ( $object_to_apply_coupon instanceof WC_Order ) { + $order = wc_get_order( $object_to_apply_coupon->get_id() ); + $this->save_coupon_data_to_order_item( $coupon, $discounts, $item, $order ); + } + } + + /** + * @param WC_Coupon $coupon + * @param WC_Discounts $discounts + * @param object $item + * + * @return void + */ + protected function save_coupon_data_to_cart_item( $coupon, $discounts, $item ) { + $coupon_codes = WC()->cart->get_applied_coupons(); + + $item_key = $item->object['key']; + + $item_object = WC()->cart->cart_contents[ $item_key ]; + $coupon_info = $item_object[ static::DOKAN_COUPON_META_KEY ] ?? []; + + $coupon_info = array_filter( + $coupon_info, function ( $coupon_temp ) use ( $coupon_codes ) { + return in_array( $coupon_temp['coupon_code'], $coupon_codes, true ); + } + ); + + $item_wise_discounts = $discounts->get_discounts(); + + if ( isset( $item_wise_discounts[ $coupon->get_code() ][ $item_key ] ) ) { + $discount_amount = $item_wise_discounts[ $coupon->get_code() ][ $item_key ]; + + $coupon_info[ $coupon->get_code() ] = [ + 'discount' => $discount_amount, + 'coupon_code' => $coupon->get_code(), + 'per_item' => $discount_amount / $item_object['quantity'], + 'product_id' => $item_object['product_id'], + ]; + } + + $item_object[ static::DOKAN_COUPON_META_KEY ] = $coupon_info; + WC()->cart->cart_contents[ $item_object['key'] ] = $item_object; + } + + /** + * Save coupon data to order item + * + * @param WC_Coupon $coupon + * @param WC_Discounts $discounts + * @param object $item + * @param WC_Order $order + * + * @return void + * @throws \Exception + */ + protected function save_coupon_data_to_order_item( $coupon, $discounts, $item, $order ) { + $coupon_codes = $order->get_coupon_codes(); + + /** @var WC_Order_Item_Product $item */ + $order_item = $item->object; + $item_key = $order_item->get_id(); + + $coupon_info = $order_item->get_meta( static::DOKAN_COUPON_META_KEY ); + + if ( ! is_array( $coupon_info ) ) { + $coupon_info = []; + } + + $coupon_info = array_filter( + $coupon_info, function ( $coupon_temp ) use ( $coupon_codes ) { + return in_array( $coupon_temp['coupon_code'], $coupon_codes, true ); + } + ); + + $item_wise_discounts = $discounts->get_discounts(); + + if ( isset( $item_wise_discounts[ $coupon->get_code() ][ $item_key ] ) ) { + $discount_amount = $item_wise_discounts[ $coupon->get_code() ][ $item_key ]; + + $coupon_info[ $coupon->get_code() ] = [ + 'discount' => $discount_amount, + 'coupon_code' => $coupon->get_code(), + 'per_item' => $discount_amount / $order_item->get_quantity(), + 'product_id' => $order_item->get_product_id(), + ]; + } + + wc_update_order_item_meta( $order_item->get_id(), static::DOKAN_COUPON_META_KEY, $coupon_info ); + } + + /** + * Add coupon info to order item + * + * @param $item + * @param $cart_item_key + * @param $values + * + * @return void + */ + public function add_coupon_info_to_order_item( $item, $cart_item_key, $values ) { + if ( ! empty( $values[ static::DOKAN_COUPON_META_KEY ] ) ) { + $total_discount = 0; + $limit_reached = ''; + $coupon_info = $values[ static::DOKAN_COUPON_META_KEY ]; + // Adjust coupon discount greater than product price + foreach ( $coupon_info as $key => $coupon ) { + $total_discount += $coupon['discount']; + $product = wc_get_product( $coupon['product_id'] ); + $total_product_price = $product->get_price() * $values['quantity']; + if ( $limit_reached === 'exit' ) { + $coupon_info[ $key ]['discount'] = 0; + } + if ( $total_discount > $total_product_price && $limit_reached !== 'exit' ) { + $remain_discount = $total_discount - $total_product_price; + $coupon_info[ $key ]['discount'] = $coupon['discount'] - $remain_discount; + $limit_reached = 'exit'; + } + } + $item->add_meta_data( static::DOKAN_COUPON_META_KEY, $coupon_info, true ); + } + } + + /** + * Remove coupon info from cart item + * + * @param $coupon_code + * + * @return void + */ + public function remove_coupon_info_from_cart_item( $coupon_code ) { + $cart = WC()->cart; + $cart_contents = $cart->cart_contents; + + foreach ( $cart_contents as $cart_item_key => $cart_item ) { + $coupon_info = $cart_item[ static::DOKAN_COUPON_META_KEY ] ?? []; + if ( isset( $coupon_info[ $coupon_code ] ) ) { + unset( $coupon_info[ $coupon_code ] ); + + $cart_contents[ $cart_item_key ][ static::DOKAN_COUPON_META_KEY ] = $coupon_info; + } + } + + $cart->set_cart_contents( $cart_contents ); + } +} diff --git a/includes/Vendor/Hooks.php b/includes/Vendor/Hooks.php index 95d5830e19..e30e339755 100644 --- a/includes/Vendor/Hooks.php +++ b/includes/Vendor/Hooks.php @@ -17,5 +17,8 @@ public function __construct() { // init Vendor Settings Manager new SettingsApi\Manager(); + + // vendor coupon distribution + new Coupon(); } } From 2dc425d4097789a65f22ec8a0a26b0c047e57d36 Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Mon, 23 Dec 2024 16:57:56 +0600 Subject: [PATCH 02/12] refactor: using chat-gpt --- includes/Vendor/Coupon.php | 164 ++++++++++++++++++------------------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/includes/Vendor/Coupon.php b/includes/Vendor/Coupon.php index 101a9a053b..a5795749b6 100644 --- a/includes/Vendor/Coupon.php +++ b/includes/Vendor/Coupon.php @@ -19,70 +19,70 @@ public function __construct() { add_action( 'dokan_wc_coupon_applied', [ $this, 'save_item_coupon_discount' ], 10, 3 ); } - /* - * Apply line item coupon discount + /** + * Intercept coupon application to apply line item coupon discount. * - * @param int $apply_quantity - * @param object $item - * @param WC_Coupon $coupon - * @param WC_Discounts $discounts + * @param int $apply_quantity Number of items the coupon applies to. + * @param object $item The cart or order item object. + * @param WC_Coupon $coupon The coupon being applied. + * @param WC_Discounts $discounts Discount object. * * @return int - * @throws \Exception */ - public function intercept_wc_coupon( $apply_quantity, $item, $coupon, $discounts ): int { + public function intercept_wc_coupon( int $apply_quantity, $item, WC_Coupon $coupon, WC_Discounts $discounts ): int { remove_filter( 'woocommerce_coupon_get_apply_quantity', [ $this, 'intercept_wc_coupon' ], 15 ); - $discounts = clone $discounts; - $discounts->apply_coupon( $coupon ); - do_action( 'dokan_wc_coupon_applied', $coupon, $discounts, $item, $apply_quantity, $this ); + $discounts_clone = clone $discounts; + $discounts_clone->apply_coupon( $coupon ); + + do_action( 'dokan_wc_coupon_applied', $coupon, $discounts_clone, $item, $apply_quantity, $this ); add_filter( 'woocommerce_coupon_get_apply_quantity', [ $this, 'intercept_wc_coupon' ], 15, 4 ); return $apply_quantity; } - /* - * Apply line item coupon discount - * - * @param WC_Coupon $coupon - * @param WC_Discounts $discounts - * @param object $item - * - * @return void - */ - public function save_item_coupon_discount( $coupon, $discounts, $item ) { + /** + * Save coupon discount data for an item. + * + * @param WC_Coupon $coupon The coupon being applied. + * @param WC_Discounts $discounts Discount object. + * @param object $item The cart or order item object. + * + * @return void + */ + public function save_item_coupon_discount( WC_Coupon $coupon, WC_Discounts $discounts, $item ): void { $object_to_apply_coupon = $discounts->get_object(); if ( $object_to_apply_coupon instanceof WC_Cart ) { $this->save_coupon_data_to_cart_item( $coupon, $discounts, $item ); - } - - if ( $object_to_apply_coupon instanceof WC_Order ) { + } elseif ( $object_to_apply_coupon instanceof WC_Order ) { $order = wc_get_order( $object_to_apply_coupon->get_id() ); $this->save_coupon_data_to_order_item( $coupon, $discounts, $item, $order ); } } /** - * @param WC_Coupon $coupon - * @param WC_Discounts $discounts - * @param object $item - * - * @return void - */ - protected function save_coupon_data_to_cart_item( $coupon, $discounts, $item ) { - $coupon_codes = WC()->cart->get_applied_coupons(); - + * Save coupon data to a cart item. + * + * @param WC_Coupon $coupon The coupon being applied. + * @param WC_Discounts $discounts Discount object. + * @param object $item The cart item object. + * + * @return void + */ + protected function save_coupon_data_to_cart_item( WC_Coupon $coupon, WC_Discounts $discounts, $item ): void { + $cart = WC()->cart; + $coupon_codes = $cart->get_applied_coupons(); $item_key = $item->object['key']; + $item_object = $cart->cart_contents[ $item_key ]; - $item_object = WC()->cart->cart_contents[ $item_key ]; - $coupon_info = $item_object[ static::DOKAN_COUPON_META_KEY ] ?? []; - + $coupon_info = $item_object[ self::DOKAN_COUPON_META_KEY ] ?? []; $coupon_info = array_filter( - $coupon_info, function ( $coupon_temp ) use ( $coupon_codes ) { - return in_array( $coupon_temp['coupon_code'], $coupon_codes, true ); - } + $coupon_info, + function ( $coupon_temp ) use ( $coupon_codes ) { + return in_array( $coupon_temp['coupon_code'], $coupon_codes, true ); + } ); $item_wise_discounts = $discounts->get_discounts(); @@ -91,45 +91,46 @@ protected function save_coupon_data_to_cart_item( $coupon, $discounts, $item ) { $discount_amount = $item_wise_discounts[ $coupon->get_code() ][ $item_key ]; $coupon_info[ $coupon->get_code() ] = [ - 'discount' => $discount_amount, + 'discount' => $discount_amount, 'coupon_code' => $coupon->get_code(), - 'per_item' => $discount_amount / $item_object['quantity'], - 'product_id' => $item_object['product_id'], + 'per_item' => $discount_amount / $item_object['quantity'], + 'product_id' => $item_object['product_id'], ]; } - $item_object[ static::DOKAN_COUPON_META_KEY ] = $coupon_info; - WC()->cart->cart_contents[ $item_object['key'] ] = $item_object; + $item_object[ self::DOKAN_COUPON_META_KEY ] = $coupon_info; + $cart->cart_contents[ $item_object['key'] ] = $item_object; } /** - * Save coupon data to order item + * Save coupon data to an order item. * - * @param WC_Coupon $coupon - * @param WC_Discounts $discounts - * @param object $item - * @param WC_Order $order + * @param WC_Coupon $coupon The coupon being applied. + * @param WC_Discounts $discounts Discount object. + * @param object $item The order item object. + * @param WC_Order $order The order object. * * @return void * @throws \Exception */ - protected function save_coupon_data_to_order_item( $coupon, $discounts, $item, $order ) { + protected function save_coupon_data_to_order_item( WC_Coupon $coupon, WC_Discounts $discounts, $item, WC_Order $order ): void { $coupon_codes = $order->get_coupon_codes(); - /** @var WC_Order_Item_Product $item */ + /** @var WC_Order_Item_Product $order_item */ $order_item = $item->object; $item_key = $order_item->get_id(); - $coupon_info = $order_item->get_meta( static::DOKAN_COUPON_META_KEY ); + $coupon_info = $order_item->get_meta( self::DOKAN_COUPON_META_KEY ); if ( ! is_array( $coupon_info ) ) { $coupon_info = []; } $coupon_info = array_filter( - $coupon_info, function ( $coupon_temp ) use ( $coupon_codes ) { - return in_array( $coupon_temp['coupon_code'], $coupon_codes, true ); - } + $coupon_info, + function ( $coupon_temp ) use ( $coupon_codes ) { + return in_array( $coupon_temp['coupon_code'], $coupon_codes, true ); + } ); $item_wise_discounts = $discounts->get_discounts(); @@ -138,65 +139,64 @@ protected function save_coupon_data_to_order_item( $coupon, $discounts, $item, $ $discount_amount = $item_wise_discounts[ $coupon->get_code() ][ $item_key ]; $coupon_info[ $coupon->get_code() ] = [ - 'discount' => $discount_amount, + 'discount' => $discount_amount, 'coupon_code' => $coupon->get_code(), - 'per_item' => $discount_amount / $order_item->get_quantity(), - 'product_id' => $order_item->get_product_id(), + 'per_item' => $discount_amount / $order_item->get_quantity(), + 'product_id' => $order_item->get_product_id(), ]; } - wc_update_order_item_meta( $order_item->get_id(), static::DOKAN_COUPON_META_KEY, $coupon_info ); + wc_update_order_item_meta( $order_item->get_id(), self::DOKAN_COUPON_META_KEY, $coupon_info ); } /** - * Add coupon info to order item + * Add coupon info to an order item during checkout. * - * @param $item - * @param $cart_item_key - * @param $values - * - * @return void + * @param WC_Order_Item_Product $item The order item object. + * @param string $cart_item_key The cart item key. + * @param array $values Cart item values. */ - public function add_coupon_info_to_order_item( $item, $cart_item_key, $values ) { - if ( ! empty( $values[ static::DOKAN_COUPON_META_KEY ] ) ) { + public function add_coupon_info_to_order_item( $item, $cart_item_key, $values ): void { + if ( ! empty( $values[ self::DOKAN_COUPON_META_KEY ] ) ) { $total_discount = 0; - $limit_reached = ''; - $coupon_info = $values[ static::DOKAN_COUPON_META_KEY ]; - // Adjust coupon discount greater than product price + $limit_reached = false; + $coupon_info = $values[ self::DOKAN_COUPON_META_KEY ]; + foreach ( $coupon_info as $key => $coupon ) { $total_discount += $coupon['discount']; $product = wc_get_product( $coupon['product_id'] ); $total_product_price = $product->get_price() * $values['quantity']; - if ( $limit_reached === 'exit' ) { + + if ( $limit_reached ) { $coupon_info[ $key ]['discount'] = 0; } - if ( $total_discount > $total_product_price && $limit_reached !== 'exit' ) { + + if ( $total_discount > $total_product_price && $limit_reached === false ) { $remain_discount = $total_discount - $total_product_price; $coupon_info[ $key ]['discount'] = $coupon['discount'] - $remain_discount; - $limit_reached = 'exit'; + $limit_reached = true; } } - $item->add_meta_data( static::DOKAN_COUPON_META_KEY, $coupon_info, true ); + + $item->add_meta_data( self::DOKAN_COUPON_META_KEY, $coupon_info, true ); } } /** - * Remove coupon info from cart item - * - * @param $coupon_code + * Remove coupon info from a cart item when a coupon is removed. * - * @return void + * @param string $coupon_code The coupon code being removed. */ - public function remove_coupon_info_from_cart_item( $coupon_code ) { + public function remove_coupon_info_from_cart_item( string $coupon_code ): void { $cart = WC()->cart; $cart_contents = $cart->cart_contents; foreach ( $cart_contents as $cart_item_key => $cart_item ) { - $coupon_info = $cart_item[ static::DOKAN_COUPON_META_KEY ] ?? []; + $coupon_info = $cart_item[ self::DOKAN_COUPON_META_KEY ] ?? []; + if ( isset( $coupon_info[ $coupon_code ] ) ) { unset( $coupon_info[ $coupon_code ] ); - - $cart_contents[ $cart_item_key ][ static::DOKAN_COUPON_META_KEY ] = $coupon_info; + $cart_contents[ $cart_item_key ][ self::DOKAN_COUPON_META_KEY ] = $coupon_info; } } From ba4767c355fdf52b2cf25dbfa559f1492959dfd9 Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Mon, 23 Dec 2024 17:15:51 +0600 Subject: [PATCH 03/12] process coupon info child order --- includes/Vendor/Coupon.php | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/includes/Vendor/Coupon.php b/includes/Vendor/Coupon.php index a5795749b6..80aeb43565 100644 --- a/includes/Vendor/Coupon.php +++ b/includes/Vendor/Coupon.php @@ -147,6 +147,42 @@ function ( $coupon_temp ) use ( $coupon_codes ) { } wc_update_order_item_meta( $order_item->get_id(), self::DOKAN_COUPON_META_KEY, $coupon_info ); + + // apply coupon sub order + if ( $order->get_meta( 'has_sub_order' ) ) { + $this->process_coupon_into_child_orders( $order, $coupon ); + } + } + + /** + * Process coupon for child orders. + * + * @param WC_Order $order + * @param WC_Coupon $coupon + * + * @return void + * @throws \Exception + */ + public function process_coupon_into_child_orders( WC_Order $order, WC_Coupon $coupon ): void { + $sub_orders = dokan()->order->get_child_orders( $order->get_id() ); + foreach ( $sub_orders as $sub_order ) { + // Check if the coupon is already applied + $used_coupons = $sub_order->get_coupon_codes(); + $coupon_code = $coupon->get_code(); + if ( in_array( $coupon_code, $used_coupons, true ) ) { + continue; + } + + // Apply the coupon + $coupon = new WC_Coupon( $coupon_code ); + $discount = new WC_Discounts( $sub_order ); + $discount->apply_coupon( $coupon ); + // Add the coupon to the order + $sub_order->apply_coupon( $coupon_code ); + // Recalculate totals + $sub_order->calculate_totals(); + $sub_order->save(); + } } /** From f098cf46c139162b2b86cf1d4f5df22e1055b0e8 Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Mon, 23 Dec 2024 19:25:59 +0600 Subject: [PATCH 04/12] handle coupon remove --- includes/Vendor/Coupon.php | 59 +++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/includes/Vendor/Coupon.php b/includes/Vendor/Coupon.php index 80aeb43565..8d1b1ca483 100644 --- a/includes/Vendor/Coupon.php +++ b/includes/Vendor/Coupon.php @@ -7,6 +7,8 @@ use WC_Order; use WC_Cart; use WC_Order_Item_Product; +use WC_Data; +use Exception; class Coupon { @@ -17,6 +19,40 @@ public function __construct() { add_action( 'woocommerce_removed_coupon', [ $this, 'remove_coupon_info_from_cart_item' ] ); add_filter( 'woocommerce_coupon_get_apply_quantity', [ $this, 'intercept_wc_coupon' ], 15, 4 ); add_action( 'dokan_wc_coupon_applied', [ $this, 'save_item_coupon_discount' ], 10, 3 ); + add_filter( 'woocommerce_pre_delete_order_item', [ $this, 'remove_coupon_info_from_order_item' ], 10, 2 ); + } + + /** + * Remove coupon info from an order item when a coupon is removed. + * + * @param mixed $check + * @param WC_Data $data + * @param bool $force_delete + * + * @return mixed + * @throws Exception + */ + public function remove_coupon_info_from_order_item( $check, WC_Data $data ) { + if ( $data instanceof \WC_Order_Item_Coupon ) { + $removed_coupon = $data->get_code(); + $order = wc_get_order( $data->get_order_id() ); + $order_items = $order->get_items(); + + foreach ( $order_items as $order_item ) { + $item = $order_item->get_meta( self::DOKAN_COUPON_META_KEY, true ); + + if ( isset( $item[ $removed_coupon ] ) ) { + unset( $item[ $removed_coupon ] ); + wc_update_order_item_meta( $order_item->get_id(), self::DOKAN_COUPON_META_KEY, $item ); + } + } + + if ( $order->get_meta( 'has_sub_order' ) ) { + $this->remove_coupon_into_child_orders( $order, $removed_coupon ); + } + } + + return $check; } /** @@ -50,6 +86,7 @@ public function intercept_wc_coupon( int $apply_quantity, $item, WC_Coupon $coup * @param object $item The cart or order item object. * * @return void + * @throws Exception */ public function save_item_coupon_discount( WC_Coupon $coupon, WC_Discounts $discounts, $item ): void { $object_to_apply_coupon = $discounts->get_object(); @@ -111,7 +148,7 @@ function ( $coupon_temp ) use ( $coupon_codes ) { * @param WC_Order $order The order object. * * @return void - * @throws \Exception + * @throws Exception */ protected function save_coupon_data_to_order_item( WC_Coupon $coupon, WC_Discounts $discounts, $item, WC_Order $order ): void { $coupon_codes = $order->get_coupon_codes(); @@ -161,7 +198,7 @@ function ( $coupon_temp ) use ( $coupon_codes ) { * @param WC_Coupon $coupon * * @return void - * @throws \Exception + * @throws Exception */ public function process_coupon_into_child_orders( WC_Order $order, WC_Coupon $coupon ): void { $sub_orders = dokan()->order->get_child_orders( $order->get_id() ); @@ -179,12 +216,26 @@ public function process_coupon_into_child_orders( WC_Order $order, WC_Coupon $co $discount->apply_coupon( $coupon ); // Add the coupon to the order $sub_order->apply_coupon( $coupon_code ); - // Recalculate totals - $sub_order->calculate_totals(); $sub_order->save(); } } + /** + * @param WC_Order $order + * @param string $removed_coupon + * @return void + */ + private function remove_coupon_into_child_orders( WC_Order $order, string $removed_coupon ) { + $sub_orders = dokan()->order->get_child_orders( $order->get_id() ); + foreach ( $sub_orders as $sub_order ) { + $used_coupons = $sub_order->get_coupon_codes(); + if ( in_array( $removed_coupon, $used_coupons, true ) ) { + $sub_order->remove_coupon( $removed_coupon ); + $sub_order->save(); + } + } + } + /** * Add coupon info to an order item during checkout. * From a126a95631e22721ca5aa391417cb9aea2cfc278 Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Tue, 24 Dec 2024 16:44:16 +0600 Subject: [PATCH 05/12] distribute copuon info into sub orders --- includes/Order/Manager.php | 76 +++++++++++++++++++++----------------- includes/Vendor/Coupon.php | 15 +++----- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/includes/Order/Manager.php b/includes/Order/Manager.php index 345d4c4fe6..f33e3940b2 100644 --- a/includes/Order/Manager.php +++ b/includes/Order/Manager.php @@ -8,6 +8,7 @@ use WC_Order_Refund; use WeDevs\Dokan\Cache; use WeDevs\Dokan\Utilities\OrderUtil; +use WeDevs\Dokan\Vendor\Coupon; use WP_Error; /** @@ -628,7 +629,7 @@ public function create_sub_order( $parent_order, $seller_id, $seller_products ) $this->create_taxes( $order, $parent_order, $seller_products ); // add coupons if any - $this->create_coupons( $order, $parent_order, $seller_products ); + $this->create_coupons( $order, $parent_order ); $order->save(); // need to save order data before passing it to a hook @@ -814,24 +815,18 @@ private function create_shipping( $order, $parent_order ) { * * @param WC_Order $order * @param WC_Order $parent_order - * @param array $products * * @return void */ - private function create_coupons( $order, $parent_order, $products ) { + private function create_coupons( $order, $parent_order ) { if ( dokan()->is_pro_exists() && property_exists( dokan_pro(), 'vendor_discount' ) ) { // remove vendor discount coupon code changes remove_filter( 'woocommerce_order_get_items', [ dokan_pro()->vendor_discount->woocommerce_hooks, 'replace_coupon_name' ], 10 ); } - $used_coupons = $parent_order->get_items( 'coupon' ); - $product_ids = array_map( - function ( $item ) { - return $item->get_product_id(); - }, $products - ); + $parent_coupons = $parent_order->get_items( 'coupon' ); - if ( ! $used_coupons ) { + if ( ! $parent_coupons ) { return; } @@ -841,32 +836,45 @@ function ( $item ) { return; } - foreach ( $used_coupons as $item ) { - /** - * @var WC_Order_Item_Coupon $item - */ - $coupon = new \WC_Coupon( $item->get_code() ); - - if ( - apply_filters( 'dokan_should_copy_coupon_to_sub_order', true, $coupon, $item, $order ) && - ( - array_intersect( $product_ids, $coupon->get_product_ids() ) || - apply_filters( 'dokan_is_order_have_admin_coupon', false, $coupon, [ $seller_id ], $product_ids ) - ) - ) { - $new_item = new WC_Order_Item_Coupon(); - $new_item->set_props( - [ - 'code' => $item->get_code(), - 'discount' => $item->get_discount(), - 'discount_tax' => $item->get_discount_tax(), - ] - ); - - $new_item->add_meta_data( 'coupon_data', $coupon->get_data() ); + $parent_coupons = array_map( + function ( $coupon ) { + /** @var WC_Order_Item_Coupon $coupon */ + return $coupon->get_code(); + }, + $parent_coupons + ); - $order->add_item( $new_item ); + $order_items = $order->get_items(); + $used_coupons_data = []; + foreach ( $order_items as $order_item ) { + $item_coupons = $order_item->get_meta( Coupon::DOKAN_COUPON_META_KEY, true ); + if ( ! is_array( $item_coupons ) ) { + continue; } + foreach ( $item_coupons as $code => $item ) { + if ( ! isset( $used_coupons_data[ $code ] ) ) { + $used_coupons_data[ $code ] = 0; + } + if ( in_array( $code, $parent_coupons, true ) ) { + $used_coupons_data[ $code ] += $item['discount']; + } + } + } + + foreach ( $used_coupons_data as $code => $total_discount ) { + $coupon = new \WC_Coupon( $code ); + $coupon_item = new WC_Order_Item_Coupon(); + $coupon_item->set_props( + [ + 'code' => $code, + 'discount' => $total_discount, + 'discount_tax' => 0, + ] + ); + $coupon_info = $coupon->get_short_info(); + $coupon_item->add_meta_data( 'coupon_info', $coupon_info ); + $coupon_item->add_meta_data( 'coupon_data', $coupon->get_data() ); + $order->add_item( $coupon_item ); } } diff --git a/includes/Vendor/Coupon.php b/includes/Vendor/Coupon.php index 8d1b1ca483..4b55ae1ca9 100644 --- a/includes/Vendor/Coupon.php +++ b/includes/Vendor/Coupon.php @@ -130,8 +130,8 @@ function ( $coupon_temp ) use ( $coupon_codes ) { $coupon_info[ $coupon->get_code() ] = [ 'discount' => $discount_amount, 'coupon_code' => $coupon->get_code(), - 'per_item' => $discount_amount / $item_object['quantity'], - 'product_id' => $item_object['product_id'], + 'per_qty_amount' => $discount_amount / $item_object['quantity'], + 'quantity' => $item_object['quantity'], ]; } @@ -178,8 +178,8 @@ function ( $coupon_temp ) use ( $coupon_codes ) { $coupon_info[ $coupon->get_code() ] = [ 'discount' => $discount_amount, 'coupon_code' => $coupon->get_code(), - 'per_item' => $discount_amount / $order_item->get_quantity(), - 'product_id' => $order_item->get_product_id(), + 'per_qty_amount' => $discount_amount / $order_item->get_quantity(), + 'quantity' => $order_item->get_quantity(), ]; } @@ -209,11 +209,6 @@ public function process_coupon_into_child_orders( WC_Order $order, WC_Coupon $co if ( in_array( $coupon_code, $used_coupons, true ) ) { continue; } - - // Apply the coupon - $coupon = new WC_Coupon( $coupon_code ); - $discount = new WC_Discounts( $sub_order ); - $discount->apply_coupon( $coupon ); // Add the coupon to the order $sub_order->apply_coupon( $coupon_code ); $sub_order->save(); @@ -251,7 +246,7 @@ public function add_coupon_info_to_order_item( $item, $cart_item_key, $values ): foreach ( $coupon_info as $key => $coupon ) { $total_discount += $coupon['discount']; - $product = wc_get_product( $coupon['product_id'] ); + $product = wc_get_product( $item->get_product_id() ); $total_product_price = $product->get_price() * $values['quantity']; if ( $limit_reached ) { From 95f27e66545010e93737c31aaca34781c0a78e19 Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Thu, 26 Dec 2024 11:04:51 +0600 Subject: [PATCH 06/12] fix: coderabbitai feedback --- includes/Vendor/Coupon.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/includes/Vendor/Coupon.php b/includes/Vendor/Coupon.php index 4b55ae1ca9..160ab35d6a 100644 --- a/includes/Vendor/Coupon.php +++ b/includes/Vendor/Coupon.php @@ -130,7 +130,7 @@ function ( $coupon_temp ) use ( $coupon_codes ) { $coupon_info[ $coupon->get_code() ] = [ 'discount' => $discount_amount, 'coupon_code' => $coupon->get_code(), - 'per_qty_amount' => $discount_amount / $item_object['quantity'], + 'per_qty_amount' => $item_object['quantity'] > 0 ? ( $discount_amount / $item_object['quantity'] ) : 0, 'quantity' => $item_object['quantity'], ]; } @@ -255,7 +255,8 @@ public function add_coupon_info_to_order_item( $item, $cart_item_key, $values ): if ( $total_discount > $total_product_price && $limit_reached === false ) { $remain_discount = $total_discount - $total_product_price; - $coupon_info[ $key ]['discount'] = $coupon['discount'] - $remain_discount; + $adjusted_discount = max( $coupon['discount'] - $remain_discount, 0 ); + $coupon_info[ $key ]['discount'] = $adjusted_discount; $limit_reached = true; } } From 418422b53924a4470b4d30bd2a2ded007331b8da Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Thu, 26 Dec 2024 11:12:35 +0600 Subject: [PATCH 07/12] fix: coderabbitai feedback --- includes/Vendor/Coupon.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/includes/Vendor/Coupon.php b/includes/Vendor/Coupon.php index 160ab35d6a..3f16a18bd0 100644 --- a/includes/Vendor/Coupon.php +++ b/includes/Vendor/Coupon.php @@ -27,7 +27,6 @@ public function __construct() { * * @param mixed $check * @param WC_Data $data - * @param bool $force_delete * * @return mixed * @throws Exception @@ -36,6 +35,9 @@ public function remove_coupon_info_from_order_item( $check, WC_Data $data ) { if ( $data instanceof \WC_Order_Item_Coupon ) { $removed_coupon = $data->get_code(); $order = wc_get_order( $data->get_order_id() ); + if ( ! $order ) { + return $check; + } $order_items = $order->get_items(); foreach ( $order_items as $order_item ) { From 2923cbee8dd86595ecc5166b9ef5b744a185ab4f Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Mon, 30 Dec 2024 12:58:26 +0600 Subject: [PATCH 08/12] added refs for coupon intercepter --- includes/Vendor/Coupon.php | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/includes/Vendor/Coupon.php b/includes/Vendor/Coupon.php index 3f16a18bd0..c71dff6ade 100644 --- a/includes/Vendor/Coupon.php +++ b/includes/Vendor/Coupon.php @@ -58,12 +58,24 @@ public function remove_coupon_info_from_order_item( $check, WC_Data $data ) { } /** - * Intercept coupon application to apply line item coupon discount. + * Intercepts coupon application to handle line-item coupon discounts. * - * @param int $apply_quantity Number of items the coupon applies to. + * WooCommerce removes the coupon from the order and recalculates totals. For reference, see: + * @see https://github.com/woocommerce/woocommerce/blob/8abd6e97ca598381cb07287a2e7b735799cb55d5/plugins/woocommerce/includes/abstracts/abstract-wc-order.php#L1339 + * + * WooCommerce does not provide a direct hook to retrieve coupon amounts per line item. However, + * the `get_discounts` method of the `WC_Discounts` class allows access to this information. + * This implementation utilizes the following steps to calculate line-item discounts: + * + * 1. Remove the interfering WC hook used by Dokan hook. + * 2. Reapply the coupon to the order or cart. + * 3. Trigger the Dokan action to apply the coupon to the order or cart. + * 4. Reattach the interfering WC hook used by Dokan hook. + * + * @param int $apply_quantity The number of items to which the coupon applies. * @param object $item The cart or order item object. * @param WC_Coupon $coupon The coupon being applied. - * @param WC_Discounts $discounts Discount object. + * @param WC_Discounts $discounts The discount object managing the coupon. * * @return int */ @@ -178,10 +190,10 @@ function ( $coupon_temp ) use ( $coupon_codes ) { $discount_amount = $item_wise_discounts[ $coupon->get_code() ][ $item_key ]; $coupon_info[ $coupon->get_code() ] = [ - 'discount' => $discount_amount, + 'discount' => $discount_amount, 'coupon_code' => $coupon->get_code(), - 'per_qty_amount' => $discount_amount / $order_item->get_quantity(), - 'quantity' => $order_item->get_quantity(), + 'per_qty_amount' => $order_item->get_quantity() > 0 ? $discount_amount / $order_item->get_quantity() : 0, + 'quantity' => $order_item->get_quantity(), ]; } From 08fb859bcdc663b2ddc911a5c57f75317c410ff7 Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Tue, 31 Dec 2024 12:35:53 +0600 Subject: [PATCH 09/12] fix: remove coupon child order update parent meta data create simple test case for coupon apply --- includes/Vendor/Coupon.php | 21 ++++++++- tests/php/src/Coupon/CouponTest.php | 70 +++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 tests/php/src/Coupon/CouponTest.php diff --git a/includes/Vendor/Coupon.php b/includes/Vendor/Coupon.php index c71dff6ade..8e14f63274 100644 --- a/includes/Vendor/Coupon.php +++ b/includes/Vendor/Coupon.php @@ -40,17 +40,36 @@ public function remove_coupon_info_from_order_item( $check, WC_Data $data ) { } $order_items = $order->get_items(); + $product_ids = []; + foreach ( $order_items as $order_item ) { $item = $order_item->get_meta( self::DOKAN_COUPON_META_KEY, true ); if ( isset( $item[ $removed_coupon ] ) ) { unset( $item[ $removed_coupon ] ); wc_update_order_item_meta( $order_item->get_id(), self::DOKAN_COUPON_META_KEY, $item ); + $product_ids[] = $order_item->get_product_id(); } } - + // Remove coupon with child orders if ( $order->get_meta( 'has_sub_order' ) ) { $this->remove_coupon_into_child_orders( $order, $removed_coupon ); + } else { + // Remove coupon from child order items + $parent_order = wc_get_order( $order->get_parent_id() ); + if ( ! empty( $product_ids ) && $parent_order ) { + $order_items = $parent_order->get_items(); + foreach ( $order_items as $order_item ) { + /** @var WC_Order_Item_Product $order_item */ + if ( in_array( $order_item->get_product_id(), $product_ids, true ) ) { + $item = $order_item->get_meta( self::DOKAN_COUPON_META_KEY, true ); + if ( isset( $item[ $removed_coupon ] ) ) { + unset( $item[ $removed_coupon ] ); + wc_update_order_item_meta( $order_item->get_id(), self::DOKAN_COUPON_META_KEY, $item ); + } + } + } + } } } diff --git a/tests/php/src/Coupon/CouponTest.php b/tests/php/src/Coupon/CouponTest.php new file mode 100644 index 0000000000..3eca0a0b69 --- /dev/null +++ b/tests/php/src/Coupon/CouponTest.php @@ -0,0 +1,70 @@ + 'test_coupon1', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 20, + ], + ], + ]; + $coupon_codes = array_merge( $coupon_codes, $coupons ); + foreach ( $coupon_codes as $coupon_item ) { + $this->factory()->coupon->create_object( $coupon_item ); + } + return $coupon_codes; + } + public function test_coupon_can_be_applied() { + $order_id = $this->create_multi_vendor_order(); + $order = wc_get_order( $order_id ); + + $coupons = $this->create_coupon(); + + // Apply the coupon to the order + foreach ( $coupons as $coupon ) { + $order->apply_coupon( $coupon['code'] ); + $order->calculate_totals(); + $order->save(); + } + + $expected_parent = [ + 'total' => 58, + 'discount' => 7, + ]; + $expected = [ + [ + 'total' => 27, + 'discount' => 3, + ], + [ + 'total' => 21, + 'discount' => 4, + ], + ]; + + $this->assertEquals( $expected_parent['discount'], $order->get_total_discount() ); + $this->assertEquals( $expected_parent['total'], $order->get_total() ); + + // order has sub orders + $sub_orders = dokan_get_suborder_ids_by( $order_id ); + foreach ( $sub_orders as $key => $sub_order_id ) { + $sub_order = wc_get_order( $sub_order_id ); + $this->assertEquals( $expected[ $key ]['discount'], $sub_order->get_total_discount() ); + $this->assertEquals( $expected[ $key ]['total'], $sub_order->get_total() ); + } + } +} From 5b234d53ca13753bd321bec5bfad4d4bc39e8dd5 Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Thu, 2 Jan 2025 14:29:57 +0600 Subject: [PATCH 10/12] added test coupon test case --- tests/php/src/Coupon/CouponTest.php | 240 ++++++++++++++++++++++------ 1 file changed, 195 insertions(+), 45 deletions(-) diff --git a/tests/php/src/Coupon/CouponTest.php b/tests/php/src/Coupon/CouponTest.php index 3eca0a0b69..49ef36518b 100644 --- a/tests/php/src/Coupon/CouponTest.php +++ b/tests/php/src/Coupon/CouponTest.php @@ -2,69 +2,219 @@ namespace WeDevs\Dokan\Test\Coupon; +use Exception; use WeDevs\Dokan\Test\DokanTestCase; -use WC_Coupon; +/** + * @group coupon-discount + */ class CouponTest extends DokanTestCase { + public int $product_id1; + public int $product_id2; + + public function set_up() { + parent::set_up(); + + $this->seller_id1 = $this->factory()->seller->create(); + $this->seller_id2 = $this->factory()->seller->create(); + + $this->product_id1 = $this->factory()->product + ->set_seller_id( $this->seller_id1 ) + ->create( + [ + 'name' => 'Test Product 1', + 'regular_price' => 50, + 'price' => 50, + ] + ); + $this->product_id2 = $this->factory()->product + ->set_seller_id( $this->seller_id2 ) + ->create( + [ + 'name' => 'Test Product 2', + 'regular_price' => 30, + 'price' => 30, + ] + ); + + $this->customer_id = $this->factory()->customer->create( [ 'email' => 'customer@gmail.com' ] ); + } + + public function get_order_data(): array { + return [ + 'status' => 'wc-completed', + 'customer_id' => $this->customer_id, + 'meta_data' => [], + 'line_items' => [ + [ + 'product_id' => $this->product_id1, // price 50 + 'quantity' => 2, + ], + [ + 'product_id' => $this->product_id2, // price 30 + 'quantity' => 1, + ], + ], + ]; + } + /** - * @param $coupons array - * @return array + * Test coupon data provider for all products. + * + * @return array[] */ - protected function create_coupon( array $coupons = [] ): array { - $coupon_codes = [ - [ - 'code' => 'test_coupon1', - 'status' => 'publish', - 'meta' => [ - 'discount_type' => 'percent', - 'coupon_amount' => 20, + public function data_provider_for_all_products(): array { + return [ + 'coupon-pd-20' => [ + [ + 'code' => 'pd-20', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 20, + ], ], + [ + 'discount' => 26, + 'total_sub_order' => 2, + ], ], ]; - $coupon_codes = array_merge( $coupon_codes, $coupons ); - foreach ( $coupon_codes as $coupon_item ) { - $this->factory()->coupon->create_object( $coupon_item ); - } - return $coupon_codes; } - public function test_coupon_can_be_applied() { - $order_id = $this->create_multi_vendor_order(); + + /** + * Test coupon with all products. + * + * @dataProvider data_provider_for_all_products + * @throws Exception + */ + public function test_coupon_with_all_products( $input, $expected ) { + $coupon1 = $this->factory()->coupon->create_and_get( $input ); + + $order_id = $this->factory()->order + ->set_item_shipping() + ->set_item_coupon( $coupon1 ) + ->create( $this->get_order_data() ); + $order = wc_get_order( $order_id ); + $this->assertEquals( $expected['discount'], $order->get_total_discount(), 'Discount should be 26' ); + // sub order + $sub_orders = dokan_get_suborder_ids_by( $order_id ); + $this->assertCount( $expected['total_sub_order'], $sub_orders, 'Sub orders count should be 2' ); + } - $coupons = $this->create_coupon(); + /** + * Test coupon data provider for specific products. + * + * @return array[] + */ + public function data_provider_for_specific_products(): array { + return [ + 'coupon-pd-20' => [ + [ + 'code' => 'pd-20', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'fixed_product', + 'coupon_amount' => 5, + 'individual_use' => 'no', + ], + ], + [ 'discount' => 10 ], + ], + ]; + } - // Apply the coupon to the order - foreach ( $coupons as $coupon ) { - $order->apply_coupon( $coupon['code'] ); - $order->calculate_totals(); - $order->save(); - } + /** + * Test coupon with specific products. + * + * @dataProvider data_provider_for_specific_products + * @throws Exception + */ + public function test_coupon_with_specific_products( $input, $expected ) { + $input['meta']['product_ids'] = [ $this->product_id1 ]; + $coupon1 = $this->factory()->coupon->create_and_get( $input ); - $expected_parent = [ - 'total' => 58, - 'discount' => 7, - ]; - $expected = [ - [ - 'total' => 27, - 'discount' => 3, + $order_id = $this->factory()->order + ->set_item_shipping() + ->set_item_coupon( $coupon1 ) + ->create( $this->get_order_data() ); + + $order = wc_get_order( $order_id ); + $this->assertEquals( $expected['discount'], $order->get_total_discount(), 'Discount should be 10' ); + } + + public function test_coupon_with_usage_limit() { + $input = [ + 'code' => 'pd-20', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 20, + 'usage_limit' => 1, + ], + ]; + + $coupon1 = $this->factory()->coupon->create_and_get( $input ); + + $order_id = $this->factory()->order + ->set_item_shipping() + ->set_item_coupon( $coupon1 ) + ->create( $this->get_order_data() ); + + $order = wc_get_order( $order_id ); + $this->assertEquals( 26, $order->get_total_discount() ); + + $order_id = $this->factory()->order + ->set_item_shipping() + ->set_item_coupon( $coupon1 ) + ->create( $this->get_order_data() ); + + $order = wc_get_order( $order_id ); + $this->assertEquals( 0, $order->get_total_discount(), 'Discount should be 0' ); + } + + public function test_coupon_with_minimum_amount() { + $input = [ + 'code' => 'pd-20', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 20, + 'minimum_amount' => 200, ], - [ - 'total' => 21, - 'discount' => 4, + ]; + + $coupon1 = $this->factory()->coupon->create_and_get( $input ); + + $order_id = $this->factory()->order + ->set_item_shipping() + ->set_item_coupon( $coupon1 ) + ->create( $this->get_order_data() ); + + $order = wc_get_order( $order_id ); + $this->assertNotContains( $input['code'], $order->get_coupon_codes(), 'Coupon code should not be applied' ); + } + + public function test_coupon_with_email_restrictions() { + $input = [ + 'code' => 'pd-20', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 20, + 'customer_email' => [ 'customer1@gmail.com' ], ], ]; + $coupon1 = $this->factory()->coupon->create_and_get( $input ); - $this->assertEquals( $expected_parent['discount'], $order->get_total_discount() ); - $this->assertEquals( $expected_parent['total'], $order->get_total() ); + $order_id = $this->factory()->order + ->set_item_shipping() + ->set_item_coupon( $coupon1 ) + ->create( $this->get_order_data() ); - // order has sub orders - $sub_orders = dokan_get_suborder_ids_by( $order_id ); - foreach ( $sub_orders as $key => $sub_order_id ) { - $sub_order = wc_get_order( $sub_order_id ); - $this->assertEquals( $expected[ $key ]['discount'], $sub_order->get_total_discount() ); - $this->assertEquals( $expected[ $key ]['total'], $sub_order->get_total() ); - } + $order = wc_get_order( $order_id ); + $this->assertNotContains( $input['code'], $order->get_coupon_codes(), 'Coupon code should not be applied' ); } } From 7b5d06dd894759f9e5007e0012c2eb14a72a7060 Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Fri, 3 Jan 2025 15:29:56 +0600 Subject: [PATCH 11/12] added data provider annotation --- tests/php/src/Coupon/CouponTest.php | 362 ++++++++++++++++------------ 1 file changed, 214 insertions(+), 148 deletions(-) diff --git a/tests/php/src/Coupon/CouponTest.php b/tests/php/src/Coupon/CouponTest.php index 49ef36518b..8db305800e 100644 --- a/tests/php/src/Coupon/CouponTest.php +++ b/tests/php/src/Coupon/CouponTest.php @@ -2,7 +2,6 @@ namespace WeDevs\Dokan\Test\Coupon; -use Exception; use WeDevs\Dokan\Test\DokanTestCase; /** @@ -12,18 +11,24 @@ class CouponTest extends DokanTestCase { public int $product_id1; public int $product_id2; + public int $category_id1; - public function set_up() { - parent::set_up(); - + /** + * Test coupon data provider for all coupons. + * + * @return array[] + */ + public function data_provider(): array { $this->seller_id1 = $this->factory()->seller->create(); $this->seller_id2 = $this->factory()->seller->create(); + // create product category and assign to product + $this->category_id1 = $this->factory()->term->create( [ 'taxonomy' => 'product_cat' ] ); + $this->product_id1 = $this->factory()->product ->set_seller_id( $this->seller_id1 ) ->create( [ - 'name' => 'Test Product 1', 'regular_price' => 50, 'price' => 50, ] @@ -32,17 +37,14 @@ public function set_up() { ->set_seller_id( $this->seller_id2 ) ->create( [ - 'name' => 'Test Product 2', 'regular_price' => 30, 'price' => 30, ] ); $this->customer_id = $this->factory()->customer->create( [ 'email' => 'customer@gmail.com' ] ); - } - public function get_order_data(): array { - return [ + $order_items = [ 'status' => 'wc-completed', 'customer_id' => $this->customer_id, 'meta_data' => [], @@ -57,28 +59,181 @@ public function get_order_data(): array { ], ], ]; - } + $product = wc_get_product( $this->product_id1 ); + $product->set_category_ids( [ $this->category_id1 ] ); + $product->save(); - /** - * Test coupon data provider for all products. - * - * @return array[] - */ - public function data_provider_for_all_products(): array { return [ - 'coupon-pd-20' => [ + 'discount_type_percentage' => [ + [ + 'coupons' => [ + [ + 'code' => 'percent-10', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 20, + ], + ], + ], + 'order_items' => $order_items, + ], + [ + 'discount' => 26, + ], + ], + 'discount_type_fixed_product' => [ + [ + 'coupons' => [ + [ + 'code' => 'fixed-product-5', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'fixed_product', + 'coupon_amount' => 5, + ], + ], + ], + 'order_items' => $order_items, + ], + [ + 'discount' => 15, + ], + ], + 'minimum_amount' => [ + [ + 'coupons' => [ + [ + 'code' => 'min-10', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 10, + 'minimum_amount' => 150, + ], + ], + ], + 'order_items' => $order_items, + ], + [ + 'discount' => 0, + ], + ], + 'product_ids' => [ + [ + 'coupons' => [ + [ + 'code' => 'adm-10', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 10, + 'product_ids' => [ $this->product_id1 ], + ], + ], + ], + 'order_items' => $order_items, + ], + [ + 'discount' => 10, + ], + ], + 'product_categories' => [ + [ + 'coupons' => [ + [ + 'code' => 'cat-10', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 10, + 'product_categories' => [ $this->category_id1 ], + ], + ], + ], + 'order_items' => $order_items, + ], + [ + 'discount' => 10, + ], + ], + 'excluded_product_categories' => [ + [ + 'coupons' => [ + [ + 'code' => 'ex-cat-10', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 10, + 'excluded_product_categories' => [ $this->category_id1 ], + ], + ], + ], + 'order_items' => $order_items, + ], + [ + 'discount' => 13, + ], + ], + 'email_restrictions' => [ + [ + 'coupons' => [ + [ + 'code' => 'email-10', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'fixed_product', + 'coupon_amount' => 10, + 'customer_email' => [ 'customer@gmail.com' ], + ], + ], + ], + 'order_items' => $order_items, + ], + [ + 'discount' => 0, + ], + ], + 'exclude_product_ids' => [ + [ + 'coupons' => [ + [ + 'code' => 'ex-prod-10', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 10, + 'exclude_product_ids' => [ $this->product_id1 ], + ], + ], + ], + 'order_items' => $order_items, + ], + [ + 'discount' => 3, + ], + ], + 'single vendor order' => [ [ - 'code' => 'pd-20', - 'status' => 'publish', - 'meta' => [ - 'discount_type' => 'percent', - 'coupon_amount' => 20, + 'single_vendor' => true, + 'coupons' => [ + [ + 'code' => 'percent-10', + 'status' => 'publish', + 'meta' => [ + 'discount_type' => 'percent', + 'coupon_amount' => 10, + 'free_shipping' => 'yes', + ], + ], ], - ], - [ - 'discount' => 26, - 'total_sub_order' => 2, + 'order_items' => $order_items, ], + [ + 'discount' => 13, + 'free_shipping' => true, + ], ], ]; } @@ -86,135 +241,46 @@ public function data_provider_for_all_products(): array { /** * Test coupon with all products. * - * @dataProvider data_provider_for_all_products - * @throws Exception - */ - public function test_coupon_with_all_products( $input, $expected ) { - $coupon1 = $this->factory()->coupon->create_and_get( $input ); - - $order_id = $this->factory()->order - ->set_item_shipping() - ->set_item_coupon( $coupon1 ) - ->create( $this->get_order_data() ); - - $order = wc_get_order( $order_id ); - $this->assertEquals( $expected['discount'], $order->get_total_discount(), 'Discount should be 26' ); - // sub order - $sub_orders = dokan_get_suborder_ids_by( $order_id ); - $this->assertCount( $expected['total_sub_order'], $sub_orders, 'Sub orders count should be 2' ); - } - - /** - * Test coupon data provider for specific products. - * - * @return array[] + * @dataProvider data_provider */ - public function data_provider_for_specific_products(): array { - return [ - 'coupon-pd-20' => [ + public function test_coupon( $input, $expected ) { + $order_factory = $this->factory()->order; + if ( isset( $input['shipping'] ) ) { + $order_factory->set_item_shipping( $input['shipping'] ); + } else { + $order_factory->set_item_shipping( [ - 'code' => 'pd-20', - 'status' => 'publish', - 'meta' => [ - 'discount_type' => 'fixed_product', - 'coupon_amount' => 5, - 'individual_use' => 'no', - ], - ], - [ 'discount' => 10 ], - ], - ]; - } - - /** - * Test coupon with specific products. - * - * @dataProvider data_provider_for_specific_products - * @throws Exception - */ - public function test_coupon_with_specific_products( $input, $expected ) { - $input['meta']['product_ids'] = [ $this->product_id1 ]; - $coupon1 = $this->factory()->coupon->create_and_get( $input ); - - $order_id = $this->factory()->order - ->set_item_shipping() - ->set_item_coupon( $coupon1 ) - ->create( $this->get_order_data() ); - - $order = wc_get_order( $order_id ); - $this->assertEquals( $expected['discount'], $order->get_total_discount(), 'Discount should be 10' ); - } - - public function test_coupon_with_usage_limit() { - $input = [ - 'code' => 'pd-20', - 'status' => 'publish', - 'meta' => [ - 'discount_type' => 'percent', - 'coupon_amount' => 20, - 'usage_limit' => 1, - ], - ]; - - $coupon1 = $this->factory()->coupon->create_and_get( $input ); - - $order_id = $this->factory()->order - ->set_item_shipping() - ->set_item_coupon( $coupon1 ) - ->create( $this->get_order_data() ); - - $order = wc_get_order( $order_id ); - $this->assertEquals( 26, $order->get_total_discount() ); - - $order_id = $this->factory()->order - ->set_item_shipping() - ->set_item_coupon( $coupon1 ) - ->create( $this->get_order_data() ); - - $order = wc_get_order( $order_id ); - $this->assertEquals( 0, $order->get_total_discount(), 'Discount should be 0' ); - } - - public function test_coupon_with_minimum_amount() { - $input = [ - 'code' => 'pd-20', - 'status' => 'publish', - 'meta' => [ - 'discount_type' => 'percent', - 'coupon_amount' => 20, - 'minimum_amount' => 200, - ], - ]; - - $coupon1 = $this->factory()->coupon->create_and_get( $input ); + 'name' => 'Flat Rate', + 'amount' => 10, + ] + ); + } - $order_id = $this->factory()->order - ->set_item_shipping() - ->set_item_coupon( $coupon1 ) - ->create( $this->get_order_data() ); + foreach ( $input['coupons'] as $coupon ) { + $coupon_item = $this->factory()->coupon->create_and_get( $coupon ); + $order_factory->set_item_coupon( $coupon_item ); + } - $order = wc_get_order( $order_id ); - $this->assertNotContains( $input['code'], $order->get_coupon_codes(), 'Coupon code should not be applied' ); - } + $order_items = $input['order_items']; - public function test_coupon_with_email_restrictions() { - $input = [ - 'code' => 'pd-20', - 'status' => 'publish', - 'meta' => [ - 'discount_type' => 'percent', - 'coupon_amount' => 20, - 'customer_email' => [ 'customer1@gmail.com' ], - ], - ]; - $coupon1 = $this->factory()->coupon->create_and_get( $input ); + if ( isset( $input['line_items'] ) ) { + $order_items['line_items'] = []; + foreach ( $input['line_items'] as $line_item ) { + $product_factory = $this->factory()->product; + if ( isset( $line_item['seller_id'] ) ) { + $product_factory->set_seller_id( $line_item['seller_id'] ); + } + $order_items['line_items'][] = $product_factory->create( $line_item ); + } + } - $order_id = $this->factory()->order - ->set_item_shipping() - ->set_item_coupon( $coupon1 ) - ->create( $this->get_order_data() ); + $order_id = $order_factory->create( $order_items ); $order = wc_get_order( $order_id ); - $this->assertNotContains( $input['code'], $order->get_coupon_codes(), 'Coupon code should not be applied' ); + if ( isset( $expected['count_order'] ) ) { + $sub_orders = dokan_get_suborder_ids_by( $order_id ) ?? []; + $this->assertCount( $expected['count_order'], $sub_orders ); + } + $this->assertEquals( $expected['discount'], $order->get_discount_total() ); } } From 799883993a0cbb385d0c9efda9bdb5077079fd68 Mon Sep 17 00:00:00 2001 From: KAMRUZZAMAN Date: Fri, 3 Jan 2025 18:27:07 +0600 Subject: [PATCH 12/12] remove line items creation declare variable scope --- tests/php/src/Coupon/CouponTest.php | 113 +++++++++------------------- 1 file changed, 34 insertions(+), 79 deletions(-) diff --git a/tests/php/src/Coupon/CouponTest.php b/tests/php/src/Coupon/CouponTest.php index 8db305800e..210e7ee49f 100644 --- a/tests/php/src/Coupon/CouponTest.php +++ b/tests/php/src/Coupon/CouponTest.php @@ -9,32 +9,28 @@ */ class CouponTest extends DokanTestCase { - public int $product_id1; - public int $product_id2; - public int $category_id1; - /** * Test coupon data provider for all coupons. * * @return array[] */ public function data_provider(): array { - $this->seller_id1 = $this->factory()->seller->create(); - $this->seller_id2 = $this->factory()->seller->create(); + $seller_id1 = $this->factory()->seller->create(); + $seller_id2 = $this->factory()->seller->create(); // create product category and assign to product - $this->category_id1 = $this->factory()->term->create( [ 'taxonomy' => 'product_cat' ] ); + $category_id1 = $this->factory()->term->create( [ 'taxonomy' => 'product_cat' ] ); - $this->product_id1 = $this->factory()->product - ->set_seller_id( $this->seller_id1 ) + $product_id1 = $this->factory()->product + ->set_seller_id( $seller_id1 ) ->create( [ 'regular_price' => 50, 'price' => 50, ] ); - $this->product_id2 = $this->factory()->product - ->set_seller_id( $this->seller_id2 ) + $product_id2 = $this->factory()->product + ->set_seller_id( $seller_id2 ) ->create( [ 'regular_price' => 30, @@ -42,25 +38,26 @@ public function data_provider(): array { ] ); - $this->customer_id = $this->factory()->customer->create( [ 'email' => 'customer@gmail.com' ] ); + $customer_id = $this->factory()->customer->create( [ 'email' => 'customer@gmail.com' ] ); $order_items = [ 'status' => 'wc-completed', - 'customer_id' => $this->customer_id, + 'customer_id' => $customer_id, 'meta_data' => [], 'line_items' => [ [ - 'product_id' => $this->product_id1, // price 50 + 'product_id' => $product_id1, // price 50 'quantity' => 2, ], [ - 'product_id' => $this->product_id2, // price 30 + 'product_id' => $product_id2, // price 30 'quantity' => 1, ], ], ]; - $product = wc_get_product( $this->product_id1 ); - $product->set_category_ids( [ $this->category_id1 ] ); + + $product = wc_get_product( $product_id1 ); + $product->set_category_ids( [ $category_id1 ] ); $product->save(); return [ @@ -79,7 +76,7 @@ public function data_provider(): array { 'order_items' => $order_items, ], [ - 'discount' => 26, + 'discount' => 26, // 20% of 130 = 26 ], ], 'discount_type_fixed_product' => [ @@ -97,7 +94,7 @@ public function data_provider(): array { 'order_items' => $order_items, ], [ - 'discount' => 15, + 'discount' => 15, // Order items quantity 3, discount 5 = 3 * 5 = 15 ], ], 'minimum_amount' => [ @@ -116,7 +113,7 @@ public function data_provider(): array { 'order_items' => $order_items, ], [ - 'discount' => 0, + 'discount' => 0, // Order total 130, minimum amount 150, so no discount ], ], 'product_ids' => [ @@ -128,14 +125,14 @@ public function data_provider(): array { 'meta' => [ 'discount_type' => 'percent', 'coupon_amount' => 10, - 'product_ids' => [ $this->product_id1 ], + 'product_ids' => [ $product_id1 ], ], ], ], 'order_items' => $order_items, ], [ - 'discount' => 10, + 'discount' => 10, // Product id 1, discount 10% ], ], 'product_categories' => [ @@ -147,14 +144,14 @@ public function data_provider(): array { 'meta' => [ 'discount_type' => 'percent', 'coupon_amount' => 10, - 'product_categories' => [ $this->category_id1 ], + 'product_categories' => [ $category_id1 ], ], ], ], 'order_items' => $order_items, ], [ - 'discount' => 10, + 'discount' => 10, // Product category 1, discount 10% ], ], 'excluded_product_categories' => [ @@ -166,14 +163,14 @@ public function data_provider(): array { 'meta' => [ 'discount_type' => 'percent', 'coupon_amount' => 10, - 'excluded_product_categories' => [ $this->category_id1 ], + 'excluded_product_categories' => [ $category_id1 ], ], ], ], 'order_items' => $order_items, ], [ - 'discount' => 13, + 'discount' => 13, // Excluded product category 1, discount 10% ], ], 'email_restrictions' => [ @@ -192,7 +189,7 @@ public function data_provider(): array { 'order_items' => $order_items, ], [ - 'discount' => 0, + 'discount' => 0, // Customer email is matched, discount 10 ], ], 'exclude_product_ids' => [ @@ -204,37 +201,16 @@ public function data_provider(): array { 'meta' => [ 'discount_type' => 'percent', 'coupon_amount' => 10, - 'exclude_product_ids' => [ $this->product_id1 ], + 'exclude_product_ids' => [ $product_id1 ], ], ], ], 'order_items' => $order_items, ], [ - 'discount' => 3, + 'discount' => 3, // Excluded product id 1, discount 10% ], ], - 'single vendor order' => [ - [ - 'single_vendor' => true, - 'coupons' => [ - [ - 'code' => 'percent-10', - 'status' => 'publish', - 'meta' => [ - 'discount_type' => 'percent', - 'coupon_amount' => 10, - 'free_shipping' => 'yes', - ], - ], - ], - 'order_items' => $order_items, - ], - [ - 'discount' => 13, - 'free_shipping' => true, - ], - ], ]; } @@ -244,43 +220,22 @@ public function data_provider(): array { * @dataProvider data_provider */ public function test_coupon( $input, $expected ) { - $order_factory = $this->factory()->order; - if ( isset( $input['shipping'] ) ) { - $order_factory->set_item_shipping( $input['shipping'] ); - } else { - $order_factory->set_item_shipping( - [ - 'name' => 'Flat Rate', - 'amount' => 10, - ] - ); - } + $order_factory = $this->factory()->order + ->set_item_shipping( + [ + 'name' => 'Flat Rate', + 'amount' => 10, + ] + ); foreach ( $input['coupons'] as $coupon ) { $coupon_item = $this->factory()->coupon->create_and_get( $coupon ); $order_factory->set_item_coupon( $coupon_item ); } - $order_items = $input['order_items']; - - if ( isset( $input['line_items'] ) ) { - $order_items['line_items'] = []; - foreach ( $input['line_items'] as $line_item ) { - $product_factory = $this->factory()->product; - if ( isset( $line_item['seller_id'] ) ) { - $product_factory->set_seller_id( $line_item['seller_id'] ); - } - $order_items['line_items'][] = $product_factory->create( $line_item ); - } - } - - $order_id = $order_factory->create( $order_items ); + $order_id = $order_factory->create( $input['order_items'] ); $order = wc_get_order( $order_id ); - if ( isset( $expected['count_order'] ) ) { - $sub_orders = dokan_get_suborder_ids_by( $order_id ) ?? []; - $this->assertCount( $expected['count_order'], $sub_orders ); - } $this->assertEquals( $expected['discount'], $order->get_discount_total() ); } }