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.
