Merge pull request #16203 from woocommerce/feature/discounts-class-16201
Manual discounts
This commit is contained in:
commit
eb64d59343
|
@ -110,13 +110,11 @@ final class WC_Cart_Totals {
|
||||||
*
|
*
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
* @param object $cart Cart object to calculate totals for.
|
* @param object $cart Cart object to calculate totals for.
|
||||||
* @param object $customer Customer who owns this cart.
|
|
||||||
*/
|
*/
|
||||||
public function __construct( &$cart = null, &$customer = null ) {
|
public function __construct( &$cart = null ) {
|
||||||
$this->object = $cart;
|
|
||||||
$this->calculate_tax = wc_tax_enabled() && ! $customer->get_is_vat_exempt();
|
|
||||||
|
|
||||||
if ( is_a( $cart, 'WC_Cart' ) ) {
|
if ( is_a( $cart, 'WC_Cart' ) ) {
|
||||||
|
$this->object = $cart;
|
||||||
|
$this->calculate_tax = wc_tax_enabled() && ! $cart->get_customer()->get_is_vat_exempt();
|
||||||
$this->calculate();
|
$this->calculate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +231,7 @@ final class WC_Cart_Totals {
|
||||||
$fee->total = wc_add_number_precision_deep( $fee->object->amount );
|
$fee->total = wc_add_number_precision_deep( $fee->object->amount );
|
||||||
|
|
||||||
if ( $this->calculate_tax && $fee->object->taxable ) {
|
if ( $this->calculate_tax && $fee->object->taxable ) {
|
||||||
$fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->object->tax_class, $this->customer ), false );
|
$fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->object->tax_class, $this->object->get_customer() ), false );
|
||||||
$fee->total_tax = array_sum( $fee->taxes );
|
$fee->total_tax = array_sum( $fee->taxes );
|
||||||
|
|
||||||
if ( ! $this->round_at_subtotal() ) {
|
if ( ! $this->round_at_subtotal() ) {
|
||||||
|
@ -321,7 +319,7 @@ final class WC_Cart_Totals {
|
||||||
*/
|
*/
|
||||||
protected function get_item_tax_rates( $item ) {
|
protected function get_item_tax_rates( $item ) {
|
||||||
$tax_class = $item->product->get_tax_class();
|
$tax_class = $item->product->get_tax_class();
|
||||||
return isset( $this->item_tax_rates[ $tax_class ] ) ? $this->item_tax_rates[ $tax_class ] : $this->item_tax_rates[ $tax_class ] = WC_Tax::get_rates( $item->product->get_tax_class(), $this->customer );
|
return isset( $this->item_tax_rates[ $tax_class ] ) ? $this->item_tax_rates[ $tax_class ] : $this->item_tax_rates[ $tax_class ] = WC_Tax::get_rates( $item->product->get_tax_class(), $this->object->get_customer() );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -492,29 +490,53 @@ final class WC_Cart_Totals {
|
||||||
protected function calculate_discounts() {
|
protected function calculate_discounts() {
|
||||||
$this->set_coupons();
|
$this->set_coupons();
|
||||||
|
|
||||||
$discounts = new WC_Discounts( $this->items );
|
$discounts = new WC_Discounts( $this->object );
|
||||||
|
|
||||||
foreach ( $this->coupons as $coupon ) {
|
foreach ( $this->coupons as $coupon ) {
|
||||||
$discounts->apply_coupon( $coupon );
|
$discounts->apply_discount( $coupon );
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->discount_totals = $discounts->get_discounts( true );
|
$this->discount_totals = $discounts->get_discounts_by_item( true );
|
||||||
$this->totals['discounts_total'] = array_sum( $this->discount_totals );
|
$this->totals['discounts_total'] = array_sum( $this->discount_totals );
|
||||||
|
$this->object->coupon_discount_amounts = $discounts->get_discounts_by_coupon();
|
||||||
|
|
||||||
// See how much tax was 'discounted'.
|
// See how much tax was 'discounted' per item and per coupon.
|
||||||
if ( $this->calculate_tax ) {
|
if ( $this->calculate_tax ) {
|
||||||
foreach ( $this->discount_totals as $cart_item_key => $discount ) {
|
$coupon_discount_tax_amounts = array();
|
||||||
$item = $this->items[ $cart_item_key ];
|
$item_taxes = 0;
|
||||||
if ( $item->product->is_taxable() ) {
|
|
||||||
$taxes = WC_Tax::calc_tax( $discount, $item->tax_rates, false );
|
foreach ( $discounts->get_discounts( true ) as $coupon_code => $coupon_discounts ) {
|
||||||
$this->totals['discounts_tax_total'] += $this->round_at_subtotal() ? array_sum( $taxes ) : wc_round_tax_total( array_sum( $taxes ), 0 );
|
$coupon_discount_tax_amounts[ $coupon_code ] = 0;
|
||||||
|
|
||||||
|
foreach ( $coupon_discounts as $item_key => $item_discount ) {
|
||||||
|
$item = $this->items[ $item_key ];
|
||||||
|
|
||||||
|
if ( $item->product->is_taxable() ) {
|
||||||
|
$item_tax = array_sum( WC_Tax::calc_tax( $item_discount, $item->tax_rates, false ) );
|
||||||
|
$item_taxes += $item_tax;
|
||||||
|
$coupon_discount_tax_amounts[ $coupon_code ] += $item_tax;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$applied_coupons = $discounts->get_applied_coupons();
|
$this->totals['discounts_tax_total'] = $item_taxes;
|
||||||
$this->object->coupon_discount_amounts = wp_list_pluck( $applied_coupons, 'discount' );
|
$this->object->coupon_discount_tax_amounts = $coupon_discount_tax_amounts;
|
||||||
$this->object->coupon_discount_tax_amounts = wp_list_pluck( $applied_coupons, 'discount_tax' );
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return discounted tax amount for an item.
|
||||||
|
*
|
||||||
|
* @param object $item
|
||||||
|
* @param int $discount_amount
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function get_item_discount_tax( $item, $discount_amount ) {
|
||||||
|
if ( $item->product->is_taxable() ) {
|
||||||
|
$taxes = WC_Tax::calc_tax( $discount_amount, $item->tax_rates, false );
|
||||||
|
return array_sum( $taxes );
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1098,6 +1098,16 @@ class WC_Cart {
|
||||||
return ( $first_item_subtotal < $second_item_subtotal ) ? 1 : -1;
|
return ( $first_item_subtotal < $second_item_subtotal ) ? 1 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cart's owner.
|
||||||
|
*
|
||||||
|
* @since 3.2.0
|
||||||
|
* @return WC_Customer
|
||||||
|
*/
|
||||||
|
public function get_customer() {
|
||||||
|
return WC()->customer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate totals for the items in the cart.
|
* Calculate totals for the items in the cart.
|
||||||
*/
|
*/
|
||||||
|
@ -1396,7 +1406,7 @@ class WC_Cart {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VAT exemption done at this point - so all totals are correct before exemption
|
// VAT exemption done at this point - so all totals are correct before exemption
|
||||||
if ( WC()->customer->get_is_vat_exempt() ) {
|
if ( $this->get_customer()->get_is_vat_exempt() ) {
|
||||||
$this->remove_taxes();
|
$this->remove_taxes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1412,7 +1422,7 @@ class WC_Cart {
|
||||||
$this->tax_total = WC_Tax::get_tax_total( $this->taxes );
|
$this->tax_total = WC_Tax::get_tax_total( $this->taxes );
|
||||||
|
|
||||||
// VAT exemption done at this point - so all totals are correct before exemption
|
// VAT exemption done at this point - so all totals are correct before exemption
|
||||||
if ( WC()->customer->get_is_vat_exempt() ) {
|
if ( $this->get_customer()->get_is_vat_exempt() ) {
|
||||||
$this->remove_taxes();
|
$this->remove_taxes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1533,12 +1543,12 @@ class WC_Cart {
|
||||||
'ID' => get_current_user_id(),
|
'ID' => get_current_user_id(),
|
||||||
),
|
),
|
||||||
'destination' => array(
|
'destination' => array(
|
||||||
'country' => WC()->customer->get_shipping_country(),
|
'country' => $this->get_customer()->get_shipping_country(),
|
||||||
'state' => WC()->customer->get_shipping_state(),
|
'state' => $this->get_customer()->get_shipping_state(),
|
||||||
'postcode' => WC()->customer->get_shipping_postcode(),
|
'postcode' => $this->get_customer()->get_shipping_postcode(),
|
||||||
'city' => WC()->customer->get_shipping_city(),
|
'city' => $this->get_customer()->get_shipping_city(),
|
||||||
'address' => WC()->customer->get_shipping_address(),
|
'address' => $this->get_customer()->get_shipping_address(),
|
||||||
'address_2' => WC()->customer->get_shipping_address_2(),
|
'address_2' => $this->get_customer()->get_shipping_address_2(),
|
||||||
),
|
),
|
||||||
'cart_subtotal' => $this->get_displayed_subtotal(),
|
'cart_subtotal' => $this->get_displayed_subtotal(),
|
||||||
),
|
),
|
||||||
|
@ -1596,8 +1606,8 @@ class WC_Cart {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( 'yes' === get_option( 'woocommerce_shipping_cost_requires_address' ) ) {
|
if ( 'yes' === get_option( 'woocommerce_shipping_cost_requires_address' ) ) {
|
||||||
if ( ! WC()->customer->has_calculated_shipping() ) {
|
if ( ! $this->get_customer()->has_calculated_shipping() ) {
|
||||||
if ( ! WC()->customer->get_shipping_country() || ( ! WC()->customer->get_shipping_state() && ! WC()->customer->get_shipping_postcode() ) ) {
|
if ( ! $this->get_customer()->get_shipping_country() || ( ! $this->get_customer()->get_shipping_state() && ! $this->get_customer()->get_shipping_postcode() ) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -739,279 +739,22 @@ class WC_Coupon extends WC_Legacy_Coupon {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure coupon exists or throw exception.
|
* Check if a coupon is valid for the cart.
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_exists.
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_exists() {
|
|
||||||
if ( ! $this->get_id() ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_NOT_EXIST );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure coupon usage limit is valid or throw exception.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_usage_limit.
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_usage_limit() {
|
|
||||||
if ( $this->get_usage_limit() > 0 && $this->get_usage_count() >= $this->get_usage_limit() ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_USAGE_LIMIT_REACHED );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure coupon user usage limit is valid or throw exception.
|
|
||||||
*
|
|
||||||
* Per user usage limit - check here if user is logged in (against user IDs).
|
|
||||||
* Checked again for emails later on in WC_Cart::check_customer_coupons().
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_user_usage_limit.
|
|
||||||
* @param int $user_id
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_user_usage_limit( $user_id = 0 ) {
|
|
||||||
if ( empty( $user_id ) ) {
|
|
||||||
$user_id = get_current_user_id();
|
|
||||||
}
|
|
||||||
if ( $this->get_usage_limit_per_user() > 0 && is_user_logged_in() && $this->get_id() && $this->data_store ) {
|
|
||||||
$usage_count = $this->data_store->get_usage_by_user_id( $this, $user_id );
|
|
||||||
if ( $usage_count >= $this->get_usage_limit_per_user() ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_USAGE_LIMIT_REACHED );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure coupon date is valid or throw exception.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_expiry_date.
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_expiry_date() {
|
|
||||||
if ( $this->get_date_expires() && current_time( 'timestamp', true ) > $this->get_date_expires()->getTimestamp() ) {
|
|
||||||
throw new Exception( $error_code = self::E_WC_COUPON_EXPIRED );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure coupon amount is valid or throw exception.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_minimum_amount.
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_minimum_amount() {
|
|
||||||
if ( $this->get_minimum_amount() > 0 && apply_filters( 'woocommerce_coupon_validate_minimum_amount', $this->get_minimum_amount() > WC()->cart->get_displayed_subtotal(), $this ) ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_MIN_SPEND_LIMIT_NOT_MET );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure coupon amount is valid or throw exception.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_maximum_amount.
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_maximum_amount() {
|
|
||||||
if ( $this->get_maximum_amount() > 0 && apply_filters( 'woocommerce_coupon_validate_maximum_amount', $this->get_maximum_amount() < WC()->cart->get_displayed_subtotal(), $this ) ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_MAX_SPEND_LIMIT_MET );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure coupon is valid for products in the cart is valid or throw exception.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_product_ids.
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_product_ids() {
|
|
||||||
if ( sizeof( $this->get_product_ids() ) > 0 ) {
|
|
||||||
$valid_for_cart = false;
|
|
||||||
if ( ! WC()->cart->is_empty() ) {
|
|
||||||
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
|
|
||||||
if ( in_array( $cart_item['product_id'], $this->get_product_ids() ) || in_array( $cart_item['variation_id'], $this->get_product_ids() ) || in_array( $cart_item['data']->get_parent_id(), $this->get_product_ids() ) ) {
|
|
||||||
$valid_for_cart = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( ! $valid_for_cart ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_NOT_APPLICABLE );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure coupon is valid for product categories in the cart is valid or throw exception.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_product_categories.
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_product_categories() {
|
|
||||||
if ( sizeof( $this->get_product_categories() ) > 0 ) {
|
|
||||||
$valid_for_cart = false;
|
|
||||||
if ( ! WC()->cart->is_empty() ) {
|
|
||||||
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
|
|
||||||
if ( $this->get_exclude_sale_items() && $cart_item['data'] && $cart_item['data']->is_on_sale() ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$product_cats = wc_get_product_cat_ids( $cart_item['product_id'] );
|
|
||||||
|
|
||||||
// If we find an item with a cat in our allowed cat list, the coupon is valid
|
|
||||||
if ( sizeof( array_intersect( $product_cats, $this->get_product_categories() ) ) > 0 ) {
|
|
||||||
$valid_for_cart = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( ! $valid_for_cart ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_NOT_APPLICABLE );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure coupon is valid for sale items in the cart is valid or throw exception.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_sale_items.
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_sale_items() {
|
|
||||||
if ( $this->get_exclude_sale_items() ) {
|
|
||||||
$valid_for_cart = false;
|
|
||||||
|
|
||||||
if ( ! WC()->cart->is_empty() ) {
|
|
||||||
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
|
|
||||||
$product = $cart_item['data'];
|
|
||||||
|
|
||||||
if ( ! $product->is_on_sale() ) {
|
|
||||||
$valid_for_cart = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( ! $valid_for_cart ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_NOT_VALID_SALE_ITEMS );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All exclusion rules must pass at the same time for a product coupon to be valid.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_excluded_items.
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_excluded_items() {
|
|
||||||
if ( ! WC()->cart->is_empty() && $this->is_type( wc_get_product_coupon_types() ) ) {
|
|
||||||
$valid = false;
|
|
||||||
|
|
||||||
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
|
|
||||||
if ( $this->is_valid_for_product( $cart_item['data'], $cart_item ) ) {
|
|
||||||
$valid = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! $valid ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_NOT_APPLICABLE );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cart discounts cannot be added if non-eligible product is found in cart.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_eligible_items.
|
|
||||||
*/
|
|
||||||
private function validate_cart_excluded_items() {
|
|
||||||
if ( ! $this->is_type( wc_get_product_coupon_types() ) ) {
|
|
||||||
$this->validate_cart_excluded_product_ids();
|
|
||||||
$this->validate_cart_excluded_product_categories();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exclude products from cart.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_excluded_product_ids.
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_cart_excluded_product_ids() {
|
|
||||||
// Exclude Products.
|
|
||||||
if ( sizeof( $this->get_excluded_product_ids() ) > 0 ) {
|
|
||||||
$valid_for_cart = true;
|
|
||||||
if ( ! WC()->cart->is_empty() ) {
|
|
||||||
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
|
|
||||||
if ( in_array( $cart_item['product_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['variation_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['data']->get_parent_id(), $this->get_excluded_product_ids() ) ) {
|
|
||||||
$valid_for_cart = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( ! $valid_for_cart ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_EXCLUDED_PRODUCTS );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exclude categories from cart.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->validate_coupon_excluded_product_categories.
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function validate_cart_excluded_product_categories() {
|
|
||||||
if ( sizeof( $this->get_excluded_product_categories() ) > 0 ) {
|
|
||||||
$valid_for_cart = true;
|
|
||||||
if ( ! WC()->cart->is_empty() ) {
|
|
||||||
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
|
|
||||||
if ( $this->get_exclude_sale_items() && $cart_item['data'] && $cart_item['data']->is_on_sale() ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$product_cats = wc_get_product_cat_ids( $cart_item['product_id'] );
|
|
||||||
|
|
||||||
if ( sizeof( array_intersect( $product_cats, $this->get_excluded_product_categories() ) ) > 0 ) {
|
|
||||||
$valid_for_cart = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( ! $valid_for_cart ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_EXCLUDED_CATEGORIES );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a coupon is valid.
|
|
||||||
*
|
*
|
||||||
* @deprecated 3.2.0 In favor of WC_Discounts->is_coupon_valid.
|
* @deprecated 3.2.0 In favor of WC_Discounts->is_coupon_valid.
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
* @return bool Validity.
|
* @return bool Validity.
|
||||||
*/
|
*/
|
||||||
public function is_valid() {
|
public function is_valid() {
|
||||||
wc_deprecated_function( 'is_valid', '3.2', 'WC_Discounts->is_coupon_valid' );
|
$discounts = new WC_Discounts( WC()->cart );
|
||||||
|
$valid = $discounts->is_coupon_valid( $this );
|
||||||
|
|
||||||
try {
|
if ( is_wp_error( $valid ) ) {
|
||||||
$this->validate_exists();
|
$this->error_message = $valid->get_error_message();
|
||||||
$this->validate_usage_limit();
|
|
||||||
$this->validate_user_usage_limit();
|
|
||||||
$this->validate_expiry_date();
|
|
||||||
$this->validate_minimum_amount();
|
|
||||||
$this->validate_maximum_amount();
|
|
||||||
$this->validate_product_ids();
|
|
||||||
$this->validate_product_categories();
|
|
||||||
$this->validate_sale_items();
|
|
||||||
$this->validate_excluded_items();
|
|
||||||
$this->validate_cart_excluded_items();
|
|
||||||
|
|
||||||
if ( ! apply_filters( 'woocommerce_coupon_is_valid', true, $this ) ) {
|
|
||||||
throw new Exception( self::E_WC_COUPON_INVALID_FILTERED );
|
|
||||||
}
|
|
||||||
} catch ( Exception $e ) {
|
|
||||||
$this->error_message = $this->get_coupon_error( $e->getMessage() );
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return $valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A discount.
|
||||||
|
*
|
||||||
|
* Represents a fixed, percent or coupon based discount calculated by WC_Discounts class.
|
||||||
|
*
|
||||||
|
* @author Automattic
|
||||||
|
* @package WooCommerce/Classes
|
||||||
|
* @version 3.2.0
|
||||||
|
* @since 3.2.0
|
||||||
|
*/
|
||||||
|
class WC_Discount {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data array, with defaults.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $data = array(
|
||||||
|
'amount' => 0, // Discount amount.
|
||||||
|
'discount_type' => 'fixed', // Fixed, percent, or coupon.
|
||||||
|
'discount_total' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get discount amount.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function get_amount() {
|
||||||
|
return $this->data['amount'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discount amount - either fixed or percentage.
|
||||||
|
*
|
||||||
|
* @param string $raw_amount Amount discount gives.
|
||||||
|
*/
|
||||||
|
public function set_amount( $raw_amount ) {
|
||||||
|
$this->data['amount'] = wc_format_decimal( $raw_amount );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get discount type.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function get_discount_type() {
|
||||||
|
return $this->data['discount_type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set discount type.
|
||||||
|
*
|
||||||
|
* @param string $discount_type Type of discount.
|
||||||
|
*/
|
||||||
|
public function set_discount_type( $discount_type ) {
|
||||||
|
$this->data['discount_type'] = $discount_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get discount total.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function get_discount_total() {
|
||||||
|
return $this->data['discount_total'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discount total.
|
||||||
|
*
|
||||||
|
* @param string $total Total discount applied.
|
||||||
|
*/
|
||||||
|
public function set_discount_total( $total ) {
|
||||||
|
$this->data['discount_total'] = wc_format_decimal( $total );
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,24 +27,52 @@ class WC_Discounts {
|
||||||
/**
|
/**
|
||||||
* An array of discounts which have been applied to items.
|
* An array of discounts which have been applied to items.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array[] Code => Item Key => Value
|
||||||
*/
|
*/
|
||||||
protected $discounts = array();
|
protected $discounts = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of applied coupons codes and total discount.
|
* An array of applied WC_Discount objects.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $applied_coupons = array();
|
protected $manual_discounts = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor. @todo accept order objects.
|
||||||
*
|
*
|
||||||
* @param array $items Items to discount.
|
* @param array $object Cart or order object.
|
||||||
*/
|
*/
|
||||||
public function __construct( $items = array() ) {
|
public function __construct( $object = array() ) {
|
||||||
$this->set_items( $items );
|
if ( is_a( $object, 'WC_Cart' ) ) {
|
||||||
|
$this->set_items_from_cart( $object );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalise cart/order items which will be discounted.
|
||||||
|
*
|
||||||
|
* @since 3.2.0
|
||||||
|
* @param array $cart Cart object.
|
||||||
|
*/
|
||||||
|
public function set_items_from_cart( $cart ) {
|
||||||
|
$this->items = $this->discounts = $this->manual_discounts = array();
|
||||||
|
|
||||||
|
if ( ! is_a( $cart, 'WC_Cart' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $cart->get_cart() as $key => $cart_item ) {
|
||||||
|
$item = new stdClass();
|
||||||
|
$item->key = $key;
|
||||||
|
$item->object = $cart_item;
|
||||||
|
$item->product = $cart_item['data'];
|
||||||
|
$item->quantity = $cart_item['quantity'];
|
||||||
|
$item->price = wc_add_number_precision_deep( $item->product->get_price() ) * $item->quantity;
|
||||||
|
$this->items[ $key ] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
uasort( $this->items, array( $this, 'sort_by_price' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,25 +86,76 @@ class WC_Discounts {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get discount by key without precision.
|
* Get discount by key with or without precision.
|
||||||
*
|
*
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
* @param string $key name of discount row to return.
|
* @param string $key name of discount row to return.
|
||||||
|
* @param bool $in_cents Should the totals be returned in cents, or without precision.
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_discount( $key ) {
|
public function get_discount( $key, $in_cents = false ) {
|
||||||
return isset( $this->discounts[ $key ] ) ? wc_remove_number_precision_deep( $this->discounts[ $key ] ) : 0;
|
$item_discount_totals = $this->get_discounts_by_item( $in_cents );
|
||||||
|
return isset( $item_discount_totals[ $key ] ) ? ( $in_cents ? $item_discount_totals[ $key ] : wc_remove_number_precision( $item_discount_totals[ $key ] ) ) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all discount totals with precision.
|
* Get all discount totals.
|
||||||
*
|
*
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
* @param bool $in_cents Should the totals be returned in cents, or without precision.
|
* @param bool $in_cents Should the totals be returned in cents, or without precision.
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_discounts( $in_cents = false ) {
|
public function get_discounts( $in_cents = false ) {
|
||||||
return $in_cents ? $this->discounts : wc_remove_number_precision_deep ( $this->discounts );
|
$discounts = $this->discounts;
|
||||||
|
|
||||||
|
foreach ( $this->get_manual_discounts() as $manual_discount_key => $manual_discount ) {
|
||||||
|
$discounts[ $manual_discount_key ] = $manual_discount->get_discount_total();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $in_cents ? $discounts : wc_remove_number_precision_deep( $discounts );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all discount totals per item.
|
||||||
|
*
|
||||||
|
* @since 3.2.0
|
||||||
|
* @param bool $in_cents Should the totals be returned in cents, or without precision.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_discounts_by_item( $in_cents = false ) {
|
||||||
|
$discounts = $this->discounts;
|
||||||
|
$item_discount_totals = array_shift( $discounts );
|
||||||
|
|
||||||
|
foreach ( $discounts as $item_discounts ) {
|
||||||
|
foreach ( $item_discounts as $item_key => $item_discount ) {
|
||||||
|
$item_discount_totals[ $item_key ] += $item_discount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $in_cents ? $item_discount_totals : wc_remove_number_precision_deep( $item_discount_totals );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all discount totals per coupon.
|
||||||
|
*
|
||||||
|
* @since 3.2.0
|
||||||
|
* @param bool $in_cents Should the totals be returned in cents, or without precision.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_discounts_by_coupon( $in_cents = false ) {
|
||||||
|
$coupon_discount_totals = array_map( 'array_sum', $this->discounts );
|
||||||
|
|
||||||
|
return $in_cents ? $coupon_discount_totals : wc_remove_number_precision_deep( $coupon_discount_totals );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an array of manual discounts which have been applied.
|
||||||
|
*
|
||||||
|
* @since 3.2.0
|
||||||
|
* @return WC_Discount[]
|
||||||
|
*/
|
||||||
|
public function get_manual_discounts() {
|
||||||
|
return $this->manual_discounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,41 +177,89 @@ class WC_Discounts {
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
public function get_discounted_price_in_cents( $item ) {
|
public function get_discounted_price_in_cents( $item ) {
|
||||||
return $item->price - $this->discounts[ $item->key ];
|
return $item->price - $this->get_discount( $item->key, true );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of applied coupons with name value pairs - name being
|
* Get total remaining after discounts.
|
||||||
* the coupon code, and value being the total amount disounted.
|
|
||||||
*
|
*
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
* @return array
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function get_applied_coupons() {
|
protected function get_total_after_discounts() {
|
||||||
return wc_remove_number_precision_deep( $this->applied_coupons );
|
$total_to_discount = 0;
|
||||||
|
|
||||||
|
foreach ( $this->items as $item ) {
|
||||||
|
$total_to_discount += $this->get_discounted_price_in_cents( $item );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $this->manual_discounts as $key => $value ) {
|
||||||
|
$total_to_discount = $total_to_discount - $value->get_discount_total();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $total_to_discount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set cart/order items which will be discounted.
|
* Generate a unique ID for a discount.
|
||||||
*
|
*
|
||||||
* @since 3.2.0
|
* @param WC_Discount $discount Discount object.
|
||||||
* @param array $items List items.
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function set_items( $items ) {
|
protected function generate_discount_id( $discount ) {
|
||||||
$this->items = array();
|
$discount_id = '';
|
||||||
$this->discounts = array();
|
$index = 1;
|
||||||
$this->applied_coupons = array();
|
while ( ! $discount_id ) {
|
||||||
|
$discount_id = 'discount-' . $discount->get_amount() . ( 'percent' === $discount->get_discount_type() ? '%' : '' );
|
||||||
|
|
||||||
if ( ! empty( $items ) && is_array( $items ) ) {
|
if ( 1 < $index ) {
|
||||||
foreach ( $items as $key => $item ) {
|
$discount_id .= '-' . $index;
|
||||||
$this->items[ $key ] = $item;
|
|
||||||
$this->items[ $key ]->key = $key;
|
|
||||||
$this->items[ $key ]->price = $item->subtotal;
|
|
||||||
}
|
}
|
||||||
$this->discounts = array_fill_keys( array_keys( $items ), 0 );
|
|
||||||
|
if ( isset( $this->manual_discounts[ $discount_id ] ) ) {
|
||||||
|
$index ++;
|
||||||
|
$discount_id = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $discount_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a discount to all items.
|
||||||
|
*
|
||||||
|
* @param string|object $raw_discount Accepts a string (fixed or percent discounts), or WC_Coupon object.
|
||||||
|
* @return bool|WP_Error True if applied or WP_Error instance in failure.
|
||||||
|
*/
|
||||||
|
public function apply_discount( $raw_discount ) {
|
||||||
|
if ( is_a( $raw_discount, 'WC_Coupon' ) ) {
|
||||||
|
return $this->apply_coupon( $raw_discount );
|
||||||
}
|
}
|
||||||
|
|
||||||
uasort( $this->items, array( $this, 'sort_by_price' ) );
|
$discount = new WC_Discount;
|
||||||
|
|
||||||
|
if ( strstr( $raw_discount, '%' ) ) {
|
||||||
|
$discount->set_discount_type( 'percent' );
|
||||||
|
$discount->set_amount( trim( $raw_discount, '%' ) );
|
||||||
|
} elseif ( 0 < absint( $raw_discount ) ) {
|
||||||
|
$discount->set_discount_type( 'fixed' );
|
||||||
|
$discount->set_amount( wc_add_number_precision( absint( $raw_discount ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $discount->get_amount() ) {
|
||||||
|
return new WP_Error( 'invalid_coupon', __( 'Invalid discount', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_to_discount = $this->get_total_after_discounts();
|
||||||
|
|
||||||
|
if ( 'percent' === $discount->get_discount_type() ) {
|
||||||
|
$discount->set_discount_total( $discount->get_amount() * ( $total_to_discount / 100 ) );
|
||||||
|
} else {
|
||||||
|
$discount->set_discount_total( min( $discount->get_amount(), $total_to_discount ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->manual_discounts[ $this->generate_discount_id( $discount ) ] = $discount;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -142,22 +269,15 @@ class WC_Discounts {
|
||||||
* @param WC_Coupon $coupon Coupon object being applied to the items.
|
* @param WC_Coupon $coupon Coupon object being applied to the items.
|
||||||
* @return bool|WP_Error True if applied or WP_Error instance in failure.
|
* @return bool|WP_Error True if applied or WP_Error instance in failure.
|
||||||
*/
|
*/
|
||||||
public function apply_coupon( $coupon ) {
|
protected function apply_coupon( $coupon ) {
|
||||||
if ( ! is_a( $coupon, 'WC_Coupon' ) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$is_coupon_valid = $this->is_coupon_valid( $coupon );
|
$is_coupon_valid = $this->is_coupon_valid( $coupon );
|
||||||
|
|
||||||
if ( is_wp_error( $is_coupon_valid ) ) {
|
if ( is_wp_error( $is_coupon_valid ) ) {
|
||||||
return $is_coupon_valid;
|
return $is_coupon_valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! isset( $this->applied_coupons[ $coupon->get_code() ] ) ) {
|
if ( ! isset( $this->discounts[ $coupon->get_code() ] ) ) {
|
||||||
$this->applied_coupons[ $coupon->get_code() ] = array(
|
$this->discounts[ $coupon->get_code() ] = array_fill_keys( array_keys( $this->items ), 0 );
|
||||||
'discount' => 0,
|
|
||||||
'discount_tax' => 0,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$items_to_apply = $this->get_items_to_apply_coupon( $coupon );
|
$items_to_apply = $this->get_items_to_apply_coupon( $coupon );
|
||||||
|
@ -166,36 +286,27 @@ class WC_Discounts {
|
||||||
// Core discounts are handled here as of 3.2.
|
// Core discounts are handled here as of 3.2.
|
||||||
switch ( $coupon->get_discount_type() ) {
|
switch ( $coupon->get_discount_type() ) {
|
||||||
case 'percent' :
|
case 'percent' :
|
||||||
$this->apply_percentage_discount( $items_to_apply, $coupon->get_amount(), $coupon );
|
$this->apply_coupon_percent( $coupon, $items_to_apply );
|
||||||
break;
|
break;
|
||||||
case 'fixed_product' :
|
case 'fixed_product' :
|
||||||
$this->apply_fixed_product_discount( $items_to_apply, wc_add_number_precision( $coupon->get_amount() ), $coupon );
|
$this->apply_coupon_fixed_product( $coupon, $items_to_apply );
|
||||||
break;
|
break;
|
||||||
case 'fixed_cart' :
|
case 'fixed_cart' :
|
||||||
$this->apply_fixed_cart_discount( $items_to_apply, wc_add_number_precision( $coupon->get_amount() ), $coupon );
|
$this->apply_coupon_fixed_cart( $coupon, $items_to_apply );
|
||||||
break;
|
break;
|
||||||
default :
|
default :
|
||||||
if ( has_action( 'woocommerce_discounts_apply_coupon_' . $coupon_type ) ) {
|
foreach ( $items_to_apply as $item ) {
|
||||||
// Allow custom coupon types to control this in their class per item, unless the new action is used.
|
$discounted_price = $this->get_discounted_price_in_cents( $item );
|
||||||
do_action( 'woocommerce_discounts_apply_coupon_' . $coupon_type, $coupon, $items_to_apply, $this );
|
$price_to_discount = wc_remove_number_precision( ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price : $discounted_price );
|
||||||
} else {
|
$discount = min( $discounted_price, wc_add_number_precision( $coupon->get_discount_amount( $price_to_discount ), $item->object ) );
|
||||||
// Fallback to old coupon-logic.
|
|
||||||
foreach ( $items_to_apply as $item ) {
|
|
||||||
$discounted_price = $this->get_discounted_price_in_cents( $item );
|
|
||||||
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price : $discounted_price;
|
|
||||||
$discount = min( $discounted_price, $coupon->get_discount_amount( $price_to_discount, $item->object ) );
|
|
||||||
$discount_tax = $this->get_item_discount_tax( $item, $discount );
|
|
||||||
|
|
||||||
// Store totals.
|
// Store code and discount amount per item.
|
||||||
$this->discounts[ $item->key ] += $discount;
|
$this->discounts[ $coupon->get_code() ][ $item->key ] += $discount;
|
||||||
if ( $coupon ) {
|
|
||||||
$this->applied_coupons[ $coupon->get_code() ]['discount'] += $discount;
|
|
||||||
$this->applied_coupons[ $coupon->get_code() ]['discount_tax'] += $discount_tax;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -271,12 +382,11 @@ class WC_Discounts {
|
||||||
* Apply percent discount to items and return an array of discounts granted.
|
* Apply percent discount to items and return an array of discounts granted.
|
||||||
*
|
*
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
|
* @param WC_Coupon $coupon Coupon object. Passed through filters.
|
||||||
* @param array $items_to_apply Array of items to apply the coupon to.
|
* @param array $items_to_apply Array of items to apply the coupon to.
|
||||||
* @param int $amount Amount of discount.
|
|
||||||
* @param WC_Coupon $coupon Coupon object if appliable. Passed through filters.
|
|
||||||
* @return int Total discounted.
|
* @return int Total discounted.
|
||||||
*/
|
*/
|
||||||
protected function apply_percentage_discount( $items_to_apply, $amount, $coupon = null ) {
|
protected function apply_coupon_percent( $coupon, $items_to_apply ) {
|
||||||
$total_discount = 0;
|
$total_discount = 0;
|
||||||
|
|
||||||
foreach ( $items_to_apply as $item ) {
|
foreach ( $items_to_apply as $item ) {
|
||||||
|
@ -287,17 +397,12 @@ class WC_Discounts {
|
||||||
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
|
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
|
||||||
|
|
||||||
// Run coupon calculations.
|
// Run coupon calculations.
|
||||||
$discount = $amount * ( $price_to_discount / 100 );
|
$discount = $coupon->get_amount() * ( $price_to_discount / 100 );
|
||||||
$discount = min( $discounted_price, apply_filters( 'woocommerce_coupon_get_discount_amount', $discount, $price_to_discount, $item->object, false, $coupon ) );
|
$discount = min( $discounted_price, apply_filters( 'woocommerce_coupon_get_discount_amount', $discount, $price_to_discount, $item->object, false, $coupon ) );
|
||||||
$discount_tax = $this->get_item_discount_tax( $item, $discount );
|
$total_discount += $discount;
|
||||||
|
|
||||||
// Store totals.
|
// Store code and discount amount per item.
|
||||||
$total_discount += $discount;
|
$this->discounts[ $coupon->get_code() ][ $item->key ] += $discount;
|
||||||
$this->discounts[ $item->key ] += $discount;
|
|
||||||
if ( $coupon ) {
|
|
||||||
$this->applied_coupons[ $coupon->get_code() ]['discount'] += $discount;
|
|
||||||
$this->applied_coupons[ $coupon->get_code() ]['discount_tax'] += $discount_tax;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return $total_discount;
|
return $total_discount;
|
||||||
}
|
}
|
||||||
|
@ -306,13 +411,14 @@ class WC_Discounts {
|
||||||
* Apply fixed product discount to items.
|
* Apply fixed product discount to items.
|
||||||
*
|
*
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
* @param array $items_to_apply Array of items to apply the coupon to.
|
* @param WC_Coupon $coupon Coupon object. Passed through filters.
|
||||||
* @param int $amount Amount of discount.
|
* @param array $items_to_apply Array of items to apply the coupon to.
|
||||||
* @param WC_Coupon $coupon Coupon object if appliable. Passed through filters.
|
* @param int $amount Fixed discount amount to apply in cents. Leave blank to pull from coupon.
|
||||||
* @return int Total discounted.
|
* @return int Total discounted.
|
||||||
*/
|
*/
|
||||||
protected function apply_fixed_product_discount( $items_to_apply, $amount, $coupon = null ) {
|
protected function apply_coupon_fixed_product( $coupon, $items_to_apply, $amount = null ) {
|
||||||
$total_discount = 0;
|
$total_discount = 0;
|
||||||
|
$amount = $amount ? $amount: wc_add_number_precision( $coupon->get_amount() );
|
||||||
|
|
||||||
foreach ( $items_to_apply as $item ) {
|
foreach ( $items_to_apply as $item ) {
|
||||||
// Find out how much price is available to discount for the item.
|
// Find out how much price is available to discount for the item.
|
||||||
|
@ -322,17 +428,12 @@ class WC_Discounts {
|
||||||
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
|
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
|
||||||
|
|
||||||
// Run coupon calculations.
|
// Run coupon calculations.
|
||||||
$discount = $amount * $item->quantity;
|
$discount = $amount * $item->quantity;
|
||||||
$discount = min( $discounted_price, apply_filters( 'woocommerce_coupon_get_discount_amount', $discount, $price_to_discount, $item->object, false, $coupon ) );
|
$discount = min( $discounted_price, apply_filters( 'woocommerce_coupon_get_discount_amount', $discount, $price_to_discount, $item->object, false, $coupon ) );
|
||||||
$discount_tax = $this->get_item_discount_tax( $item, $discount );
|
$total_discount += $discount;
|
||||||
|
|
||||||
// Store totals.
|
// Store code and discount amount per item.
|
||||||
$total_discount += $discount;
|
$this->discounts[ $coupon->get_code() ][ $item->key ] += $discount;
|
||||||
$this->discounts[ $item->key ] += $discount;
|
|
||||||
if ( $coupon ) {
|
|
||||||
$this->applied_coupons[ $coupon->get_code() ]['discount'] += $discount;
|
|
||||||
$this->applied_coupons[ $coupon->get_code() ]['discount_tax'] += $discount_tax;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return $total_discount;
|
return $total_discount;
|
||||||
}
|
}
|
||||||
|
@ -341,33 +442,33 @@ class WC_Discounts {
|
||||||
* Apply fixed cart discount to items.
|
* Apply fixed cart discount to items.
|
||||||
*
|
*
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
* @param array $items_to_apply Array of items to apply the coupon to.
|
* @param WC_Coupon $coupon Coupon object. Passed through filters.
|
||||||
* @param int $cart_discount Fixed discount amount to apply.
|
* @param array $items_to_apply Array of items to apply the coupon to.
|
||||||
* @param WC_Coupon $coupon Coupon object if appliable. Passed through filters.
|
* @param int $amount Fixed discount amount to apply in cents. Leave blank to pull from coupon.
|
||||||
* @return int Total discounted.
|
* @return int Total discounted.
|
||||||
*/
|
*/
|
||||||
protected function apply_fixed_cart_discount( $items_to_apply, $cart_discount, $coupon = null ) {
|
protected function apply_coupon_fixed_cart( $coupon, $items_to_apply, $amount = null ) {
|
||||||
$total_discount = 0;
|
$total_discount = 0;
|
||||||
|
$amount = $amount ? $amount: wc_add_number_precision( $coupon->get_amount() );
|
||||||
$items_to_apply = array_filter( $items_to_apply, array( $this, 'filter_products_with_price' ) );
|
$items_to_apply = array_filter( $items_to_apply, array( $this, 'filter_products_with_price' ) );
|
||||||
|
|
||||||
if ( ! $item_count = array_sum( wp_list_pluck( $items_to_apply, 'quantity' ) ) ) {
|
if ( ! $item_count = array_sum( wp_list_pluck( $items_to_apply, 'quantity' ) ) ) {
|
||||||
return $total_discount;
|
return $total_discount;
|
||||||
}
|
}
|
||||||
|
|
||||||
$per_item_discount = floor( $cart_discount / $item_count ); // round it down to the nearest cent.
|
$per_item_discount = floor( $amount / $item_count ); // round it down to the nearest cent.
|
||||||
|
|
||||||
if ( $per_item_discount > 0 ) {
|
if ( $per_item_discount > 0 ) {
|
||||||
$total_discounted = $this->apply_fixed_product_discount( $items_to_apply, $per_item_discount, $coupon );
|
$total_discounted = $this->apply_coupon_fixed_product( $coupon, $items_to_apply, $per_item_discount );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is still discount remaining, repeat the process.
|
* If there is still discount remaining, repeat the process.
|
||||||
*/
|
*/
|
||||||
if ( $total_discounted > 0 && $total_discounted < $cart_discount ) {
|
if ( $total_discounted > 0 && $total_discounted < $amount ) {
|
||||||
$total_discounted = $total_discounted + $this->apply_fixed_cart_discount( $items_to_apply, $cart_discount - $total_discounted );
|
$total_discounted = $total_discounted + $this->apply_coupon_fixed_cart( $coupon, $items_to_apply, $amount - $total_discounted );
|
||||||
}
|
}
|
||||||
|
} elseif ( $amount > 0 ) {
|
||||||
} elseif ( $cart_discount > 0 ) {
|
$total_discounted = $this->apply_coupon_fixed_cart_remainder( $coupon, $items_to_apply, $amount );
|
||||||
$total_discounted = $this->apply_fixed_cart_discount_remainder( $items_to_apply, $cart_discount, $coupon );
|
|
||||||
}
|
}
|
||||||
return $total_discount;
|
return $total_discount;
|
||||||
}
|
}
|
||||||
|
@ -377,12 +478,12 @@ class WC_Discounts {
|
||||||
* until the amount is expired, discounting 1 cent at a time.
|
* until the amount is expired, discounting 1 cent at a time.
|
||||||
*
|
*
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
* @param array $items_to_apply Array of items to apply the coupon to.
|
|
||||||
* @param int $cart_discount Fixed discount amount to apply.
|
|
||||||
* @param WC_Coupon $coupon Coupon object if appliable. Passed through filters.
|
* @param WC_Coupon $coupon Coupon object if appliable. Passed through filters.
|
||||||
|
* @param array $items_to_apply Array of items to apply the coupon to.
|
||||||
|
* @param int $amount Fixed discount amount to apply.
|
||||||
* @return int Total discounted.
|
* @return int Total discounted.
|
||||||
*/
|
*/
|
||||||
protected function apply_fixed_cart_discount_remainder( $items_to_apply, $remaining_discount, $coupon = null ) {
|
protected function apply_coupon_fixed_cart_remainder( $coupon, $items_to_apply, $amount ) {
|
||||||
$total_discount = 0;
|
$total_discount = 0;
|
||||||
|
|
||||||
foreach ( $items_to_apply as $item ) {
|
foreach ( $items_to_apply as $item ) {
|
||||||
|
@ -394,43 +495,25 @@ class WC_Discounts {
|
||||||
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
|
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
|
||||||
|
|
||||||
// Run coupon calculations.
|
// Run coupon calculations.
|
||||||
$discount = min( $discounted_price, 1 );
|
$discount = min( $discounted_price, 1 );
|
||||||
$discount_tax = $this->get_item_discount_tax( $item, $discount );
|
|
||||||
|
|
||||||
// Store totals.
|
// Store totals.
|
||||||
$total_discount += $discount;
|
$total_discount += $discount;
|
||||||
$this->discounts[ $item->key ] += $discount;
|
|
||||||
if ( $coupon ) {
|
|
||||||
$this->applied_coupons[ $coupon->get_code() ]['discount'] += $discount;
|
|
||||||
$this->applied_coupons[ $coupon->get_code() ]['discount_tax'] += $discount_tax;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $total_discount >= $remaining_discount ) {
|
// Store code and discount amount per item.
|
||||||
|
$this->discounts[ $coupon->get_code() ][ $item->key ] += $discount;
|
||||||
|
|
||||||
|
if ( $total_discount >= $amount ) {
|
||||||
break 2;
|
break 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( $total_discount >= $remaining_discount ) {
|
if ( $total_discount >= $amount ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $total_discount;
|
return $total_discount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return discounted tax amount for an item.
|
|
||||||
*
|
|
||||||
* @param object $item
|
|
||||||
* @param int $discount_amount
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
protected function get_item_discount_tax( $item, $discount_amount ) {
|
|
||||||
if ( $item->product->is_taxable() ) {
|
|
||||||
$taxes = WC_Tax::calc_tax( $discount_amount, $item->tax_rates, false );
|
|
||||||
return array_sum( $taxes );
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Validation & Error Handling
|
| Validation & Error Handling
|
||||||
|
@ -785,7 +868,7 @@ class WC_Discounts {
|
||||||
$this->validate_coupon_excluded_items( $coupon );
|
$this->validate_coupon_excluded_items( $coupon );
|
||||||
$this->validate_coupon_eligible_items( $coupon );
|
$this->validate_coupon_eligible_items( $coupon );
|
||||||
|
|
||||||
if ( ! apply_filters( 'woocommerce_discount_is_coupon_valid', true, $coupon, $this ) ) {
|
if ( ! apply_filters( 'woocommerce_coupon_is_valid', true, $coupon, $this ) ) {
|
||||||
throw new Exception( __( 'Coupon is not valid.', 'woocommerce' ), 100 );
|
throw new Exception( __( 'Coupon is not valid.', 'woocommerce' ), 100 );
|
||||||
}
|
}
|
||||||
} catch ( Exception $e ) {
|
} catch ( Exception $e ) {
|
||||||
|
|
10
phpunit.xml
10
phpunit.xml
|
@ -18,8 +18,10 @@
|
||||||
<whitelist addUncoveredFilesFromWhitelist="true">
|
<whitelist addUncoveredFilesFromWhitelist="true">
|
||||||
<directory suffix=".php">.</directory>
|
<directory suffix=".php">.</directory>
|
||||||
<exclude>
|
<exclude>
|
||||||
<directory suffix=".php">./apigen/</directory>
|
<directory>./apigen/</directory>
|
||||||
<directory suffix=".php">./i18n/</directory>
|
<directory>./assets/</directory>
|
||||||
|
<directory>./dummy-data/</directory>
|
||||||
|
<directory>./i18n/</directory>
|
||||||
<directory suffix=".php">./includes/api/legacy/</directory>
|
<directory suffix=".php">./includes/api/legacy/</directory>
|
||||||
<directory suffix=".php">./includes/gateways/simplify-commerce-deprecated/</directory>
|
<directory suffix=".php">./includes/gateways/simplify-commerce-deprecated/</directory>
|
||||||
<directory suffix=".php">./includes/gateways/simplify-commerce/includes/</directory>
|
<directory suffix=".php">./includes/gateways/simplify-commerce/includes/</directory>
|
||||||
|
@ -33,7 +35,9 @@
|
||||||
<directory suffix=".php">./includes/widgets/</directory>
|
<directory suffix=".php">./includes/widgets/</directory>
|
||||||
<directory suffix=".php">./templates/</directory>
|
<directory suffix=".php">./templates/</directory>
|
||||||
<directory>./tests/</directory>
|
<directory>./tests/</directory>
|
||||||
<directory suffix=".php">./tmp/</directory>
|
<directory>./vendor/</directory>
|
||||||
|
<directory>./.*/</directory>
|
||||||
|
<directory>./tmp/</directory>
|
||||||
</exclude>
|
</exclude>
|
||||||
</whitelist>
|
</whitelist>
|
||||||
</filter>
|
</filter>
|
||||||
|
|
|
@ -18,8 +18,10 @@
|
||||||
<whitelist addUncoveredFilesFromWhitelist="true">
|
<whitelist addUncoveredFilesFromWhitelist="true">
|
||||||
<directory suffix=".php">.</directory>
|
<directory suffix=".php">.</directory>
|
||||||
<exclude>
|
<exclude>
|
||||||
<directory suffix=".php">./apigen/</directory>
|
<directory>./apigen/</directory>
|
||||||
<directory suffix=".php">./i18n/</directory>
|
<directory>./assets/</directory>
|
||||||
|
<directory>./dummy-data/</directory>
|
||||||
|
<directory>./i18n/</directory>
|
||||||
<directory suffix=".php">./includes/api/legacy/</directory>
|
<directory suffix=".php">./includes/api/legacy/</directory>
|
||||||
<directory suffix=".php">./includes/gateways/simplify-commerce-deprecated/</directory>
|
<directory suffix=".php">./includes/gateways/simplify-commerce-deprecated/</directory>
|
||||||
<directory suffix=".php">./includes/gateways/simplify-commerce/includes/</directory>
|
<directory suffix=".php">./includes/gateways/simplify-commerce/includes/</directory>
|
||||||
|
@ -33,7 +35,9 @@
|
||||||
<directory suffix=".php">./includes/widgets/</directory>
|
<directory suffix=".php">./includes/widgets/</directory>
|
||||||
<directory suffix=".php">./templates/</directory>
|
<directory suffix=".php">./templates/</directory>
|
||||||
<directory>./tests/</directory>
|
<directory>./tests/</directory>
|
||||||
<directory suffix=".php">./tmp/</directory>
|
<directory>./vendor/</directory>
|
||||||
|
<directory>./.*/</directory>
|
||||||
|
<directory>./tmp/</directory>
|
||||||
</exclude>
|
</exclude>
|
||||||
</whitelist>
|
</whitelist>
|
||||||
</filter>
|
</filter>
|
||||||
|
|
|
@ -25,7 +25,7 @@ class WC_Helper_Shipping {
|
||||||
update_option( 'woocommerce_flat_rate_settings', $flat_rate_settings );
|
update_option( 'woocommerce_flat_rate_settings', $flat_rate_settings );
|
||||||
update_option( 'woocommerce_flat_rate', array() );
|
update_option( 'woocommerce_flat_rate', array() );
|
||||||
WC_Cache_Helper::get_transient_version( 'shipping', true );
|
WC_Cache_Helper::get_transient_version( 'shipping', true );
|
||||||
WC()->shipping->unregister_shipping_methods();
|
WC()->shipping->load_shipping_methods();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for the discount class.
|
||||||
|
* @package WooCommerce\Tests\Discounts
|
||||||
|
*/
|
||||||
|
class WC_Tests_Discount extends WC_Unit_Test_Case {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test get and set ID.
|
||||||
|
*/
|
||||||
|
public function test_get_set_amount() {
|
||||||
|
$discount = new WC_Discount;
|
||||||
|
$discount->set_amount( '10' );
|
||||||
|
$this->assertEquals( '10', $discount->get_amount() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_get_set_type() {
|
||||||
|
$discount = new WC_Discount;
|
||||||
|
|
||||||
|
$discount->set_discount_type( 'fixed' );
|
||||||
|
$this->assertEquals( 'fixed', $discount->get_discount_type() );
|
||||||
|
|
||||||
|
$discount->set_discount_type( 'percent' );
|
||||||
|
$this->assertEquals( 'percent', $discount->get_discount_type() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test get and set discount total.
|
||||||
|
*/
|
||||||
|
public function test_get_set_discount_total() {
|
||||||
|
$discount = new WC_Discount;
|
||||||
|
$discount->set_discount_total( 1000 );
|
||||||
|
$this->assertEquals( 1000, $discount->get_discount_total() );
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,38 +6,10 @@
|
||||||
*/
|
*/
|
||||||
class WC_Tests_Discounts extends WC_Unit_Test_Case {
|
class WC_Tests_Discounts extends WC_Unit_Test_Case {
|
||||||
|
|
||||||
protected function get_items_for_discounts_class() {
|
|
||||||
$items = array();
|
|
||||||
$precision = pow( 10, wc_get_price_decimals() );
|
|
||||||
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
|
|
||||||
$item = (object) array(
|
|
||||||
'key' => '',
|
|
||||||
'quantity' => 0,
|
|
||||||
'price' => 0,
|
|
||||||
'product' => false,
|
|
||||||
'price_includes_tax' => wc_prices_include_tax(),
|
|
||||||
'subtotal' => 0,
|
|
||||||
'subtotal_tax' => 0,
|
|
||||||
'subtotal_taxes' => array(),
|
|
||||||
'total' => 0,
|
|
||||||
'total_tax' => 0,
|
|
||||||
'taxes' => array(),
|
|
||||||
'discounted_price' => 0,
|
|
||||||
);
|
|
||||||
$item->object = $cart_item;
|
|
||||||
$item->quantity = $cart_item['quantity'];
|
|
||||||
$item->subtotal = $cart_item['data']->get_price() * $precision * $cart_item['quantity'];
|
|
||||||
$item->product = $cart_item['data'];
|
|
||||||
$item->tax_rates = WC_Tax::get_rates( $item->product->get_tax_class() );
|
|
||||||
$items[ $cart_item_key ] = $item;
|
|
||||||
}
|
|
||||||
return $items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test get and set items.
|
* Test get and set items.
|
||||||
*/
|
*/
|
||||||
public function test_get_set_items() {
|
public function test_get_set_items_from_cart() {
|
||||||
// Create dummy product - price will be 10
|
// Create dummy product - price will be 10
|
||||||
$product = WC_Helper_Product::create_simple_product();
|
$product = WC_Helper_Product::create_simple_product();
|
||||||
|
|
||||||
|
@ -52,22 +24,22 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
|
||||||
|
|
||||||
// Test setting items to the cart.
|
// Test setting items to the cart.
|
||||||
$discounts = new WC_Discounts();
|
$discounts = new WC_Discounts();
|
||||||
$discounts->set_items( $this->get_items_for_discounts_class() );
|
$discounts->set_items_from_cart( WC()->cart );
|
||||||
$this->assertEquals( 1, count( $discounts->get_items() ) );
|
$this->assertEquals( 1, count( $discounts->get_items() ) );
|
||||||
|
|
||||||
// Test setting items to an order.
|
// Test setting items to an order.
|
||||||
$discounts = new WC_Discounts();
|
$discounts = new WC_Discounts();
|
||||||
$discounts->set_items( $this->get_items_for_discounts_class() );
|
$discounts->set_items_from_cart( WC()->cart );
|
||||||
$this->assertEquals( 1, count( $discounts->get_items() ) );
|
$this->assertEquals( 1, count( $discounts->get_items() ) );
|
||||||
|
|
||||||
// Empty array of items.
|
// Empty array of items.
|
||||||
$discounts = new WC_Discounts();
|
$discounts = new WC_Discounts();
|
||||||
$discounts->set_items( array() );
|
$discounts->set_items_from_cart( array() );
|
||||||
$this->assertEquals( array(), $discounts->get_items() );
|
$this->assertEquals( array(), $discounts->get_items() );
|
||||||
|
|
||||||
// Invalid items.
|
// Invalid items.
|
||||||
$discounts = new WC_Discounts();
|
$discounts = new WC_Discounts();
|
||||||
$discounts->set_items( false );
|
$discounts->set_items_from_cart( false );
|
||||||
$this->assertEquals( array(), $discounts->get_items() );
|
$this->assertEquals( array(), $discounts->get_items() );
|
||||||
|
|
||||||
// Cleanup.
|
// Cleanup.
|
||||||
|
@ -76,59 +48,6 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
|
||||||
$order->delete( true );
|
$order->delete( true );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* test get_applied_coupons
|
|
||||||
*/
|
|
||||||
public function test_get_applied_coupons() {
|
|
||||||
$discounts = new WC_Discounts();
|
|
||||||
$product = WC_Helper_Product::create_simple_product();
|
|
||||||
WC()->cart->add_to_cart( $product->get_id(), 1 );
|
|
||||||
$discounts->set_items( $this->get_items_for_discounts_class() );
|
|
||||||
|
|
||||||
// Test applying multiple coupons and getting totals.
|
|
||||||
$coupon = WC_Helper_Coupon::create_coupon( 'test' );
|
|
||||||
$coupon->set_amount( 50 );
|
|
||||||
$coupon->set_discount_type( 'percent' );
|
|
||||||
$coupon->save();
|
|
||||||
$discounts->apply_coupon( $coupon );
|
|
||||||
|
|
||||||
$this->assertEquals( array( 'test' => array( 'discount' => 5, 'discount_tax' => 0 ) ), $discounts->get_applied_coupons() );
|
|
||||||
|
|
||||||
$coupon2 = WC_Helper_Coupon::create_coupon( 'test2' );
|
|
||||||
$coupon2->set_code( 'test2' );
|
|
||||||
$coupon2->set_amount( 50 );
|
|
||||||
$coupon2->set_discount_type( 'percent' );
|
|
||||||
$coupon->save();
|
|
||||||
$discounts->apply_coupon( $coupon2 );
|
|
||||||
|
|
||||||
$this->assertEquals( array( 'test' => array( 'discount' => 5, 'discount_tax' => 0 ), 'test2' => array( 'discount' => 2.50, 'discount_tax' => 0 ) ), $discounts->get_applied_coupons() );
|
|
||||||
|
|
||||||
$discounts->apply_coupon( $coupon );
|
|
||||||
$this->assertEquals( array( 'test' => array( 'discount' => 6.25, 'discount_tax' => 0 ), 'test2' => array( 'discount' => 2.50, 'discount_tax' => 0 ) ), $discounts->get_applied_coupons() );
|
|
||||||
|
|
||||||
// Test different coupon types.
|
|
||||||
WC()->cart->empty_cart();
|
|
||||||
WC()->cart->add_to_cart( $product->get_id(), 2 );
|
|
||||||
$coupon->set_discount_type( 'fixed_product' );
|
|
||||||
$coupon->set_amount( 2 );
|
|
||||||
$coupon->save();
|
|
||||||
$discounts->set_items( $this->get_items_for_discounts_class() );
|
|
||||||
$discounts->apply_coupon( $coupon );
|
|
||||||
$this->assertEquals( array( 'test' => array( 'discount' => 4, 'discount_tax' => 0 ) ), $discounts->get_applied_coupons() );
|
|
||||||
|
|
||||||
$coupon->set_discount_type( 'fixed_cart' );
|
|
||||||
$coupon->save();
|
|
||||||
$discounts->set_items( $this->get_items_for_discounts_class() );
|
|
||||||
$discounts->apply_coupon( $coupon );
|
|
||||||
$this->assertEquals( array( 'test' => array( 'discount' => 2, 'discount_tax' => 0 ) ), $discounts->get_applied_coupons() );
|
|
||||||
|
|
||||||
// Cleanup.
|
|
||||||
WC()->cart->empty_cart();
|
|
||||||
$product->delete( true );
|
|
||||||
$coupon->delete( true );
|
|
||||||
$coupon2->delete( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test applying a coupon (make sure it changes prices).
|
* Test applying a coupon (make sure it changes prices).
|
||||||
*/
|
*/
|
||||||
|
@ -146,21 +65,21 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
|
||||||
|
|
||||||
// Apply a percent discount.
|
// Apply a percent discount.
|
||||||
$coupon->set_discount_type( 'percent' );
|
$coupon->set_discount_type( 'percent' );
|
||||||
$discounts->set_items( $this->get_items_for_discounts_class() );
|
$discounts->set_items_from_cart( WC()->cart );
|
||||||
$discounts->apply_coupon( $coupon );
|
$discounts->apply_discount( $coupon );
|
||||||
$this->assertEquals( 9, $discounts->get_discounted_price( current( $discounts->get_items() ) ) );
|
$this->assertEquals( 9, $discounts->get_discounted_price( current( $discounts->get_items() ) ), print_r( $discounts->get_discounts(), true ) );
|
||||||
|
|
||||||
// Apply a fixed cart coupon.
|
// Apply a fixed cart coupon.
|
||||||
$coupon->set_discount_type( 'fixed_cart' );
|
$coupon->set_discount_type( 'fixed_cart' );
|
||||||
$discounts->set_items( $this->get_items_for_discounts_class() );
|
$discounts->set_items_from_cart( WC()->cart );
|
||||||
$discounts->apply_coupon( $coupon );
|
$discounts->apply_discount( $coupon );
|
||||||
$this->assertEquals( 0, $discounts->get_discounted_price( current( $discounts->get_items() ) ) );
|
$this->assertEquals( 0, $discounts->get_discounted_price( current( $discounts->get_items() ) ), print_r( $discounts->get_discounts(), true ) );
|
||||||
|
|
||||||
// Apply a fixed product coupon.
|
// Apply a fixed product coupon.
|
||||||
$coupon->set_discount_type( 'fixed_product' );
|
$coupon->set_discount_type( 'fixed_product' );
|
||||||
$discounts->set_items( $this->get_items_for_discounts_class() );
|
$discounts->set_items_from_cart( WC()->cart );
|
||||||
$discounts->apply_coupon( $coupon );
|
$discounts->apply_discount( $coupon );
|
||||||
$this->assertEquals( 0, $discounts->get_discounted_price( current( $discounts->get_items() ) ) );
|
$this->assertEquals( 0, $discounts->get_discounted_price( current( $discounts->get_items() ) ), print_r( $discounts->get_discounts(), true ) );
|
||||||
|
|
||||||
// Cleanup.
|
// Cleanup.
|
||||||
WC()->cart->empty_cart();
|
WC()->cart->empty_cart();
|
||||||
|
@ -431,14 +350,15 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
|
||||||
$products[] = $product;
|
$products[] = $product;
|
||||||
}
|
}
|
||||||
|
|
||||||
$discounts->set_items( $this->get_items_for_discounts_class() );
|
$discounts->set_items_from_cart( WC()->cart );
|
||||||
|
|
||||||
foreach ( $test['coupons'] as $coupon_props ) {
|
foreach ( $test['coupons'] as $coupon_props ) {
|
||||||
$coupon->set_props( $coupon_props );
|
$coupon->set_props( $coupon_props );
|
||||||
$discounts->apply_coupon( $coupon );
|
$discounts->apply_discount( $coupon );
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertEquals( $test['expected_total_discount'], array_sum( $discounts->get_discounts() ), 'Test case ' . $test_index . ' failed (' . print_r( $test, true ) . ' - ' . print_r( $discounts->get_discounts(), true ) . ')' );
|
$all_discounts = $discounts->get_discounts();
|
||||||
|
$this->assertEquals( $test['expected_total_discount'], array_sum( $all_discounts['test'] ), 'Test case ' . $test_index . ' failed (' . print_r( $test, true ) . ' - ' . print_r( $discounts->get_discounts(), true ) . ')' );
|
||||||
|
|
||||||
// Clean.
|
// Clean.
|
||||||
WC()->cart->empty_cart();
|
WC()->cart->empty_cart();
|
||||||
|
@ -452,4 +372,88 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
|
||||||
update_option( 'woocommerce_calc_taxes', 'no' );
|
update_option( 'woocommerce_calc_taxes', 'no' );
|
||||||
$coupon->delete( true );
|
$coupon->delete( true );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test apply_discount method.
|
||||||
|
*/
|
||||||
|
public function test_apply_discount() {
|
||||||
|
$tax_rate = array(
|
||||||
|
'tax_rate_country' => '',
|
||||||
|
'tax_rate_state' => '',
|
||||||
|
'tax_rate' => '20.0000',
|
||||||
|
'tax_rate_name' => 'VAT',
|
||||||
|
'tax_rate_priority' => '1',
|
||||||
|
'tax_rate_compound' => '0',
|
||||||
|
'tax_rate_shipping' => '1',
|
||||||
|
'tax_rate_order' => '1',
|
||||||
|
'tax_rate_class' => '',
|
||||||
|
);
|
||||||
|
$tax_rate2 = array(
|
||||||
|
'tax_rate_country' => '',
|
||||||
|
'tax_rate_state' => '',
|
||||||
|
'tax_rate' => '20.0000',
|
||||||
|
'tax_rate_name' => 'VAT',
|
||||||
|
'tax_rate_priority' => '1',
|
||||||
|
'tax_rate_compound' => '0',
|
||||||
|
'tax_rate_shipping' => '1',
|
||||||
|
'tax_rate_order' => '1',
|
||||||
|
'tax_rate_class' => 'reduced-rate',
|
||||||
|
);
|
||||||
|
$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
|
||||||
|
$tax_rate_id2 = WC_Tax::_insert_tax_rate( $tax_rate2 );
|
||||||
|
update_option( 'woocommerce_calc_taxes', 'yes' );
|
||||||
|
|
||||||
|
$product = WC_Helper_Product::create_simple_product();
|
||||||
|
$product2 = WC_Helper_Product::create_simple_product();
|
||||||
|
|
||||||
|
$product->set_tax_class( '' );
|
||||||
|
$product2->set_tax_class( 'reduced-rate' );
|
||||||
|
|
||||||
|
$product->save();
|
||||||
|
$product2->save();
|
||||||
|
|
||||||
|
// Add product to the cart.
|
||||||
|
WC()->cart->empty_cart();
|
||||||
|
WC()->cart->add_to_cart( $product->get_id(), 1 );
|
||||||
|
WC()->cart->add_to_cart( $product2->get_id(), 1 );
|
||||||
|
|
||||||
|
$discounts = new WC_Discounts();
|
||||||
|
$discounts->set_items_from_cart( WC()->cart );
|
||||||
|
|
||||||
|
$discounts->apply_discount( '50%' );
|
||||||
|
$all_discounts = $discounts->get_discounts();
|
||||||
|
$this->assertEquals( 10, $all_discounts['discount-50%'], print_r( $all_discounts, true ) );
|
||||||
|
|
||||||
|
$discounts->apply_discount( '50%' );
|
||||||
|
$all_discounts = $discounts->get_discounts();
|
||||||
|
$this->assertEquals( 10, $all_discounts['discount-50%'], print_r( $all_discounts, true ) );
|
||||||
|
$this->assertEquals( 5, $all_discounts['discount-50%-2'], print_r( $all_discounts, true ) );
|
||||||
|
|
||||||
|
// Test fixed discounts.
|
||||||
|
$discounts = new WC_Discounts();
|
||||||
|
$discounts->set_items_from_cart( WC()->cart );
|
||||||
|
|
||||||
|
$discounts->apply_discount( '5' );
|
||||||
|
$all_discounts = $discounts->get_discounts();
|
||||||
|
$this->assertEquals( 5, $all_discounts['discount-500'] );
|
||||||
|
|
||||||
|
$discounts->apply_discount( '5' );
|
||||||
|
$all_discounts = $discounts->get_discounts();
|
||||||
|
$this->assertEquals( 5, $all_discounts['discount-500'], print_r( $all_discounts, true ) );
|
||||||
|
$this->assertEquals( 5, $all_discounts['discount-500-2'], print_r( $all_discounts, true ) );
|
||||||
|
|
||||||
|
$discounts->apply_discount( '15' );
|
||||||
|
$all_discounts = $discounts->get_discounts();
|
||||||
|
$this->assertEquals( 5, $all_discounts['discount-500'], print_r( $all_discounts, true ) );
|
||||||
|
$this->assertEquals( 5, $all_discounts['discount-500-2'], print_r( $all_discounts, true ) );
|
||||||
|
$this->assertEquals( 10, $all_discounts['discount-1500'], print_r( $all_discounts, true ) );
|
||||||
|
|
||||||
|
// Cleanup.
|
||||||
|
WC()->cart->empty_cart();
|
||||||
|
$product->delete( true );
|
||||||
|
$product2->delete( true );
|
||||||
|
WC_Tax::_delete_tax_rate( $tax_rate_id );
|
||||||
|
WC_Tax::_delete_tax_rate( $tax_rate_id2 );
|
||||||
|
update_option( 'woocommerce_calc_taxes', 'no' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,10 @@ class WC_Tests_Totals extends WC_Unit_Test_Case {
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
$this->ids = array();
|
$this->ids = array();
|
||||||
|
|
||||||
|
if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
|
||||||
|
define( 'WOOCOMMERCE_CHECKOUT', 1 );
|
||||||
|
}
|
||||||
|
|
||||||
$tax_rate = array(
|
$tax_rate = array(
|
||||||
'tax_rate_country' => '',
|
'tax_rate_country' => '',
|
||||||
'tax_rate_state' => '',
|
'tax_rate_state' => '',
|
||||||
|
@ -42,7 +46,10 @@ class WC_Tests_Totals extends WC_Unit_Test_Case {
|
||||||
'tax_rate_class' => '',
|
'tax_rate_class' => '',
|
||||||
);
|
);
|
||||||
$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
|
$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
|
||||||
|
|
||||||
update_option( 'woocommerce_calc_taxes', 'yes' );
|
update_option( 'woocommerce_calc_taxes', 'yes' );
|
||||||
|
update_option( 'woocommerce_default_customer_address', 'base' );
|
||||||
|
update_option( 'woocommerce_tax_based_on', 'base' );
|
||||||
|
|
||||||
$product = WC_Helper_Product::create_simple_product();
|
$product = WC_Helper_Product::create_simple_product();
|
||||||
$product2 = WC_Helper_Product::create_simple_product();
|
$product2 = WC_Helper_Product::create_simple_product();
|
||||||
|
@ -67,7 +74,7 @@ class WC_Tests_Totals extends WC_Unit_Test_Case {
|
||||||
|
|
||||||
add_action( 'woocommerce_cart_calculate_fees', array( $this, 'add_cart_fees_callback' ) );
|
add_action( 'woocommerce_cart_calculate_fees', array( $this, 'add_cart_fees_callback' ) );
|
||||||
|
|
||||||
$this->totals = new WC_Cart_Totals( WC()->cart, WC()->customer );
|
$this->totals = new WC_Cart_Totals( WC()->cart );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,6 +108,8 @@ class WC_Tests_Totals extends WC_Unit_Test_Case {
|
||||||
foreach ( $this->ids['tax_rate_ids'] as $tax_rate_id ) {
|
foreach ( $this->ids['tax_rate_ids'] as $tax_rate_id ) {
|
||||||
WC_Tax::_delete_tax_rate( $tax_rate_id );
|
WC_Tax::_delete_tax_rate( $tax_rate_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->ids = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue