@@ -49,6 +49,13 @@ class WC_Stripe_Webhook_Handler extends WC_Stripe_Payment_Gateway {
4949 */
5050 protected $ deferred_webhook_action = 'wc_stripe_deferred_webhook ' ;
5151
52+ /**
53+ * The order object being processed.
54+ *
55+ * @var WC_Order|null
56+ */
57+ protected $ resolved_order = null ;
58+
5259 /**
5360 * Constructor.
5461 *
@@ -71,7 +78,7 @@ public function __construct() {
7178 // plugin when this code first appears.
7279 WC_Stripe_Webhook_State::get_monitoring_began_at ();
7380
74- add_action ( $ this ->deferred_webhook_action , [ $ this , 'process_deferred_webhook ' ], 10 , 2 );
81+ add_action ( $ this ->deferred_webhook_action , [ $ this , 'process_deferred_webhook ' ], 10 , 3 );
7582 }
7683
7784 /**
@@ -269,6 +276,9 @@ public function process_webhook_payment( $notification, $retry = true ) {
269276 return ;
270277 }
271278
279+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
280+ $ this ->resolved_order = $ order ;
281+
272282 $ order_id = $ order ->get_id ();
273283
274284 $ is_pending_receiver = ( 'receiver ' === $ notification ->data ->object ->flow );
@@ -402,6 +412,9 @@ public function process_webhook_dispute( $notification ) {
402412 return ;
403413 }
404414
415+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
416+ $ this ->resolved_order = $ order ;
417+
405418 $ this ->set_stripe_order_status_before_hold ( $ order , $ order ->get_status () );
406419
407420 $ needs_response = in_array ( $ notification ->data ->object ->status , [ 'needs_response ' , 'warning_needs_response ' ], true );
@@ -444,6 +457,9 @@ public function process_webhook_dispute_closed( $notification ) {
444457 return ;
445458 }
446459
460+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
461+ $ this ->resolved_order = $ order ;
462+
447463 if ( 'lost ' === $ status ) {
448464 $ message = __ ( 'The dispute was lost or accepted. ' , 'woocommerce-gateway-stripe ' );
449465 } elseif ( 'won ' === $ status ) {
@@ -495,6 +511,9 @@ public function process_webhook_capture( $notification ) {
495511 return ;
496512 }
497513
514+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
515+ $ this ->resolved_order = $ order ;
516+
498517 if ( WC_Stripe_Helper::payment_method_allows_manual_capture ( $ order ->get_payment_method () ) ) {
499518 $ charge = $ order ->get_transaction_id ();
500519 $ captured = $ order ->get_meta ( '_stripe_charge_captured ' , true );
@@ -567,6 +586,9 @@ public function process_webhook_charge_succeeded( $notification ) {
567586 return ;
568587 }
569588
589+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
590+ $ this ->resolved_order = $ order ;
591+
570592 if ( ! $ order ->has_status ( OrderStatus::ON_HOLD ) ) {
571593 return ;
572594 }
@@ -625,6 +647,9 @@ public function process_webhook_charge_failed( $notification ) {
625647 return ;
626648 }
627649
650+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
651+ $ this ->resolved_order = $ order ;
652+
628653 // If order status is already in failed status don't continue.
629654 if ( $ order ->has_status ( OrderStatus::FAILED ) ) {
630655 return ;
@@ -665,6 +690,9 @@ public function process_webhook_source_canceled( $notification ) {
665690 }
666691 }
667692
693+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
694+ $ this ->resolved_order = $ order ;
695+
668696 // Don't proceed if payment method isn't Stripe.
669697 if ( 'stripe ' !== $ order ->get_payment_method () ) {
670698 WC_Stripe_Logger::log ( 'Canceled webhook abort: Order was not processed by Stripe: ' . $ order ->get_id () );
@@ -697,6 +725,9 @@ public function process_webhook_refund( $notification ) {
697725 $ order = WC_Stripe_Helper::get_order_by_charge_id ( $ notification ->data ->object ->id );
698726 }
699727
728+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
729+ $ this ->resolved_order = $ order ;
730+
700731 if ( ! $ order ) {
701732 WC_Stripe_Logger::log ( 'Could not find order via charge ID: ' . $ notification ->data ->object ->id );
702733 return ;
@@ -785,6 +816,9 @@ public function process_webhook_refund_updated( $notification ) {
785816 return ;
786817 }
787818
819+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
820+ $ this ->resolved_order = $ order ;
821+
788822 $ order_id = $ order ->get_id ();
789823
790824 if ( 'stripe ' === substr ( (string ) $ order ->get_payment_method (), 0 , 6 ) ) {
@@ -888,6 +922,9 @@ public function process_review_opened( $notification ) {
888922 }
889923 }
890924
925+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
926+ $ this ->resolved_order = $ order ;
927+
891928 $ this ->set_stripe_order_status_before_hold ( $ order , $ order ->get_status () );
892929
893930 $ message = sprintf (
@@ -929,6 +966,9 @@ public function process_review_closed( $notification ) {
929966 }
930967 }
931968
969+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
970+ $ this ->resolved_order = $ order ;
971+
932972 /* translators: 1) The reason type. */
933973 $ message = sprintf ( __ ( 'The opened review for this order is now closed. Reason: (%s) ' , 'woocommerce-gateway-stripe ' ), $ notification ->data ->object ->reason );
934974
@@ -1047,6 +1087,9 @@ public function process_payment_intent( $notification ) {
10471087 return ;
10481088 }
10491089
1090+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
1091+ $ this ->resolved_order = $ order ;
1092+
10501093 if ( $ this ->lock_order_payment ( $ order , $ intent ) ) {
10511094 return ;
10521095 }
@@ -1082,7 +1125,7 @@ public function process_payment_intent( $notification ) {
10821125 $ process_webhook_async = apply_filters ( 'wc_stripe_process_payment_intent_webhook_async ' , true , $ order , $ intent , $ notification );
10831126 $ is_awaiting_action = $ order ->get_meta ( '_stripe_upe_waiting_for_redirect ' ) ?? false ;
10841127
1085- // Process the webhook now if it's for a voucher or wallet payment , or if filtered to process immediately and order is not awaiting action.
1128+ // Process the webhook now if it's for a voucher or wallet payment, or if filtered to process immediately and order is not awaiting action.
10861129 if ( $ is_voucher_payment || $ is_wallet_payment || ( ! $ process_webhook_async && ! $ is_awaiting_action ) ) {
10871130 $ charge = $ this ->get_latest_charge_from_intent ( $ intent );
10881131
@@ -1096,6 +1139,8 @@ public function process_payment_intent( $notification ) {
10961139
10971140 $ charge ->is_webhook_response = true ;
10981141 $ this ->process_response ( $ charge , $ order );
1142+
1143+ $ this ->run_webhook_received_action ( (string ) $ notification ->type , $ notification , $ this ->resolved_order );
10991144 } else {
11001145 WC_Stripe_Logger::log ( "Processing $ notification ->type ( $ intent ->id ) asynchronously for order $ order_id. " );
11011146
@@ -1112,7 +1157,6 @@ public function process_payment_intent( $notification ) {
11121157 do_action ( 'wc_gateway_stripe_process_payment_intent_incomplete ' , $ order );
11131158 }
11141159 }
1115-
11161160 break ;
11171161 default :
11181162 if ( $ is_voucher_payment && 'payment_intent.payment_failed ' === $ notification ->type ) {
@@ -1153,6 +1197,9 @@ public function process_setup_intent( $notification ) {
11531197 return ;
11541198 }
11551199
1200+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
1201+ $ this ->resolved_order = $ order ;
1202+
11561203 $ allowed_payment_processing_statuses = [ OrderStatus::PENDING , OrderStatus::FAILED ];
11571204
11581205 $ allowed_payment_processing_statuses = apply_filters_deprecated (
@@ -1227,8 +1274,9 @@ protected function defer_webhook_processing( $webhook_notification, $additional_
12271274 time () + $ this ->deferred_webhook_delay ,
12281275 $ this ->deferred_webhook_action ,
12291276 [
1230- 'type ' => $ webhook_notification ->type ,
1231- 'data ' => $ additional_data ,
1277+ 'type ' => $ webhook_notification ->type ,
1278+ 'data ' => $ additional_data ,
1279+ 'notification ' => $ webhook_notification ,
12321280 ]
12331281 );
12341282 }
@@ -1240,8 +1288,9 @@ protected function defer_webhook_processing( $webhook_notification, $additional_
12401288 *
12411289 * @param string $webhook_type The webhook event name/type.
12421290 * @param array $additional_data Additional data passed to the scheduled job.
1291+ * @param stdClass $notification The webhook notification payload.
12431292 */
1244- public function process_deferred_webhook ( $ webhook_type , $ additional_data ) {
1293+ public function process_deferred_webhook ( $ webhook_type , $ additional_data, $ notification = null ) {
12451294 try {
12461295 switch ( $ webhook_type ) {
12471296 case 'payment_intent.succeeded ' :
@@ -1253,6 +1302,9 @@ public function process_deferred_webhook( $webhook_type, $additional_data ) {
12531302 throw new Exception ( "Missing required data. 'order_id' is invalid or not found for the deferred ' {$ webhook_type }' event. " );
12541303 }
12551304
1305+ // Set the order being processed for the `wc_stripe_webhook_received` action later.
1306+ $ this ->resolved_order = $ order ;
1307+
12561308 if ( empty ( $ intent_id ) ) {
12571309 throw new Exception ( "Missing required data. 'intent_id' is missing for the deferred ' {$ webhook_type }' event. " );
12581310 }
@@ -1269,6 +1321,8 @@ public function process_deferred_webhook( $webhook_type, $additional_data ) {
12691321 throw new Exception ( "Unsupported webhook type: {$ webhook_type }" );
12701322 break ;
12711323 }
1324+
1325+ $ this ->run_webhook_received_action ( (string ) $ webhook_type , $ notification , $ this ->resolved_order );
12721326 } catch ( Exception $ e ) {
12731327 WC_Stripe_Logger::log ( 'Error processing deferred webhook: ' . $ e ->getMessage () );
12741328
@@ -1334,6 +1388,8 @@ public function process_account_updated( $notification ) {
13341388 public function process_webhook ( $ request_body ) {
13351389 $ notification = json_decode ( $ request_body );
13361390
1391+ $ this ->resolved_order = null ;
1392+
13371393 switch ( $ notification ->type ) {
13381394 case 'account.updated ' :
13391395 $ this ->process_account_updated ( $ notification );
@@ -1397,8 +1453,41 @@ public function process_webhook( $request_body ) {
13971453 $ this ->process_setup_intent ( $ notification );
13981454
13991455 }
1456+
1457+ // These events might be processed async. Skip the action trigger for them here. The trigger will be called inside the specific methods.
1458+ if ( 'payment_intent.succeeded ' === $ notification ->type || 'payment_intent.amount_capturable_updated ' === $ notification ->type ) {
1459+ return ;
1460+ }
1461+
1462+ $ this ->run_webhook_received_action ( $ notification ->type , $ notification , $ this ->resolved_order );
14001463 }
14011464
1465+ /**
1466+ * Helper function to run the `wc_stripe_webhook_received` action consistently.
1467+ *
1468+ * @param string $webhook_type The type of webhook that was processed.
1469+ * @param object $notification The webhook data sent from Stripe.
1470+ * @param WC_Order|null $order The order being processed by the webhook.
1471+ */
1472+ private function run_webhook_received_action ( string $ webhook_type , object $ notification , ?WC_Order $ order = null ): void {
1473+ try {
1474+ /**
1475+ * Fires after a webhook has been processed, but before we respond to Stripe.
1476+ * This allows for custom processing of the webhook after it has been processed.
1477+ * Note that the $order parameter may be null in various cases, especially when processing
1478+ * webhooks unrelated to orders, such as account updates.
1479+ *
1480+ * @since 9.8.0
1481+ *
1482+ * @param string $webhook_type The type of webhook that was processed.
1483+ * @param object $notification The webhook data sent from Stripe.
1484+ * @param WC_Order|null $order The order being processed by the webhook.
1485+ */
1486+ do_action ( 'wc_stripe_webhook_received ' , $ webhook_type , $ notification , $ this ->resolved_order );
1487+ } catch ( Throwable $ e ) {
1488+ WC_Stripe_Logger::error ( 'Error in wc_stripe_webhook_received action: ' . $ e ->getMessage (), [ 'error ' => $ e ] );
1489+ }
1490+ }
14021491 /**
14031492 * Fetches an order from a payment intent.
14041493 *
0 commit comments