diff --git a/changelog.txt b/changelog.txt index 56ce7f88b..25930c613 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,7 @@ *** Changelog *** = 8.9.0 - xxxx-xx-xx = +* Update - Enhance webhook processing to enable retrieving orders using payment_intent metadata. * Dev - Minor updates to the webhook handler class related to payment method names constants. * Tweak - Improve error message displayed when payment method creation fails in classic checkout. * Dev - Replace two occurrences of payment method names with their constant equivalents. diff --git a/includes/abstracts/abstract-wc-stripe-payment-gateway.php b/includes/abstracts/abstract-wc-stripe-payment-gateway.php index 251506e9d..ecf9778a8 100644 --- a/includes/abstracts/abstract-wc-stripe-payment-gateway.php +++ b/includes/abstracts/abstract-wc-stripe-payment-gateway.php @@ -481,8 +481,9 @@ public function generate_payment_request( $order, $prepared_payment_method ) { $metadata = [ __( 'customer_name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ), __( 'customer_email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ), - 'order_id' => $order->get_order_number(), - 'site_url' => esc_url( get_site_url() ), + 'order_id' => $order->get_order_number(), + 'site_url' => esc_url( get_site_url() ), + 'signature' => $this->get_order_signature( $order ), ]; if ( $this->has_subscription( $order->get_id() ) ) { @@ -2324,4 +2325,23 @@ public function get_balance_transaction_id_from_charge( $charge ) { return $balance_transaction_id; } + + /** + * Generates a unique signature for an order. + * + * This signature is included as metadata in Stripe requests and used to identify the order when webhooks are received. + * + * @param WC_Order $order The Order object. + * @return string The order's unique signature. Format: order_id:md5(order_id-order_key-customer_id-order_total). + */ + protected function get_order_signature( $order ) { + $signature = [ + absint( $order->get_id() ), + $order->get_order_key(), + $order->get_customer_id() ?? '', + WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $order->get_currency() ), + ]; + + return sprintf( '%d:%s', $order->get_id(), md5( implode( '-', $signature ) ) ); + } } diff --git a/includes/class-wc-stripe-webhook-handler.php b/includes/class-wc-stripe-webhook-handler.php index e8daf4247..548b1bee8 100644 --- a/includes/class-wc-stripe-webhook-handler.php +++ b/includes/class-wc-stripe-webhook-handler.php @@ -892,9 +892,14 @@ public function get_partial_amount_to_charge( $notification ) { return false; } + /** + * Handles the processing of a payment intent webhook. + * + * @param stdClass $notification The webhook notification from Stripe. + */ public function process_payment_intent_success( $notification ) { $intent = $notification->data->object; - $order = WC_Stripe_Helper::get_order_by_intent_id( $intent->id ); + $order = $this->get_order_from_intent( $intent ); if ( ! $order ) { WC_Stripe_Logger::log( 'Could not find order via intent ID: ' . $intent->id ); @@ -1199,6 +1204,43 @@ public function process_webhook( $request_body ) { } } + + /** + * Fetches an order from a payment intent. + * + * @param stdClass $intent The Stripe PaymentIntent object. + * @return WC_Order|false The order object, or false if not found. + */ + private function get_order_from_intent( $intent ) { + // Attempt to get the order from the intent metadata. + if ( isset( $intent->metadata->signature ) ) { + $signature = wc_clean( $intent->metadata->signature ); + $data = explode( ':', $signature ); + + // Verify we received the order ID and signature (hash). + $order = isset( $data[0], $data[1] ) ? wc_get_order( absint( $data[0] ) ) : false; + + if ( $order ) { + $intent_id = WC_Stripe_Helper::get_intent_id_from_order( $order ); + + // Return the order if the intent ID matches. + if ( $intent->id === $intent_id ) { + return $order; + } + + /** + * If the order has no intent ID stored, we may have failed to store it during the initial payment request. + * Confirm that the signature matches the order, otherwise fall back to finding the order via the intent ID. + */ + if ( empty( $intent_id ) && $this->get_order_signature( $order ) === $signature ) { + return $order; + } + } + } + + // Fall back to finding the order via the intent ID. + return WC_Stripe_Helper::get_order_by_intent_id( $intent->id ); + } } new WC_Stripe_Webhook_Handler(); diff --git a/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php b/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php index f82c16979..92867deaa 100644 --- a/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php +++ b/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php @@ -1750,6 +1750,7 @@ public function get_metadata_from_order( $order ) { 'order_id' => $order->get_order_number(), 'order_key' => $order->get_order_key(), 'payment_type' => $payment_type, + 'signature' => $this->get_order_signature( $order ), ]; return apply_filters( 'wc_stripe_intent_metadata', $metadata, $order ); diff --git a/readme.txt b/readme.txt index 93d5ff904..6915f645d 100644 --- a/readme.txt +++ b/readme.txt @@ -111,6 +111,7 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o == Changelog == = 8.9.0 - xxxx-xx-xx = +* Update - Enhance webhook processing to enable retrieving orders using payment_intent metadata. * Dev - Minor updates to the webhook handler class related to payment method names constants. * Tweak - Improve error message displayed when payment method creation fails in classic checkout. * Dev - Replace two occurrences of payment method names with their constant equivalents. diff --git a/tests/phpunit/test-class-wc-stripe-upe-payment-gateway.php b/tests/phpunit/test-class-wc-stripe-upe-payment-gateway.php index 7a33639a1..e3598461d 100644 --- a/tests/phpunit/test-class-wc-stripe-upe-payment-gateway.php +++ b/tests/phpunit/test-class-wc-stripe-upe-payment-gateway.php @@ -218,9 +218,10 @@ private function get_order_details( $order ) { 'customer_name' => 'Jeroen Sormani', 'customer_email' => 'admin@example.org', 'site_url' => 'http://example.org', - 'order_id' => $order_id, + 'order_id' => $order_number, 'order_key' => $order_key, 'payment_type' => 'single', + 'signature' => sprintf( '%d:%s', $order->get_id(), md5( implode( '-', [ absint( $order->get_id() ), $order->get_order_key(), $order->get_customer_id(), $amount ] ) ) ), ]; return [ $amount, $description, $metadata ]; }