Invalidate coupon cache after hold/usage data is modified in datastore via SQL (#41538)

* Invalidate coupon cache after direct SQL queries

* Add changelog

* Add unit test

* Address feedback
This commit is contained in:
Jorge A. Torres 2023-11-17 15:25:10 +00:00 committed by GitHub
parent 00e1a29194
commit 1d00e76fda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 2 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fixes an issue where coupon usage is not correctly recorded when HPOS is active.

View File

@ -347,6 +347,8 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
} else {
add_post_meta( $coupon->get_id(), '_used_by', strtolower( $used_by ) );
}
$this->refresh_coupon_data( $coupon );
}
/**
@ -375,6 +377,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
if ( $meta_id ) {
delete_metadata_by_mid( 'post', $meta_id );
$coupon->set_used_by( (array) get_post_meta( $coupon->get_id(), '_used_by' ) );
$this->refresh_coupon_data( $coupon );
}
}
@ -405,6 +408,8 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
)
);
$this->refresh_coupon_data( $coupon );
// Get the latest value direct from the DB, instead of possibly the WP meta cache.
return (int) $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = 'usage_count' AND post_id = %d;", $id ) );
}
@ -556,7 +561,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
$result = $wpdb->query( $insert_statement ); // WPCS: unprepared SQL ok.
if ( false !== $result ) {
// Clear meta cache.
wp_cache_delete( WC_Coupon::generate_meta_cache_key( $coupon->get_id(), 'coupons' ), 'coupons' );
$this->refresh_coupon_data( $coupon );
break;
}
}
@ -651,7 +656,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
$result = $wpdb->query( $insert_statement ); // WPCS: unprepared SQL ok.
if ( false !== $result ) {
// Clear meta cache.
wp_cache_delete( WC_Coupon::generate_meta_cache_key( $coupon->get_id(), 'coupons' ), 'coupons' );
$this->refresh_coupon_data( $coupon );
break;
}
}
@ -693,6 +698,18 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
); // WPCS: unprepared SQL ok.
}
/**
* This function clears coupon data from the WP cache after certain operations which, for performance reasons,
* are done via SQL queries.
*
* @param \WC_Coupon $coupon The coupon object.
* @return void
*/
private function refresh_coupon_data( &$coupon ) {
wp_cache_delete( $coupon->get_meta_cache_key(), 'coupons' );
wp_cache_delete( $coupon->get_id(), 'post_meta' );
}
/**
* Return a coupon code for a specific ID.
*

View File

@ -3198,4 +3198,45 @@ class OrdersTableDataStoreTests extends HposTestCase {
remove_all_actions( 'woocommerce_webhook_process_delivery' );
remove_all_actions( 'woocommerce_webhook_should_deliver' );
}
/**
* @testDox Check that functions in the datastore correctly hold and release coupons from the order.
*/
public function test_datastore_coupon_methods() {
$this->toggle_cot_authoritative( true );
$coupon = new \WC_Coupon();
$coupon->set_code( '10off' );
$coupon->set_discount_type( 'percent' );
$coupon->set_amount( 10.0 );
$coupon->set_usage_limit_per_user( 2 );
$coupon->save();
$product = WC_Helper_Product::create_simple_product( true );
WC()->cart->add_to_cart( $product->get_id(), 1 );
WC()->cart->add_discount( $coupon->get_code() );
$this->assertEquals( 0, $coupon->get_data_store()->get_usage_by_email( $coupon, 'user@woo.test' ) );
$order_id = WC()->checkout->create_order(
array(
'billing_email' => 'user@woo.test',
'payment_method' => 'dummy',
)
);
$this->assertEquals( 1, $coupon->get_data_store()->get_tentative_usages_for_user( $coupon->get_id(), array( 'user@woo.test' ) ) );
$this->assertEquals( 1, $coupon->get_data_store()->get_usage_by_email( $coupon, 'user@woo.test' ) );
wc_get_order( $order_id )->payment_complete();
$this->assertEquals( 0, $coupon->get_data_store()->get_tentative_usages_for_user( $coupon->get_id(), array( 'user@woo.test' ) ) );
$this->assertEquals( 1, $coupon->get_data_store()->get_usage_by_email( $coupon, 'user@woo.test' ) );
// Load a fresh copy of the coupon and make sure things look ok.
$coupon = new \WC_Coupon( $coupon->get_id() );
$this->assertContains( 'user@woo.test', $coupon->get_used_by( 'edit' ) );
}
}