WooCommerce Plugin Development | Chapter 7 | Discount Plugin

by | Dec 8, 2025 | Plugin Development, Web App Development | 0 comments

Chapter 7: Multiple Coupons with Active / Inactive State

Until now, your plugin supported one coupon configuration. In this chapter we’ll refactor it so that:

  • You can define multiple coupons in admin
  • Each coupon has:
    • Code
    • Discount type (flat/percentage)
    • Discount value
    • Min / max order value
    • Customer type (any/new/existing)
    • Status: active / inactive
  • At checkout, when a customer enters a code, the plugin:
    • Looks up a matching active coupon
    • Applies its specific rules

We’ll store all coupons in a single option:

sdr_coupons

as an array of coupon rule arrays.

From this chapter onward, we no longer use the old single-coupon options like sdr_discount_type, sdr_flat_discount_amount, etc., for logic. They can remain in DB but are ignored.

1. Data Structure for Coupons

Each coupon in sdr_coupons will look like:

array(

    ‘code’          => ‘WELCOME10’,

    ‘discount_type’ => ‘percentage’, // or ‘flat’

    ‘discount_value’=> 10,           // 10% or 10 currency units

    ‘min_order’     => 1000,

    ‘max_order’     => 0,            // 0 = no limit

    ‘customer_type’ => ‘new’,        // any|new|existing

    ‘status’        => ‘active’,     // active|inactive

);

2. Update the Admin Save Handler (Global + Add Coupon)

We’ll keep:

  • Global options:
    • sdr_checkout_message
    • sdr_enable_discount (master switch)
  • New behavior:
    • A Global Settings form to update message + master enable
    • A New Coupon form to add coupons into sdr_coupons

Replace your existing sdr_handle_settings_form_submit() with this:

/**
 * Handle settings form submission (global settings + add coupon).
 */
function sdr_handle_settings_form_submit() {

    // GLOBAL SETTINGS SAVE.
    if ( isset( $_POST['sdr_global_settings_submit'] ) ) {

        // Check nonce.
        if ( ! isset( $_POST['sdr_global_settings_nonce'] ) || ! wp_verify_nonce( $_POST['sdr_global_settings_nonce'], 'sdr_save_global_settings' ) ) {
            return;
        }

        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            return;
        }

        $message = isset( $_POST['sdr_checkout_message'] )
            ? wp_kses_post( wp_unslash( $_POST['sdr_checkout_message'] ) )
            : '';

        $enable_discount = isset( $_POST['sdr_enable_discount'] ) && 'yes' === $_POST['sdr_enable_discount']
            ? 'yes'
            : 'no';

        update_option( 'sdr_checkout_message', $message );
        update_option( 'sdr_enable_discount', $enable_discount );

        add_settings_error(
            'sdr_messages',
            'sdr_message',
            __( 'Global settings saved.', 'simple-discount-rules' ),
            'updated'
        );
    }

    // ADD NEW COUPON.
    if ( isset( $_POST['sdr_add_coupon_submit'] ) ) {

        // Check nonce.
        if ( ! isset( $_POST['sdr_add_coupon_nonce'] ) || ! wp_verify_nonce( $_POST['sdr_add_coupon_nonce'], 'sdr_add_coupon' ) ) {
            return;
        }

        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            return;
        }

        $code = isset( $_POST['sdr_coupon_code'] )
            ? strtoupper( sanitize_text_field( wp_unslash( $_POST['sdr_coupon_code'] ) ) )
            : '';

        $discount_type = isset( $_POST['sdr_discount_type'] )
            ? sanitize_text_field( wp_unslash( $_POST['sdr_discount_type'] ) )
            : 'flat';

        if ( ! in_array( $discount_type, array( 'flat', 'percentage' ), true ) ) {
            $discount_type = 'flat';
        }

        $discount_value = isset( $_POST['sdr_discount_value'] )
            ? floatval( wp_unslash( $_POST['sdr_discount_value'] ) )
            : 0;

        if ( $discount_value < 0 ) {
            $discount_value = 0;
        }

        $min_order = isset( $_POST['sdr_min_order_amount'] )
            ? floatval( wp_unslash( $_POST['sdr_min_order_amount'] ) )
            : 0;

        if ( $min_order < 0 ) {
            $min_order = 0;
        }

        $max_order = isset( $_POST['sdr_max_order_amount'] )
            ? floatval( wp_unslash( $_POST['sdr_max_order_amount'] ) )
            : 0;

        if ( $max_order < 0 ) {
            $max_order = 0;
        }

        $customer_type = isset( $_POST['sdr_customer_type'] )
            ? sanitize_text_field( wp_unslash( $_POST['sdr_customer_type'] ) )
            : 'any';

        if ( ! in_array( $customer_type, array( 'any', 'new', 'existing' ), true ) ) {
            $customer_type = 'any';
        }

        $status = isset( $_POST['sdr_status'] )
            ? sanitize_text_field( wp_unslash( $_POST['sdr_status'] ) )
            : 'active';

        if ( ! in_array( $status, array( 'active', 'inactive' ), true ) ) {
            $status = 'inactive';
        }

        // Basic validation.
        if ( empty( $code ) || $discount_value <= 0 ) { add_settings_error( 'sdr_messages', 'sdr_coupon_error', __( 'Coupon code and discount value are required.', 'simple-discount-rules' ), 'error' ); return; } $coupons = get_option( 'sdr_coupons', array() ); if ( ! is_array( $coupons ) ) { $coupons = array(); } $new_coupon = array( 'code' => $code,
            'discount_type' => $discount_type,
            'discount_value'=> $discount_value,
            'min_order'     => $min_order,
            'max_order'     => $max_order,
            'customer_type' => $customer_type,
            'status'        => $status,
        );

        $coupons[] = $new_coupon;

        update_option( 'sdr_coupons', $coupons );

        add_settings_error(
            'sdr_messages',
            'sdr_coupon_added',
            __( 'New coupon added.', 'simple-discount-rules' ),
            'updated'
        );
    }
}
add_action( 'admin_init', 'sdr_handle_settings_form_submit' );

3. Handle Toggle / Delete Actions for Coupons

We’ll allow toggling active/inactive and deleting coupons via GET links from the list table.
Add this function:

/**
 * Handle coupon row actions: toggle status / delete.
 */
function sdr_handle_coupon_row_actions() {

    if ( ! isset( $_GET['sdr_coupon_action'], $_GET['sdr_coupon_index'] ) ) {
        return;
    }

    if ( ! current_user_can( 'manage_woocommerce' ) ) {
        return;
    }

    if ( ! isset( $_GET['sdr_coupon_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['sdr_coupon_nonce'] ) ), 'sdr_coupon_action' ) ) {
        return;
    }

    $action = sanitize_text_field( wp_unslash( $_GET['sdr_coupon_action'] ) );
    $index  = intval( $_GET['sdr_coupon_index'] );

    $coupons = get_option( 'sdr_coupons', array() );
    if ( ! is_array( $coupons ) || ! isset( $coupons[ $index ] ) ) {
        return;
    }

    if ( 'toggle' === $action ) {
        $current_status = isset( $coupons[ $index ]['status'] ) ? $coupons[ $index ]['status'] : 'inactive';
        $coupons[ $index ]['status'] = ( 'active' === $current_status ) ? 'inactive' : 'active';

        update_option( 'sdr_coupons', $coupons );

        add_settings_error(
            'sdr_messages',
            'sdr_coupon_toggled',
            __( 'Coupon status updated.', 'simple-discount-rules' ),
            'updated'
        );
    } elseif ( 'delete' === $action ) {

        unset( $coupons[ $index ] );
        $coupons = array_values( $coupons ); // re-index

        update_option( 'sdr_coupons', $coupons );

        add_settings_error(
            'sdr_messages',
            'sdr_coupon_deleted',
            __( 'Coupon deleted.', 'simple-discount-rules' ),
            'updated'
        );
    }
}
add_action( 'admin_init', 'sdr_handle_coupon_row_actions' );

4. Update the Settings Page to Show “Global Settings + New Coupon + Coupon List”

Now we replace sdr_render_settings_page() so it:

  1. Shows global helper message + master enable (Form 1)
  2. Shows “Add New Coupon” form (Form 2)
  3. Lists all coupons with toggle/delete actions

Replace your existing sdr_render_settings_page() with this version:


<?php
/**
 * Render the plugin settings page.
 */
function sdr_render_settings_page() {

    $current_message = get_option(
        'sdr_checkout_message',
        __( 'Enter your special discount code at checkout to claim the offer.', 'simple-discount-rules' )
    );

    $enable_discount = get_option( 'sdr_enable_discount', 'yes' );
    $coupons         = get_option( 'sdr_coupons', array() );
    if ( ! is_array( $coupons ) ) {
        $coupons = array();
    }

    // Show any messages.
    settings_errors( 'sdr_messages' );
    ?>

    <div class="wrap">
        <h1><?php esc_html_e( 'Simple Discount Rules – Settings', 'simple-discount-rules' ); ?></h1>

        <h2><?php esc_html_e( 'Global Settings', 'simple-discount-rules' ); ?></h2>

        <form method="post" action="">
            <?php wp_nonce_field( 'sdr_save_global_settings', 'sdr_global_settings_nonce' ); ?>

            <table class="form-table" role="presentation">
                <tbody>

                    <tr>
                        <th scope="row">
                            <?php esc_html_e( 'Enable All Discounts', 'simple-discount-rules' ); ?>
                        </th>
                        <td>
                            <label>
                                <input type="checkbox" name="sdr_enable_discount" value="yes" <?php checked( $enable_discount, 'yes' ); ?> />
                                <?php esc_html_e( 'Enable all coupon-based rules', 'simple-discount-rules' ); ?>
                            </label>
                            <p class="description"><?php esc_html_e( 'Uncheck to disable all discounts.', 'simple-discount-rules' ); ?></p>
                        </td>
                    </tr>

                    <tr>
                        <th scope="row">
                            <label for="sdr_checkout_message"><?php esc_html_e( 'Checkout Helper Message', 'simple-discount-rules' ); ?></label>
                        </th>
                        <td>
                            <textarea name="sdr_checkout_message" id="sdr_checkout_message" rows="4" cols="50" class="large-text"><?php echo esc_textarea( $current_message ); ?></textarea>
                            <p class="description"><?php esc_html_e( 'Shown near the coupon box.', 'simple-discount-rules' ); ?></p>
                        </td>
                    </tr>

                </tbody>
            </table>

            <?php submit_button( __( 'Save Global Settings', 'simple-discount-rules' ), 'primary', 'sdr_global_settings_submit' ); ?>

        </form>

        <hr />

        <h2><?php esc_html_e( 'Add New Coupon', 'simple-discount-rules' ); ?></h2>

        <form method="post" action="">
            <?php wp_nonce_field( 'sdr_add_coupon', 'sdr_add_coupon_nonce' ); ?>

            <table class="form-table" role="presentation">
                <tbody>

                    <tr>
                        <th scope="row"><label for="sdr_coupon_code"><?php esc_html_e( 'Coupon Code', 'simple-discount-rules' ); ?></label></th>
                        <td>
                            <input type="text" id="sdr_coupon_code" name="sdr_coupon_code" class="regular-text" />
                            <p class="description"><?php esc_html_e( 'Example: WELCOME10', 'simple-discount-rules' ); ?></p>
                        </td>
                    </tr>

                    <tr>
                        <th scope="row"><label for="sdr_discount_type"><?php esc_html_e( 'Discount Type', 'simple-discount-rules' ); ?></label></th>
                        <td>
                            <select name="sdr_discount_type" id="sdr_discount_type">
                                <option value="flat"><?php esc_html_e( 'Flat', 'simple-discount-rules' ); ?></option>
                                <option value="percentage"><?php esc_html_e( 'Percentage', 'simple-discount-rules' ); ?></option>
                            </select>
                        </td>
                    </tr>

                    <tr>
                        <th scope="row"><label for="sdr_discount_value"><?php esc_html_e( 'Discount Value', 'simple-discount-rules' ); ?></label></th>
                        <td>
                            <input type="number" step="0.01" min="0" id="sdr_discount_value" name="sdr_discount_value" class="small-text" />
                            <p class="description"><?php echo wp_kses_post( __( 'Flat = currency value, Percentage = percent value.', 'simple-discount-rules' ) ); ?></p>
                        </td>
                    </tr>

                    <tr>
                        <th scope="row"><label for="sdr_min_order_amount"><?php esc_html_e( 'Minimum Order Value', 'simple-discount-rules' ); ?></label></th>
                        <td>
                            <input type="number" step="0.01" min="0" id="sdr_min_order_amount" name="sdr_min_order_amount" class="small-text" />
                        </td>
                    </tr>

                    <tr>
                        <th scope="row"><label for="sdr_max_order_amount"><?php esc_html_e( 'Maximum Order Value', 'simple-discount-rules' ); ?></label></th>
                        <td>
                            <input type="number" step="0.01" min="0" id="sdr_max_order_amount" name="sdr_max_order_amount" class="small-text" />
                        </td>
                    </tr>

                    <tr>
                        <th scope="row"><label for="sdr_customer_type"><?php esc_html_e( 'Eligible Customers', 'simple-discount-rules' ); ?></label></th>
                        <td>
                            <select name="sdr_customer_type" id="sdr_customer_type">
                                <option value="any"><?php esc_html_e( 'All customers', 'simple-discount-rules' ); ?></option>
                                <option value="new"><?php esc_html_e( 'New customers', 'simple-discount-rules' ); ?></option>
                                <option value="existing"><?php esc_html_e( 'Existing customers', 'simple-discount-rules' ); ?></option>
                            </select>
                        </td>
                    </tr>

                    <tr>
                        <th scope="row"><label for="sdr_status"><?php esc_html_e( 'Status', 'simple-discount-rules' ); ?></label></th>
                        <td>
                            <select name="sdr_status" id="sdr_status">
                                <option value="active"><?php esc_html_e( 'Active', 'simple-discount-rules' ); ?></option>
                                <option value="inactive"><?php esc_html_e( 'Inactive', 'simple-discount-rules' ); ?></option>
                            </select>
                        </td>
                    </tr>

                </tbody>
            </table>

            <?php submit_button( __( 'Add Coupon', 'simple-discount-rules' ), 'secondary', 'sdr_add_coupon_submit' ); ?>

        </form>

        <hr />

        <h2><?php esc_html_e( 'Existing Coupons', 'simple-discount-rules' ); ?></h2>

        <?php if ( empty( $coupons ) ) : ?>

            <p><?php esc_html_e( 'No coupons found.', 'simple-discount-rules' ); ?></p>

        <?php else : ?>

            <table class="widefat fixed striped">
                <thead>
                    <tr>
                        <th>Code</th>
                        <th>Type</th>
                        <th>Value</th>
                        <th>Min</th>
                        <th>Max</th>
                        <th>Customer</th>
                        <th>Status</th>
                        <th>Actions</th>
                    </tr>
                </thead>

                <tbody>

                <?php foreach ( $coupons as $index => $coupon ) : ?>

                    <?php
                    $code      = $coupon['code'];
                    $type      = $coupon['discount_type'];
                    $value     = $coupon['discount_value'];
                    $min       = $coupon['min_order'];
                    $max       = $coupon['max_order'];
                    $cust_type = $coupon['customer_type'];
                    $status    = $coupon['status'];
                    ?>

                    <tr>
                        <td><code><?php echo esc_html( $code ); ?></code></td>
                        <td><?php echo ( 'percentage' === $type ) ? 'Percentage' : 'Flat'; ?></td>
                        <td><?php echo esc_html( $value ); ?></td>
                        <td><?php echo $min ? wc_price( $min ) : '&mdash;'; ?></td>
                        <td><?php echo $max ? wc_price( $max ) : '&mdash;'; ?></td>
                        <td><?php echo esc_html( ucfirst( $cust_type ) ); ?></td>
                        <td><?php echo ( 'active' === $status ) ? 'Active' : 'Inactive'; ?></td>
                        <td>---</td>
                    </tr>

                <?php endforeach; ?>

                </tbody>
            </table>

        <?php endif; ?>

    </div>

<?php
}
?>

5. Update the Discount Logic to Use the Coupon List

We already have sdr_is_customer_eligible() from Chapter 6. Now we change sdr_apply_flat_discount() so it:

  1. Checks global enable
  2. Reads the entered code from session
  3. Looks up the first active coupon whose code matches (case-insensitive)
  4. Uses that coupon’s settings instead of global options

Replace your existing sdr_apply_flat_discount() with:

/**
 * Apply a discount when the correct custom coupon code is entered.
 * Works with multiple coupons defined in sdr_coupons option.
 *
 * @param WC_Cart $cart Cart object.
 */
function sdr_apply_flat_discount( $cart ) {

    if ( ! sdr_is_woocommerce_active() ) {
        return;
    }

    // Don't run in admin except during AJAX.
    if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
        return;
    }

    // Check global enable switch.
    $enabled = get_option( 'sdr_enable_discount', 'yes' );
    if ( 'yes' !== $enabled ) {
        return;
    }

    // Fetch the entered code from the session.
    $entered_code = '';
    if ( class_exists( 'WC' ) && WC()->session ) {
        $entered_code = WC()->session->get( 'sdr_coupon_code', '' );
    }

    if ( empty( $entered_code ) ) {
        return;
    }

    // Load all stored coupons.
    $coupons = get_option( 'sdr_coupons', array() );
    if ( ! is_array( $coupons ) || empty( $coupons ) ) {

        // User tried to apply a code but no coupons exist.
        if ( isset( $_POST['sdr_coupon_code'] ) ) {
            wc_add_notice(
                __( 'The discount code you entered is not valid.', 'simple-discount-rules' ),
                'error'
            );
        }
        return;
    }

    // Find a matching active coupon.
    $matched_coupon = null;

    foreach ( $coupons as $coupon ) {

        $code   = isset( $coupon['code'] ) ? $coupon['code'] : '';
        $status = isset( $coupon['status'] ) ? $coupon['status'] : 'inactive';

        if ( 'active' !== $status || empty( $code ) ) {
            continue;
        }

        // Case-insensitive compare.
        if ( 0 === strcasecmp( $entered_code, $code ) ) {
            $matched_coupon = $coupon;
            break;
        }
    }

    if ( ! $matched_coupon ) {

        if ( isset( $_POST['sdr_coupon_code'] ) ) {
            wc_add_notice(
                __( 'The discount code you entered is not valid.', 'simple-discount-rules' ),
                'error'
            );
        }

        return;
    }

    // Extract coupon fields safely.
    $coupon_code    = isset( $matched_coupon['code'] ) ? $matched_coupon['code'] : '';
    $discount_type  = isset( $matched_coupon['discount_type'] ) ? $matched_coupon['discount_type'] : 'flat';
    $discount_value = isset( $matched_coupon['discount_value'] ) ? floatval( $matched_coupon['discount_value'] ) : 0;
    $min_order      = isset( $matched_coupon['min_order'] ) ? floatval( $matched_coupon['min_order'] ) : 0;
    $max_order      = isset( $matched_coupon['max_order'] ) ? floatval( $matched_coupon['max_order'] ) : 0;
    $customer_type  = isset( $matched_coupon['customer_type'] ) ? $matched_coupon['customer_type'] : 'any';

    if ( $discount_value <= 0 ) { return; } $cart_subtotal = floatval( $cart->get_subtotal() );

    // Minimum order restriction.
    if ( $min_order > 0 && $cart_subtotal < $min_order ) { if ( isset( $_POST['sdr_coupon_code'] ) ) { $msg = sprintf( __( 'This discount is only valid for orders of at least %s.', 'simple-discount-rules' ), wc_price( $min_order ) ); wc_add_notice( $msg, 'error' ); } return; } // Maximum order restriction. if ( $max_order > 0 && $cart_subtotal > $max_order ) {

        if ( isset( $_POST['sdr_coupon_code'] ) ) {
            $msg = sprintf(
                __( 'This discount is only valid for orders up to %s.', 'simple-discount-rules' ),
                wc_price( $max_order )
            );
            wc_add_notice( $msg, 'error' );
        }

        return;
    }

    // Validate customer type (new/existing).
    if ( function_exists( 'sdr_is_customer_eligible' ) ) {

        list( $eligible, $error_message ) = sdr_is_customer_eligible( $customer_type );

        if ( ! $eligible ) {

            if ( isset( $_POST['sdr_coupon_code'] ) && ! empty( $error_message ) ) {
                wc_add_notice( $error_message, 'error' );
            }

            return;
        }
    }

    // Begin discount calculation.
    $discount = 0.0;

    if ( 'percentage' === $discount_type ) {

        $discount = ( $cart_subtotal * $discount_value ) / 100;

        if ( $discount > $cart_subtotal ) {
            $discount = $cart_subtotal;
        }

    } else {

        // Flat discount.
        $discount = min( $discount_value, $cart_subtotal );
    }

    if ( $discount <= 0 ) { return; } // Show success message only when user applies code manually. if ( isset( $_POST['sdr_coupon_code'] ) ) { if ( 'percentage' === $discount_type ) { $notice = sprintf( __( 'Your %s%% discount has been applied!', 'simple-discount-rules' ), esc_html( $discount_value ) ); } else { $notice = __( 'Your special discount has been applied!', 'simple-discount-rules' ); } wc_add_notice( $notice, 'success' ); } // Build discount label. if ( 'percentage' === $discount_type ) { $label = sprintf( __( 'Special Discount (%1$s – %2$s%%)', 'simple-discount-rules' ), esc_html( $coupon_code ), esc_html( $discount_value ) ); } else { $label = sprintf( __( 'Special Discount (%s)', 'simple-discount-rules' ), esc_html( $coupon_code ) ); } // Apply the discount as a negative fee. $cart->add_fee( $label, - $discount );
}

add_action( 'woocommerce_cart_calculate_fees', 'sdr_apply_flat_discount', 20, 1 );

 

6. Test Your Multi-Coupon Setup

  1. In WooCommerce → Simple Discount Rules:
    • Turn on “Enable All Discounts”
    • Add several coupons:
      • WELCOME10: 10% off, min ₹1000, customer type “new”, status active
      • FLAT200: flat 200, min ₹500, status active
      • VIP50: 50% off, customer type “existing”, status inactive
  2. In checkout:
    • Enter WELCOME10 as a new user → discount applies, respects min value
    • Enter FLAT200 → correct fixed amount applies
    • Enter VIP50 → no discount (inactive), error “code not valid”
    • Toggle VIP50 to Active in admin → try again → now works for existing customers

You now have a tiny discount rules engine that supports multiple conditions and multiple coupons.

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *