diff --git a/includes/admin/class-wc-rest-payments-webhook-controller.php b/includes/admin/class-wc-rest-payments-webhook-controller.php index eb6ba948441..10ae6ed9733 100644 --- a/includes/admin/class-wc-rest-payments-webhook-controller.php +++ b/includes/admin/class-wc-rest-payments-webhook-controller.php @@ -37,15 +37,24 @@ class WC_REST_Payments_Webhook_Controller extends WC_Payments_REST_Controller { */ private $wcpay_db; + /** + * WC Payments Account. + * + * @var WC_Payments_Account + */ + private $account; + /** * WC_REST_Payments_Webhook_Controller constructor. * * @param WC_Payments_API_Client $api_client WC_Payments_API_Client instance. * @param WC_Payments_DB $wcpay_db WC_Payments_DB instance. + * @param WC_Payments_Account $account WC_Payments_Account instance. */ - public function __construct( WC_Payments_API_Client $api_client, WC_Payments_DB $wcpay_db ) { + public function __construct( WC_Payments_API_Client $api_client, WC_Payments_DB $wcpay_db, WC_Payments_Account $account ) { parent::__construct( $api_client ); $this->wcpay_db = $wcpay_db; + $this->account = $account; } /** @@ -75,13 +84,17 @@ public function handle_webhook( $request ) { try { // Extract information about the webhook event. - $event_type = $this->read_rest_property( $body, 'type' ); - $event_data = $this->read_rest_property( $body, 'data' ); - $event_object = $this->read_rest_property( $event_data, 'object' ); + $event_type = $this->read_rest_property( $body, 'type' ); + + Logger::debug( 'Webhook recieved: ' . $event_type ); + Logger::debug( 'Webhook body: ' . var_export( $body, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export switch ( $event_type ) { case 'charge.refund.updated': - $this->process_webhook_refund_updated( $event_object ); + $this->process_webhook_refund_updated( $body ); + break; + case 'account.updated': + $this->account->refresh_account_data(); break; } } catch ( WC_Payments_Rest_Request_Exception $e ) { @@ -98,12 +111,15 @@ public function handle_webhook( $request ) { /** * Process webhook refund updated. * - * @param array $event_object The event that triggered the webhook. + * @param array $event_body The event that triggered the webhook. * * @throws WC_Payments_Rest_Request_Exception Required parameters not found. * @throws Exception Unable to resolve charge ID to order. */ - private function process_webhook_refund_updated( $event_object ) { + private function process_webhook_refund_updated( $event_body ) { + $event_data = $this->read_rest_property( $event_body, 'data' ); + $event_object = $this->read_rest_property( $event_data, 'object' ); + // First, check the reason for the update. We're only interesting in a status of failed. $status = $this->read_rest_property( $event_object, 'status' ); if ( 'failed' !== $status ) { diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index 322bae9d527..071c8cf784a 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -431,10 +431,31 @@ private function get_cached_account_data() { } // Cache the account details so we don't call the server every time. - set_transient( self::ACCOUNT_TRANSIENT, $account, 2 * HOUR_IN_SECONDS ); + $this->cache_account( $account ); return $account; } + /** + * Caches account data for two hours + * + * @param array $account - Account data to cache. + */ + private function cache_account( $account ) { + set_transient( self::ACCOUNT_TRANSIENT, $account, 2 * HOUR_IN_SECONDS ); + } + + /** + * Refetches account data. + */ + public function refresh_account_data() { + try { + delete_transient( self::ACCOUNT_TRANSIENT ); + $this->get_cached_account_data(); + } catch ( Exception $e ) { + WCPay\Logger::error( "Failed to refresh account data. Error: $e" ); + } + } + /** * Checks if the cached account can be used in the current plugin state. * diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index 7d17e693cb1..e058e3982ec 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -476,7 +476,7 @@ public static function init_rest_api() { $timeline_controller->register_routes(); include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-webhook-controller.php'; - $webhook_controller = new WC_REST_Payments_Webhook_Controller( self::$api_client, self::$db_helper ); + $webhook_controller = new WC_REST_Payments_Webhook_Controller( self::$api_client, self::$db_helper, self::$account ); $webhook_controller->register_routes(); } diff --git a/tests/admin/test-class-wc-rest-payments-webhook.php b/tests/admin/test-class-wc-rest-payments-webhook.php index 775ae394533..d32f19be7cb 100644 --- a/tests/admin/test-class-wc-rest-payments-webhook.php +++ b/tests/admin/test-class-wc-rest-payments-webhook.php @@ -48,12 +48,14 @@ public function setUp() { ->disableOriginalConstructor() ->getMock(); + $account = new WC_Payments_Account( $mock_api_client ); + $this->mock_db_wrapper = $this->getMockBuilder( WC_Payments_DB::class ) ->disableOriginalConstructor() ->setMethods( array( 'order_from_charge_id' ) ) ->getMock(); - $this->controller = new WC_REST_Payments_Webhook_Controller( $mock_api_client, $this->mock_db_wrapper ); + $this->controller = new WC_REST_Payments_Webhook_Controller( $mock_api_client, $this->mock_db_wrapper, $account ); // Setup a test request. $this->request = new WP_REST_Request( @@ -113,7 +115,7 @@ public function test_webhook_with_no_type_property() { */ public function test_webhook_with_no_object_property() { // Setup test request data. - $this->request_body['type'] = 'unknown.webhook.event'; + $this->request_body['type'] = 'charge.refund.updated'; unset( $this->request_body['data']['object'] ); $this->request->set_body( wp_json_encode( $this->request_body ) ); @@ -128,11 +130,11 @@ public function test_webhook_with_no_object_property() { } /** - * Test a webhook with no object property. + * Test a webhook with no data property. */ public function test_webhook_with_no_data_property() { // Setup test request data. - $this->request_body['type'] = 'unknown.webhook.event'; + $this->request_body['type'] = 'charge.refund.updated'; unset( $this->request_body['data'] ); $this->request->set_body( wp_json_encode( $this->request_body ) ); diff --git a/tests/test-class-wc-payments-account.php b/tests/test-class-wc-payments-account.php index e006fa8a813..c37294700be 100644 --- a/tests/test-class-wc-payments-account.php +++ b/tests/test-class-wc-payments-account.php @@ -369,4 +369,65 @@ public function test_try_is_stripe_connected_returns_true_when_connected_with_li remove_filter( 'wcpay_dev_mode', '__return_true' ); } + + public function test_refresh_account_data_with_empty_cache() { + $expected_account = [ + 'account_id' => 'acc_test', + 'live_publishable_key' => 'pk_test_', + 'test_publishable_key' => 'pk_live_', + 'has_pending_requirements' => true, + 'current_deadline' => 12345, + 'is_live' => true, + ]; + + // Make sure cache is clear. + delete_transient( WC_Payments_Account::ACCOUNT_TRANSIENT ); + + $this->mock_api_client->expects( $this->once() )->method( 'get_account_data' )->will( $this->returnValue( $expected_account ) ); + $this->wcpay_account->refresh_account_data(); + + $cached_account = get_transient( WC_Payments_Account::ACCOUNT_TRANSIENT ); + $this->assertEquals( $expected_account, $cached_account, 'Account is not cached' ); + } + + public function test_refresh_account_data_with_existing_cache() { + $expected_account = [ + 'account_id' => 'acc_test', + 'live_publishable_key' => 'pk_test_', + 'test_publishable_key' => 'pk_live_', + 'has_pending_requirements' => true, + 'current_deadline' => 12345, + 'is_live' => true, + ]; + + $existing_cache = $expected_account; + $existing_cache['current_deadline'] = 11111; + set_transient( WC_Payments_Account::ACCOUNT_TRANSIENT, $existing_cache ); + + $this->mock_api_client->expects( $this->once() )->method( 'get_account_data' )->will( $this->returnValue( $expected_account ) ); + $this->wcpay_account->refresh_account_data(); + + $cached_account = get_transient( WC_Payments_Account::ACCOUNT_TRANSIENT ); + $this->assertEquals( $expected_account, $cached_account, 'Cached account is not updated' ); + } + + public function test_refresh_account_data_clears_cache_on_failure() { + $account = [ + 'account_id' => 'acc_test', + 'live_publishable_key' => 'pk_test_', + 'test_publishable_key' => 'pk_live_', + 'has_pending_requirements' => true, + 'current_deadline' => 12345, + 'is_live' => true, + ]; + set_transient( WC_Payments_Account::ACCOUNT_TRANSIENT, $account ); + + $this->mock_api_client->expects( $this->once() )->method( 'get_account_data' )->will( + $this->throwException( new WC_Payments_API_Exception( 'test', 'wcpay_account_not_found', 401 ) ) + ); + $this->wcpay_account->refresh_account_data(); + + $cached_account = get_transient( WC_Payments_Account::ACCOUNT_TRANSIENT ); + $this->assertEquals( [], $cached_account, 'Cached account is not cleared' ); + } }