diff --git a/src/Tickets/Commerce/Gateways/Stripe/Hooks.php b/src/Tickets/Commerce/Gateways/Stripe/Hooks.php index 440080837a..6512571fa7 100644 --- a/src/Tickets/Commerce/Gateways/Stripe/Hooks.php +++ b/src/Tickets/Commerce/Gateways/Stripe/Hooks.php @@ -11,6 +11,7 @@ use Exception; use TEC\Tickets\Commerce\Order; use TEC\Tickets\Commerce\Status\Status_Handler; +use TEC\Tickets\Commerce\Gateways\Stripe\Webhooks; /** * Class Hooks @@ -79,17 +80,31 @@ protected function add_filters() { public function process_async_stripe_webhook( int $order_id ): void { $order = tec_tc_get_order( $order_id ); - if ( ! $order || ! $order instanceof WP_Post || ! $order->ID ) { + if ( ! $order ) { return; } - $pending_webhooks = get_post_meta( $order->ID, '_tec_tickets_commerce_stripe_webhook_pending' ); + if ( ! $order instanceof WP_Post ) { + return; + } + + if ( ! $order->ID ) { + return; + } + + $webhooks = tribe( Webhooks::class ); + + $pending_webhooks = $webhooks->get_pending_webhooks( $order->ID ); // On multiple checkout completes, make sure we dont process the same webhook twice. - delete_post_meta( $order->ID, '_tec_tickets_commerce_stripe_webhook_pending' ); + $webhooks->delete_pending_webhooks(); foreach ( $pending_webhooks as $pending_webhook ) { - if ( ! ( is_array( $pending_webhook ) && isset( $pending_webhook['new_status'], $pending_webhook['metadata'], $pending_webhook['old_status'] ) ) ) { + if ( ! ( is_array( $pending_webhook ) ) ) { + continue; + } + + if ( ! isset( $pending_webhook['new_status'], $pending_webhook['metadata'], $pending_webhook['old_status'] ) ) { continue; } diff --git a/src/Tickets/Commerce/Gateways/Stripe/Webhooks.php b/src/Tickets/Commerce/Gateways/Stripe/Webhooks.php index cad7190e9e..8179fe8786 100644 --- a/src/Tickets/Commerce/Gateways/Stripe/Webhooks.php +++ b/src/Tickets/Commerce/Gateways/Stripe/Webhooks.php @@ -66,6 +66,15 @@ class Webhooks extends Abstract_Webhooks { */ public const OPTION_KNOWN_WEBHOOKS = 'tickets-commerce-stripe-known-webhooks'; + /** + * Option name for the option to store pending webhooks. + * + * @since TBD + * + * @var string + */ + public const PENDING_WEBHOOKS_KEY = '_tec_tickets_commerce_stripe_webhook_pending'; + /** * Nonce key for webhook on-demand set up. * @@ -89,6 +98,56 @@ public function get_merchant(): Abstract_Merchant { return tribe( Merchant::class ); } + /** + * Add a pending webhook to the order. + * + * @since TBD + * + * @param int $order_id Order ID. + * @param string $new_status New status. + * @param string $old_status Old status. + * @param array $metadata Metadata. + * + * @return void + */ + public function add_pending_webhook( int $order_id, string $new_status, string $old_status, array $metadata = [] ): void { + add_post_meta( + $order_id, + self::PENDING_WEBHOOKS_KEY, + [ + 'new_status' => $new_status, + 'metadata' => $metadata, + 'old_status' => $old_status, + ] + ); + } + + /** + * Get the pending webhooks for an order. + * + * @since TBD + * + * @param int $order_id Order ID. + * + * @return array + */ + public function get_pending_webhooks( int $order_id ): array { + return (array) get_post_meta( $order_id, self::PENDING_WEBHOOKS_KEY ); + } + + /** + * Delete the pending webhooks for an order. + * + * @since TBD + * + * @param int $order_id Order ID. + * + * @return void + */ + public function delete_pending_webhooks( int $order_id ): void { + delete_post_meta( $order_id, self::PENDING_WEBHOOKS_KEY ); + } + /** * Attempts to get the database option for the valid key from Stripe * This function was introduced to enable a cache-free polling of the database for the Valid Key, it will include a diff --git a/src/Tickets/Commerce/Gateways/Stripe/Webhooks/Handler.php b/src/Tickets/Commerce/Gateways/Stripe/Webhooks/Handler.php index b5e2f718ac..1bfbc0900c 100644 --- a/src/Tickets/Commerce/Gateways/Stripe/Webhooks/Handler.php +++ b/src/Tickets/Commerce/Gateways/Stripe/Webhooks/Handler.php @@ -2,6 +2,7 @@ namespace TEC\Tickets\Commerce\Gateways\Stripe\Webhooks; +use Codeception\Lib\Interfaces\Web; use TEC\Tickets\Commerce\Status as Commerce_Status; use TEC\Tickets\Commerce\Order; @@ -10,6 +11,7 @@ use WP_Error; use WP_REST_Request; use WP_REST_Response; +use TEC\Tickets\Commerce\Gateways\Stripe\Webhooks; /** * Class Handler @@ -131,15 +133,7 @@ public static function get_handler_method_for_event( $type ) { public static function update_order_status( \WP_Post $order, Commerce_Status\Status_Interface $status, array $metadata = [] ) { if ( ! tribe( Order::class )->is_checkout_completed( $order->ID ) ) { - add_post_meta( - $order->ID, - '_tec_tickets_commerce_stripe_webhook_pending', - [ - 'new_status' => $status->get_wp_slug(), - 'metadata' => $metadata, - 'old_status' => $order->post_status, - ] - ); + tribe( Webhooks::class )->add_pending_webhook( $order->ID, $status->get_wp_slug(), $order->post_status, $metadata ); /** * We can't return WP_Error because that will make Stripe think that diff --git a/tests/commerce_integration/TEC/Tickets/Commerce/Gateways/Stripe/Hooks_Test.php b/tests/commerce_integration/TEC/Tickets/Commerce/Gateways/Stripe/Hooks_Test.php index 89f78f7a70..04bdab4449 100644 --- a/tests/commerce_integration/TEC/Tickets/Commerce/Gateways/Stripe/Hooks_Test.php +++ b/tests/commerce_integration/TEC/Tickets/Commerce/Gateways/Stripe/Hooks_Test.php @@ -37,15 +37,7 @@ public function test_it_processes_async_stripe_webhooks() { $this->assertTrue( as_has_scheduled_action( 'tec_tickets_commerce_async_webhook_process', null, 'tec-tickets-commerce-stripe-webhooks' ) ); - add_post_meta( - $order->ID, - '_tec_tickets_commerce_stripe_webhook_pending', - [ - 'new_status' => $wp_status_slug_from_slug( Completed::SLUG ), - 'metadata' => [], - 'old_status' => $wp_status_slug_from_slug( Created::SLUG ), - ] - ); + tribe( Webhooks::class )->add_pending_webhook( $order->ID, $wp_status_slug_from_slug( Completed::SLUG ), $wp_status_slug_from_slug( Created::SLUG ) ); $this->assertSame( $wp_status_slug_from_slug( Created::SLUG ), $order->post_status ); do_action( 'tec_tickets_commerce_async_webhook_process', $order->ID ); @@ -54,33 +46,17 @@ public function test_it_processes_async_stripe_webhooks() { $this->assertSame( $wp_status_slug_from_slug( Completed::SLUG ), $refreshed_order->post_status ); - $this->assertEmpty( get_post_meta( $order->ID, '_tec_tickets_commerce_stripe_webhook_pending' ) ); + $this->assertEmpty( tribe( Webhooks::class )->get_pending_webhooks( $order->ID ) ); - add_post_meta( - $order->ID, - '_tec_tickets_commerce_stripe_webhook_pending', - [ - 'new_status' => $wp_status_slug_from_slug( Completed::SLUG ), - 'metadata' => [], - 'old_status' => $wp_status_slug_from_slug( Created::SLUG ), - ] - ); + tribe( Webhooks::class )->add_pending_webhook( $order->ID, $wp_status_slug_from_slug( Completed::SLUG ), $wp_status_slug_from_slug( Created::SLUG ) ); - add_post_meta( - $order->ID, - '_tec_tickets_commerce_stripe_webhook_pending', - [ - 'new_status' => $wp_status_slug_from_slug( Pending::SLUG ), - 'metadata' => [], - 'old_status' => $wp_status_slug_from_slug( Completed::SLUG ), - ] - ); + tribe( Webhooks::class )->add_pending_webhook( $order->ID, $wp_status_slug_from_slug( Pending::SLUG ), $wp_status_slug_from_slug( Completed::SLUG ) ); do_action( 'tec_tickets_commerce_async_webhook_process', $order->ID ); $refreshed_order = tec_tc_get_order( $order->ID ); - $this->assertEmpty( get_post_meta( $order->ID, '_tec_tickets_commerce_stripe_webhook_pending' ) ); + $this->assertEmpty( tribe( Webhooks::class )->get_pending_webhooks( $order->ID ) ); $this->assertSame( $wp_status_slug_from_slug( Pending::SLUG ), $refreshed_order->post_status ); } @@ -106,19 +82,11 @@ public function test_it_reschedules_async_stripe_webhooks_when_encounter_issues( $this->assertSame( $wp_status_slug_from_slug( Created::SLUG ), $refreshed_order->post_status ); - add_post_meta( - $order->ID, - '_tec_tickets_commerce_stripe_webhook_pending', - [ - 'new_status' => $wp_status_slug_from_slug( Completed::SLUG ), - 'metadata' => [], - 'old_status' => $wp_status_slug_from_slug( Pending::SLUG ), - ] - ); + tribe( Webhooks::class )->add_pending_webhook( $order->ID, $wp_status_slug_from_slug( Completed::SLUG ), $wp_status_slug_from_slug( Pending::SLUG ) ); // Issue is encountered here - Different old status do_action( 'tec_tickets_commerce_async_webhook_process', $order->ID ); - $this->assertEmpty( get_post_meta( $order->ID, '_tec_tickets_commerce_stripe_webhook_pending' ) ); + $this->assertEmpty( tribe( Webhooks::class )->get_pending_webhooks( $order->ID ) ); $refreshed_order = tec_tc_get_order( $order->ID ); @@ -126,19 +94,11 @@ public function test_it_reschedules_async_stripe_webhooks_when_encounter_issues( tribe( Order::class )->lock_order( $order->ID ); - add_post_meta( - $order->ID, - '_tec_tickets_commerce_stripe_webhook_pending', - [ - 'new_status' => $wp_status_slug_from_slug( Completed::SLUG ), - 'metadata' => [], - 'old_status' => $wp_status_slug_from_slug( Pending::SLUG ), - ] - ); + tribe( Webhooks::class )->add_pending_webhook( $order->ID, $wp_status_slug_from_slug( Completed::SLUG ), $wp_status_slug_from_slug( Pending::SLUG ) ); // Issue is encountered here - Order is locked. do_action( 'tec_tickets_commerce_async_webhook_process', $order->ID ); - $this->assertEmpty( get_post_meta( $order->ID, '_tec_tickets_commerce_stripe_webhook_pending' ) ); + $this->assertEmpty( tribe( Webhooks::class )->get_pending_webhooks( $order->ID ) ); $refreshed_order = tec_tc_get_order( $order->ID ); @@ -148,18 +108,11 @@ public function test_it_reschedules_async_stripe_webhooks_when_encounter_issues( $this->set_class_fn_return( Order::class, 'modify_status', false ); - add_post_meta( - $order->ID, - '_tec_tickets_commerce_stripe_webhook_pending', - [ - 'new_status' => $wp_status_slug_from_slug( Completed::SLUG ), - 'metadata' => [], - 'old_status' => $wp_status_slug_from_slug( Pending::SLUG ), - ] - ); + tribe( Webhooks::class )->add_pending_webhook( $order->ID, $wp_status_slug_from_slug( Completed::SLUG ), $wp_status_slug_from_slug( Pending::SLUG ) ); + // Issue is encountered here - Modify status will fail. do_action( 'tec_tickets_commerce_async_webhook_process', $order->ID ); - $this->assertEmpty( get_post_meta( $order->ID, '_tec_tickets_commerce_stripe_webhook_pending' ) ); + $this->assertEmpty( tribe( Webhooks::class )->get_pending_webhooks( $order->ID ) ); $refreshed_order = tec_tc_get_order( $order->ID ); @@ -167,20 +120,13 @@ public function test_it_reschedules_async_stripe_webhooks_when_encounter_issues( uopz_unset_return( Order::class, 'modify_status' ); - add_post_meta( - $order->ID, - '_tec_tickets_commerce_stripe_webhook_pending', - [ - 'new_status' => $wp_status_slug_from_slug( Completed::SLUG ), - 'metadata' => [], - 'old_status' => $wp_status_slug_from_slug( Created::SLUG ), - ] - ); + tribe( Webhooks::class )->add_pending_webhook( $order->ID, $wp_status_slug_from_slug( Completed::SLUG ), $wp_status_slug_from_slug( Created::SLUG ) ); + // Issue is encountered here - Success do_action( 'tec_tickets_commerce_async_webhook_process', $order->ID ); $refreshed_order = tec_tc_get_order( $order->ID ); - $this->assertEmpty( get_post_meta( $order->ID, '_tec_tickets_commerce_stripe_webhook_pending' ) ); + $this->assertEmpty( tribe( Webhooks::class )->get_pending_webhooks( $order->ID ) ); $this->assertSame( $wp_status_slug_from_slug( Completed::SLUG ), $refreshed_order->post_status ); } @@ -204,15 +150,7 @@ public function test_it_should_bail_async_stripe_webhooks_when_end_result_is_don $this->assertTrue( as_has_scheduled_action( 'tec_tickets_commerce_async_webhook_process', null, 'tec-tickets-commerce-stripe-webhooks' ) ); - add_post_meta( - $order->ID, - '_tec_tickets_commerce_stripe_webhook_pending', - [ - 'new_status' => $wp_status_slug_from_slug( Completed::SLUG ), - 'metadata' => [], - 'old_status' => $wp_status_slug_from_slug( Pending::SLUG ), - ] - ); + tribe( Webhooks::class )->add_pending_webhook( $order->ID, $wp_status_slug_from_slug( Completed::SLUG ), $wp_status_slug_from_slug( Pending::SLUG ) ); $refreshed_order = tec_tc_get_order( $order->ID ); @@ -223,6 +161,6 @@ public function test_it_should_bail_async_stripe_webhooks_when_end_result_is_don $this->assertSame( $wp_status_slug_from_slug( Completed::SLUG ), $refreshed_order->post_status ); - $this->assertEmpty( get_post_meta( $order->ID, '_tec_tickets_commerce_stripe_webhook_pending' ) ); + $this->assertEmpty( tribe( Webhooks::class )->get_pending_webhooks( $order->ID ) ); } }