diff --git a/includes/Feed/FeedUploadUtils.php b/includes/Feed/FeedUploadUtils.php index 4dd14772f..39de738b3 100644 --- a/includes/Feed/FeedUploadUtils.php +++ b/includes/Feed/FeedUploadUtils.php @@ -163,9 +163,13 @@ public static function get_coupons_data( array $query_args ): array { // Map target type. Coupons that apply both a discount and free shipping are already // filtered out in is_valid_coupon - $is_free_shipping = $coupon->get_free_shipping(); + $is_free_shipping = $coupon->get_free_shipping(); + $target_shipping_options = ''; if ( $is_free_shipping ) { - $target_type = self::TARGET_TYPE_SHIPPING; + $target_type = self::TARGET_TYPE_SHIPPING; + $value_type = self::VALUE_TYPE_PERCENTAGE; + $percent_off = '100'; // 100% off shipping + $target_shipping_options = [ 'STANDARD' ]; // options are STANDARD, RUSH, EXPEDITED, TWO_DAY } else { $target_type = self::TARGET_TYPE_LINE_ITEM; } @@ -212,7 +216,7 @@ public static function get_coupons_data( array $query_args ): array { 'fixed_amount_off' => $fixed_amount_off, 'application_type' => self::APPLICATION_TYPE_BUYER_APPLIED, 'target_type' => $target_type, - 'target_shipping_option_types' => '', // Not needed for offsite checkout + 'target_shipping_option_types' => $target_shipping_options, 'target_granularity' => $target_granularity, 'target_selection' => $target_selection, 'start_date_time' => $start_date_time, diff --git a/includes/Feed/PromotionsFeed.php b/includes/Feed/PromotionsFeed.php index 6bc01cd10..bcd995b4b 100644 --- a/includes/Feed/PromotionsFeed.php +++ b/includes/Feed/PromotionsFeed.php @@ -25,7 +25,7 @@ */ class PromotionsFeed extends AbstractFeed { /** Header for the promotions feed file. @var string */ - const PROMOTIONS_FEED_HEADER = 'offer_id,title,value_type,percent_off,fixed_amount_off,application_type,target_type,target_shipipng_option_types,target_granularity,target_selection,start_date_time,end_date_time,coupon_codes,public_coupon_code,target_filter,target_product_retailer_ids,target_product_group_retailer_ids,target_product_set_retailer_ids,redeem_limit_per_user,min_subtotal,min_quantity,offer_terms,redemption_limit_per_seller,target_quantity,prerequisite_filter,prerequisite_product_retailer_ids,prerequisite_product_group_retailer_ids,prerequisite_product_set_retailer_ids,exclude_sale_priced_products' . PHP_EOL; + const PROMOTIONS_FEED_HEADER = 'offer_id,title,value_type,percent_off,fixed_amount_off,application_type,target_type,target_shipping_option_types,target_granularity,target_selection,start_date_time,end_date_time,coupon_codes,public_coupon_code,target_filter,target_product_retailer_ids,target_product_group_retailer_ids,target_product_set_retailer_ids,redeem_limit_per_user,min_subtotal,min_quantity,offer_terms,redemption_limit_per_seller,target_quantity,prerequisite_filter,prerequisite_product_retailer_ids,prerequisite_product_group_retailer_ids,prerequisite_product_set_retailer_ids,exclude_sale_priced_products' . PHP_EOL; /** * Constructor for promotions feed. diff --git a/tests/Unit/Feed/FeedUploadUtilsTest.php b/tests/Unit/Feed/FeedUploadUtilsTest.php index a45abe1d4..c03ff9630 100644 --- a/tests/Unit/Feed/FeedUploadUtilsTest.php +++ b/tests/Unit/Feed/FeedUploadUtilsTest.php @@ -30,7 +30,7 @@ public function setUp(): void { $this->add_filter_with_safe_teardown('pre_option_permalink_structure', function () { return '/%postname%/'; }); - + update_option( 'permalink_structure', '/%postname%/' ); global $wp_rewrite; if ( ! ( $wp_rewrite instanceof WP_Rewrite ) ) { @@ -334,6 +334,71 @@ public function test_get_coupons_data_valid_coupon_with_target_product() { $this->assertEquals( $expected_coupon, $coupon_data, 'Coupon feed data does not match expected data structure.' ); } + public function test_get_coupons_data_valid_shipping_coupon() { + // Create a coupon with a valid coupon code. + $coupon_id = self::factory()->post->create([ + 'post_type' => 'shop_coupon', + 'post_status' => 'publish', + 'post_title' => 'COUPON-CODE-1', + ]); + // Set coupon meta with free_shipping => yes + update_post_meta( $coupon_id, 'discount_type', 'fixed_cart' ); + update_post_meta( $coupon_id, 'coupon_amount', '0' ); + update_post_meta( $coupon_id, 'free_shipping', 'yes' ); + update_post_meta( $coupon_id, 'usage_limit', '' ); + update_post_meta( $coupon_id, 'limit_usage_to_x_items', '' ); + update_post_meta( $coupon_id, 'maximum_amount', '' ); + update_post_meta( $coupon_id, 'email_restrictions', array() ); + + $query_args = [ + 'post_type' => 'shop_coupon', + 'post_status' => 'publish', + 'posts_per_page' => -1, // retrieve all items + ]; + + $result = \WooCommerce\Facebook\Feed\FeedUploadUtils::get_coupons_data( $query_args ); + + // Verify that one coupon is returned. + $this->assertCount( 1, $result, 'Should have returned one coupon in the feed data.' ); + $coupon_data = $result[0]; + + // Build the expected coupon shape according to how FeedUploadUtils outputs the data. + $expected_coupon = [ + 'offer_id' => $coupon_id, // coupon ID as an integer + 'title' => 'coupon-code-1', // lowercased coupon post title + 'value_type' => 'PERCENTAGE', + 'fixed_amount_off' => '0', // empty string output + 'percent_off' => '100', // as a string + 'application_type' => 'BUYER_APPLIED', + 'target_type' => 'SHIPPING', + 'target_granularity' => 'ORDER_LEVEL', + 'target_selection' => 'ALL_CATALOG_PRODUCTS', + 'start_date_time' => $coupon_data['start_date_time'], // use the output from the coupon post date/time + 'end_date_time' => '', + 'coupon_codes' => ['coupon-code-1'], + 'public_coupon_code' => '', + 'target_filter' => '', + 'target_product_retailer_ids' => '', + 'target_product_group_retailer_ids' => '', + 'target_product_set_retailer_ids' => '', + 'redeem_limit_per_user' => 0, + 'min_subtotal' => '', + 'min_quantity' => '', + 'offer_terms' => '', + 'redemption_limit_per_seller' => 0, + 'target_quantity' => '', + 'prerequisite_filter' => '', + 'prerequisite_product_retailer_ids' => '', + 'prerequisite_product_group_retailer_ids' => '', + 'prerequisite_product_set_retailer_ids' => '', + 'exclude_sale_priced_products' => false, + 'target_shipping_option_types' => ['STANDARD'], + ]; + + // Assert that the coupon data exactly matches the expected shape. + $this->assertEquals( $expected_coupon, $coupon_data, 'Coupon feed data does not match expected data structure.' ); + } + public function test_get_coupons_data_coupon_with_included_excluded_products() { // Create products for inclusion and exclusion. $product1 = new WC_Product_Simple();