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.

Leave a Reply