Coupon usage limits per user (using email + ID). Closes #3314

This commit is contained in:
Mike Jolley 2013-10-01 11:48:27 +01:00
parent 088f565b98
commit c20a44c423
5 changed files with 139 additions and 58 deletions

View File

@ -145,6 +145,12 @@ class WC_Meta_Box_Coupon_Data {
'min' => '0'
) ) );
// Usage limit
woocommerce_wp_text_input( array( 'id' => 'usage_limit_per_user', 'label' => __( 'Usage limit per user', 'woocommerce' ), 'placeholder' => _x( 'Unlimited usage', 'placeholder', 'woocommerce' ), 'description' => __( 'How many times this coupon can be used by an invidual user. Uses billing email for guests, and user ID for logged in users.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array(
'step' => '1',
'min' => '0'
) ) );
// Expiry date
woocommerce_wp_text_input( array( 'id' => 'expiry_date', 'label' => __( 'Expiry date', 'woocommerce' ), 'placeholder' => _x('Never expire', 'placeholder', 'woocommerce'), 'description' => __( 'The date this coupon will expire, <code>YYYY-MM-DD</code>.', 'woocommerce' ), 'class' => 'short date-picker', 'custom_attributes' => array( 'pattern' => "[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" ) ) );
@ -180,16 +186,17 @@ class WC_Meta_Box_Coupon_Data {
WC_Admin_Meta_Boxes::add_error( __( 'Coupon code already exists - customers will use the latest coupon with this code.', 'woocommerce' ) );
// Add/Replace data to array
$type = woocommerce_clean( $_POST['discount_type'] );
$amount = woocommerce_clean( $_POST['coupon_amount'] );
$usage_limit = empty( $_POST['usage_limit'] ) ? '' : absint( $_POST['usage_limit'] );
$individual_use = isset( $_POST['individual_use'] ) ? 'yes' : 'no';
$expiry_date = woocommerce_clean( $_POST['expiry_date'] );
$apply_before_tax = isset( $_POST['apply_before_tax'] ) ? 'yes' : 'no';
$free_shipping = isset( $_POST['free_shipping'] ) ? 'yes' : 'no';
$exclude_sale_items = isset( $_POST['exclude_sale_items'] ) ? 'yes' : 'no';
$minimum_amount = woocommerce_clean( $_POST['minimum_amount'] );
$customer_email = array_filter( array_map( 'trim', explode( ',', woocommerce_clean( $_POST['customer_email'] ) ) ) );
$type = woocommerce_clean( $_POST['discount_type'] );
$amount = woocommerce_clean( $_POST['coupon_amount'] );
$usage_limit = empty( $_POST['usage_limit'] ) ? '' : absint( $_POST['usage_limit'] );
$usage_limit_per_user = empty( $_POST['usage_limit_per_user'] ) ? '' : absint( $_POST['usage_limit_per_user'] );
$individual_use = isset( $_POST['individual_use'] ) ? 'yes' : 'no';
$expiry_date = woocommerce_clean( $_POST['expiry_date'] );
$apply_before_tax = isset( $_POST['apply_before_tax'] ) ? 'yes' : 'no';
$free_shipping = isset( $_POST['free_shipping'] ) ? 'yes' : 'no';
$exclude_sale_items = isset( $_POST['exclude_sale_items'] ) ? 'yes' : 'no';
$minimum_amount = woocommerce_clean( $_POST['minimum_amount'] );
$customer_email = array_filter( array_map( 'trim', explode( ',', woocommerce_clean( $_POST['customer_email'] ) ) ) );
if ( isset( $_POST['product_ids'] ) ) {
$product_ids = implode( ',', array_filter( array_map( 'intval', (array) $_POST['product_ids'] ) ) );
@ -213,6 +220,7 @@ class WC_Meta_Box_Coupon_Data {
update_post_meta( $post_id, 'product_ids', $product_ids );
update_post_meta( $post_id, 'exclude_product_ids', $exclude_product_ids );
update_post_meta( $post_id, 'usage_limit', $usage_limit );
update_post_meta( $post_id, 'usage_limit_per_user', $usage_limit_per_user );
update_post_meta( $post_id, 'expiry_date', $expiry_date );
update_post_meta( $post_id, 'apply_before_tax', $apply_before_tax );
update_post_meta( $post_id, 'free_shipping', $free_shipping );

View File

@ -393,6 +393,10 @@ class WC_Cart {
/**
* Check for user coupons (now that we have billing email). If a coupon is invalid, add an error.
*
* Checks two types of coupons:
* 1. Where a list of customer emails are set (limits coupon usage to those defined)
* 2. Where a usage_limit_per_user is set (limits coupon usage to a number based on user ID and email)
*
* @access public
* @param array $posted
*/
@ -401,26 +405,55 @@ class WC_Cart {
foreach ( $this->applied_coupons as $key => $code ) {
$coupon = new WC_Coupon( $code );
if ( $coupon->is_valid() && is_array( $coupon->customer_email ) && sizeof( $coupon->customer_email ) > 0 ) {
if ( $coupon->is_valid() ) {
$coupon->customer_email = array_map( 'sanitize_email', $coupon->customer_email );
// Limit to defined email addresses
if ( is_array( $coupon->customer_email ) && sizeof( $coupon->customer_email ) > 0 ) {
$coupon->customer_email = array_map( 'sanitize_email', $coupon->customer_email );
if ( is_user_logged_in() ) {
$current_user = wp_get_current_user();
$check_emails[] = $current_user->user_email;
if ( is_user_logged_in() ) {
$current_user = wp_get_current_user();
$check_emails[] = $current_user->user_email;
}
$check_emails[] = $posted['billing_email'];
$check_emails = array_map( 'sanitize_email', array_map( 'strtolower', $check_emails ) );
if ( 0 == sizeof( array_intersect( $check_emails, $coupon->customer_email ) ) ) {
$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED );
// Remove the coupon
unset( $this->applied_coupons[ $key ] );
WC()->session->set( 'coupon_codes', $this->applied_coupons );
WC()->session->set( 'refresh_totals', true );
}
}
$check_emails[] = $posted['billing_email'];
$check_emails = array_map( 'sanitize_email', array_map( 'strtolower', $check_emails ) );
// Usage limits per user - check against billing and user email and user ID
if ( $coupon->usage_limit_per_user > 0 ) {
$used_by = get_post_meta( $this->id, '_used_by' );
if ( 0 == sizeof( array_intersect( $check_emails, $coupon->customer_email ) ) ) {
$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED );
if ( is_user_logged_in() ) {
$current_user = wp_get_current_user();
$check_emails[] = $current_user->user_email;
}
$check_emails[] = $posted['billing_email'];
$check_emails = array_map( 'sanitize_email', array_map( 'strtolower', $check_emails ) );
// Remove the coupon
unset( $this->applied_coupons[ $key ] );
$usage_count = sizeof( array_keys( $used_by, get_current_user_id() ) );
WC()->session->set( 'coupon_codes', $this->applied_coupons );
WC()->session->set( 'refresh_totals', true );
foreach ( $check_emails as $check_email )
$usage_count = $usage_count + sizeof( array_keys( $used_by, $check_email ) );
if ( $usage_count >= $coupon->usage_limit_per_user ) {
$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED );
// Remove the coupon
unset( $this->applied_coupons[ $key ] );
WC()->session->set( 'coupon_codes', $this->applied_coupons );
WC()->session->set( 'refresh_totals', true );
}
}
}
}

View File

@ -51,6 +51,9 @@ class WC_Coupon {
/** @public int Coupon usage limit. */
public $usage_limit;
/** @public int Coupon usage limit per user. */
public $usage_limit_per_user;
/** @public int Coupon usage count. */
public $usage_count;
@ -104,22 +107,23 @@ class WC_Coupon {
if ( $coupon_data ) {
$this->id = absint( $coupon_data['id'] );
$this->type = esc_html( $coupon_data['type'] );
$this->amount = esc_html( $coupon_data['amount'] );
$this->individual_use = esc_html( $coupon_data['individual_use'] );
$this->product_ids = is_array( $coupon_data['product_ids'] ) ? $coupon_data['product_ids'] : array();
$this->exclude_product_ids = is_array( $coupon_data['exclude_product_ids'] ) ? $coupon_data['exclude_product_ids'] : array();
$this->usage_limit = absint( $coupon_data['usage_limit'] );
$this->usage_count = absint( $coupon_data['usage_count'] );
$this->expiry_date = esc_html( $coupon_data['expiry_date'] );
$this->apply_before_tax = esc_html( $coupon_data['apply_before_tax'] );
$this->free_shipping = esc_html( $coupon_data['free_shipping'] );
$this->product_categories = is_array( $coupon_data['product_categories'] ) ? $coupon_data['product_categories'] : array();
$this->exclude_product_categories = is_array( $coupon_data['exclude_product_categories'] ) ? $coupon_data['exclude_product_categories'] : array();
$this->exclude_sale_items = esc_html( $coupon_data['exclude_sale_items'] );
$this->minimum_amount = esc_html( $coupon_data['minimum_amount'] );
$this->customer_email = esc_html( $coupon_data['customer_email'] );
$this->id = absint( $coupon_data['id'] );
$this->type = esc_html( $coupon_data['type'] );
$this->amount = esc_html( $coupon_data['amount'] );
$this->individual_use = esc_html( $coupon_data['individual_use'] );
$this->product_ids = is_array( $coupon_data['product_ids'] ) ? $coupon_data['product_ids'] : array();
$this->exclude_product_ids = is_array( $coupon_data['exclude_product_ids'] ) ? $coupon_data['exclude_product_ids'] : array();
$this->usage_limit = absint( $coupon_data['usage_limit'] );
$this->usage_limit_per_user = absint( $coupon_data['usage_limit_per_user'] );
$this->usage_count = absint( $coupon_data['usage_count'] );
$this->expiry_date = esc_html( $coupon_data['expiry_date'] );
$this->apply_before_tax = esc_html( $coupon_data['apply_before_tax'] );
$this->free_shipping = esc_html( $coupon_data['free_shipping'] );
$this->product_categories = is_array( $coupon_data['product_categories'] ) ? $coupon_data['product_categories'] : array();
$this->exclude_product_categories = is_array( $coupon_data['exclude_product_categories'] ) ? $coupon_data['exclude_product_categories'] : array();
$this->exclude_sale_items = esc_html( $coupon_data['exclude_sale_items'] );
$this->minimum_amount = esc_html( $coupon_data['minimum_amount'] );
$this->customer_email = esc_html( $coupon_data['customer_email'] );
} else {
@ -138,21 +142,22 @@ class WC_Coupon {
$this->coupon_custom_fields = get_post_meta( $this->id );
$load_data = array(
'discount_type' => 'fixed_cart',
'coupon_amount' => 0,
'individual_use' => 'no',
'product_ids' => '',
'exclude_product_ids' => '',
'usage_limit' => '',
'usage_count' => '',
'expiry_date' => '',
'apply_before_tax' => 'yes',
'free_shipping' => 'no',
'product_categories' => array(),
'exclude_product_categories' => array(),
'exclude_sale_items' => 'no',
'minimum_amount' => '',
'customer_email' => array()
'discount_type' => 'fixed_cart',
'coupon_amount' => 0,
'individual_use' => 'no',
'product_ids' => '',
'exclude_product_ids' => '',
'usage_limit' => '',
'usage_limit_per_user' => '',
'usage_count' => '',
'expiry_date' => '',
'apply_before_tax' => 'yes',
'free_shipping' => 'no',
'product_categories' => array(),
'exclude_product_categories' => array(),
'exclude_sale_items' => 'no',
'minimum_amount' => '',
'customer_email' => array()
);
foreach ( $load_data as $key => $default )
@ -213,11 +218,15 @@ class WC_Coupon {
* Increase usage count fo current coupon.
*
* @access public
* @param string $used_by Either user ID or billing email
* @return void
*/
public function inc_usage_count() {
public function inc_usage_count( $used_by = '' ) {
$this->usage_count++;
update_post_meta( $this->id, 'usage_count', $this->usage_count );
if ( $used_by )
add_post_meta( $this->id, '_used_by', strtolower( $used_by ) );
}
@ -225,11 +234,19 @@ class WC_Coupon {
* Decrease usage count fo current coupon.
*
* @access public
* @param string $used_by Either user ID or billing email
* @return void
*/
public function dcr_usage_count() {
public function dcr_usage_count( $used_by = '' ) {
global $wpdb;
$this->usage_count--;
update_post_meta( $this->id, 'usage_count', $this->usage_count );
// Delete 1 used by meta
$meta_id = $wpdb->get_var( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE meta_key = '_used_by' AND meta_value = %s AND post_id = %d LIMIT 1;", $used_by, $this->id ) );
if ( $meta_id )
delete_metadata_by_mid( 'post', $meta_id );
}
/**
@ -267,6 +284,18 @@ class WC_Coupon {
}
}
// Per user usage limit - check here if user is logged in (aginst user IDs)
// Checked again for emails later on in WC_Cart::check_customer_coupons()
if ( $this->usage_limit_per_user > 0 && is_user_logged_in() ) {
$used_by = get_post_meta( $this->id, '_used_by' );
$usage_count = sizeof( array_keys( $used_by, get_current_user_id() ) );
if ( $usage_count >= $this->usage_limit_per_user ) {
$valid = false;
$error_code = self::E_WC_COUPON_USAGE_LIMIT_REACHED;
}
}
// Expired
if ( $this->expiry_date ) {
if ( current_time( 'timestamp' ) > $this->expiry_date ) {

View File

@ -1364,7 +1364,12 @@ class WC_Order {
continue;
$coupon = new WC_Coupon( $code );
$coupon->inc_usage_count();
$used_by = $this->user_id;
if ( ! $used_by )
$used_by = $this->billing_email;
$coupon->inc_usage_count( $used_by );
}
}
@ -1390,7 +1395,12 @@ class WC_Order {
continue;
$coupon = new WC_Coupon( $code );
$coupon->dcr_usage_count();
$used_by = $this->user_id;
if ( ! $used_by )
$used_by = $this->billing_email;
$coupon->dcr_usage_count( $used_by );
}
}

View File

@ -185,6 +185,7 @@ Yes you can! Join in on our [GitHub repository](http://github.com/woothemes/wooc
* Feature - woocommerce_get_featured_product_ids function.
* Feature - WOOCOMMERCE_DELIMITER to customise the pipes for attributes
* Feature - Standardized, default credit card form for gateways to use if they support 'default_credit_card_form'.
* Feature - Coupon usage limits per user (using email + ID).
* Tweak - Added pagination to tax rate screens.
* Tweak - Added filter to check the 'Create account' checkbox on checkout by default.
* Tweak - Update CPT parameters for 'product_variation' and 'shop_coupon' to be no longer public.