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

Allow payment_intent_succeeded webhook to handle orders without intent_id attached #3118

Merged
merged 13 commits into from
Oct 21, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -19,6 +19,7 @@
* Add - Add compatibility between Multi-Currency and WooCommerce UPS shipping extension.
* Add - Add compatibility between Multi-Currency and WooCommerce FedEx shipping extension.
* Fix - Fix decimal error with shipping calculations with Multi-Currency.
* Fix - Allow payment_intent_succeeded webhook to handle orders without intent_id attached.

= 3.1.0 - 2021-10-06 =
* Fix - Issue affecting analytics for Multi-Currency orders made with a zero-decimal to non-zero decimal conversion.
Expand Down
9 changes: 9 additions & 0 deletions includes/admin/class-wc-rest-payments-webhook-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,15 @@ private function process_webhook_payment_intent_succeeded( $event_body ) {

// Look up the order related to this charge.
$order = $this->wcpay_db->order_from_intent_id( $intent_id );

if ( ! $order ) {
// Retrieving order with order_id in case intent_id was not properly set.
Logger::debug( 'intent_id not found, using order_id to retrieve order' );
$metadata = $this->read_rest_property( $event_object, 'metadata' );
$order_id = $metadata['order_id'];
$order = $this->wcpay_db->order_from_order_id( $order_id );
}

if ( ! $order ) {
throw new Invalid_Payment_Method_Exception(
sprintf(
Expand Down
6 changes: 3 additions & 3 deletions includes/class-wc-payment-gateway-wcpay.php
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,9 @@ public function process_payment_for_order( $cart, $payment_information, $additio
$next_action = $intent['next_action'];
}

$this->attach_intent_info_to_order( $order, $intent_id, $status, $payment_method, $customer_id, $charge_id, $currency );
$this->attach_exchange_info_to_order( $order, $charge_id );

if ( ! empty( $intent ) ) {
if ( ! in_array( $status, self::SUCCESSFUL_INTENT_STATUS, true ) ) {
$intent_failed = true;
Expand Down Expand Up @@ -1099,9 +1102,6 @@ public function process_payment_for_order( $cart, $payment_information, $additio
}
}

$this->attach_intent_info_to_order( $order, $intent_id, $status, $payment_method, $customer_id, $charge_id, $currency );
$this->attach_exchange_info_to_order( $order, $charge_id );

if ( isset( $response ) ) {
return $response;
}
Expand Down
15 changes: 13 additions & 2 deletions includes/class-wc-payments-db.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function order_from_charge_id( $charge_id ) {
$order_id = $this->order_id_from_meta_key_value( self::META_KEY_CHARGE_ID, $charge_id );

if ( $order_id ) {
return wc_get_order( $order_id );
return $this->order_from_order_id( $order_id );
}
return false;
}
Expand All @@ -41,7 +41,7 @@ public function order_from_intent_id( $intent_id ) {
$order_id = $this->order_id_from_meta_key_value( self::META_KEY_INTENT_ID, $intent_id );

if ( $order_id ) {
return wc_get_order( $order_id );
return $this->order_from_order_id( $order_id );
}
return false;
}
Expand All @@ -67,4 +67,15 @@ private function order_id_from_meta_key_value( $meta_key, $meta_value ) {
);
return $order_id;
}

/**
* Retrieve an order using order ID.
*
* @param string $order_id WC Order Id.
*
* @return null|WC_Order
*/
public function order_from_order_id( $order_id ) {
return wc_get_order( ( $order_id ) );
}
}
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ Please note that our support for the checkout block is still experimental and th
* Add - Add compatibility between Multi-Currency and WooCommerce UPS shipping extension.
* Add - Add compatibility between Multi-Currency and WooCommerce FedEx shipping extension.
* Fix - Fix decimal error with shipping calculations with Multi-Currency.
* Fix - Allow payment_intent_succeeded webhook to handle orders without intent_id attached.

= 3.1.0 - 2021-10-06 =
* Fix - Issue affecting analytics for Multi-Currency orders made with a zero-decimal to non-zero decimal conversion.
Expand Down
52 changes: 51 additions & 1 deletion tests/unit/admin/test-class-wc-rest-payments-webhook.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function setUp() {

$this->mock_db_wrapper = $this->getMockBuilder( WC_Payments_DB::class )
->disableOriginalConstructor()
->setMethods( [ 'order_from_charge_id', 'order_from_intent_id' ] )
->setMethods( [ 'order_from_charge_id', 'order_from_intent_id', 'order_from_order_id' ] )
->getMock();

$this->mock_remote_note_service = $this->createMock( WC_Payments_Remote_Note_Service::class );
Expand Down Expand Up @@ -562,6 +562,56 @@ public function test_payment_intent_successful_and_completes_order() {
$this->assertEquals( [ 'result' => 'success' ], $response_data );
}

/**
* Tests that a payment_intent.succeeded event will complete the order even if the intent was not properly attached into the order.
*/
public function test_payment_intent_successful_and_completes_order_without_intent_id() {
$this->request_body['type'] = 'payment_intent.succeeded';
$this->request_body['data']['object'] = [
'id' => 'pi_123123123123123', // payment_intent's ID.
'object' => 'payment_intent',
'amount' => 1500,
'charges' => [],
'currency' => 'eur',
'metadata' => [ 'order_id' => 'id_1323' ], // Using order_id inside of the intent metadata to find the order.
];

$this->request->set_body( wp_json_encode( $this->request_body ) );

$mock_order = $this->createMock( WC_Order::class );

$mock_order
->expects( $this->once() )
->method( 'has_status' )
->with( [ 'processing', 'completed' ] )
->willReturn( false );

$mock_order
->expects( $this->once() )
->method( 'payment_complete' );

$this->mock_db_wrapper
->expects( $this->once() )
->method( 'order_from_intent_id' )
->with( 'pi_123123123123123' )
->willReturn( null );

$this->mock_db_wrapper
->expects( $this->once() )
->method( 'order_from_order_id' )
->with( 'id_1323' )
->willReturn( $mock_order );

// Run the test.
$response = $this->controller->handle_webhook( $this->request );

// Check the response.
$response_data = $response->get_data();

$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( [ 'result' => 'success' ], $response_data );
}

/**
* Tests that a payment_intent.succeeded event will not complete the order
* if it is already completed/processed.
Expand Down