From 93eaed140bbb5af01a90137287c32a8c04381fce Mon Sep 17 00:00:00 2001 From: Daniel Guerra <15204776+danielmx-dev@users.noreply.github.com> Date: Fri, 12 Jul 2024 18:19:22 +0300 Subject: [PATCH] Use the customer ID saved in the subscription to process renewal payments (#9065) Co-authored-by: Brett Shumaker --- ...x-8892-use-correct-customer-id-in-renewals | 4 ++ includes/class-payment-information.php | 21 +++++- includes/class-wc-payment-gateway-wcpay.php | 10 ++- ...wc-payment-gateway-wcpay-subscriptions.php | 4 +- tests/unit/test-class-payment-information.php | 17 +++++ ...wc-payment-gateway-wcpay-subscriptions.php | 70 +++++++++++++++++++ 6 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 changelog/fix-8892-use-correct-customer-id-in-renewals diff --git a/changelog/fix-8892-use-correct-customer-id-in-renewals b/changelog/fix-8892-use-correct-customer-id-in-renewals new file mode 100644 index 00000000000..2e367a199dd --- /dev/null +++ b/changelog/fix-8892-use-correct-customer-id-in-renewals @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Use the customer id saved in the subscription to process renewal payments. diff --git a/includes/class-payment-information.php b/includes/class-payment-information.php index 38191bf7ded..2fd39f78739 100644 --- a/includes/class-payment-information.php +++ b/includes/class-payment-information.php @@ -105,6 +105,13 @@ class Payment_Information { */ private $payment_method_stripe_id; + /** + * The WCPay Customer ID that owns the payment token. + * + * @var string + */ + private $customer_id; + /** * Payment information constructor. * @@ -117,6 +124,7 @@ class Payment_Information { * @param string $cvc_confirmation The CVC confirmation for this payment method. * @param string $fingerprint The attached fingerprint. * @param string $payment_method_stripe_id The Stripe ID of the payment method used for this payment. + * @param string $customer_id The WCPay Customer ID that owns the payment token. * * @throws Invalid_Payment_Method_Exception When no payment method is found in the provided request. */ @@ -129,7 +137,8 @@ public function __construct( Payment_Capture_Type $manual_capture = null, string $cvc_confirmation = null, string $fingerprint = '', - string $payment_method_stripe_id = null + string $payment_method_stripe_id = null, + string $customer_id = null ) { if ( empty( $payment_method ) && empty( $token ) && ! \WC_Payments::is_network_saved_cards_enabled() ) { // If network-wide cards are enabled, a payment method or token may not be specified and the platform default one will be used. @@ -147,6 +156,7 @@ public function __construct( $this->cvc_confirmation = $cvc_confirmation; $this->fingerprint = $fingerprint; $this->payment_method_stripe_id = $payment_method_stripe_id; + $this->customer_id = $customer_id; } /** @@ -436,4 +446,13 @@ public function get_fingerprint() { public function get_payment_method_stripe_id() { return $this->payment_method_stripe_id; } + + /** + * Returns the WCPay Customer ID that owns the payment token. + * + * @return string The WCPay Customer ID. + */ + public function get_customer_id() { + return $this->customer_id; + } } diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index c5126c398e0..e1e6a3543c9 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -1475,10 +1475,16 @@ public function process_payment_for_order( $cart, $payment_information, $schedul $amount = $order->get_total(); $metadata = $this->get_metadata_from_order( $order, $payment_information->get_payment_type() ); - $customer_details_options = [ + $customer_details_options = [ 'is_woopay' => filter_var( $metadata['paid_on_woopay'] ?? false, FILTER_VALIDATE_BOOLEAN ), ]; - list( $user, $customer_id ) = $this->manage_customer_details_for_order( $order, $customer_details_options ); + + if ( $payment_information->get_customer_id() ) { + $user = $order->get_user(); + $customer_id = $payment_information->get_customer_id(); + } else { + list( $user, $customer_id ) = $this->manage_customer_details_for_order( $order, $customer_details_options ); + } $intent_failed = false; $payment_needed = $amount > 0; diff --git a/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php b/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php index da7a1379c14..31ec70bedf8 100644 --- a/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php +++ b/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php @@ -331,8 +331,10 @@ public function scheduled_subscription_payment( $amount, $renewal_order ) { return; } + $customer_id = $this->order_service->get_customer_id_for_order( $renewal_order ); + try { - $payment_information = new Payment_Information( '', $renewal_order, Payment_Type::RECURRING(), $token, Payment_Initiated_By::MERCHANT(), null, null, '', $this->get_payment_method_to_use_for_intent() ); + $payment_information = new Payment_Information( '', $renewal_order, Payment_Type::RECURRING(), $token, Payment_Initiated_By::MERCHANT(), null, null, '', $this->get_payment_method_to_use_for_intent(), $customer_id ); $this->process_payment_for_order( null, $payment_information, true ); } catch ( API_Exception $e ) { Logger::error( 'Error processing subscription renewal: ' . $e->getMessage() ); diff --git a/tests/unit/test-class-payment-information.php b/tests/unit/test-class-payment-information.php index b6cfd8c3960..3067e1c13b2 100644 --- a/tests/unit/test-class-payment-information.php +++ b/tests/unit/test-class-payment-information.php @@ -98,6 +98,23 @@ public function test_set_token_updates_token() { $this->assertTrue( $payment_information->is_using_saved_payment_method() ); } + public function test_get_customer_id() { + $expected_customer_id = 'old_customer_id'; + $payment_information = new Payment_Information( + self::PAYMENT_METHOD, + null, + Payment_Type::SINGLE(), + $this->card_token, + null, + null, + null, + '', + null, + $expected_customer_id + ); + $this->assertEquals( $expected_customer_id, $payment_information->get_customer_id() ); + } + public function test_get_payment_method_from_request() { $payment_method = Payment_Information::get_payment_method_from_request( [ self::PAYMENT_METHOD_REQUEST_KEY => self::PAYMENT_METHOD ] diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php index 151b3b919fe..8d011f7f508 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php @@ -354,6 +354,76 @@ public function test_scheduled_subscription_payment() { $this->assertEquals( 'processing', $renewal_order->get_status() ); } + public function test_scheduled_subscription_payment_with_saved_customer_id() { + $saved_customer_id = self::CUSTOMER_ID . '_old'; + + $renewal_order = WC_Helper_Order::create_order( self::USER_ID ); + + $token = WC_Helper_Token::create_token( self::PAYMENT_METHOD_ID, self::USER_ID ); + $renewal_order->add_payment_token( $token ); + + $this->order_service->set_customer_id_for_order( $renewal_order, $saved_customer_id ); + + $mock_subscription = new WC_Subscription(); + + $this->mock_wcs_get_subscriptions_for_renewal_order( [ '1' => $mock_subscription ] ); + + $this->mock_customer_service + ->expects( $this->never() ) + ->method( 'get_customer_id_by_user_id' ); + + $request = $this->mock_wcpay_request( Create_And_Confirm_Intention::class ); + + $request->expects( $this->once() ) + ->method( 'set_customer' ) + ->with( $saved_customer_id ); + + $request->expects( $this->once() ) + ->method( 'set_payment_method' ) + ->with( self::PAYMENT_METHOD_ID ); + + $request->expects( $this->once() ) + ->method( 'set_cvc_confirmation' ) + ->with( null ); + + $request->expects( $this->once() ) + ->method( 'set_amount' ) + ->with( 5000 ) + ->willReturn( $request ); + + $request->expects( $this->once() ) + ->method( 'set_currency_code' ) + ->with( 'usd' ) + ->willReturn( $request ); + + $request->expects( $this->never() ) + ->method( 'setup_future_usage' ); + + $request->expects( $this->once() ) + ->method( 'set_capture_method' ) + ->with( false ); + + $request->expects( $this->once() ) + ->method( 'set_off_session' ) + ->with( true ); + + $request->expects( $this->once() ) + ->method( 'set_capture_method' ) + ->with( false ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( WC_Helper_Intention::create_intention() ); + + $this->mock_customer_service + ->expects( $this->never() ) + ->method( 'update_customer_for_user' ); + + $this->wcpay_gateway->scheduled_subscription_payment( $renewal_order->get_total(), $renewal_order ); + + $this->assertEquals( 'processing', $renewal_order->get_status() ); + } + public function test_scheduled_subscription_payment_fails_when_token_is_missing() { $renewal_order = WC_Helper_Order::create_order( self::USER_ID );