Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9780c40
Add hook to allow for custom webhook post-processing
daledupreez Jul 7, 2025
3331c99
Merge branch 'develop' into add/hook-for-inbound-webhooks
wjrosa Jul 18, 2025
aa7989c
Moving action inside specific methods when processing async events
wjrosa Jul 18, 2025
5866fb2
Adding a try...catch block
wjrosa Jul 18, 2025
e991d11
Changelog and readme entries
wjrosa Jul 18, 2025
c52d7a1
Update includes/class-wc-stripe-webhook-handler.php
wjrosa Jul 21, 2025
c8bd4ba
Merge branch 'develop' into add/hook-for-inbound-webhooks
wjrosa Jul 21, 2025
09b792a
Merge branch 'develop' into add/hook-for-inbound-webhooks
wjrosa Jul 22, 2025
a63cbe1
Introducing a protected variable to store the order being processed
wjrosa Jul 22, 2025
cd17df2
Passing the whole notification object to deferred events
wjrosa Jul 22, 2025
acb696a
Fix tests
wjrosa Jul 22, 2025
bbc92e7
Merge branch 'develop' into add/hook-for-inbound-webhooks
wjrosa Jul 28, 2025
78cf5b4
Merge branch 'develop' into add/hook-for-inbound-webhooks
wjrosa Jul 29, 2025
91fdac9
Merge branch 'develop' into add/hook-for-inbound-webhooks
wjrosa Jul 29, 2025
c442208
Moving action after the deferred webhook switch
wjrosa Jul 29, 2025
29511dc
Merge branch 'develop' into add/hook-for-inbound-webhooks
diegocurbelo Jul 31, 2025
12bfe13
Merge branch 'develop' into add/hook-for-inbound-webhooks
wjrosa Aug 1, 2025
6f83270
Add try/catch to main hook; catch Throwable instead of Exception
daledupreez Aug 4, 2025
479c7b2
Refactor action triggers into helper function and add explicit docs f…
daledupreez Aug 4, 2025
c8e4b0a
Update changelog.txt
wjrosa Aug 4, 2025
09581c7
Readme entry update
wjrosa Aug 4, 2025
349378d
Update includes/class-wc-stripe-webhook-handler.php
wjrosa Aug 4, 2025
0b78f6f
Renaming the action to wc_stripe_webhook_received
wjrosa Aug 4, 2025
e6d8b32
Merge branch 'add/hook-for-inbound-webhooks' of https://github.com/wo…
wjrosa Aug 4, 2025
a6a1e14
Changelog and readme entries
wjrosa Aug 4, 2025
908dec4
Merge branch 'develop' into add/hook-for-inbound-webhooks
daledupreez Aug 18, 2025
353ff3e
Merge branch 'develop' into add/hook-for-inbound-webhooks
daledupreez Aug 19, 2025
be30ddd
Fix changelog entry location
daledupreez Aug 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*** Changelog ***

= 9.8.0 - xxxx-xx-xx =
* Add - Adds a new action (`wc_stripe_webhook_processed`) to allow hooking into the webhook processing flow
* Dev - Use product type constants that were added in WooCommerce 9.7
* Dev - Removes the inclusion of the deprecated WC_Stripe_Order class
* Add - Introduces a new banner to promote the Optimized Checkout feature in the Stripe settings page for versions 9.8 and above
Expand Down
106 changes: 100 additions & 6 deletions includes/class-wc-stripe-webhook-handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ class WC_Stripe_Webhook_Handler extends WC_Stripe_Payment_Gateway {
*/
protected $deferred_webhook_action = 'wc_stripe_deferred_webhook';

/**
* The order object being processed.
*
* @var WC_Order
*/
protected $resolved_order;

/**
* Constructor.
*
Expand All @@ -71,7 +78,7 @@ public function __construct() {
// plugin when this code first appears.
WC_Stripe_Webhook_State::get_monitoring_began_at();

add_action( $this->deferred_webhook_action, [ $this, 'process_deferred_webhook' ], 10, 2 );
add_action( $this->deferred_webhook_action, [ $this, 'process_deferred_webhook' ], 10, 3 );
}

/**
Expand Down Expand Up @@ -269,6 +276,9 @@ public function process_webhook_payment( $notification, $retry = true ) {
return;
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

$order_id = $order->get_id();

$is_pending_receiver = ( 'receiver' === $notification->data->object->flow );
Expand Down Expand Up @@ -402,6 +412,9 @@ public function process_webhook_dispute( $notification ) {
return;
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

$this->set_stripe_order_status_before_hold( $order, $order->get_status() );

$needs_response = in_array( $notification->data->object->status, [ 'needs_response', 'warning_needs_response' ], true );
Expand Down Expand Up @@ -444,6 +457,9 @@ public function process_webhook_dispute_closed( $notification ) {
return;
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

if ( 'lost' === $status ) {
$message = __( 'The dispute was lost or accepted.', 'woocommerce-gateway-stripe' );
} elseif ( 'won' === $status ) {
Expand Down Expand Up @@ -495,6 +511,9 @@ public function process_webhook_capture( $notification ) {
return;
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

if ( WC_Stripe_Helper::payment_method_allows_manual_capture( $order->get_payment_method() ) ) {
$charge = $order->get_transaction_id();
$captured = $order->get_meta( '_stripe_charge_captured', true );
Expand Down Expand Up @@ -567,6 +586,9 @@ public function process_webhook_charge_succeeded( $notification ) {
return;
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

if ( ! $order->has_status( OrderStatus::ON_HOLD ) ) {
return;
}
Expand Down Expand Up @@ -625,6 +647,9 @@ public function process_webhook_charge_failed( $notification ) {
return;
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

// If order status is already in failed status don't continue.
if ( $order->has_status( OrderStatus::FAILED ) ) {
return;
Expand Down Expand Up @@ -665,6 +690,9 @@ public function process_webhook_source_canceled( $notification ) {
}
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

// Don't proceed if payment method isn't Stripe.
if ( 'stripe' !== $order->get_payment_method() ) {
WC_Stripe_Logger::log( 'Canceled webhook abort: Order was not processed by Stripe: ' . $order->get_id() );
Expand Down Expand Up @@ -697,6 +725,9 @@ public function process_webhook_refund( $notification ) {
$order = WC_Stripe_Helper::get_order_by_charge_id( $notification->data->object->id );
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

if ( ! $order ) {
WC_Stripe_Logger::log( 'Could not find order via charge ID: ' . $notification->data->object->id );
return;
Expand Down Expand Up @@ -785,6 +816,9 @@ public function process_webhook_refund_updated( $notification ) {
return;
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

$order_id = $order->get_id();

if ( 'stripe' === substr( (string) $order->get_payment_method(), 0, 6 ) ) {
Expand Down Expand Up @@ -888,6 +922,9 @@ public function process_review_opened( $notification ) {
}
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

$this->set_stripe_order_status_before_hold( $order, $order->get_status() );

$message = sprintf(
Expand Down Expand Up @@ -929,6 +966,9 @@ public function process_review_closed( $notification ) {
}
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

/* translators: 1) The reason type. */
$message = sprintf( __( 'The opened review for this order is now closed. Reason: (%s)', 'woocommerce-gateway-stripe' ), $notification->data->object->reason );

Expand Down Expand Up @@ -1047,6 +1087,9 @@ public function process_payment_intent( $notification ) {
return;
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

if ( $this->lock_order_payment( $order, $intent ) ) {
return;
}
Expand Down Expand Up @@ -1082,7 +1125,7 @@ public function process_payment_intent( $notification ) {
$process_webhook_async = apply_filters( 'wc_stripe_process_payment_intent_webhook_async', true, $order, $intent, $notification );
$is_awaiting_action = $order->get_meta( '_stripe_upe_waiting_for_redirect' ) ?? false;

// 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.
// 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.
if ( $is_voucher_payment || $is_wallet_payment || ( ! $process_webhook_async && ! $is_awaiting_action ) ) {
$charge = $this->get_latest_charge_from_intent( $intent );

Expand All @@ -1096,6 +1139,22 @@ public function process_payment_intent( $notification ) {

$charge->is_webhook_response = true;
$this->process_response( $charge, $order );

try {
/**
* Fires after a webhook has been processed, but before we respond to Stripe.
* This allows for custom processing of the webhook after it has been processed.
*
* @since 9.8.0
*
* @param string $webhook_type The type of webhook that was processed.
* @param object $notification The webhook data sent from Stripe.
* @param WC_Order $order The order being processed by the webhook.
*/
do_action( 'wc_stripe_webhook_processed', (string) $notification->type, $notification, $this->resolved_order );
} catch ( Exception $e ) {
WC_Stripe_Logger::error( 'Error in wc_stripe_webhook_processed action: ' . $e->getMessage(), [ 'error' => $e ] );
}
} else {
WC_Stripe_Logger::log( "Processing $notification->type ($intent->id) asynchronously for order $order_id." );

Expand All @@ -1112,7 +1171,6 @@ public function process_payment_intent( $notification ) {
do_action( 'wc_gateway_stripe_process_payment_intent_incomplete', $order );
}
}

break;
default:
if ( $is_voucher_payment && 'payment_intent.payment_failed' === $notification->type ) {
Expand Down Expand Up @@ -1153,6 +1211,9 @@ public function process_setup_intent( $notification ) {
return;
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

$allowed_payment_processing_statuses = [ OrderStatus::PENDING, OrderStatus::FAILED ];

$allowed_payment_processing_statuses = apply_filters_deprecated(
Expand Down Expand Up @@ -1225,8 +1286,9 @@ protected function defer_webhook_processing( $webhook_notification, $additional_
time() + $this->deferred_webhook_delay,
$this->deferred_webhook_action,
[
'type' => $webhook_notification->type,
'data' => $additional_data,
'type' => $webhook_notification->type,
'data' => $additional_data,
'notification' => $webhook_notification,
]
);
}
Expand All @@ -1238,8 +1300,9 @@ protected function defer_webhook_processing( $webhook_notification, $additional_
*
* @param string $webhook_type The webhook event name/type.
* @param array $additional_data Additional data passed to the scheduled job.
* @param stdClass $notification The webhook notification payload.
*/
public function process_deferred_webhook( $webhook_type, $additional_data ) {
public function process_deferred_webhook( $webhook_type, $additional_data, $notification ) {
try {
switch ( $webhook_type ) {
case 'payment_intent.succeeded':
Expand All @@ -1251,6 +1314,9 @@ public function process_deferred_webhook( $webhook_type, $additional_data ) {
throw new Exception( "Missing required data. 'order_id' is invalid or not found for the deferred '{$webhook_type}' event." );
}

// Set the order being processed for the `wc_stripe_webhook_processed` action later.
$this->resolved_order = $order;

if ( empty( $intent_id ) ) {
throw new Exception( "Missing required data. 'intent_id' is missing for the deferred '{$webhook_type}' event." );
}
Expand All @@ -1262,6 +1328,17 @@ public function process_deferred_webhook( $webhook_type, $additional_data ) {
}

$this->handle_deferred_payment_intent_succeeded( $order, $intent_id );
/**
* Fires after a webhook has been processed, but before we respond to Stripe.
* This allows for custom processing of the webhook after it has been processed.
*
* @since 9.8.0
*
* @param string $webhook_type The type of webhook that was processed.
* @param object $notification The webhook data sent from Stripe.
* @param WC_Order $order The order being processed by the webhook.
*/
do_action( 'wc_stripe_webhook_processed', (string) $webhook_type, $notification, $this->resolved_order );
break;
default:
throw new Exception( "Unsupported webhook type: {$webhook_type}" );
Expand Down Expand Up @@ -1395,6 +1472,23 @@ public function process_webhook( $request_body ) {
$this->process_setup_intent( $notification );

}

// These events might be processed async. Skip the action trigger for them here. The trigger will be called inside the specific methods.
if ( 'payment_intent.succeeded' === $notification->type || 'payment_intent.amount_capturable_updated' === $notification->type ) {
return;
}

/**
* Fires after a webhook has been processed, but before we respond to Stripe.
* This allows for custom processing of the webhook after it has been processed.
*
* @since 9.8.0
*
* @param string $webhook_type The type of webhook that was processed.
* @param object $notification The webhook data sent from Stripe.
* @param WC_Order $order The order being processed by the webhook.
*/
do_action( 'wc_stripe_webhook_processed', (string) $notification->type, $notification, $this->resolved_order );
}

/**
Expand Down
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
== Changelog ==

= 9.8.0 - xxxx-xx-xx =
* Add - Adds a new action (`wc_stripe_webhook_processed`) to allow hooking into the webhook processing flow
* Dev - Use product type constants that were added in WooCommerce 9.7
* Dev - Removes the inclusion of the deprecated WC_Stripe_Order class
* Add - Introduces a new banner to promote the Optimized Checkout feature in the Stripe settings page for versions 9.8 and above
Expand Down
Loading
Loading