Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Improve coupon validation with extensible filter hook #2509

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
115 changes: 74 additions & 41 deletions includes/Order/Hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
namespace WeDevs\Dokan\Order;

use Exception;
use WC_Coupon;
use WC_Discounts;
use WC_Order;
use WC_Product;

// don't call the file directly
if ( ! defined( 'ABSPATH' ) ) {
Expand Down Expand Up @@ -46,7 +49,7 @@
add_action( 'dokan_checkout_update_order_meta', 'dokan_sync_insert_order' );

// prevent non-vendor coupons from being added
add_filter( 'woocommerce_coupon_is_valid', [ $this, 'ensure_vendor_coupon' ], 10, 3 );
add_filter( 'woocommerce_coupon_is_valid', [ $this, 'ensure_coupon_is_valid' ], 10, 3 );

if ( is_admin() ) {
add_action( 'woocommerce_process_shop_order_meta', 'dokan_sync_insert_order', 60 );
Expand Down Expand Up @@ -407,64 +410,94 @@
* sure a product of the admin is in the cart. Otherwise it wouldn't be
* possible to distribute the coupon in sub orders.
*
* @param boolean $valid
* @param \WC_Coupon $coupon
* @param \WC_Discounts $discount
* @since DOKAN_SINCE Refactored to make it more flexible, and added filter
*
* @throws Exception
* @return boolean|Exception
* @param boolean $valid Whether the coupon is currently considered valid.
* @param WC_Coupon $coupon The coupon object being validated.
* @param WC_Discounts $discounts The discount object containing cart/order items being validated.
*
* @return boolean True if the coupon is valid, false otherwise
* @throws Exception When the coupon is invalid for multiple vendors
*/
public function ensure_vendor_coupon( $valid, $coupon, $discount ) {
public function ensure_coupon_is_valid( bool $valid, WC_Coupon $coupon, WC_Discounts $discounts ): bool {
$available_vendors = [];
$available_products = [];

if ( WC()->cart && ! WC()->cart->is_empty() ) {
foreach ( WC()->cart->get_cart() as $item ) {
$product_id = $item['data']->get_id();
$available_vendors[] = (int) dokan_get_vendor_by_product( $product_id, true );
$available_products[] = $product_id;
}
} else {
foreach ( $discount->get_items() as $item ) {
if ( ! isset( $item->product ) || ! $item->product instanceof \WC_Product ) {
continue;
}
foreach ( $discounts->get_items() as $item ) {
if ( ! isset( $item->product ) || ! $item->product instanceof WC_Product ) {
continue;
}

$item_id = $item->product->get_id();
$item_id = $item->product->get_id();

$available_vendors[] = (int) dokan_get_vendor_by_product( $item_id, true );
$available_products[] = $item_id;
}
}
$available_vendors[] = (int) dokan_get_vendor_by_product( $item_id, true );
$available_products[] = $item_id;
}

$available_vendors = array_unique( $available_vendors );

if ( $coupon->is_type( 'fixed_cart' ) && count( $available_vendors ) > 1 ) {
throw new Exception( esc_html__( 'This coupon is invalid for multiple vendors.', 'dokan-lite' ) );
}

// Make sure applied coupon created by admin
if ( apply_filters( 'dokan_ensure_admin_have_create_coupon', $valid, $coupon, $available_vendors, $available_products ) ) {
return true;
}

if ( ! apply_filters( 'dokan_ensure_vendor_coupon', true ) ) {
return $valid;
}

// A coupon must be bound with a product
if ( ! dokan()->is_pro_exists() && count( $coupon->get_product_ids() ) === 0 ) {
throw new Exception( esc_html__( 'A coupon must be restricted with a vendor product.', 'dokan-lite' ) );
/**
* Filter to customize minimum amount validation for vendor coupons.
*
* Allows modifying whether a coupon should be valid based on the minimum order amount requirement.
*
* @since DOKAN_SINCE
*
* @param bool $is_valid True if the minimum amount requirement is met
* @param WC_Coupon $coupon The coupon object being validated
* @param float $subtotal The current order subtotal
* @param WC_Discounts $discounts The WC_Discounts object containing cart/order details
*/
if ( ! apply_filters( 'dokan_coupon_validate_minimum_amount', true, $coupon, $discounts ) ) {
throw new Exception(
sprintf(
/* translators: %s: minimum spend amount for coupon */
esc_html__( 'The minimum spend for this coupon is %s', 'dokan' ),

Check failure on line 459 in includes/Order/Hooks.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Mismatched text domain. Expected 'dokan-lite' but got 'dokan'.
mralaminahamed marked this conversation as resolved.
Show resolved Hide resolved
esc_html( wc_price( $coupon->get_minimum_amount() ) )
),
108
);
}

$coupon_id = $coupon->get_id();
$vendor_id = intval( get_post_field( 'post_author', $coupon_id ) );

if ( ! in_array( $vendor_id, $available_vendors, true ) ) {
return false;
/**
* Filter to customize maximum amount validation for vendor coupons.
*
* Allows modifying whether a coupon should be valid based on the maximum order amount requirement.
*
* @since DOKAN_SINCE
*
* @param bool $is_valid True if the maximum amount requirement is met
* @param WC_Coupon $coupon The coupon object being validated
* @param float $subtotal The current order subtotal
* @param WC_Discounts $discounts The WC_Discounts object containing cart/order details
*/
if ( ! apply_filters( 'dokan_coupon_validate_maximum_amount', true, $coupon, $discounts ) ) {
throw new Exception(
sprintf(
/* translators: %s: maximum spend amount for coupon */
esc_html__( 'The maximum spend for this coupon is %s', 'dokan' ),

Check failure on line 482 in includes/Order/Hooks.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Mismatched text domain. Expected 'dokan-lite' but got 'dokan'.
esc_html( wc_price( $coupon->get_maximum_amount() ) )
),
108
);
}

return $valid;
/**
* Filter the validity of a coupon.
*
* @since DOKAN_SINCE
*
* @param boolean $valid The validity of the coupon.
* @param \WC_Coupon $coupon The coupon object.
* @param array $available_vendors List of available vendors.
* @param array $available_products List of available products.
* @param \WC_Discounts $discounts The discount object, which contains the order details.
*/
return apply_filters( 'dokan_coupon_is_valid', $valid, $coupon, $available_vendors, $available_products, $discounts );
}

/**
Expand Down
Loading