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 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

Lesson 77 Implementation: Building the Administrator Payment Review Dashboard for Flipnzee Auctions

In the previous lesson, buyers were able to upload payment proof securely through the payment page, with uploaded receipts stored in the WordPress Media Library and linked to the corresponding transaction.

This lesson shifted focus from the buyer to the administrator by introducing a dedicated payment review dashboard. Administrators can now view submitted payments, inspect transaction details, and prepare payments for verification.


Objective

The primary goal of this lesson was to create an administrator interface that allows the Flipnzee team to review buyer payment submissions before approving website ownership transfers.

By the end of this implementation, administrators could:

  • View all submitted payments.
  • Open detailed transaction information.
  • Review payment metadata.
  • Prepare payment status management.
  • Lay the foundation for future payment verification.

Step 1 – Creating the Admin Payments Page

A new administrator page was created.

File created

admin/class-admin-payments.php

The page was implemented as a dedicated admin class.

class Flipnzee_Admin_Payments {

    /**
     * Render Payments page.
     *
     * @return void
     */
    public static function render_page() {

        ?>

        <div class="wrap">

            <h1>Buyer Payments</h1>

            <p>

                Review buyer payment submissions before approving
                the transfer of ownership.

            </p>

        </div>

        <?php
    }
}

This provided a clean starting point for the administrator payment workflow.


Step 2 – Registering the Payments Menu

A new submenu was added beneath the Flipnzee Auctions admin menu.

add_submenu_page(
    'flipnzee-auctions',
    'Payments',
    'Payments',
    'manage_options',
    'flipnzee-payments',
    array(
        'Flipnzee_Admin_Payments',
        'render_page',
    )
);

This created a dedicated Payments section for administrators.


Step 3 – Loading Submitted Payments

The Payments page was connected to the transaction table.

global $wpdb;

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

$payments = $wpdb->get_results(
    "
    SELECT *
    FROM {$table}
    WHERE payment_status = 'submitted'
    ORDER BY updated_at DESC
    "
);

Only transactions that had submitted payment proofs were displayed.


Step 4 – Handling Empty Results

Before rendering the table, the plugin checks whether submitted payments exist.

if ( empty( $payments ) ) {

    echo '<p>No payment submissions found.</p>';

} else {

    // Display payment table.

}

This prevents empty tables and provides useful feedback to administrators.


Step 5 – Building the Payments Table

A professional WordPress admin table was introduced.

<table class="widefat striped">

    <thead>

        <tr>

            <th>ID</th>
            <th>Listing</th>
            <th>Buyer</th>
            <th>Amount</th>
            <th>Gateway</th>
            <th>Status</th>
            <th>Submitted</th>
            <th>Actions</th>

        </tr>

    </thead>

The table closely follows the standard WordPress administration interface.


Step 6 – Displaying Submitted Payments

Each submitted payment is displayed using a loop.

<?php foreach ( $payments as $payment ) : ?>

<tr>

    <td><?php echo esc_html( $payment->id ); ?></td>

    <td><?php echo esc_html( $payment->listing_id ); ?></td>

    <td><?php echo esc_html( $payment->buyer_id ); ?></td>

    <td><?php echo esc_html(
        number_format_i18n(
            $payment->winning_bid,
            2
        )
    ); ?></td>

    <td><?php echo esc_html(
        $payment->payment_gateway
    ); ?></td>

    <td><?php echo esc_html(
        ucfirst( $payment->payment_status )
    ); ?></td>

    <td><?php echo esc_html(
        $payment->updated_at
    ); ?></td>

</tr>

<?php endforeach; ?>

The administrator can immediately identify submitted payments requiring review.


Step 7 – Adding the View Details Button

Instead of displaying placeholder text, each payment now links to a detailed transaction page.

<td>

    <a
        class="button button-primary"
        href="<?php echo esc_url(
            admin_url(
                'admin.php?page=flipnzee-transaction-details&transaction_id=' .
                absint( $payment->id )
            )
        ); ?>">

        View Details

    </a>

</td>

This significantly improves navigation between the payment dashboard and transaction details.


Step 8 – Enhancing the Transaction Details Page

The existing transaction details page was expanded with payment information.

Additional rows were added to display:

<tr>
    <th>Payment Status</th>
    <td><?php echo esc_html(
        ucfirst( $transaction['payment_status'] )
    ); ?></td>
</tr>

<tr>
    <th>Payment Gateway</th>
    <td><?php echo esc_html(
        $transaction['payment_gateway']
    ); ?></td>
</tr>

<tr>
    <th>Payment Submitted</th>
    <td><?php echo esc_html(
        $transaction['payment_submitted_at']
    ); ?></td>
</tr>

Administrators can now review payment-specific information alongside the transaction details.


Step 9 – Creating the Payment Management Section

A dedicated Payment Management panel was introduced.

<h2>Payment Management</h2>

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

This prepares the interface for future payment verification actions.


Step 10 – Securing the Form

The management form was protected using a WordPress nonce.

wp_nonce_field(
    'flipnzee_update_payment_status',
    'flipnzee_payment_nonce'
);

This ensures only legitimate administrators can submit payment updates.


Step 11 – Payment Status Dropdown

Administrators can now select a payment status.

<select
    name="payment_status"
    id="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>

Although the update handler will be implemented in the next lesson, the interface is now fully prepared.


Challenges Encountered

Several issues arose during development.

Method Name Mismatch

Initially, the Payments submenu referenced render_page(), while the class still used render().

Standardizing on render_page() resolved the fatal error.


PHP and HTML Mixing

While building the Payment Management form, HTML was accidentally placed inside an open PHP block.

Example:

<?php

wp_nonce_field(...);

<input ...>

Closing PHP before the HTML resolved the syntax error.


Duplicate Status Rows

During iterative development, duplicate Payment Status rows were unintentionally introduced.

Cleaning up duplicate markup produced a clearer transaction details page.


Payment vs Transaction Status

One important architectural decision emerged during development.

The plugin now distinguishes between:

  • status (overall transaction lifecycle)
  • payment_status (buyer payment lifecycle)

This separation prepares the plugin for multiple payment gateways, including Escrow.com, without affecting the broader transaction workflow.


Testing Performed

The implementation was tested by:

  • Opening the new Payments admin menu.
  • Confirming submitted transactions appear in the table.
  • Verifying payment amounts and gateways display correctly.
  • Opening transaction details using the View Details button.
  • Confirming payment metadata is displayed.
  • Checking the Payment Management form renders correctly.
  • Validating PHP syntax after each modification.

Lessons Learned

This implementation reinforced several WordPress development practices:

  • Separate administrator workflows from buyer workflows.
  • Keep transaction management and payment management independent.
  • Use dedicated admin pages instead of overloading existing screens.
  • Secure administrator forms using nonces.
  • Build reusable interfaces that can support additional payment gateways in future.

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


Current Progress

At the end of Lesson 77, the Flipnzee Auctions plugin now includes:

  • ✅ Administrator Payments menu
  • ✅ Submitted Payments dashboard
  • ✅ Payment listing table
  • ✅ View Details navigation
  • ✅ Enhanced transaction details page
  • ✅ Payment metadata display
  • ✅ Payment Management interface
  • ✅ Secure administrator form ready for processing

The actual processing of payment status updates will be completed in the next lesson.


Next Lesson

Lesson 78: Processing Administrator Payment Status Updates

In the next lesson, we will connect the Payment Management form to the backend by:

  • Registering the administrator POST handler.
  • Verifying administrator permissions and nonces.
  • Updating the payment_status field in the database.
  • Redirecting administrators with success messages.
  • Preparing the workflow for payment approval, rejection, and future Escrow.com integration.

This will complete the first functional administrator payment verification workflow in the Flipnzee Auctions plugin.

Lesson 75 Implementation: Refactoring the Buyer Payment Page into Modular Components

As the Flipnzee payment system evolved, the class-payment-page.php file gradually accumulated multiple responsibilities. It was responsible for retrieving transactions, validating payment requests, rendering payment instructions, displaying transaction information, and generating the payment gateway interface.

Rather than continuing to add more features to an increasingly large method, this lesson focused on improving the internal architecture of the payment page through refactoring.

Although no new user-facing functionality was introduced, this refactoring significantly improves code readability, maintainability, and prepares the payment system for future features such as payment proof uploads, administrator verification, and live payment gateway integrations.


Why Refactor?

One of the most common problems in software development is allowing a single function to become too large.

Our original render() method was responsible for:

  • Loading transactions
  • Validating requests
  • Processing payment gateway selection
  • Rendering manual payment instructions
  • Displaying transaction information
  • Displaying the payment gateway selector

Instead of adding even more functionality to this method, we separated the interface into reusable helper methods.


Step 1 – Extract the Transaction Summary

The payment summary table was moved into its own private method.

Instead of embedding the HTML directly inside render(), we created:

private static function render_transaction_summary( $transaction ) {
?>

<h2>Payment</h2>

<table class="widefat striped">

<tr>
    <th>Transaction ID</th>
    <td><?php echo esc_html( $transaction->id ); ?></td>
</tr>

<tr>
    <th>Winning Bid</th>
    <td>
        <?php
        echo esc_html(
            number_format_i18n(
                $transaction->winning_bid,
                2
            )
        );
        ?>
    </td>
</tr>

<tr>
    <th>Status</th>
    <td><?php echo esc_html( ucfirst( $transaction->status ) ); ?></td>
</tr>

<tr>
    <th>Payment Status</th>
    <td><?php echo esc_html( ucfirst( $transaction->payment_status ) ); ?></td>
</tr>

<tr>
    <th>Payment Gateway</th>
    <td>
        <?php
        echo esc_html(
            Flipnzee_Payment_Manager::get_gateway_name( $transaction )
        );
        ?>
    </td>
</tr>

</table>

<?php
}

The main render method now simply calls:

self::render_transaction_summary( $transaction );

Step 2 – Extract the Gateway Selector

Next, the payment gateway selection form was moved into a dedicated helper method.

private static function render_gateway_selector( $gateways ) {
?>

<form method="post">

<?php
wp_nonce_field(
    'flipnzee_payment_action',
    'flipnzee_payment_nonce'
);
?>

<h3>Select Payment Method</h3>

<div class="flipnzee-payment-gateways">

<?php foreach ( $gateways as $gateway_id => $gateway ) : ?>

<p>

<label>

<input
    type="radio"
    name="payment_gateway"
    value="<?php echo esc_attr( $gateway_id ); ?>"
    <?php checked( $gateway['enabled'] ); ?>
    <?php disabled( ! $gateway['enabled'] ); ?>
>

<?php echo esc_html( $gateway['label'] ); ?>

<?php if ( ! $gateway['enabled'] ) : ?>

<em>(Coming Soon)</em>

<?php endif; ?>

</label>

</p>

<?php endforeach; ?>

</div>

<p class="flipnzee-payment-actions">

<button
    type="submit"
    name="flipnzee_payment_completed"
    class="button"
>
I've Completed Payment
</button>

<button
    type="submit"
    name="flipnzee_continue_payment"
    class="button button-primary"
>
Continue to Payment
</button>

</p>

</form>

<?php
}

This method now encapsulates the entire payment gateway interface.


Step 3 – Extract Manual Payment Instructions

The manual payment interface was also separated into its own method.

private static function render_manual_payment( $transaction ) {
?>

<div class="notice notice-success">

    <p><strong>Manual Payment Selected</strong></p>

    <p>Please complete your payment using the instructions below.</p>

</div>

<div class="flipnzee-manual-payment">

<h3>Payment Instructions</h3>

<p>Thank you for choosing Manual Payment.</p>

<p>Please use the transaction reference below when sending your payment.</p>

<table class="widefat striped">

<tr>
    <th>Reference Number</th>
    <td>
        <?php
        echo esc_html(
            'FLIP-' . str_pad(
                $transaction->id,
                6,
                '0',
                STR_PAD_LEFT
            )
        );
        ?>
    </td>
</tr>

<tr>
    <th>Amount</th>
    <td>
        <?php
        echo esc_html(
            number_format_i18n(
                $transaction->winning_bid,
                2
            )
        );
        ?>
    </td>
</tr>

<tr>
    <th>Status</th>
    <td>Awaiting Payment</td>
</tr>

</table>

<h4>Important</h4>

<ul>

<li>Include the reference number with your payment.</li>

<li>Keep proof of payment for verification.</li>

<li>Your transaction will be reviewed before ownership transfer.</li>

</ul>

</div>

<?php
}

Step 4 – Simplify the Gateway Router

Previously, the switch statement contained a large block of HTML for the Manual Payment gateway.

After refactoring, it became much cleaner:

switch ( $selected_gateway ) {

    case 'manual':

        self::render_manual_payment( $transaction );

        break;

    case 'escrow':
        // Escrow placeholder.
        break;

    case 'stripe':
    case 'paypal':
    case 'razorpay':
    case 'crypto':
        // Future gateways.
        break;

    default:
        ?>
        <div class="notice notice-error">
            <p>Unknown payment gateway.</p>
        </div>
        <?php
        break;
}

The routing logic now clearly expresses intent without mixing presentation and control flow.


Benefits of the Refactoring

This refactoring provides several long-term advantages:

  • Cleaner render() method
  • Smaller, focused helper methods
  • Easier debugging
  • Improved readability
  • Better separation of concerns
  • Simpler extension for future payment gateways
  • Reduced code duplication
  • Easier maintenance as the payment workflow grows

Testing

After completing the refactoring:

  • The Buyer Payment Page displayed exactly the same information as before.
  • Manual Payment instructions continued to work.
  • The transaction summary rendered correctly.
  • Payment gateway selection remained functional.
  • No user-facing functionality changed.
  • PHP syntax validation completed successfully.

This confirmed that the refactoring preserved existing behavior while improving the internal architecture.


Lessons Learned

As features accumulate, it is often beneficial to pause and improve the code structure before introducing additional functionality. Separating presentation into dedicated helper methods makes the codebase easier to navigate, simplifies future enhancements, and reduces the risk of introducing bugs when extending existing features.

This lesson also reinforced the importance of keeping rendering logic modular so that future components—such as payment proof uploads, administrator verification, and live gateway integrations—can be added with minimal impact on existing code.

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


Conclusion

Lesson 75 focused on improving the maintainability of the Buyer Payment Page through thoughtful refactoring. By extracting the transaction summary, payment gateway selector, and manual payment instructions into reusable helper methods, the payment page is now significantly cleaner and better organized. This modular architecture provides a solid foundation for the next stage of development, where buyers will be able to upload payment proof and administrators will verify completed payments before ownership transfer.

Lesson 76: Building Secure Payment Proof Uploads for Manual Payments in the Flipnzee Auctions Plugin

In the previous lesson, the Flipnzee Auctions plugin was refactored to separate the payment page into reusable methods. With the foundation now in place, the next logical step was to allow buyers to securely upload proof of payment after completing a manual bank transfer or other offline payment.

This lesson focused on implementing a complete payment proof upload workflow using WordPress’ built-in Media Library functions while ensuring security through nonce verification and preventing duplicate uploads.


Objective

Implement a secure payment proof upload system that:

  • Allows buyers to upload payment receipts.
  • Stores uploaded files in the WordPress Media Library.
  • Saves the attachment ID in the transaction table.
  • Prevents duplicate uploads.
  • Displays appropriate confirmation messages.
  • Prepares the plugin for future admin verification.

Step 1 – Creating the Upload Section

A new upload section was added beneath the manual payment instructions.

<h3>Upload Payment Proof</h3>

<p>
After completing your payment, upload your receipt or screenshot below.
</p>

<form
    method="post"
    enctype="multipart/form-data"
>

Using multipart/form-data is essential whenever files are uploaded.


Step 2 – Protecting the Form with a WordPress Nonce

Every upload request should be protected against CSRF attacks.

wp_nonce_field(
    'flipnzee_upload_proof',
    'flipnzee_upload_nonce'
);

The nonce is later verified before processing the upload.


Step 3 – Creating the File Input

The upload field accepts common payment proof formats.

<input
    type="file"
    name="flipnzee_payment_proof"
    accept=".jpg,.jpeg,.png,.pdf"
    required
>

Supported file types include:

  • JPG
  • JPEG
  • PNG
  • PDF

Step 4 – Detecting Upload Requests

Inside the main render method, the plugin detects whether the buyer submitted a payment proof.

if (
    isset( $_POST['flipnzee_upload_payment_proof'] ) &&
    isset( $_FILES['flipnzee_payment_proof'] ) &&
    ! empty( $_FILES['flipnzee_payment_proof']['name'] )
) {

}

This ensures uploads are only processed when the upload button is pressed.


Step 5 – Verifying the Nonce

Before accepting any uploaded file, the nonce is validated.

if (
    ! isset( $_POST['flipnzee_upload_nonce'] ) ||
    ! wp_verify_nonce(
        sanitize_text_field(
            wp_unslash( $_POST['flipnzee_upload_nonce'] )
        ),
        'flipnzee_upload_proof'
    )
) {
    return '<p>Security check failed.</p>';
}

This protects the upload endpoint from forged requests.


Step 6 – Loading WordPress Upload Libraries

Instead of manually moving files, WordPress provides built-in upload helpers.

require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/image.php';

These libraries automatically handle:

  • Uploads
  • File validation
  • Image metadata
  • Media Library integration

Step 7 – Uploading the File

The upload is performed using WordPress’ native API.

$attachment_id = media_handle_upload(
    'flipnzee_payment_proof',
    0
);

Successful uploads immediately become Media Library attachments.


Step 8 – Saving the Attachment ID

After a successful upload, the attachment ID is saved against the transaction.

Flipnzee_Payment_Manager::save_payment_proof(
    $transaction->id,
    $attachment_id
);

This updates the transaction record with:

  • payment_proof_id
  • payment_status

Step 9 – Refreshing the Transaction

One subtle issue appeared during testing.

Although the database updated successfully, the current $transaction object still contained the old values because it had been loaded before the upload.

Refreshing the transaction solved the issue.

$transaction = Flipnzee_Payment_Manager::get_transaction(
    $transaction->id
);

This immediately reflects the latest payment information.


Step 10 – Preventing Duplicate Uploads

Instead of always displaying the upload form, the page now checks whether a payment proof already exists.

<?php if ( empty( $transaction->payment_proof_id ) ) : ?>

<!-- Upload Form -->

<?php else : ?>

<div class="notice notice-success">

    <p>

        <strong>Payment Proof Already Submitted.</strong>

        Your payment proof has already been uploaded and is awaiting verification by the Flipnzee team.

    </p>

</div>

<?php endif; ?>

This prevents accidental duplicate submissions.


Step 11 – Testing

The upload workflow was tested thoroughly.

Successful tests included:

  • Uploading PNG payment receipts.
  • Verifying files appear in the WordPress Media Library.
  • Confirming payment_proof_id is stored in the database.
  • Confirming payment_status changes to submitted.
  • Confirming duplicate uploads are prevented.
  • Confirming success messages appear after upload.

Challenges Encountered

Several issues were encountered during implementation.

Transaction Not Refreshing

Although the database updated correctly, the upload form continued appearing because the transaction object had not been refreshed after saving the payment proof.

Reloading the transaction solved this issue.


Media Library Confusion

Initially it appeared that uploads were failing because the uploaded image was not immediately visible.

The issue turned out to be Media Library filtering and caching rather than the upload itself.


Conditional Rendering

Wrapping the upload form inside a conditional block required careful placement of the opening and closing PHP tags to avoid syntax errors.

Once corrected, the page behaved exactly as expected.


What Was Achieved

By the end of this lesson, the Flipnzee Auctions plugin could:

  • Accept secure payment proof uploads.
  • Store uploaded receipts in the Media Library.
  • Save attachment IDs with transactions.
  • Prevent duplicate uploads.
  • Display confirmation messages.
  • Prepare transactions for future verification.

Lessons Learned

Several important WordPress development concepts were reinforced:

  • Always use nonces when processing forms.
  • Prefer WordPress Media APIs over custom upload code.
  • Refresh database objects after updates.
  • Use conditional rendering to improve user experience.
  • Store Media Library attachment IDs instead of file paths whenever possible.

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


Next Lesson

In Lesson 77, the payment experience will be polished further by:

  • Displaying Submitted for Verification instead of Pending.
  • Hiding payment options once proof has been uploaded.
  • Showing a cleaner buyer confirmation page.
  • Adding links to view uploaded payment proof.
  • Beginning the admin verification workflow.

This will complete the buyer-side manual payment journey and prepare the plugin for administrator approval of submitted payments.

Lesson 73 Implementation: Processing Payment Gateway Selection Securely in Flipnzee

In the previous lesson, the payment page displayed multiple payment gateways dynamically. However, clicking Continue to Payment did not actually process the buyer’s selection.

In this lesson, the payment page was enhanced to securely process the selected payment gateway using WordPress security best practices. This establishes the routing architecture that future payment integrations such as Escrow.com, Stripe, PayPal, Razorpay, and USDT Cryptocurrency will use.


What Was Implemented

The payment page now:

  • Processes the submitted payment form
  • Verifies the WordPress nonce
  • Sanitizes user input
  • Validates the selected gateway
  • Routes requests using a switch statement
  • Displays gateway-specific responses
  • Prepares the plugin for future payment integrations

This completes the payment gateway routing layer.


Step 1: Detect Form Submission

The payment page first checks whether the buyer has submitted the payment form.

if ( isset( $_POST['flipnzee_continue_payment'] ) ) {

    // Process payment

}

This ensures the processing code only runs after the buyer clicks Continue to Payment.


Step 2: Verify the WordPress Nonce

Before processing any submitted data, the request is verified using a WordPress nonce.

if (
    ! isset( $_POST['flipnzee_payment_nonce'] ) ||
    ! wp_verify_nonce(
        sanitize_text_field(
            wp_unslash( $_POST['flipnzee_payment_nonce'] )
        ),
        'flipnzee_payment_action'
    )
) {
    return '<p>Security check failed.</p>';
}

Why?

This protects the payment page from:

  • CSRF attacks
  • Forged form submissions
  • External malicious requests

Using nonces is a standard WordPress security practice.


Step 3: Sanitize the Selected Gateway

The selected payment gateway is retrieved safely.

$selected_gateway = '';

if ( isset( $_POST['payment_gateway'] ) ) {

    $selected_gateway = sanitize_text_field(
        wp_unslash( $_POST['payment_gateway'] )
    );

}

This removes unsafe input before it reaches the application logic.


Step 4: Validate the Gateway

Before routing, the submitted gateway is checked against the list of available gateways.

if ( ! isset( $gateways[ $selected_gateway ] ) ) {

    return '<p>Invalid payment gateway selected.</p>';

}

This prevents invalid or manipulated gateway values from being processed.


Step 5: Route Using a Switch Statement

The payment gateway router directs each gateway to its own processing block.

switch ( $selected_gateway ) {

    case 'manual':
        ?>
        <div class="notice notice-success">
            <p>
                Manual Payment selected.
                Payment instructions will be displayed in the next lesson.
            </p>
        </div>
        <?php
        break;

    case 'escrow':
        ?>
        <div class="notice notice-info">
            <p>
                Escrow.com integration will be available in a future release.
            </p>
        </div>
        <?php
        break;

    case 'stripe':
    case 'paypal':
    case 'razorpay':
    case 'crypto':
        ?>
        <div class="notice notice-warning">
            <p>
                This payment gateway is not yet available.
            </p>
        </div>
        <?php
        break;

    default:
        ?>
        <div class="notice notice-error">
            <p>Unknown payment gateway.</p>
        </div>
        <?php
        break;
}

This routing structure keeps each payment provider isolated, making future integrations straightforward.


Testing the Implementation

After updating the payment page:

  1. Open the buyer payment page.
  2. Select Manual Payment.
  3. Click Continue to Payment.

The page now displays:

Manual Payment selected. Payment instructions will be displayed in the next lesson.

The transaction information remains visible, confirming that the form was processed successfully.


Why This Design Matters

Instead of embedding payment logic directly into the page, a routing layer has been introduced.

This provides several advantages:

  • Cleaner code organization
  • Easier maintenance
  • Independent gateway implementations
  • Better scalability
  • Simpler testing
  • Future extensibility

When additional gateways are implemented, each will simply receive its own case inside the existing router without affecting the others.


Lessons Learned

During implementation, several improvements were made:

  • Used WordPress nonces to secure form submissions.
  • Sanitized all user-submitted values before processing.
  • Validated gateway IDs against the registered gateway list.
  • Built a centralized gateway routing mechanism.
  • Kept the payment architecture flexible for future integrations.
  • Successfully tested Manual Payment routing without affecting existing transaction data.

Current Payment Flow

Buyer Opens Payment Page
           │
           ▼
Select Payment Gateway
           │
           ▼
Continue to Payment
           │
           ▼
Verify WordPress Nonce
           │
           ▼
Sanitize User Input
           │
           ▼
Validate Gateway
           │
           ▼
Gateway Router (switch)
           │
 ┌─────────┼───────────────┐
 │         │               │
 ▼         ▼               ▼
Manual   Escrow        Future Gateways
Payment  (Coming Soon) (Stripe, PayPal,
                         Razorpay, USDT)

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


Conclusion

Lesson 73 transformed the payment page from a static gateway selector into a secure routing system capable of processing buyer selections. By combining nonce verification, input sanitization, gateway validation, and switch-based routing, Flipnzee now has a solid payment processing foundation. Future lessons can build upon this architecture to implement real payment instructions, proof-of-payment submission, and live integrations with services such as Escrow.com, Stripe, PayPal, Razorpay, and USDT Cryptocurrency.

Lesson 71 Implementation: Introducing the Payment Manager for Transaction Validation

After successfully creating the buyer payment page in the previous lesson, the next logical improvement was to separate payment-related business logic from the presentation layer. Instead of allowing the payment page to directly decide whether a transaction could be paid, a dedicated Payment Manager class was introduced.

This lesson focuses on improving the plugin architecture while preparing the foundation for future payment gateway integrations such as Stripe, PayPal, Razorpay, or manual bank transfer.


Objective

The goals of this lesson were to:

  • Create a dedicated Flipnzee_Payment_Manager class.
  • Centralize payment validation logic.
  • Validate transactions before displaying the payment page.
  • Prepare a placeholder method for future payment gateway integrations.
  • Keep the payment page clean and easier to maintain.

Why This Refactoring Was Needed

In Lesson 70, the payment page displayed transaction details directly after fetching the transaction.

As more features are added, such as:

  • payment expiry
  • completed payments
  • cancelled transactions
  • multiple payment gateways

placing all validation logic inside the page would quickly become difficult to maintain.

Instead, all payment-related decisions should live inside a dedicated manager.

This follows the Single Responsibility Principle, where each class has one clear responsibility.


Step 1 — Creating the Payment Manager

A new file was created:

includes/class-payment-manager.php

The initial class structure:

<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Flipnzee_Payment_Manager {

}

This class will eventually become the central place for everything related to buyer payments.


Step 2 — Registering the New Class

The loader was updated so WordPress loads the new class automatically.

Example:

require_once FLIPNZEE_AUCTIONS_PLUGIN_DIR . 'includes/class-payment-manager.php';

Step 3 — Moving Validation into the Manager

Instead of checking the transaction status directly inside the payment page, a reusable validation method was created.

public static function can_pay( $transaction ) {

    if ( ! $transaction ) {
        return false;
    }

    if ( strtolower( trim( $transaction->status ) ) !== 'pending' ) {
        return false;
    }

    return true;
}

Why strtolower()?

During testing, it was discovered that database values may use different capitalization.

Examples:

Pending
pending
PENDING

Using:

strtolower( trim( $transaction->status ) )

ensures all of these are treated consistently.


Step 4 — Validating the Transaction

The payment page now retrieves the transaction:

$transaction = Flipnzee_Payment_Manager::get_transaction(
    $transaction_id
);

If nothing is found:

if ( ! $transaction ) {
    return '<p>Transaction not found.</p>';
}

Then the Payment Manager determines whether payment is still allowed.

if ( ! Flipnzee_Payment_Manager::can_pay( $transaction ) ) {
    return '<p>This transaction is no longer available for payment.</p>';
}

This keeps the page itself very clean.


Step 5 — Preparing Gateway Support

Since payment gateways will be implemented in future lessons, a placeholder method was added.

public static function get_gateway_name( $transaction ) {

    return 'Manual Payment (Coming Soon)';
}

The payment page simply calls:

echo esc_html(
    Flipnzee_Payment_Manager::get_gateway_name( $transaction )
);

Later, this method will automatically return:

  • Manual Bank Transfer
  • Stripe
  • Razorpay
  • PayPal

without changing the payment page.


Debugging Along the Way

During implementation, a few useful issues were discovered.

1. Transaction Status Case Sensitivity

Initially the validation checked:

$transaction->status !== 'Pending'

However, the database stored:

pending

As a result, every payment was incorrectly rejected.

The validation was updated to:

strtolower( trim( $transaction->status ) ) !== 'pending'

making it much more reliable.


2. Missing Payment Gateway Column

An attempt was made to display:

$transaction->payment_gateway

This produced an undefined property warning because the database table does not yet contain a payment_gateway column.

Instead of adding a temporary workaround, the gateway display was moved into:

Flipnzee_Payment_Manager::get_gateway_name()

which currently returns a placeholder while the database schema is prepared in a future lesson.


Testing

Several scenarios were tested.

✅ Existing transaction loads correctly.

✅ Pending transaction is accepted.

✅ Invalid transaction ID displays:

Transaction not found.

✅ Placeholder payment gateway displays:

Manual Payment (Coming Soon)

No PHP warnings remain.


Final Result

The payment page now displays:

  • Transaction ID
  • Winning Bid
  • Status
  • Payment Status
  • Payment Gateway

with validation handled entirely by the Payment Manager.

The interface remains clean while the underlying architecture becomes much more maintainable.

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


What Was Learned

This lesson demonstrated the value of separating business logic from presentation.

Instead of embedding validation throughout the payment page, all payment-related decisions are now centralized in a dedicated manager. This approach makes the code easier to read, easier to test, and far simpler to extend as new payment gateways and payment workflows are introduced.

It also highlighted the importance of real-world debugging—such as handling inconsistent database values (e.g., Pending vs. pending) and recognizing when a database schema needs to evolve rather than patching around missing fields.

With the Flipnzee_Payment_Manager now in place, the plugin has a solid foundation for implementing gateway-specific payment processing in upcoming lessons while keeping the payment page itself clean and focused.

Lesson 70: Building a Dedicated Payment Page for Flipnzee Auctions

After completing the buyer purchase history and purchase details pages in the previous lesson, the next logical step was to create a dedicated payment page. Instead of redirecting buyers to a non-existent URL after clicking “Pay Now”, the plugin now provides a proper payment page that serves as the foundation for future payment gateway integration.

Although no payment gateway is connected yet, this lesson establishes the complete page architecture that future lessons will build upon.


Objective

The goal of this lesson was to:

  • Create a dedicated payment page.
  • Register a new shortcode for the payment page.
  • Display transaction information securely.
  • Ensure only logged-in users can access the page.
  • Prepare the plugin for Stripe, PayPal and other payment integrations.

What We Built

The payment workflow now looks like this:

Completed Auction
        │
        ▼
Transaction Created
        │
        ▼
Buyer clicks "Pay Now"
        │
        ▼
/payment/?transaction_id=1
        │
        ▼
Payment Page
        │
        ▼
(Future)
Stripe / PayPal / Crypto Payment

Step 1 — Create a New Payment Page Class

A new file was created:

includes/class-payment-page.php

The file begins with the standard WordPress security check.

<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Flipnzee_Payment_Page {

    public static function render() {

        if ( ! is_user_logged_in() ) {
            return '<p>Please log in to continue.</p>';
        }

        ob_start();

        $transaction_id = isset( $_GET['transaction_id'] )
            ? absint( $_GET['transaction_id'] )
            : 0;

        // Display payment information here.

        return ob_get_clean();
    }
}

Step 2 — Register a New Shortcode

The shortcode registry inside

includes/class-shortcodes.php

was extended with a new shortcode.

add_shortcode(
    'flipnzee_payment_page',
    array(
        'Flipnzee_Payment_Page',
        'render'
    )
);

This allows WordPress pages to render the payment interface using:

[flipnzee_payment_page]

Step 3 — Load the Payment Page Class

The plugin loader was updated so the new class becomes available throughout the plugin.

Example:

require_once FLIPNZEE_PLUGIN_PATH . 'includes/class-payment-page.php';

Without loading the class, WordPress would be unable to locate the shortcode callback.


Step 4 — Retrieve the Transaction ID

The page accepts the transaction through the URL.

Example:

/payment/?transaction_id=1

The transaction ID is safely extracted using:

$transaction_id = isset( $_GET['transaction_id'] )
    ? absint( $_GET['transaction_id'] )
    : 0;

Using absint() ensures only positive integer IDs are accepted.


Step 5 — Fetch Transaction Details

Using the transaction ID, the plugin queries the transaction table.

Example:

global $wpdb;

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

$transaction = $wpdb->get_row(
    $wpdb->prepare(
        "SELECT * FROM {$table} WHERE id = %d",
        $transaction_id
    )
);

This returns the complete transaction record for display.


Step 6 — Display Payment Information

The page currently displays essential information including:

  • Transaction ID
  • Winning Bid
  • Transaction Status
  • Payment Status

Example output:

Payment

Transaction ID     1
Winning Bid        ₹55,555,609
Status             Pending
Payment Status     Pending

This provides buyers with confirmation that the transaction exists before payment processing is added.


Step 7 — Create a WordPress Payment Page

A new WordPress page was created:

Payment

The page content contains only the shortcode:

[flipnzee_payment_page]

This keeps presentation separate from business logic, making the system easier to maintain.


Testing

The payment page was tested by visiting:

/payment/?transaction_id=1

The page correctly displayed:

  • Transaction ID
  • Winning Bid
  • Pending Status
  • Pending Payment Status

The previous 404 Not Found error was successfully resolved.


Files Modified

includes/class-payment-page.php (new)
includes/class-shortcodes.php
includes/class-loader.php (or plugin loader)

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


Lessons Learned

During implementation, several important WordPress plugin development concepts were reinforced:

  • Create reusable functionality using dedicated classes.
  • Register features using WordPress shortcodes.
  • Load new classes through the plugin loader.
  • Validate user input using absint().
  • Protect pages by requiring user authentication.
  • Use output buffering (ob_start() / ob_get_clean()) for shortcode rendering.
  • Keep page layout separate from business logic.

Current Workflow

Auction Ends
      │
      ▼
Winner Determined
      │
      ▼
Transaction Created
      │
      ▼
"My Purchases"
      │
      ▼
Pay Now
      │
      ▼
Payment Page
      │
      ▼
(Future)
Payment Gateway Integration

What’s Next?

With the payment page now in place, the plugin is ready for the next phase of development. Future lessons will focus on transforming this informational page into a fully functional checkout by adding payment gateway integration, order summaries, payment processing, status updates, and buyer/seller notifications.

Lesson 70 establishes the foundation that all future payment functionality will build upon.

Lesson 66 Implementation: Building a Transaction Details Page for Completed Auctions

One of the biggest advantages of developing your own WordPress plugin is that you can continuously improve the user experience. In the previous lessons, the Flipnzee Auctions plugin was already creating transactions automatically after an auction ended and displaying them in a Transactions table. However, there was no way to inspect a transaction in detail.

In this lesson, a dedicated Transaction Details page was introduced. This page provides administrators with complete information about an individual auction transaction and lays the foundation for future features such as escrow management, payment verification, domain transfer tracking, and audit logs.


What We Built

Instead of only viewing a transaction inside a table, administrators can now click a View action to open a dedicated page displaying all transaction information.

Current information displayed includes:

  • Transaction ID
  • Auction ID
  • Listing ID
  • Seller ID
  • Buyer ID
  • Winning Bid
  • Transaction Status
  • Created Date
  • Updated Date

This provides a much cleaner workflow compared to searching through database records manually.


Step 1 – Creating the Transaction Details Admin Page

A new admin class was created:

admin/class-admin-transaction-details.php

This class is responsible for rendering the Transaction Details screen inside the WordPress admin dashboard.

Initially, the page only displayed a placeholder message while the routing and menu registration were tested.


Step 2 – Registering the Admin Page

The new page was registered inside the plugin’s admin menu.

Unlike normal menu pages, this page is hidden from the sidebar because it is accessed directly from the Transactions table using a URL similar to:

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

This keeps the admin menu clean while still allowing administrators to access detailed information.


Step 3 – Loading Transaction Data

Inside the render_page() method, the transaction ID is safely retrieved using:

$transaction_id = isset( $_GET['transaction_id'] )
	? absint( $_GET['transaction_id'] )
	: 0;

Using absint() ensures only valid numeric IDs are accepted.

The transaction is then retrieved from the custom database table using a prepared SQL query.

This protects the plugin against SQL injection while ensuring the correct transaction is loaded.


Step 4 – Handling Invalid Transactions

Good plugins never assume that data always exists.

If an invalid transaction ID is supplied, the plugin now displays an error message instead of generating PHP warnings or fatal errors.

Example:

Transaction not found.

This small validation greatly improves the robustness of the plugin.


Step 5 – Displaying Transaction Information

After confirming that the transaction exists, the placeholder content was replaced with a professional information table.

The page now displays:

FieldDescription
IDInternal transaction ID
AuctionAuction record ID
ListingWordPress listing ID
SellerSeller user ID
BuyerBuyer user ID
Winning BidFinal auction amount
StatusCurrent transaction status
CreatedCreation timestamp
UpdatedLast update timestamp

This information is presented using a WordPress widefat striped table for a consistent admin experience.


Step 6 – Troubleshooting During Development

Like most real-world development sessions, implementation was not completely straightforward.

Several issues were encountered, including:

  • PHP parse errors caused by misplaced braces.
  • Accidental duplication of an if statement during copy-and-paste.
  • Mixed HTML and PHP tags while replacing placeholder content.
  • Leftover placeholder code causing unexpected output.
  • Additional syntax validation before uploading the plugin.

Each issue was resolved by:

  • Running PHP syntax checks:
php -l admin/class-admin-transaction-details.php
  • Carefully reviewing opening and closing braces.
  • Replacing only the affected code block instead of rewriting the entire file.
  • Testing after every small change.

This incremental debugging approach made it much easier to locate and resolve problems.


Final Result

The Flipnzee Auctions plugin now includes a dedicated Transaction Details page.

Administrators can:

  • Open a completed transaction
  • View all important transaction information
  • Verify buyer and seller IDs
  • Review the winning bid
  • Check the current transaction status
  • See creation and update timestamps

The page is now ready for future enhancements without requiring any structural redesign.


Why This Matters

Although this page currently displays basic information, it establishes the foundation for a complete transaction management system.

Future lessons can build upon this page by adding:

  • Buyer profile links
  • Seller profile links
  • Listing title instead of ID
  • Auction title
  • Escrow status
  • Payment history
  • Domain transfer progress
  • Shipping information (for physical products)
  • Internal administrator notes
  • Activity timeline
  • Email history
  • Downloadable invoices

Because the framework is already in place, adding these features will be much easier.

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:


Lessons Learned

During this implementation, several important development practices were reinforced:

  • Build features incrementally rather than all at once.
  • Validate user input before querying the database.
  • Always use prepared SQL statements.
  • Check for missing records gracefully.
  • Run PHP syntax checks before uploading changes.
  • Test every modification immediately to catch errors early.
  • Use dedicated detail pages instead of overcrowding list tables.

Conclusion

Lesson 66 significantly improves the administrative experience of the Flipnzee Auctions plugin. Instead of viewing transactions only in a summary table, administrators can now inspect individual transactions on a dedicated page with all essential details.

More importantly, this page serves as the foundation for advanced transaction management features planned for future lessons, bringing the plugin another step closer to a production-ready auction platform.

Lesson 65 Implementation: Adding Transaction Status Management to Flipnzee Auctions

After building the Transactions dashboard in the previous lesson, the next improvement was to make the transactions interactive. Instead of simply displaying transaction records, administrators should be able to manage the progress of each transaction as the auction moves through its post-sale lifecycle.

In this lesson, we implemented the foundation for transaction status management, allowing administrators to update transaction statuses securely from the WordPress admin area while recording every status change in the activity log.


Objective

The goal of this lesson was to transform the Transactions page from a read-only report into the beginning of a transaction management system.

Instead of every transaction remaining permanently in a Pending state, administrators can now move transactions through different stages.


Initial Workflow

Before this lesson, every completed auction produced a transaction like this:

TransactionStatus
#1Pending
#2Pending

Although transactions were stored correctly, there was no mechanism to update their progress.


Step 1 — Extend the Transaction Manager

The first task was adding a reusable method responsible for updating transaction status.

File modified:

includes/class-transaction-manager.php

Method added:

/**
 * Update a transaction status.
 *
 * @param int    $transaction_id Transaction ID.
 * @param string $status         New status.
 * @return bool
 */
public static function update_status(
	$transaction_id,
	$status
) {

	global $wpdb;

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

	$updated = $wpdb->update(
		$table,
		array(
			'status' => sanitize_text_field( $status ),
		),
		array(
			'id' => absint( $transaction_id ),
		),
		array(
			'%s',
		),
		array(
			'%d',
		)
	);

	if ( class_exists( 'Flipnzee_Activity_Log' ) ) {

		Flipnzee_Activity_Log::log(
			'transaction_status_updated',
			0,
			get_current_user_id(),
			sprintf(
				'Transaction #%d marked as %s.',
				$transaction_id,
				$status
			)
		);
	}

	return false !== $updated;
}

This method centralizes all transaction status updates in one location.


Step 2 — Register an Admin Action

To process status changes securely, a new WordPress admin action was registered.

File modified:

flipnzee-auctions.php

Code added:

add_action(
	'admin_post_flipnzee_update_transaction_status',
	array(
		'Flipnzee_Transaction_Manager',
		'handle_status_update',
	)
);

This allows WordPress to execute a custom handler whenever an administrator clicks a transaction action link.


Step 3 — Handle Status Updates Securely

Next, a dedicated handler method was implemented.

public static function handle_status_update() {

	if ( ! current_user_can( 'manage_options' ) ) {
		wp_die( 'Permission denied.' );
	}

	check_admin_referer(
		'flipnzee_update_transaction'
	);

	$transaction_id = isset( $_GET['transaction_id'] )
		? absint( $_GET['transaction_id'] )
		: 0;

	$status = isset( $_GET['status'] )
		? sanitize_text_field(
			wp_unslash( $_GET['status'] )
		)
		: '';

	if ( $transaction_id && $status ) {

		self::update_status(
			$transaction_id,
			$status
		);
	}

	wp_safe_redirect(
		admin_url(
			'admin.php?page=flipnzee-transactions'
		)
	);

	exit;
}

The handler performs several important tasks:

  • verifies administrator permissions,
  • validates the WordPress nonce,
  • sanitizes user input,
  • updates the transaction,
  • redirects back to the Transactions page.

Step 4 — Add Status Action Links

The Transactions table was enhanced by creating a custom renderer for the Status column.

File modified:

admin/class-transactions-table.php

Method added:

public function column_status( $item ) {

	$status = esc_html( ucfirst( $item['status'] ) );

	$actions = array();

	if ( 'pending' === $item['status'] ) {

		$url = wp_nonce_url(
			admin_url(
				'admin-post.php?action=flipnzee_update_transaction_status'
				. '&transaction_id=' . $item['id']
				. '&status=paid'
			),
			'flipnzee_update_transaction'
		);

		$actions['paid'] =
			'<a href="' . esc_url( $url ) . '">Mark Paid</a>';

	} elseif ( 'paid' === $item['status'] ) {

		$url = wp_nonce_url(
			admin_url(
				'admin-post.php?action=flipnzee_update_transaction_status'
				. '&transaction_id=' . $item['id']
				. '&status=completed'
			),
			'flipnzee_update_transaction'
		);

		$actions['completed'] =
			'<a href="' . esc_url( $url ) . '">Mark Completed</a>';
	}

	return sprintf(
		'%1$s %2$s',
		$status,
		$this->row_actions( $actions )
	);
}

This introduces workflow-oriented actions directly into the Transactions page.


Step 5 — Testing the Workflow

After uploading the updated plugin, several scenarios were tested.

Successful observations included:

  • Transaction status changed from Pending to Paid.
  • The database updated correctly.
  • The Activity Log recorded the status change.
  • Administrators were redirected back to the Transactions page after the update.

This confirmed that the backend workflow was functioning as intended.


Challenges Encountered

During implementation, several issues arose that provided valuable learning opportunities.

Duplicate Methods

While extending the Transaction Manager, a duplicate update_status() method was accidentally created, resulting in a fatal PHP error. Removing the duplicate resolved the issue and reinforced the importance of keeping classes organized.

PHP Syntax Errors

While adding new methods, braces were temporarily misplaced, causing syntax errors. Incremental syntax checking with:

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

helped identify and correct these mistakes before deployment.

Transactions Table Rendering

The custom Transactions table successfully displayed transaction data and action links. Status updates from Pending to Paid worked correctly, and the database reflected the changes. However, the “Mark Completed” action did not appear after a transaction entered the Paid state.

This did not affect the underlying transaction workflow or status updates, but highlighted a rendering issue within the current WP_List_Table implementation. Since the core transaction management functionality was already operational, further refinement of the table interface was deferred to a future lesson focused on polishing the admin experience.


Lessons Learned

This lesson demonstrated several important WordPress development practices:

  • Separate business logic from user interface rendering.
  • Protect administrative actions with nonces.
  • Verify user capabilities before processing requests.
  • Centralize database updates inside dedicated manager classes.
  • Record important business events in an activity log.
  • Test functionality incrementally after every major change.

Download Source Code

Download the starting version of the plugin before the lesson:

Download the completed version after this lesson:

Final Outcome

By the end of Lesson 65, the Flipnzee Auctions plugin evolved beyond simply storing transactions. Administrators can now begin managing the transaction lifecycle by updating statuses securely through the WordPress admin interface. Although some interface refinements remain for future lessons, the underlying architecture for transaction status management is now in place.

This implementation provides a solid foundation for the next phase of development, where transaction status changes will be connected to buyer and seller notifications, escrow integration, payment workflows, and ownership transfer processes, bringing the plugin closer to supporting real-world online auctions on Flipnzee.com.