Lesson 83: Creating a Reusable Admin Dashboard Card Component in Flipnzee Auctions

Introduction

As the Flipnzee Auctions plugin continues to grow, the administration dashboard is displaying more statistics such as Total Auctions, Active Auctions, Scheduled Auctions, Closed Auctions, Pending Payments, and Paid Transactions.

Although Lesson 82 moved the dashboard styling into an external stylesheet, the PHP still contains repeated HTML blocks for every dashboard card.

This repetition makes future maintenance harder. Every time a new statistic is added, the same HTML structure must be copied again.

In this lesson, we’ll refactor the dashboard by introducing a reusable helper function that generates dashboard cards dynamically.

This follows the DRY (Don’t Repeat Yourself) principle and makes the code significantly cleaner.


What We’ll Build

Instead of writing this repeatedly:

<div class="flipnzee-dashboard-card">
    <h2><?php echo esc_html( $value ); ?></h2>
    <p><?php echo esc_html( $title ); ?></p>
</div>

we’ll create a helper function like:

private function render_dashboard_card( $title, $value ) {
    ?>
    <div class="flipnzee-dashboard-card">
        <h2><?php echo esc_html( $value ); ?></h2>
        <p><?php echo esc_html( $title ); ?></p>
    </div>
    <?php
}

Then the dashboard becomes much simpler:

foreach ( $cards as $title => $value ) {
    $this->render_dashboard_card( $title, $value );
}

Why This Refactoring Matters

Instead of maintaining multiple HTML snippets throughout the dashboard, all card rendering will be handled in one place.

Benefits include:

  • Cleaner PHP
  • Less duplicate code
  • Easier maintenance
  • Easier redesign later
  • Consistent dashboard appearance
  • Better object-oriented design

What You’ll Learn

In this lesson you’ll learn how to:

  • Identify duplicated HTML
  • Create reusable helper methods
  • Use $this->method() inside classes
  • Separate presentation logic
  • Improve code readability
  • Build reusable UI components in WordPress plugins

Implementation Plan

Step 1

Locate the dashboard card HTML inside dashboard_page().


Step 2

Create a private helper method:

private function render_dashboard_card( $title, $value )

Step 3

Move the repeated HTML into this helper.


Step 4

Replace duplicated HTML with

$this->render_dashboard_card(
    $title,
    $value
);

inside the loop.


Step 5

Test the dashboard to ensure every statistic card still renders correctly.


Expected Outcome

After completing this lesson:

  • Dashboard code becomes much shorter.
  • Only one function controls the card layout.
  • Future dashboard statistics can be added with a single array entry.
  • Any future visual changes require editing only one method.
  • The Flipnzee Auctions dashboard becomes more modular and easier to extend.

Files We’ll Modify

  • admin/class-admin.php

Difficulty Level

⭐⭐☆☆☆ (Beginner–Intermediate)


Estimated Time

20–30 minutes


Coming Up Next

In Lesson 84, we’ll continue improving the admin interface by introducing a reusable admin notice/message system, allowing success, warning, and error messages to be displayed consistently across all Flipnzee admin pages without duplicating HTML.

Lesson 82 Implementation: Refactoring the Flipnzee Admin Dashboard with External CSS

After completing the planning phase in Lesson 82, the next step was to improve the maintainability of the Flipnzee Auctions dashboard by separating presentation from PHP logic. Until now, the dashboard cards relied heavily on inline CSS, making the code difficult to maintain and extend.

In this lesson, the dashboard styling was moved into a dedicated stylesheet while keeping the existing functionality unchanged.


Goal

Refactor the Flipnzee Auctions Dashboard by:

  • Removing inline styling from the dashboard cards
  • Creating a dedicated admin.css stylesheet
  • Loading the stylesheet only on Flipnzee admin pages
  • Keeping the dashboard visually identical while improving code quality

Step 1: Created a Dedicated Admin Stylesheet

A new stylesheet was created inside the plugin.

assets/
└── css/
    ├── frontend.css
    └── admin.css

This file now contains all dashboard-related styles.

Example:

.flipnzee-dashboard-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
    margin-top: 25px;
}

.flipnzee-dashboard-card {
    flex: 1 1 220px;
    max-width: 260px;
    padding: 20px;
    background: #fff;
    border: 1px solid #ccd0d4;
    border-radius: 6px;
    box-shadow: 0 1px 2px rgba(0,0,0,.08);
    text-align: center;
}

.flipnzee-dashboard-card h2 {
    margin: 0;
    font-size: 34px;
    line-height: 1;
}

.flipnzee-dashboard-card p {
    margin-top: 10px;
    font-weight: 600;
    color: #50575e;
}

Step 2: Replaced Inline Styles

Previously, the dashboard contained markup similar to:

<div style="display:flex;flex-wrap:wrap;gap:20px;">

This was replaced with semantic class names.

<div class="flipnzee-dashboard-grid">

Likewise, each statistics card became:

<div class="flipnzee-dashboard-card">

instead of using long inline style="" attributes.


Step 3: Enqueued the Admin Stylesheet

A new function was added to the main plugin file.

function flipnzee_admin_enqueue_styles( $hook ) {

    if ( false === strpos( $hook, 'flipnzee' ) ) {
        return;
    }

    wp_enqueue_style(
        'flipnzee-admin',
        plugin_dir_url( __FILE__ ) . 'assets/css/admin.css',
        array(),
        FLIPNZEE_AUCTION_VERSION
    );
}

add_action(
    'admin_enqueue_scripts',
    'flipnzee_admin_enqueue_styles'
);

This ensures that the stylesheet loads only on Flipnzee administration pages.


Step 4: Troubleshooting

Initially, the dashboard cards continued to appear vertically stacked.

After investigation, the issue was traced to the plugin folder name on the live server.

The uploaded plugin directory had been named:

82lesson

instead of its expected plugin folder name.

Because of this, the browser returned:

404 Not Found

for:

assets/css/admin.css

Once the plugin folder was corrected and the stylesheet loaded successfully, the dashboard immediately displayed correctly.

This highlighted the importance of verifying that static assets are actually being served by the web server before assuming there is a CSS or PHP issue.


Step 5: Result

After loading the stylesheet successfully:

  • Dashboard cards displayed horizontally.
  • Responsive wrapping worked correctly.
  • Card spacing became consistent.
  • Inline CSS was removed from the dashboard.
  • The admin interface became much cleaner and easier to maintain.

The refactored dashboard now follows a much more professional plugin architecture.


Lessons Learned

This lesson reinforced several important WordPress development practices:

  • Keep presentation separate from business logic.
  • Avoid large inline style attributes.
  • Load assets only where they are needed.
  • Verify browser network requests when debugging missing CSS.
  • Organize plugin assets into dedicated CSS and JavaScript files.

Benefits of This Refactoring

  • Cleaner PHP templates
  • Easier maintenance
  • Better separation of concerns
  • Improved scalability
  • Reusable dashboard components
  • More professional WordPress plugin architecture

Testing Performed

The following tests were completed successfully:

  • ✅ Admin stylesheet loaded successfully.
  • ✅ Dashboard statistics displayed correctly.
  • ✅ Cards wrapped properly on different screen widths.
  • ✅ No PHP syntax errors were introduced.
  • ✅ No visual regressions occurred.
  • ✅ Plugin functionality remained unchanged after the refactoring.

Complete Source Code

The primary implementation consisted of:

  • Creating assets/css/admin.css
  • Replacing inline dashboard styling with reusable CSS classes
  • Registering and enqueueing the stylesheet using admin_enqueue_scripts
  • Updating the dashboard HTML to use the new class-based layout

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


Outcome: Lesson 82 modernized the Flipnzee Auctions admin dashboard by introducing a dedicated stylesheet and removing inline styling. The result is a cleaner, more maintainable codebase that aligns with WordPress development best practices while preserving the dashboard’s functionality and appearance.

Lesson 82: Add Maintenance Statistics to the Flipnzee Dashboard


Objective

Enhance the Flipnzee Auctions dashboard by displaying real-time maintenance statistics, allowing administrators to quickly monitor the health of the auction system.


Why This Lesson?

Currently, maintenance runs silently.

Administrators cannot easily determine:

  • How many auctions are active?
  • How many are scheduled?
  • How many have closed?
  • Are there expired auctions waiting to be processed?
  • How many transactions are pending?

Instead of opening multiple pages, the dashboard should provide this information at a glance.


What We’ll Build

A new Auction Maintenance Overview section on the Dashboard.

Example:

-----------------------------------------
 Flipnzee Auctions Dashboard
-----------------------------------------

Active Auctions .............. 12

Scheduled Auctions ........... 5

Closed Auctions .............. 48

Pending Transactions ......... 3

Paid Transactions ............ 21

Listings With Active Auction . 12

-----------------------------------------

Database Queries

We’ll count records directly from the existing tables.

Active auctions

SELECT COUNT(*)
FROM wp_flipnzee_auctions
WHERE status='active'

Scheduled auctions

SELECT COUNT(*)
FROM wp_flipnzee_auctions
WHERE status='draft'

Closed auctions

SELECT COUNT(*)
FROM wp_flipnzee_auctions
WHERE status='closed'

Pending payments

SELECT COUNT(*)
FROM wp_flipnzee_transactions
WHERE payment_status='pending'

Paid payments

SELECT COUNT(*)
FROM wp_flipnzee_transactions
WHERE payment_status='paid'

Benefits

Administrators can instantly verify that:

  • automatic activation is working
  • automatic closing is working
  • payment workflow is progressing
  • auction volume is increasing
  • no maintenance backlog exists

Files We’ll Modify

  • admin/class-admin.php

No database changes.

No new tables.

No schema updates.


Learning Outcomes

After completing this lesson, you’ll know how to:

  • Create an admin dashboard summary.
  • Execute aggregate database queries using $wpdb.
  • Display live system statistics.
  • Build informative WordPress admin interfaces.
  • Improve the usability of a plugin without changing its core business logic.

Estimated Difficulty

⭐⭐☆☆☆ (Beginner–Intermediate)

This lesson focuses on improving the administrator experience by presenting meaningful live statistics rather than introducing new backend logic.

It also prepares the dashboard for future enhancements, such as charts, maintenance history, and performance metrics in later lessons.

Lesson 81 Implementation: Refactoring the Auction Maintenance System for Better Maintainability

Introduction

In the previous lessons, the Flipnzee Auctions plugin gained automatic auction activation and automatic closing through WordPress Cron. Before introducing additional maintenance features, it was important to review the existing implementation and eliminate duplicate work.

During this lesson, a code audit revealed that much of the scheduled maintenance infrastructure had already been implemented in earlier development sessions. Instead of adding redundant functionality, the focus shifted to refactoring and documenting the existing code to improve maintainability while preserving backward compatibility.

This lesson demonstrates an important software engineering principle: before writing new code, first understand and improve what already exists.


Objective

The goals of this lesson were to:

  • Review the scheduled maintenance architecture.
  • Verify existing WP-Cron integration.
  • Confirm automatic activation and expiration processes.
  • Improve documentation.
  • Make maintenance functions more useful for future debugging and logging.

Existing Scheduled Maintenance

A review of the plugin confirmed that the activation hook already scheduled the maintenance event.

if ( ! wp_next_scheduled( 'flipnzee_auction_maintenance' ) ) {

    wp_schedule_event(
        time(),
        'hourly',
        'flipnzee_auction_maintenance'
    );

}

This meant no additional scheduling code was required.


Existing Maintenance Runner

The plugin also already contained a central maintenance method responsible for executing scheduled tasks.

public static function run_scheduled_maintenance() {

    self::activate_scheduled_auctions();
    self::close_expired_auctions();

}

This central runner keeps maintenance logic organized by placing all automated tasks in a single location.


Manual Maintenance Buttons

Another useful discovery was the administrator dashboard already contained manual maintenance controls.

Administrators can manually execute:

  • Activate Scheduled Auctions
  • Close Expired Auctions

without waiting for the hourly WP-Cron event.

This is extremely helpful while testing new auction features.


Refactoring close_expired_auctions()

Previously the function simply updated expired auctions but did not return any useful information.

Original structure:

$result = $wpdb->query(
    ...
);

The function now returns the number of affected auctions.

$result = $wpdb->query(
    ...
);

return (int) $result;

Why Return the Result?

Returning the number of updated auctions provides several future benefits.

Examples include:

  • maintenance reports
  • activity logs
  • cron debugging
  • unit testing
  • admin notifications

Future code can now simply do:

$closed = Flipnzee_Auction_Manager::close_expired_auctions();

which might return:

0

or

5

depending on how many auctions were closed.


Updating Documentation

The PHPDoc comments were also improved.

Before:

/**
 * Automatically close expired auctions.
 */

After:

/**
 * Automatically close expired auctions.
 *
 * @return int Number of auctions closed.
 */

Accurate documentation makes future maintenance significantly easier.


Testing

After completing the refactoring, the plugin was validated using PHP’s built-in syntax checker.

php -l includes/class-auction-manager.php

Result:

No syntax errors detected in includes/class-auction-manager.php

Additional testing confirmed:

  • WP-Cron scheduling still functions correctly.
  • Manual maintenance buttons continue working.
  • Automatic activation remains unchanged.
  • Automatic closing remains unchanged.
  • Existing functionality remains fully backward compatible.

Lessons Learned

Several important development principles emerged during this lesson.

  • Always inspect existing code before adding new functionality.
  • Refactoring often provides more value than writing duplicate code.
  • Returning useful values improves debugging and future extensibility.
  • Accurate PHPDoc comments are part of maintainable software.
  • Manual maintenance tools are invaluable during development and testing.

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


Conclusion

Although no major new feature was introduced, this lesson significantly improved the overall architecture of the Flipnzee Auctions plugin.

Instead of duplicating functionality, the maintenance system was carefully reviewed, existing capabilities were confirmed, and the codebase was refactored to make it cleaner, more informative, and easier to extend in future lessons.

This refactoring lays a stronger foundation for upcoming enhancements such as maintenance logs, cron statistics, email notifications, and more advanced background processing.

Lesson 81: Refactoring the Auction Maintenance System for Cleaner, Maintainable Code


Objective

Refactor the auction maintenance system by removing duplicate code, eliminating redundant database queries, relocating hooks to their appropriate locations, and simplifying the auction lifecycle without changing any existing functionality.


Why This Lesson?

As the Flipnzee Auctions plugin has grown, some functionality has evolved through multiple iterations. This has resulted in duplicate methods and repeated SQL queries that can make future maintenance more difficult.

The goal of this lesson is not to add new features, but to improve the internal architecture while keeping the plugin’s behaviour unchanged.


Current Maintenance Flow

WP-Cron
    │
    ▼
run_scheduled_maintenance()
    │
    ├── activate_scheduled_auctions()
    │
    └── update_expired_auctions()
            │
            ├── Close auctions
            ├── Determine winners
            ├── Fire hooks
            └── Activity log

Problems Identified

Duplicate auction-closing methods

The plugin currently contains both:

update_expired_auctions()

and

close_expired_auctions()

Both perform nearly the same responsibility.

Only one should remain.


Duplicate SQL execution

Inside close_expired_auctions() the update query is executed twice.

This increases unnecessary database activity.


Hook placed in the wrong location

The following hook currently appears inside the auction manager:

do_action(
    'flipnzee_auction_winner_determined',
    $auction,
    $winner
);

However:

  • $auction is not defined
  • $winner is not defined

The hook belongs immediately after the winner has actually been determined inside the Bid Manager.


Misleading method

The method

get_active_auctions()

returns:

active
closed

instead of only:

active

The method name and behaviour should match.


Maintenance responsibilities

The auction manager should be responsible for:

  • activating scheduled auctions
  • closing expired auctions
  • invoking winner determination

The Bid Manager should be responsible for:

  • determining winners
  • firing winner-related hooks

This keeps responsibilities separated and improves readability.


Implementation Plan

Step 1

Review the maintenance workflow.

Understand how:

  • activation
  • expiry
  • winner determination

are connected.


Step 2

Remove duplicate SQL from:

close_expired_auctions()

Step 3

Decide which method to keep:

  • update_expired_auctions()
  • close_expired_auctions()

Remove the redundant implementation.


Step 4

Move the winner hook into the Bid Manager.

After the winner has been determined:

do_action(
    'flipnzee_auction_winner_determined',
    $auction,
    $winner
);

should execute there.


Step 5

Simplify

run_scheduled_maintenance()

so that it remains the single entry point for scheduled auction processing.


Step 6

Correct

get_active_auctions()

so it returns only active auctions.


Step 7

Perform regression testing.

Verify:

  • scheduled auctions activate
  • expired auctions close
  • winners are still determined
  • transactions are still created
  • activity logging still works
  • payment workflow remains unaffected

Expected Result

After refactoring:

✔ No duplicate auction-closing methods

✔ No duplicate SQL execution

✔ Cleaner maintenance workflow

✔ Better separation of responsibilities

✔ Easier debugging

✔ Easier future enhancements


What We’ll Learn

During this lesson we’ll practice:

  • code refactoring
  • eliminating duplicate logic
  • applying the Single Responsibility Principle
  • organising WordPress plugin architecture
  • improving long-term maintainability

Why This Matters

Refactoring is an important phase in any mature software project. By removing technical debt now, Flipnzee Auctions will have a cleaner foundation for future features such as:

  • email notifications
  • real-time auction updates
  • advanced auction history
  • seller dashboards
  • buyer dashboards
  • marketplace analytics
  • REST API endpoints
  • webhook integrations

Expected Outcome

By the end of Lesson 81, the auction maintenance system will be simpler, cleaner, and easier to extend, while preserving all existing functionality and improving the overall quality of the Flipnzee Auctions codebase.

Lesson 80 Implementation: Supporting Unlimited Historical Auctions While Allowing Only One Active Auction per Listing

After planning the architecture in Lesson 80, it was time to implement one of the most important structural improvements in the Flipnzee Auctions plugin.

Earlier versions of the plugin prevented duplicate auctions by updating the existing auction whenever the same listing was selected. While this solved duplicate auction creation, it also prevented maintaining a complete auction history.

This lesson redesigns the logic so that a listing can have unlimited historical auctions while ensuring that only one auction can remain active at any given time.


The Problem

Originally, the plugin searched for any auction belonging to the listing.

$existing_auction = $wpdb->get_var(
    $wpdb->prepare(
        "SELECT id
        FROM {$table}
        WHERE listing_id = %d
        LIMIT 1",
        $listing_id
    )
);

If one existed, it was updated instead of creating a new auction.

Although simple, this approach caused a major limitation:

  • historical auctions could never be preserved
  • every new auction overwrote the previous one
  • reporting and analytics became inaccurate

New Design

Instead of checking whether the listing has ever been auctioned, the plugin now checks only for active auctions.

Conceptually the lookup becomes:

SELECT id
FROM wp_flipnzee_auctions
WHERE listing_id = ?
AND status = 'active'
LIMIT 1;

This small change completely changes the behaviour of the system.


Behaviour Before

Listing 494

Auction IDStatus
15Closed

Creating another auction resulted in:

Auction #15 being updated.

No historical record remained.


Behaviour After

Listing 494

Auction IDStatus
15Closed
21Closed
27Closed
35Active

Each completed auction remains permanently stored.

Only one auction is active.


Code Changes

The auction lookup logic was modified so only active auctions are considered duplicates.

If an active auction exists:

  • update that active auction
  • return its ID

Otherwise:

  • insert a completely new auction record

This preserves auction history while still preventing multiple active auctions for the same listing.


Testing Performed

Several scenarios were tested.

Test 1

Create first auction

Result:

  • New auction created

✔ Passed


Test 2

Create another auction while first auction is active

Result:

  • Existing active auction updated

✔ Passed


Test 3

Close auction

Result:

Auction status became Closed.

✔ Passed


Test 4

Create a new auction for the same listing

Result:

A brand-new auction record was inserted.

Previous closed auction remained untouched.

✔ Passed


Test 5

Verify All Auctions page

The administration screen correctly displayed:

Auction #35   Active
Auction #34   Closed

Both auctions belong to the same listing.

History is preserved.

✔ Passed


Benefits

The redesigned architecture provides several advantages:

  • Unlimited auction history
  • One active auction per listing
  • Cleaner reporting
  • Better analytics
  • Easier auditing
  • Marketplace-style auction lifecycle
  • Future support for auction history pages

What Was Learned

A small SQL condition can dramatically change application behaviour.

Instead of asking:

“Has this listing ever had an auction?”

the system now asks:

“Does this listing currently have an active auction?”

This subtle change aligns the plugin with how professional marketplace platforms typically manage auction lifecycles.


Source Code

The implementation focused primarily on:

  • includes/class-auction-manager.php

Key improvements included:

  • checking only for active auctions
  • preserving closed auctions
  • creating new records only when no active auction exists
  • maintaining a single active auction per listing

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


Final Result

Lesson 80 successfully redesigned the auction creation workflow.

The Flipnzee Auctions plugin now supports unlimited historical auctions while enforcing a single active auction per listing, providing a scalable foundation for future features such as auction archives, historical analytics, relisting workflows, and seller performance tracking.

Lesson 80: Preventing Duplicate Auctions for the Same Listing with Database-Level Validation

Introduction

As the Flipnzee Auctions plugin continued to mature, most of the auction workflow had become stable. Earlier lessons introduced automatic auction closing, winner detection, transaction generation, payment management, and duplicate transaction protection.

While reviewing historical auction data, another important question arose:

Can a listing accidentally have more than one active auction?

Although previous improvements had already reduced the possibility of duplicate auctions, adding another layer of protection inside the Auction Manager would make the plugin even more reliable.

This lesson focuses on auditing the auction creation workflow and implementing a final database-level safeguard that prevents multiple active auctions from being created for the same listing.


Why This Improvement Matters

A marketplace should never allow confusion about which auction is currently valid.

Without proper validation, multiple active auctions for the same listing could cause:

  • Multiple bidding interfaces
  • Conflicting highest bids
  • Incorrect winner selection
  • Duplicate transactions
  • Difficult ownership transfer

Even if such situations only occur because of programming mistakes or repeated requests, preventing them is essential.


Current Workflow Review

Before making changes, the existing auction lifecycle should be reviewed.

Listing Created
        │
        ▼
Create Auction
        │
        ▼
Auction Starts
        │
        ▼
Users Place Bids
        │
        ▼
Auction Ends
        │
        ▼
Winner Selected
        │
        ▼
Transaction Created

The only missing safeguard is ensuring that only one active auction can exist for a listing at any given time.


Objectives

In this lesson we will:

  • Audit the auction creation logic.
  • Search where auctions are inserted into the database.
  • Check whether an active auction already exists.
  • Prevent duplicate active auctions.
  • Return the existing auction instead of creating another one.
  • Test the protection with multiple creation attempts.

Implementation Plan

Step 1

Locate the auction creation method.

Search for:

create_auction(

or

$wpdb->insert(

inside

includes/class-auction-manager.php

Step 2

Before inserting a new auction, search for an existing active auction belonging to the same listing.

The validation should resemble:

SELECT id
FROM wp_flipnzee_auctions
WHERE listing_id = ?
AND status = 'active'
LIMIT 1

Step 3

If an active auction exists:

  • do not insert another record
  • return the existing auction ID

Step 4

Only if no active auction exists should the plugin execute:

$wpdb->insert(...)

Step 5

Test using:

  • Add Auction page
  • Edit Auction page
  • phpMyAdmin
  • Frontend listing page

Expected Workflow After Improvement

Create Auction
        │
        ▼
Check Active Auction
        │
   Exists?
    │       │
   Yes      No
    │        │
Return ID  Create Auction

What We Will Learn

This lesson introduces another important software engineering principle:

  • Defensive programming
  • Database validation
  • Idempotent creation methods
  • Marketplace integrity
  • Multi-layer validation
  • Business rule enforcement

Files Expected to Change

Primary file:

includes/class-auction-manager.php

Possible testing files:

admin/class-admin-add-auction.php

admin/class-admin-edit-auction.php

Expected Outcome

After completing Lesson 80:

  • A listing can never have two active auctions.
  • Duplicate auction creation attempts become harmless.
  • Historical auction records remain preserved.
  • Marketplace integrity improves.
  • The auction lifecycle becomes even more robust.

Difficulty

Intermediate


Estimated Time

30–45 minutes


Next Lesson Preview

Lesson 81 – Automatically Archive Completed Auctions and Preserve Historical Records

We’ll enhance the auction lifecycle by introducing an archival mechanism so completed auctions are retained for reporting and auditing while keeping active auction data clean and efficient.

Lesson 79: Auditing and Hardening the Transaction Creation Lifecycle in the Flipnzee Auctions Plugin

After successfully implementing the Payment Management system in the previous lessons, the next objective was to review the entire transaction creation workflow. During testing, some historical records revealed duplicate transactions for the same auction. Although these duplicates originated from earlier development versions of the plugin, this lesson focused on ensuring that such duplicates could never occur again.

Instead of simply assuming the issue had been resolved, the transaction creation logic was audited and strengthened by adding a final database validation before inserting a new transaction.


What We Wanted to Achieve

The transaction system should always follow these rules:

  • A listing can have multiple auctions over time.
  • Every auction should have only one winner.
  • Every auction should generate only one transaction.
  • Repeated callbacks or cron executions must never create duplicate transaction records.

Investigating the Transaction Lifecycle

The first step was to locate where transactions were actually inserted into the database.

Using Visual Studio Code’s global search, all $wpdb->insert() calls were reviewed.

Several insert operations were found:

  • Auction creation
  • Bid creation
  • Transaction creation

The transaction insertion code was located inside:

includes/class-transaction-manager.php

The original code directly inserted a new transaction without checking whether one already existed for the same auction.

$result = $wpdb->insert(
    $table,
    array(
        'auction_id'  => $data['auction_id'],
        'listing_id'  => $data['listing_id'],
        'seller_id'   => $data['seller_id'],
        'buyer_id'    => $data['buyer_id'],
        'winning_bid' => $data['winning_bid'],
        'status'      => 'pending',
    ),
    array(
        '%d',
        '%d',
        '%d',
        '%d',
        '%f',
        '%s',
    )
);

Although this worked correctly, it would create duplicate records if the function were accidentally executed more than once.


Adding Duplicate Transaction Protection

Before performing the insert operation, a database lookup was added.

The plugin now searches for an existing transaction belonging to the current auction.

$existing_transaction = $wpdb->get_var(
    $wpdb->prepare(
        "SELECT id
         FROM {$table}
         WHERE auction_id = %d
         LIMIT 1",
        absint( $data['auction_id'] )
    )
);

if ( $existing_transaction ) {
    return (int) $existing_transaction;
}

Only when no transaction exists does the plugin continue with the insert.

This small addition makes the transaction creation process significantly more reliable.


Why This Matters

Imagine the following sequence:

Auction Ends
        │
        ▼
Winner Determined
        │
        ▼
Create Transaction

If the creation function is accidentally triggered twice—for example by a scheduled task or callback—the previous implementation would create two database records.

With the new validation:

Auction Ends
        │
        ▼
Winner Determined
        │
        ▼
Check Existing Transaction
        │
   Exists?
    │     │
   Yes    No
    │      │
Return ID  Insert Transaction

Only one transaction can ever be created for the same auction.


Understanding Idempotent Operations

One of the most important concepts introduced in this lesson is idempotency.

An idempotent function produces the same result no matter how many times it is executed.

For example:

First execution
↓

Transaction Created

Second execution
↓

Existing transaction found

↓

No duplicate inserted

This principle is widely used in payment gateways, webhooks, APIs, and marketplace systems to prevent duplicate records.


Testing the Implementation

After updating the code:

  • The plugin was validated using PHP syntax checking.
  • A fresh auction was created.
  • The auction was allowed to end automatically.
  • A winning bidder was determined.
  • The transaction was created.
  • Payment status was updated.
  • The Transactions page was reviewed.
  • phpMyAdmin was used to verify the database.

The results confirmed:

  • Only one transaction was created.
  • Payment updates continued to function correctly.
  • No duplicate transaction records appeared.
  • The transaction lifecycle remained fully functional.

Final Transaction Lifecycle

After this improvement, the workflow became:

Create Auction
        │
        ▼
Place Bids
        │
        ▼
Auction Ends
        │
        ▼
Winner Determined
        │
        ▼
Check Existing Transaction
        │
        ▼
Create One Transaction
        │
        ▼
Payment Processing

This provides a much more robust and production-ready transaction system.


Lessons Learned

Several valuable software engineering concepts were reinforced during this implementation:

  • Always audit historical issues instead of assuming they are resolved.
  • Database validation is an effective safeguard against duplicate records.
  • Critical workflows should be idempotent whenever possible.
  • Defensive programming increases reliability in real-world applications.
  • Marketplace and escrow systems benefit greatly from multiple layers of validation.

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


Conclusion

Lesson 79 focused on strengthening the transaction creation process rather than introducing new functionality. By adding a simple database existence check before inserting a transaction, the plugin now guarantees that each auction can generate only one transaction, even if the creation routine is triggered multiple times.

This enhancement makes the Flipnzee Auctions plugin more resilient and establishes a solid foundation for the upcoming escrow and ownership transfer workflow in future lessons.

Lesson 79: Auditing and Fixing the Auction Transaction Creation Lifecycle

After successfully implementing manual payment status management in Lesson 78, we noticed an unexpected behavior during testing.

Although payment management was working perfectly, new transactions were being created before an auction had actually finished. This indicated a flaw in the auction workflow rather than in the payment management system.

Before integrating Escrow.com or any payment gateway, it is essential that every auction follows a predictable lifecycle and creates only one transaction, at the correct point in the auction process.

In this lesson, we will audit the entire transaction creation workflow and ensure that transactions are generated only after an auction closes and a winner has been determined.


What We Will Build

By the end of this lesson we will:

  • Trace where transactions are created.
  • Identify every function capable of creating a transaction.
  • Prevent duplicate transaction creation.
  • Ensure transactions are created only once.
  • Verify the transaction lifecycle from auction creation to payment.

The Problem We Discovered

During testing we observed several unexpected behaviors.

  • Transactions were sometimes created immediately after an auction was created.
  • Earlier testing produced duplicate transaction records.
  • Payment management worked correctly, but the transaction lifecycle itself was inconsistent.

Although these issues were corrected temporarily during testing, the underlying workflow still needs a proper audit.


Desired Auction Workflow

A professional auction platform should always follow this sequence.

Auction Created
        │
        ▼
Accept Bids
        │
        ▼
Auction Ends
        │
        ▼
Determine Winner
        │
        ▼
Create ONE Transaction
        │
        ▼
Pending Payment
        │
        ▼
Buyer Payment Submitted
        │
        ▼
Admin Verification
        │
        ▼
Payment Approved
        │
        ▼
Escrow Started
        │
        ▼
Ownership Transfer
        │
        ▼
Auction Completed

Every completed auction should generate exactly one transaction, and that transaction should remain the single source of truth throughout the payment and ownership transfer process.


Lesson Objectives

During this lesson we will:

Step 1

Search the entire plugin for every location that inserts records into:

wp_flipnzee_transactions

Step 2

Identify every function responsible for transaction creation.

Possible examples include:

  • winner determination
  • auction closing
  • bid completion
  • scheduled cron events
  • save handlers

Step 3

Determine which function should have exclusive responsibility for creating transactions.


Step 4

Prevent duplicate transaction creation by checking whether a transaction already exists before inserting a new record.


Step 5

Verify that transaction creation occurs only after:

  • auction end time
  • winner determination
  • successful auction closure

Step 6

Perform end-to-end testing by:

  • creating a new auction
  • placing bids
  • waiting for auction completion
  • confirming exactly one transaction is created

Expected Outcome

After completing this lesson:

  • Every auction will produce only one transaction.
  • Duplicate transactions will be impossible.
  • Transactions will be created only after auction completion.
  • The plugin will have a reliable transaction lifecycle ready for payment gateway and Escrow.com integration.

Why This Matters

Payment gateways, escrow providers, and ownership transfer systems all depend on having a single, reliable transaction record.

Fixing the transaction lifecycle now will make future features significantly easier to implement and reduce the likelihood of data inconsistencies.

This lesson focuses on strengthening the core architecture of the Flipnzee Auctions plugin before moving on to advanced payment and escrow functionality.

Lesson 78: Building Payment Status Management for Auction Transactions in the Flipnzee Plugin

In the previous lesson, we built the Transaction Details page to display complete information about a transaction. While administrators could view payment information, there was no way to manage the payment lifecycle from the WordPress dashboard.

In this lesson, we implemented a complete Payment Status Management system. Administrators can now update the payment status directly from the Transaction Details page, with all changes securely stored in the database.


What We Built

The Transaction Details page now includes a dedicated Payment Management section that allows administrators to:

  • View the current payment status
  • Select a new payment status
  • Save the updated status
  • Automatically update the transaction timestamp
  • Reload the page showing the updated information

Supported payment statuses include:

  • Pending
  • Processing
  • Paid
  • Completed
  • Cancelled
  • Refunded

Step 1: Creating the Payment Management Section

Below the transaction information table, a new section was added.

<h2><?php esc_html_e( 'Payment Management', 'flipnzee-auctions' ); ?></h2>

<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">

This form submits data securely using WordPress’ admin-post handler.


Step 2: Adding WordPress Security

To protect the form against CSRF attacks, a nonce field was added.

wp_nonce_field(
    'flipnzee_update_payment_status',
    'flipnzee_payment_nonce'
);

Every request is now verified before any database update occurs.


Step 3: Passing Required Hidden Values

Hidden fields tell WordPress which handler to execute and which transaction should be updated.

<input
    type="hidden"
    name="action"
    value="flipnzee_update_transaction_status">

<input
    type="hidden"
    name="transaction_id"
    value="<?php echo absint( $transaction->id ); ?>">

Step 4: Building the Payment Status Dropdown

Administrators can now select from predefined payment states.

<select name="payment_status">

<option value="pending">Pending</option>
<option value="processing">Processing</option>
<option value="paid">Paid</option>
<option value="completed">Completed</option>
<option value="cancelled">Cancelled</option>
<option value="refunded">Refunded</option>

</select>

The currently saved status is automatically selected.


Step 5: Adding the Update Button

A standard WordPress button submits the form.

submit_button(
    __( 'Update Payment Status', 'flipnzee-auctions' )
);

Step 6: Registering the Form Handler

Inside the constructor, we registered the admin action.

add_action(
    'admin_post_flipnzee_update_transaction_status',
    array( $this, 'update_payment_status' )
);

This tells WordPress which method should process the form submission.


Step 7: Creating update_payment_status()

A new method was added to process updates.

public function update_payment_status() {

    check_admin_referer(
        'flipnzee_update_payment_status',
        'flipnzee_payment_nonce'
    );

}

The method validates the request before making any database changes.


Step 8: Sanitizing User Input

Incoming values are sanitized before use.

$transaction_id = absint(
    $_POST['transaction_id']
);

$payment_status = sanitize_text_field(
    wp_unslash(
        $_POST['payment_status']
    )
);

Step 9: Updating the Database

The transaction record is updated using the WordPress database API.

$wpdb->update(

    $wpdb->prefix . 'flipnzee_transactions',

    array(

        'payment_status' => $payment_status,

        'updated_at' => current_time( 'mysql' ),

    ),

    array(

        'id' => $transaction_id,

    )

);

Step 10: Redirecting Back to the Transaction

After saving, the administrator is redirected back to the same transaction.

wp_safe_redirect(

    admin_url(

        'admin.php?page=flipnzee-transaction-details&transaction_id='
        . $transaction_id

    )

);

exit;

Problems We Encountered

During implementation we discovered several issues.

PHP Parse Errors

Some HTML blocks were accidentally inserted outside PHP, producing syntax errors.

These were corrected by carefully closing and reopening PHP tags where required.


Missing Transaction ID

Initially the transaction details page displayed:

Transaction not found.

The redirect URL was missing the transaction ID.

Adding:

transaction_id=

to the redirect resolved the issue.


Handler Verification

To confirm the form reached the correct handler, a temporary debug message was added.

die( 'Payment status handler reached.' );

After confirming the handler worked, the debug statement was removed.


Database Verification

Using phpMyAdmin we confirmed that updates correctly modified:

  • payment_status
  • updated_at

while leaving the remaining transaction information unchanged.


Testing Performed

The new payment workflow was tested thoroughly.

✔ Opened Transaction Details page

✔ Changed status from Pending to Completed

✔ Saved successfully

✔ Database updated correctly

✔ Timestamp refreshed automatically

✔ Page redirected back to the same transaction

✔ Updated value displayed correctly

✔ Multiple status changes tested successfully


Final Result

The Flipnzee Auctions plugin now includes a functional payment management system directly inside the WordPress administration panel.

Administrators can securely update payment statuses without editing the database manually, providing a much smoother workflow for managing completed auction transactions.

This feature also lays the groundwork for future integrations with payment gateways and escrow services, where payment states can eventually be synchronized automatically instead of being updated manually.


Source Code Summary

Register Admin Action

add_action(
    'admin_post_flipnzee_update_transaction_status',
    array( $this, 'update_payment_status' )
);

Nonce

wp_nonce_field(
    'flipnzee_update_payment_status',
    'flipnzee_payment_nonce'
);

Update Query

$wpdb->update(

    $wpdb->prefix . 'flipnzee_transactions',

    array(

        'payment_status' => $payment_status,

        'updated_at' => current_time( 'mysql' ),

    ),

    array(

        'id' => $transaction_id,

    )

);

Redirect

wp_safe_redirect(

    admin_url(
        'admin.php?page=flipnzee-transaction-details&transaction_id='
        . $transaction_id
    )

);

exit;

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:

What We Learned

  • Creating secure admin forms using admin-post.php
  • Protecting form submissions with WordPress nonces
  • Sanitizing and validating administrator input
  • Updating custom database tables using $wpdb->update()
  • Redirecting users safely after processing forms
  • Debugging form handlers and URL parameters
  • Verifying database changes using phpMyAdmin
  • Building a maintainable payment workflow for future escrow and payment gateway integration