Lesson 53: Prevent Duplicate Auctions by Enforcing One Auction Per Listing

When developing the Flipnzee Auctions plugin, a valuable architectural issue emerged during testing. Because the same listing was used repeatedly, multiple auction records were created for a single listing. Although this was acceptable during development, it exposed an important design flaw.

A marketplace listing should normally have only one auction associated with it. If multiple auction records exist for the same listing, the frontend may display an older auction instead of the latest one, leading to incorrect bid history, current bid values, and auction status.

In this lesson, the plugin will be improved to enforce a one-listing-one-auction relationship, ensuring cleaner data and more predictable behaviour.


What Problem Are We Solving?

During testing, several auction records existed for the same listing:

Listing ID 491

├── Auction #25
├── Auction #26
├── Auction #27
├── Auction #30
└── Auction #31

Although Auction #31 contained the latest bids, the frontend displayed Auction #25 because it appeared first in the query results.

The correct design should always be:

Listing ID 491
        │
        ▼
     Auction #31

One listing should always reference one auction.


Objectives

By the end of this lesson, the plugin will:

  • Prevent multiple auction records for the same listing.
  • Detect when an auction already exists.
  • Update the existing auction instead of creating another.
  • Keep auction history clean.
  • Ensure the frontend always displays the correct auction.

Implementation Plan

The implementation will include the following improvements:

Step 1

Check whether an auction already exists for the selected listing before inserting a new record.


Step 2

If an auction already exists:

  • Update its prices.
  • Update auction dates.
  • Update reserve price.
  • Update Buy Now price.
  • Preserve the same auction ID.

Step 3

Only create a new auction if no auction exists for that listing.


Step 4

Display an admin notice such as:

An auction already exists for this listing. The existing auction has been updated instead of creating a duplicate.

This makes the behaviour clear to administrators.


Step 5

Verify that frontend shortcodes always display the latest auction because only one auction record exists.


Expected Benefits

After completing this lesson:

  • Cleaner database structure.
  • No duplicate auctions.
  • Correct bid history.
  • Correct highest bidder.
  • Correct current bid.
  • Easier maintenance.
  • Better user experience.

What You Will Learn

This lesson introduces an important database design principle:

Enforce data integrity at the application level rather than relying on users to avoid mistakes.

Instead of allowing duplicate auction records and trying to handle them later, the plugin will proactively prevent them from being created.

This small architectural improvement will make the Flipnzee Auctions plugin significantly more reliable as development continues.


Coming Up Next

In the next implementation lesson, we will modify the auction creation logic so that every listing can have only one associated auction, automatically updating the existing auction whenever the administrator edits its settings instead of creating duplicate records.

Lesson 52 Implementation: Continue Displaying Closed Auctions with a Clear “Auction Ended” Status

In the previous lesson, the Flipnzee Auctions plugin stopped displaying auctions immediately after they were closed. While that prevented further bidding, it also removed the auction from public view, making it impossible for visitors to see the final auction result.

In this lesson, the plugin was improved so that closed auctions remain visible. Instead of disappearing, they now display an “Auction Ended” status and prevent any further bids from being placed.


Why This Change Was Needed

Previously, once an auction status became closed, the frontend shortcode only retrieved active auctions.

This caused several problems:

  • Visitors could no longer view completed auctions.
  • Final bid information disappeared.
  • Buyers could not see that an auction had successfully concluded.
  • There was no indication that bidding had ended.

A completed auction is still valuable information, so it should remain publicly visible.


Step 1: Update the Auction Query

Open:

includes/class-auction-manager.php

Locate the method:

public static function get_active_auctions()

Replace the query with:

public static function get_active_auctions() {

	global $wpdb;

	$table = $wpdb->prefix . 'flipnzee_auctions';

	return $wpdb->get_results(
		"SELECT *
		FROM {$table}
		WHERE status IN ('active', 'closed')
		ORDER BY auction_end ASC",
		ARRAY_A
	);
}

Instead of returning only active auctions, the method now also retrieves closed auctions.


Step 2: Detect Whether an Auction Has Ended

Inside:

includes/class-shortcodes.php

After loading the auction information, determine its current state:

$current_time = current_time( 'timestamp' );
$end_time     = strtotime( $auction['auction_end'] );

if ( $end_time <= $current_time ) {

	$status       = 'ended';
	$status_label = '🔴 Auction Ended';

} elseif ( ( $end_time - $current_time ) <= DAY_IN_SECONDS ) {

	$status       = 'ending';
	$status_label = '🟡 Ending Soon';

} else {

	$status       = 'live';
	$status_label = '🟢 Live Auction';

}

This allows the auction card to dynamically display whether the auction is live, ending soon, or has already ended.


Step 3: Display the Auction Status Badge

Above the auction title, output the status label:

<div class="flipnzee-auction-status <?php echo esc_attr( $status ); ?>">
	<?php echo esc_html( $status_label ); ?>
</div>

Visitors can now instantly recognise the auction state.


Step 4: Prevent Further Bidding

Hide the bidding form after the auction has ended.

Example:

<?php if ( $status !== 'ended' ) : ?>

	<!-- Bid form -->

<?php else : ?>

	<div class="flipnzee-auction-closed">
		<strong>🏁 Auction Closed</strong><br>
		This auction has ended. No further bids are accepted.
	</div>

<?php endif; ?>

This ensures the auction remains visible while preventing additional bids.


Step 5: Style the Closed Auction Notice

In:

assets/css/frontend.css

Add:

.flipnzee-auction-closed {
	background: #fff5f5;
	border: 1px solid #e74c3c;
	color: #b71c1c;
	padding: 12px;
	margin-top: 15px;
	border-radius: 6px;
	font-size: 14px;
}

The notice clearly communicates that bidding has finished.


Testing

The implementation was tested by viewing a closed auction.

Expected behaviour:

  • The auction card remained visible.
  • The status badge changed to Auction Ended.
  • The bid form disappeared.
  • Visitors were informed that no further bids would be accepted.

During testing, it was also observed that historical test auctions remained visible because multiple auction records had been created for the same listing during development. This highlighted an architectural improvement that would be addressed in the next lesson.

Download Source Code

Download the completed version after this lesson:


What Was Learned

This lesson improved the user experience by ensuring that completed auctions remain publicly visible instead of disappearing. Visitors can continue viewing the listing, the auction status, and historical information, while the system correctly prevents any new bids after the auction has ended.

The implementation also revealed the importance of maintaining a single auction record per listing, which would become the focus of the next lesson to simplify auction management and avoid duplicate auction histories.

Lesson 52: Display Closed Auctions Instead of Hiding Them

In the previous lesson, we introduced automatic auction closure based on the auction end time. While that prevented late bids, it also exposed an important usability issue—once an auction was marked as closed, the entire auction disappeared from the frontend.

In this lesson, we’ll improve the user experience by displaying completed auctions instead of hiding them. Visitors will still be able to see the auction results, while bidding will be disabled.


What You’ll Learn

By the end of this lesson, you’ll be able to:

  • Display both active and closed auctions.
  • Show an Auction Closed status message.
  • Continue displaying the final bid amount.
  • Display the winning bidder.
  • Keep the complete bid history visible.
  • Disable the bid form once the auction has ended.
  • Improve transparency and trust for buyers and sellers.

Why This Matters

Imagine visiting an auction page only to discover that it has completely disappeared after the auction ended.

Questions immediately arise:

  • Who won?
  • What was the final bid?
  • Was the auction successful?
  • Is the page broken?

Professional auction platforms never hide completed auctions. Instead, they preserve the auction page as a permanent record.

Examples include:

  • eBay
  • Heritage Auctions
  • Sotheby’s
  • Copart

Visitors can still review the auction outcome even though bidding has ended.


Current Behaviour

At present, our shortcode behaves roughly like this:

if ( $auction['status'] !== 'active' ) {
    return '';
}

As soon as the auction status changes to closed, nothing is displayed.


Desired Behaviour

Instead of hiding the auction, we’ll show something like this:

Status
Closed

Winning Bid
$55,555,579

Winner
Rajeev Bagra

Auction Ended
15 Aug 2026 15:14 UTC

🏆 This auction has ended.
No further bids are accepted.

The bid history will remain visible.

Only the bidding form will disappear.


Implementation Plan

During this lesson we’ll:

Step 1

Remove the logic that hides closed auctions.


Step 2

Display an “Auction Closed” badge whenever the auction status is closed.


Step 3

Continue showing:

  • Start Price
  • Current Bid
  • Highest Bidder
  • Buy Now Price
  • Auction End Time
  • Bid History

Step 4

Hide only:

  • Bid input field
  • Place Bid button

Step 5

Display a friendly message:

🏁 This auction has ended. No further bids are accepted.


Benefits

After completing this lesson, Flipnzee Auctions will provide a much more professional experience.

Visitors will be able to:

  • Verify who won the auction.
  • See the final selling price.
  • Review the complete bid history.
  • Trust that auctions are permanently recorded.
  • Understand immediately that bidding has ended.

What We’ll Build

By the end of this lesson, every completed auction page will resemble a real-world auction result page instead of disappearing completely.

This lays the foundation for future enhancements such as:

  • 🏆 Winner badges
  • 🎉 Sold ribbons
  • Seller notifications
  • Winner email notifications
  • Auction archives
  • Recently Sold listings
  • Searchable auction history

Next Lesson

Lesson 53: Highlight the Winning Bidder and Final Selling Price for Closed Auctions

In the next lesson, we’ll enhance the completed auction page by prominently displaying the winner and the final sale price with improved styling, making the auction results more visually appealing and easier to understand.

Lesson 51 Implementation: Automatically Closing Expired Auctions

One of the most important responsibilities of an auction platform is ensuring that bidding stops exactly when the auction ends. In earlier lessons, our Flipnzee Auctions plugin allowed users to place bids while the auction was active. However, there was still one significant issue—an auction could technically remain active in the database even after its scheduled end time.

In this lesson, we solved that problem by automatically closing expired auctions during the bid validation process.


The Problem

Imagine an auction scheduled to end at 15:00 UTC.

If nobody manually changes its status, the auction could continue showing as Active, allowing visitors to attempt placing bids after the deadline.

This creates several problems:

  • Bids may be accepted after the auction has ended.
  • Auction status becomes inaccurate.
  • Administrators must manually close every auction.
  • Buyers lose confidence in the auction system.

We wanted the plugin to handle this automatically.


Our Approach

Whenever a user submits a bid, the plugin now performs one additional check before accepting it:

  1. Retrieve the auction’s end time.
  2. Compare it with the current UTC time.
  3. If the auction has expired:
    • Update its status to closed.
    • Reject the bid immediately.

This ensures that expired auctions are automatically closed the first time someone interacts with them after the deadline.


Step 1: Compare the Current Time

We used PHP’s gmdate() function to generate the current UTC time and compared it with the stored auction end time.

if (
    strtotime( gmdate( 'Y-m-d H:i:s' ) ) >=
    strtotime( $auction->auction_end )
) {

Using UTC prevents issues caused by different server time zones.


Step 2: Update the Auction Status

If the auction has expired, we update the auction record in the database.

$wpdb->update(
    $auction_table,
    array(
        'status' => 'closed',
    ),
    array(
        'id' => $auction_id,
    ),
    array(
        '%s',
    ),
    array(
        '%d',
    )
);

This permanently marks the auction as closed.


Step 3: Reject the Bid

After closing the auction, the function immediately returns false.

return false;

This prevents any further processing and ensures no late bids are accepted.


Complete Code

The new logic added to place_bid() looks like this:

if (
    strtotime( gmdate( 'Y-m-d H:i:s' ) ) >=
    strtotime( $auction->auction_end )
) {

    $wpdb->update(
        $auction_table,
        array(
            'status' => 'closed',
        ),
        array(
            'id' => $auction_id,
        ),
        array(
            '%s',
        ),
        array(
            '%d',
        )
    );

    return false;
}

Why This Design Works

Instead of relying on scheduled cron jobs or manual administration, the auction closes itself naturally whenever someone attempts to interact with it after its end time.

This approach is:

  • Simple
  • Reliable
  • Lightweight
  • Easy to maintain

It also avoids unnecessary background processes for smaller websites.


Current Limitation

While the auction now closes automatically, our frontend currently hides closed auctions from visitors.

This means that once an auction expires, its page no longer displays any auction information.

Although this successfully prevents further bidding, it isn’t the best user experience because visitors cannot see:

  • the winning bidder,
  • the final bid,
  • or the auction history.

We’ll address this in the next lesson.


What We Learned

In this lesson, we enhanced the bidding system by introducing automatic auction closure.

Specifically, we learned how to:

  • compare UTC timestamps using gmdate() and strtotime(),
  • update database records using $wpdb->update(),
  • automatically change an auction’s status,
  • prevent late bids,
  • and improve the reliability of the auction workflow.


Next Lesson

In Lesson 52, we’ll improve the user experience by displaying completed auctions instead of hiding them. Visitors will still be able to view the final auction details, including the winning bidder, winning amount, bid history, and a clear “Auction Closed” status, while the bidding form will be disabled.

Lesson 51: Automatically Close Auctions After Expiry

I would revise Lesson 51 rather than discard it. The feature you implemented is still useful, but the title and objective should reflect what it actually does.

Revised Lesson 51

Lesson 51: Automatically Close Auctions After the End Time

What This Lesson Covers

In this lesson, we’ll make the auction system automatically recognize when an auction has reached its end time.

Instead of requiring an administrator to manually close auctions, the plugin will automatically update the auction status to closed whenever a visitor interacts with the auction after its scheduled end.

This prevents late bids from being accepted and ensures auctions finish at the correct time.


What We Implement

✔ Compare the current UTC time with the auction end time.

✔ Automatically change the auction status from active to closed.

✔ Prevent any future bids from being accepted.

✔ Keep the auction data intact for later display.


Why This Matters

Without automatic closure:

  • Auctions could remain active indefinitely.
  • Users might continue placing bids after the deadline.
  • Administrators would have to close every auction manually.

With this improvement:

  • Auctions close themselves automatically.
  • The database always reflects the correct status.
  • The bidding system becomes much more reliable.

What We Did

Inside the bid validation process, we checked whether the auction had already expired.

If the current UTC time is greater than or equal to the auction end time, we immediately update the auction status.

Example:

if ( strtotime( gmdate( 'Y-m-d H:i:s' ) ) >= strtotime( $auction->auction_end ) ) {

    $wpdb->update(
        $auction_table,
        array(
            'status' => 'closed',
        ),
        array(
            'id' => $auction_id,
        ),
        array(
            '%s',
        ),
        array(
            '%d',
        )
    );

    return false;
}

What Happens Now

If a visitor attempts to place a bid after the auction has ended:

  1. The plugin checks the auction end time.
  2. The auction status is automatically updated to closed.
  3. The bid is rejected.
  4. Future visitors will also see the auction as closed.

Current Limitation

At this stage, a closed auction is no longer displayed by the frontend shortcode.

While this successfully prevents further bidding, it also hides the auction from visitors.

We’ll improve this behavior in the next lesson.


Next Lesson

Lesson 52: Display Closed Auctions with Winner Information

Instead of hiding completed auctions, we’ll:

  • Display an Auction Closed badge.
  • Show the winning bidder.
  • Display the winning bid.
  • Keep the bid history visible.
  • Hide only the bidding form.
  • Create a permanent auction record for visitors.

Lesson 50 Implementation: Prevent Last-Second Bid Sniping with Automatic Auction Time Extension

One of the biggest frustrations in online auctions is bid sniping—when someone waits until the final few seconds before placing a bid, leaving other bidders with no opportunity to respond. Many professional auction platforms solve this by automatically extending the auction if a bid is received near the end.

In this lesson, we implemented the same feature in the Flipnzee Auctions plugin. Whenever a valid bid is placed during the final 60 seconds of an auction, the auction end time is automatically extended by five minutes.


What We Built

The auction system now:

  • Detects every successful bid.
  • Checks how much time remains before the auction ends.
  • If 60 seconds or less remain:
    • Extends the auction by five minutes.
    • Updates the auction end time in the database.
  • Otherwise, the auction continues normally.

This gives every interested bidder a fair opportunity to respond.


Step 1: Locate the Bid Processing Method

Open:

includes/class-bid-manager.php

Locate:

public static function place_bid(

This method already:

  • validates bids,
  • inserts the bid into the database,
  • updates the auction’s current highest bid.

Step 2: Keep the Existing Current Bid Update

After a successful bid, the plugin already updates the auction table.

$wpdb->update(
    $auction_table,
    array(
        'current_bid' => $bid_amount,
    ),
    array(
        'id' => $auction_id,
    ),
    array(
        '%f',
    ),
    array(
        '%d',
    )
);

Immediately after this block, we inserted the anti-sniping logic.


Step 3: Retrieve the Auction End Time

First, fetch the current auction end time.

$auction = $wpdb->get_row(
    $wpdb->prepare(
        "SELECT auction_end
        FROM {$auction_table}
        WHERE id = %d",
        $auction_id
    )
);

This retrieves the existing end time directly from the database.


Step 4: Calculate Remaining Time

Convert the auction end time into a Unix timestamp.

$end_time = strtotime(
    $auction->auction_end
);

$current_time = current_time(
    'timestamp'
);

$seconds_left =
    $end_time - $current_time;

Using timestamps makes it easy to compare dates mathematically.


Step 5: Detect Last-Minute Bids

Only extend the auction if less than one minute remains.

if ( $seconds_left <= 60 ) {

    // Extend auction
}

This prevents unnecessary extensions while protecting against last-second sniping.


Step 6: Extend the Auction

Add five minutes to the existing end time.

$new_end = date(
    'Y-m-d H:i:s',
    $end_time + ( 5 * 60 )
);

Notice that:

5 × 60 = 300 seconds

The new end time is generated in MySQL datetime format.


Step 7: Save the New Auction End Time

Update the auction record.

$wpdb->update(
    $auction_table,
    array(
        'auction_end' => $new_end,
    ),
    array(
        'id' => $auction_id,
    ),
    array(
        '%s',
    ),
    array(
        '%d',
    )
);

The countdown timer will now use this updated value.


Complete Anti-Sniping Code

/*
 * Anti-sniping:
 * Extend auction by 5 minutes if
 * less than 60 seconds remain.
 */
$auction = $wpdb->get_row(
    $wpdb->prepare(
        "SELECT auction_end
        FROM {$auction_table}
        WHERE id = %d",
        $auction_id
    )
);

if ( $auction ) {

    $end_time = strtotime(
        $auction->auction_end
    );

    $current_time = current_time(
        'timestamp'
    );

    $seconds_left =
        $end_time - $current_time;

    if ( $seconds_left <= 60 ) {

        $new_end = date(
            'Y-m-d H:i:s',
            $end_time + ( 5 * 60 )
        );

        $wpdb->update(
            $auction_table,
            array(
                'auction_end' => $new_end,
            ),
            array(
                'id' => $auction_id,
            ),
            array(
                '%s',
            ),
            array(
                '%d',
            )
        );
    }
}

Why This Feature Matters

Many professional auction websites use automatic auction extensions because they:

  • Prevent unfair last-second bidding.
  • Give genuine buyers time to react.
  • Encourage competitive bidding.
  • Increase seller confidence.
  • Often lead to higher final selling prices.

Instead of rewarding the fastest click during the final seconds, the auction rewards the highest genuine bidder.


Testing

Although this lesson was successfully implemented, testing was intentionally postponed.

Because auction end times depend on server time and WordPress timezone settings, it is more practical to verify the feature during a real auction approaching its end. When a bid is placed within the final 60 seconds, the auction end time should automatically increase by five minutes.

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


What We Learned

In this lesson we learned how to:

  • Improve auction fairness with anti-sniping logic.
  • Work with Unix timestamps in PHP.
  • Calculate remaining auction time.
  • Update MySQL datetime values.
  • Extend auction durations dynamically.
  • Build a feature commonly found in commercial auction platforms.

Final Thoughts

A simple five-minute extension can significantly improve the fairness and competitiveness of an online auction. By adding anti-sniping protection, Flipnzee Auctions now behaves much more like established auction marketplaces, ensuring that every bidder has a reasonable opportunity to respond before an auction closes.

Lesson 50: Prevent Last-Second Bid Sniping with an Auction Time Extension

One of the biggest frustrations in online auctions is bid sniping—when someone waits until the last few seconds to place a bid, leaving no time for other bidders to respond. Many professional auction platforms solve this by automatically extending the auction whenever a bid is placed near the end.

In this lesson, we’ll implement the same feature in the Flipnzee Auctions plugin.


What You Will Build

By the end of this lesson, your plugin will automatically extend an auction if a bid is placed during the final minute.

For example:

  • Auction ends at 10:00:00 AM
  • A bid is placed at 9:59:45 AM
  • The auction end time automatically changes to 10:05:00 AM

This gives other bidders a fair opportunity to respond.


Why This Feature Is Important

Without an extension mechanism:

  • Someone can win with a last-second bid.
  • Other bidders never get a chance to react.
  • Auctions feel unfair.

With an automatic extension:

  • Competition remains active.
  • Sellers often receive higher final prices.
  • Buyers perceive the auction as more transparent and fair.

How the Feature Will Work

Each time a bid is accepted, the plugin will:

  1. Read the current auction end time.
  2. Compare it with the current server time.
  3. Calculate the remaining time.
  4. If less than 60 seconds remain:
    • Extend the auction by 5 minutes.
  5. Save the new end time in the database.
  6. The countdown timer automatically updates on the frontend.

Logic Flow

Bid Received
      │
      ▼
Is Auction Ending Within 60 Seconds?
      │
 ┌────┴────┐
 │         │
No        Yes
 │         │
 ▼         ▼
Keep     Add 5 Minutes
Time     to auction_end
 │         │
 └────┬────┘
      ▼
Save Auction
      ▼
Update Countdown

Files We Will Modify

During this lesson we will work with:

includes/class-bid-manager.php

to update the auction end time after a successful bid.

We will also verify the countdown in:

assets/js/countdown.js

No changes should be needed there because it already reads the updated end time from the database.


What You Will Learn

In this lesson you will learn how to:

  • Work with PHP date and time functions.
  • Compare timestamps.
  • Update auction records automatically.
  • Modify existing database values.
  • Build anti-sniping protection similar to commercial auction platforms.

Expected Result

Before:

Auction Ends In

00m 40s

User places a bid.

After:

Auction Extended!

04m 59s

The countdown immediately reflects the new auction end time, giving all bidders additional time to compete.


Why This Makes Flipnzee Better

Adding automatic auction extensions moves Flipnzee Auctions closer to enterprise-grade auction platforms. It encourages fair competition, discourages last-second bid sniping, and can help sellers achieve better final prices through increased bidding activity.


Coming Up Next

In Lesson 51, we’ll build an Auction Winner System that automatically determines the winning bidder once the auction ends and records the auction result, paving the way for payment processing and order fulfillment.

Lesson 49 (Implementation): Display Professional Success and Error Messages After Bidding

In the previous lesson, the Flipnzee Auctions plugin redirected users back to the auction page after a bid was processed. While the functionality worked, users had no confirmation that their bid had been accepted or rejected.

In this implementation lesson, the plugin is enhanced to display clear success and error notifications using URL parameters, providing a much better user experience.


Objective

Implement professional notification messages that inform users whether:

  • Their bid was accepted.
  • Their bid was rejected.

Instead of silently refreshing the page, users now receive immediate feedback.


Step 1: Redirect With Status Parameter

The bid handler already redirects back to the auction page.

Instead of simply redirecting:

wp_safe_redirect( get_permalink( $listing_id ) );
exit;

it was updated to include a status parameter.

Successful bid:

wp_safe_redirect(
    add_query_arg(
        'bid',
        'success',
        get_permalink( $listing_id )
    )
);

exit;

Failed bid:

wp_safe_redirect(
    add_query_arg(
        'bid',
        'failed',
        get_permalink( $listing_id )
    )
);

exit;

This produces URLs like:

https://flipnzee.com/testing/?bid=success

or

https://flipnzee.com/testing/?bid=failed

Step 2: Detect the Status Parameter

Inside includes/class-shortcodes.php, just before displaying the auction details table, a check was added.

<?php

if ( isset( $_GET['bid'] ) ) {

    $status = sanitize_text_field(
        wp_unslash( $_GET['bid'] )
    );

    if ( 'success' === $status ) {
        ?>

        <div class="flipnzee-notice flipnzee-success">
            ✅ Your bid has been placed successfully.
        </div>

        <?php

    } elseif ( 'failed' === $status ) {
        ?>

        <div class="flipnzee-notice flipnzee-error">
            ❌ Your bid could not be accepted.
        </div>

        <?php
    }
}
?>

The plugin now safely reads the URL parameter and displays the appropriate message.


Step 3: Style the Notifications

The following CSS was added to assets/css/frontend.css.

.flipnzee-notice {

    padding: 15px;

    margin: 20px 0;

    border-radius: 6px;

    font-weight: 600;

}

.flipnzee-success {

    background: #ecfdf3;

    color: #046c4e;

    border-left: 5px solid #10b981;

}

.flipnzee-error {

    background: #fef2f2;

    color: #b91c1c;

    border-left: 5px solid #ef4444;

}

These styles make notifications stand out while remaining consistent with modern WordPress admin and frontend design.


Step 4: Test the Feature

Two scenarios were tested.

Successful Bid

After placing a valid bid, the user is redirected to:

?bid=success

and sees:

✅ Your bid has been placed successfully.

Failed Bid

If the bid is rejected, the user is redirected to:

?bid=failed

and sees:

❌ Your bid could not be accepted.

Troubleshooting During Implementation

Several issues were encountered before the feature worked correctly.

1. Notification Did Not Appear

Initially, the URL contained:

?bid=success

but no message appeared.

The issue was that the notification block had been inserted inside a section of code that wasn’t executed at the correct point in the page lifecycle.

Moving the notification logic immediately before the auction details table resolved the problem.


2. PHP Syntax Error

A parse error occurred after moving code.

Using the PHP linter helped identify the exact line causing the issue.

php -l includes/class-shortcodes.php

This quickly revealed an unexpected token error, allowing it to be corrected before uploading the plugin.


3. HTML Structure

During implementation, the winning/outbid status message was accidentally placed inside a <tr> element.

This produced invalid HTML.

The message block was moved outside the table so that the auction metadata table remained structurally correct.


4. User Display Name

While testing, the highest bidder appeared simply as:

user

instead of the expected display name.

The SQL query was working correctly—the WordPress account’s Display Name was simply set to “user”. Updating the profile’s display name fixed the issue.


Final Result

The auction page now provides immediate visual feedback whenever a bid is processed.

Users no longer have to guess whether their action succeeded, making the bidding experience more intuitive and professional.

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


What We Learned

In this lesson, we learned how to:

  • Redirect users with URL parameters.
  • Read query string values safely using sanitize_text_field() and wp_unslash().
  • Display contextual success and error notices.
  • Style frontend notifications with CSS.
  • Validate PHP files using the built-in linter.
  • Keep HTML structure valid while inserting dynamic content.

Conclusion

Although the underlying bidding logic was already functional, adding user-facing notifications significantly improved the usability of the Flipnzee Auctions plugin. Small enhancements like clear feedback messages create a smoother and more professional user experience, reducing confusion and increasing user confidence during the bidding process.

Lesson 49: Display Professional Success and Error Messages After Bid Submission

In the previous lesson, we prevented users from bidding on their own highest bid using server-side validation. Although the validation worked correctly, users received no explanation when their bid was rejected.

In this lesson, we’ll build a simple notification system that displays clear success and error messages after every bid submission.

This greatly improves the user experience and makes our auction plugin feel much more professional.


Why User Feedback Matters

Imagine clicking Place Bid and nothing appears to happen.

Questions immediately arise:

  • Was my bid accepted?
  • Was there an error?
  • Should I click again?
  • Did the auction end?

Professional websites always inform users about the result of their actions.


The Solution

We’ll use a simple three-step approach:

  1. Detect whether the bid succeeded or failed.
  2. Redirect back to the auction page with a status parameter.
  3. Display a colored notification at the top of the auction.

Example User Experience

Successful Bid

✅ Your bid has been placed successfully.

Already Highest Bidder

❌ You are already the highest bidder.

Bid Too Low

❌ Your bid must be higher than the current bid.

Auction Closed

❌ This auction has already ended.

Login Required

❌ Please log in before placing a bid.

Step 1: Return Meaningful Error Codes

Our bid manager already returns:

return new WP_Error(
    'already_highest_bidder',
    __( 'You are already the highest bidder.', 'flipnzee-auctions' )
);

We’ll use these error codes to determine which message should be shown after the redirect.


Step 2: Redirect With a Status

Instead of silently redirecting back to the auction page, we’ll append a query parameter.

For example:

?bid=success

or

?bid=already_highest_bidder

or

?bid=too_low

These parameters tell the frontend exactly what happened.


Step 3: Read the Status

When the auction page loads, we’ll check:

$_GET['bid']

Depending on its value, we’ll display the appropriate notification.


Step 4: Style the Notification

Instead of plain text, we’ll create attractive notice boxes.

Examples:

Green success box

✅ Your bid has been placed successfully.

Red error box

❌ You are already the highest bidder.

Yellow warning box

⚠ Auction has ended.

Benefits

Implementing a notification system provides several advantages:

  • Better user experience
  • Immediate feedback after bidding
  • Fewer accidental duplicate submissions
  • Easier troubleshooting
  • More professional interface
  • Consistent behavior across all bid scenarios

Real-World Examples

Many popular platforms provide similar feedback after user actions.

For example:

  • eBay displays confirmation when a bid is accepted.
  • WooCommerce shows notices after adding items to the cart.
  • WordPress displays success and error notices after saving settings.

Users have come to expect this behavior, making it an essential feature for any interactive application.


What You’ll Learn Next

In Lesson 50, we’ll implement an Auction Winner system.

When an auction ends, the plugin will automatically determine the highest bidder and mark them as the winner. This lays the foundation for future features such as winner notifications, payment processing, and auction completion workflows.

Lesson 48 Implementation – Prevent Users from Bidding on Their Own Highest Bid

In the previous lesson, we discussed why an auction system should not allow the current highest bidder to keep increasing their own bid. In this implementation lesson, we’ll add server-side validation that prevents self-bidding.

By the end of this lesson, if the highest bidder attempts to place another bid, the bid will be rejected before it is saved to the database.


Step 1: Open the Bid Manager

Open the following file:

includes/class-bid-manager.php

Locate the place_bid() method:

public static function place_bid(
    $auction_id,
    $bidder_id,
    $bid_amount
) {

Step 2: Retrieve the Current Highest Bidder

Inside the function, locate the following code:

global $wpdb;

$bid_table = $wpdb->prefix . 'flipnzee_bids';

$auction_table = $wpdb->prefix . 'flipnzee_auctions';

Immediately after it, add:

$highest_bidder = self::get_highest_bidder(
    $auction_id
);

The beginning of the function now becomes:

global $wpdb;

$bid_table = $wpdb->prefix . 'flipnzee_bids';

$auction_table = $wpdb->prefix . 'flipnzee_auctions';

$highest_bidder = self::get_highest_bidder(
    $auction_id
);

This loads the user who currently owns the highest bid.


Step 3: Locate the Current Bid

Further down in the same function, locate the code that retrieves the current bid:

$current_bid = $wpdb->get_var(
    $wpdb->prepare(
        "SELECT current_bid
        FROM {$auction_table}
        WHERE id = %d",
        $auction_id
    )
);

Step 4: Prevent Self-Bidding

Immediately after the code above, add:

if (
    $highest_bidder &&
    (int) $highest_bidder->bidder_id === (int) $bidder_id
) {

    return new WP_Error(

        'already_highest_bidder',

        __(
            'You are already the highest bidder.',
            'flipnzee-auctions'
        )

    );

}

The complete section now becomes:

$current_bid = $wpdb->get_var(
    $wpdb->prepare(
        "SELECT current_bid
        FROM {$auction_table}
        WHERE id = %d",
        $auction_id
    )
);

if (
    $highest_bidder &&
    (int) $highest_bidder->bidder_id === (int) $bidder_id
) {

    return new WP_Error(

        'already_highest_bidder',

        __(
            'You are already the highest bidder.',
            'flipnzee-auctions'
        )

    );

}

Whenever the logged-in user is already leading the auction, the function stops immediately and returns a WP_Error.


Step 5: Upload the Updated Plugin

Create a fresh ZIP archive of the plugin:

zip -r flipnzee-auctions.zip flipnzee-auctions

Upload it through:

Plugins → Add New → Upload Plugin

Activate the updated version.


Step 6: Test the Feature

Log in as the user who currently has the highest bid.

Try placing another bid.

Expected Result

The bid is rejected.

No new row is inserted into the bids table.

The current bid remains unchanged.

Although no message is displayed yet, the backend validation is working correctly.


Complete Source Code

Retrieve the Highest Bidder

global $wpdb;

$bid_table = $wpdb->prefix . 'flipnzee_bids';

$auction_table = $wpdb->prefix . 'flipnzee_auctions';

$highest_bidder = self::get_highest_bidder(
    $auction_id
);

Prevent Self-Bidding

$current_bid = $wpdb->get_var(
    $wpdb->prepare(
        "SELECT current_bid
        FROM {$auction_table}
        WHERE id = %d",
        $auction_id
    )
);

if (
    $highest_bidder &&
    (int) $highest_bidder->bidder_id === (int) $bidder_id
) {

    return new WP_Error(

        'already_highest_bidder',

        __(
            'You are already the highest bidder.',
            'flipnzee-auctions'
        )

    );

}

What We Achieved

By implementing this lesson, we made the auction system smarter and fairer.

The plugin now:

  • Prevents users from bidding against themselves.
  • Stops unnecessary increases in the auction price.
  • Uses secure server-side validation that cannot be bypassed from the browser.
  • Returns a WP_Error when a self-bid is detected.
  • Ensures the current bid and bid history remain unchanged when the validation fails.

At this stage, the validation works correctly, although users do not yet receive a visible explanation when their bid is rejected.

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:

What’s Next?

In Lesson 49, we’ll build a proper notification system to display friendly success and error messages after bid submission, including:

  • ✅ Bid placed successfully.
  • ❌ You are already the highest bidder.
  • ❌ Bid amount is too low.
  • ❌ Auction has ended.

This will complete the bidding workflow and provide users with clear, professional feedback after every action.