Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use metadata stored on the intent to fetch an order for webhook processing #3567

Merged
merged 13 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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 ***

= 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.
Expand Down
24 changes: 22 additions & 2 deletions includes/abstracts/abstract-wc-stripe-payment-gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -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() ) ) {
Expand Down Expand Up @@ -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 ) ) );
}
}
44 changes: 43 additions & 1 deletion includes/class-wc-stripe-webhook-handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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();
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
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 ==

= 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.
Expand Down
1 change: 1 addition & 0 deletions tests/phpunit/test-class-wc-stripe-upe-payment-gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ private function get_order_details( $order ) {
'order_id' => $order_id,
wjrosa marked this conversation as resolved.
Show resolved Hide resolved
'order_key' => $order_key,
'payment_type' => 'single',
'signature' => sprintf( '%d:%s', $order->get_id(), md5( $order->get_order_key() . $order->get_id() . $order->get_customer_id() ) ),
wjrosa marked this conversation as resolved.
Show resolved Hide resolved
];
return [ $amount, $description, $metadata ];
}
Expand Down
Loading