diff --git a/changelog/cleanup-upe-gateways-II b/changelog/cleanup-upe-gateways-II new file mode 100644 index 00000000000..16f3e3cd17f --- /dev/null +++ b/changelog/cleanup-upe-gateways-II @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Cleanup the deprecated payment gateway processing - part VI diff --git a/changelog/dev-bump-wc-version-8-4-0 b/changelog/dev-bump-wc-version-8-4-0 new file mode 100644 index 00000000000..5c00289cc5b --- /dev/null +++ b/changelog/dev-bump-wc-version-8-4-0 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Bump WC tested up to version to 8.4.0. \ No newline at end of file diff --git a/client/checkout/api/index.js b/client/checkout/api/index.js index 9ecdf245b62..9b05ea40a26 100644 --- a/client/checkout/api/index.js +++ b/client/checkout/api/index.js @@ -440,39 +440,6 @@ export default class WCPayAPI { } ); } - /** - * Process checkout and update payment intent via AJAX. - * - * @param {string} paymentIntentId ID of payment intent to be updated. - * @param {Object} fields Checkout fields. - * @param {string} fingerprint User fingerprint. - * @return {Promise} Promise containing redirect URL for UPE element. - */ - processCheckout( paymentIntentId, fields, fingerprint ) { - return this.request( - buildAjaxURL( getConfig( 'wcAjaxUrl' ), 'checkout', '' ), - { - ...fields, - wc_payment_intent_id: paymentIntentId, - 'wcpay-fingerprint': fingerprint, - } - ) - .then( ( response ) => { - if ( response.result === 'failure' ) { - throw new Error( response.messages ); - } - return response; - } ) - .catch( ( error ) => { - if ( error.message ) { - throw error; - } else { - // Covers the case of error on the Ajaxrequest. - throw new Error( error.statusText ); - } - } ); - } - /** * Submits shipping address to get available shipping options * from Payment Request button. diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 0348466cf19..32910b60cad 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -17,7 +17,7 @@ use WCPay\Constants\Intent_Status; use WCPay\Constants\Payment_Type; use WCPay\Constants\Payment_Method; -use WCPay\Exceptions\{ Add_Payment_Method_Exception, Amount_Too_Small_Exception, Process_Payment_Exception, Intent_Authentication_Exception, API_Exception }; +use WCPay\Exceptions\{ Add_Payment_Method_Exception, Amount_Too_Small_Exception, Process_Payment_Exception, Intent_Authentication_Exception, API_Exception, Invalid_Address_Exception}; use WCPay\Core\Server\Request\Cancel_Intention; use WCPay\Core\Server\Request\Capture_Intention; use WCPay\Core\Server\Request\Create_And_Confirm_Intention; @@ -36,7 +36,6 @@ use WCPay\Internal\Service\DuplicatePaymentPreventionService; use WCPay\Logger; use WCPay\Payment_Information; -use WCPay\Payment_Methods\UPE_Payment_Gateway; use WCPay\Payment_Methods\Link_Payment_Method; use WCPay\WooPay\WooPay_Order_Status_Sync; use WCPay\WooPay\WooPay_Utilities; @@ -48,6 +47,19 @@ use WCPay\Internal\Payment\State\CompletedState; use WCPay\Internal\Service\Level3Service; use WCPay\Internal\Service\OrderService; +use WCPay\Payment_Methods\Affirm_Payment_Method; +use WCPay\Payment_Methods\Afterpay_Payment_Method; +use WCPay\Payment_Methods\Bancontact_Payment_Method; +use WCPay\Payment_Methods\Becs_Payment_Method; +use WCPay\Payment_Methods\CC_Payment_Method; +use WCPay\Payment_Methods\Eps_Payment_Method; +use WCPay\Payment_Methods\Giropay_Payment_Method; +use WCPay\Payment_Methods\Ideal_Payment_Method; +use WCPay\Payment_Methods\Klarna_Payment_Method; +use WCPay\Payment_Methods\P24_Payment_Method; +use WCPay\Payment_Methods\Sepa_Payment_Method; +use WCPay\Payment_Methods\Sofort_Payment_Method; +use WCPay\Payment_Methods\UPE_Payment_Method; /** * Gateway class for WooPayments @@ -101,6 +113,10 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC { const USER_FORMATTED_TOKENS_LIMIT = 100; + const PROCESS_REDIRECT_ORDER_MISMATCH_ERROR_CODE = 'upe_process_redirect_order_id_mismatched'; + const UPE_APPEARANCE_TRANSIENT = 'wcpay_upe_appearance'; + const WC_BLOCKS_UPE_APPEARANCE_TRANSIENT = 'wcpay_wc_blocks_upe_appearance'; + /** * Client for making requests to the WooCommerce Payments API * @@ -185,6 +201,27 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC { */ protected $fraud_service; + /** + * UPE Payment Method for gateway. + * + * @var UPE_Payment_Method + */ + protected $payment_method; + + /** + * Array mapping payment method string IDs to classes + * + * @var UPE_Payment_Method[] + */ + protected $payment_methods = []; + + /** + * Stripe payment method type ID. + * + * @var string + */ + protected $stripe_id; + /** * WC_Payment_Gateway_WCPay constructor. * @@ -193,6 +230,8 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC { * @param WC_Payments_Customer_Service $customer_service - Customer class instance. * @param WC_Payments_Token_Service $token_service - Token class instance. * @param WC_Payments_Action_Scheduler_Service $action_scheduler_service - Action Scheduler service instance. + * @param UPE_Payment_Method $payment_method - Specific UPE_Payment_Method instance for gateway. + * @param array $payment_methods - Array of UPE payment methods. * @param Session_Rate_Limiter|null $failed_transaction_rate_limiter - Rate Limiter for failed transactions. * @param WC_Payments_Order_Service $order_service - Order class instance. * @param Duplicate_Payment_Prevention_Service $duplicate_payment_prevention_service - Service for preventing duplicate payments. @@ -205,12 +244,18 @@ public function __construct( WC_Payments_Customer_Service $customer_service, WC_Payments_Token_Service $token_service, WC_Payments_Action_Scheduler_Service $action_scheduler_service, + UPE_Payment_Method $payment_method, + array $payment_methods, Session_Rate_Limiter $failed_transaction_rate_limiter = null, WC_Payments_Order_Service $order_service, Duplicate_Payment_Prevention_Service $duplicate_payment_prevention_service, WC_Payments_Localization_Service $localization_service, WC_Payments_Fraud_Service $fraud_service ) { + $this->payment_methods = $payment_methods; + $this->payment_method = $payment_method; + $this->stripe_id = $payment_method->get_id(); + $this->payments_api_client = $payments_api_client; $this->account = $account; $this->customer_service = $customer_service; @@ -223,18 +268,23 @@ public function __construct( $this->fraud_service = $fraud_service; $this->id = static::GATEWAY_ID; - $this->icon = plugins_url( 'assets/images/payment-methods/cc.svg', WCPAY_PLUGIN_FILE ); + $this->icon = $payment_method->get_icon(); $this->has_fields = true; $this->method_title = 'WooPayments'; - $this->method_description = $this->get_method_description(); + $this->method_description = __( 'Payments made simple, with no monthly fees - designed exclusively for WooCommerce stores. Accept credit cards, debit cards, and other popular payment methods.', 'woocommerce-payments' ); - $this->title = __( 'Credit card / debit card', 'woocommerce-payments' ); - $this->description = __( 'Enter your card details', 'woocommerce-payments' ); + $this->title = $payment_method->get_title(); + $this->description = ''; $this->supports = [ 'products', 'refunds', ]; + if ( 'card' !== $this->stripe_id ) { + $this->id = self::GATEWAY_ID . '_' . $this->stripe_id; + $this->method_title = "WooPayments ($this->title)"; + } + // Define setting fields. $this->form_fields = [ 'enabled' => [ @@ -475,6 +525,95 @@ public function init_hooks() { $this->maybe_init_subscriptions_hooks(); } + /** + * Displays HTML tags for WC payment gateway radio button content. + */ + public function display_gateway_html() { + ?> +
+ get_selected_stripe_payment_type_id() ); + do_action( 'wc_payments_add_upe_payment_fields' ); + } + + /** + * Adds a token to current user from a setup intent id. + * + * @param string $setup_intent_id ID of the setup intent. + * @param WP_User $user User to add token to. + * + * @return WC_Payment_Token_CC|WC_Payment_Token_WCPay_SEPA|null The added token. + */ + public function create_token_from_setup_intent( $setup_intent_id, $user ) { + try { + $setup_intent_request = Get_Setup_Intention::create( $setup_intent_id ); + /** @var WC_Payments_API_Setup_Intention $setup_intent */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort + $setup_intent = $setup_intent_request->send(); + + $payment_method_id = $setup_intent->get_payment_method_id(); + // TODO: When adding SEPA and Sofort, we will need a new API call to get the payment method and from there get the type. + // Leaving 'card' as a hardcoded value for now to avoid the extra API call. + // $payment_method = $this->payment_methods['card'];// Maybe this should be enforced. + $payment_method = $this->payment_method; + + return $payment_method->get_payment_token_for_user( $user, $payment_method_id ); + } catch ( Exception $e ) { + wc_add_notice( WC_Payments_Utils::get_filtered_error_message( $e ), 'error', [ 'icon' => 'error' ] ); + Logger::log( 'Error when adding payment method: ' . $e->getMessage() ); + } + } + + /** + * Validate order_id received from the request vs value saved in the intent metadata. + * Throw an exception if they're not matched. + * + * @param WC_Order $order The received order to process. + * @param array $intent_metadata The metadata of attached intent to the order. + * + * @return void + * @throws Process_Payment_Exception + */ + private function validate_order_id_received_vs_intent_meta_order_id( WC_Order $order, array $intent_metadata ): void { + $intent_meta_order_id_raw = $intent_metadata['order_id'] ?? ''; + $intent_meta_order_id = is_numeric( $intent_meta_order_id_raw ) ? intval( $intent_meta_order_id_raw ) : 0; + + if ( $order->get_id() !== $intent_meta_order_id ) { + Logger::error( + sprintf( + 'UPE Process Redirect Payment - Order ID mismatched. Received: %1$d. Intent Metadata Value: %2$d', + $order->get_id(), + $intent_meta_order_id + ) + ); + + throw new Process_Payment_Exception( + __( "We're not able to process this payment due to the order ID mismatch. Please try again later.", 'woocommerce-payments' ), + self::PROCESS_REDIRECT_ORDER_MISMATCH_ERROR_CODE + ); + } + } + + /** + * Gets payment method settings to pass to client scripts + * + * @deprecated 5.0.0 + * + * @return array + */ + private function get_enabled_payment_method_config() { + wc_deprecated_function( __FUNCTION__, '5.0.0', 'WC_Payments_Checkout::get_enabled_payment_method_config' ); + return WC_Payments::get_wc_payments_checkout()->get_enabled_payment_method_config(); + } + + /** * If we're in a WooPay preflight check, remove all the checkout order processed * actions to prevent reduce available resources quantity. @@ -575,6 +714,10 @@ public function needs_https_setup() { * @return bool Whether the gateway is enabled and ready to accept payments. */ public function is_available() { + $processing_payment_method = $this->payment_methods[ $this->payment_method->get_id() ]; + if ( ! $processing_payment_method->is_enabled_at_checkout( $this->get_account_country() ) ) { + return false; + } // Disable the gateway if using live mode without HTTPS set up or the currency is not // available in the country of the account. if ( $this->needs_https_setup() || ! $this->is_available_for_current_currency() ) { @@ -683,6 +826,59 @@ public function output_payments_settings_screen() { endif; } + /** + * Log payment errors on Checkout. + * + * @throws Exception If nonce is not present or invalid or charge ID is empty or order not found. + */ + public function log_payment_error_ajax() { + try { + $is_nonce_valid = check_ajax_referer( 'wcpay_log_payment_error_nonce', false, false ); + if ( ! $is_nonce_valid ) { + throw new Exception( 'Invalid request.' ); + } + + $charge_id = isset( $_POST['charge_id'] ) ? wc_clean( wp_unslash( $_POST['charge_id'] ) ) : ''; + if ( empty( $charge_id ) ) { + throw new Exception( 'Charge ID cannot be empty.' ); + } + + // Get charge data from WCPay Server. + $request = Get_Charge::create( $charge_id ); + $request->set_hook_args( $charge_id ); + $charge_data = $request->send(); + $order_id = $charge_data['metadata']['order_id']; + + // Validate Order ID and proceed with logging errors and updating order status. + $order = wc_get_order( $order_id ); + if ( ! $order ) { + throw new Exception( 'Order not found. Unable to log error.' ); + } + + $intent_id = $charge_data['payment_intent'] ?? $order->get_meta( '_intent_id' ); + + $request = Get_Intention::create( $intent_id ); + $request->set_hook_args( $order ); + $intent = $request->send(); + + $intent_status = $intent->get_status(); + $error_message = esc_html( rtrim( $charge_data['failure_message'], '.' ) ); + + $this->order_service->mark_payment_failed( $order, $intent_id, $intent_status, $charge_id, $error_message ); + + wp_send_json_success(); + } catch ( Exception $e ) { + wp_send_json_error( + [ + 'error' => [ + 'message' => WC_Payments_Utils::get_filtered_error_message( $e ), + ], + ], + WC_Payments_Utils::get_filtered_error_status_code( $e ), + ); + } + } + /** * Displays the save to account checkbox. * @@ -709,6 +905,10 @@ public function save_payment_method_checkbox( $force_checked = false ) { * @return bool */ public function should_use_stripe_platform_on_checkout_page() { + if ( 'card' !== $this->stripe_id ) { + return false; + } + if ( WC_Payments_Features::is_woopay_eligible() && 'yes' === $this->get_option( 'platform_checkout', 'no' ) && @@ -1527,15 +1727,228 @@ public function process_payment_for_order( $cart, $payment_information, $schedul ]; } + /** + * Check for a redirect payment method on order received page or setup intent on payment methods page. + */ + public function maybe_process_upe_redirect() { + if ( $this->is_payment_methods_page() ) { + // If a payment method was added using UPE, we need to clear the cache and notify the user. + if ( $this->is_setup_intent_success_creation_redirection() ) { + wc_add_notice( __( 'Payment method successfully added.', 'woocommerce-payments' ) ); + $user = wp_get_current_user(); + $this->customer_service->clear_cached_payment_methods_for_user( $user->ID ); + } + return; + } + + if ( ! is_order_received_page() ) { + return; + } + + $payment_method = isset( $_GET['wc_payment_method'] ) ? wc_clean( wp_unslash( $_GET['wc_payment_method'] ) ) : ''; + if ( self::GATEWAY_ID !== $payment_method ) { + return; + } + + $is_nonce_valid = check_admin_referer( 'wcpay_process_redirect_order_nonce' ); + if ( ! $is_nonce_valid || empty( $_GET['wc_payment_method'] ) ) { + return; + } + + if ( ! empty( $_GET['payment_intent_client_secret'] ) ) { + $intent_id_from_request = isset( $_GET['payment_intent'] ) ? wc_clean( wp_unslash( $_GET['payment_intent'] ) ) : ''; + } elseif ( ! empty( $_GET['setup_intent_client_secret'] ) ) { + $intent_id_from_request = isset( $_GET['setup_intent'] ) ? wc_clean( wp_unslash( $_GET['setup_intent'] ) ) : ''; + } else { + return; + } + + $order_id = absint( get_query_var( 'order-received' ) ); + $order_key_from_request = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; + $save_payment_method = isset( $_GET['save_payment_method'] ) ? 'yes' === wc_clean( wp_unslash( $_GET['save_payment_method'] ) ) : false; + + if ( empty( $intent_id_from_request ) || empty( $order_id ) || empty( $order_key_from_request ) ) { + return; + } + + $order = wc_get_order( $order_id ); + + if ( ! is_a( $order, 'WC_Order' ) ) { + // the ID of non-existing order was passed in. + return; + } + + if ( $order->get_order_key() !== $order_key_from_request ) { + // Valid return url should have matching order key. + return; + } + + // Perform additional checks for non-zero-amount. For zero-amount orders, we can't compare intents because they are not attached to the order at this stage. + // Once https://github.com/Automattic/woocommerce-payments/issues/6575 is closed, this check can be applied for zero-amount orders as well. + if ( $order->get_total() > 0 && ! $this->is_proper_intent_used_with_order( $order, $intent_id_from_request ) ) { + return; + } + + $this->process_redirect_payment( $order, $intent_id_from_request, $save_payment_method ); + } + /** - * The parent method which allows to modify the child class implementation, while supporting the current design where the parent process_payment method is called from the child class. - * Mandate must be shown and acknowledged under certain conditions for Stripe Link and SEPA. - * Since WC_Payment_Gateway_WCPay represents card payment, which does not require mandate, we return false. + * Processes redirect payments. + * + * @param WC_Order $order The order being processed. + * @param string $intent_id The Stripe setup/payment intent ID for the order payment. + * @param bool $save_payment_method Boolean representing whether payment method for order should be saved. * - * @return boolean False since card payment does not require mandate. + * @throws Process_Payment_Exception When the payment intent has an error. */ - protected function is_mandate_data_required() { - return false; + public function process_redirect_payment( $order, $intent_id, $save_payment_method ) { + try { + $order_id = $order->get_id(); + if ( $order->has_status( + [ + Order_Status::PROCESSING, + Order_Status::COMPLETED, + Order_Status::ON_HOLD, + ] + ) ) { + return; + } + + Logger::log( "Begin processing UPE redirect payment for order {$order_id} for the amount of {$order->get_total()}" ); + + // Get user/customer for order. + list( $user, $customer_id ) = $this->manage_customer_details_for_order( $order ); + + $payment_needed = 0 < $order->get_total(); + + // Get payment intent to confirm status. + if ( $payment_needed ) { + $request = Get_Intention::create( $intent_id ); + $request->set_hook_args( $order ); + /** @var WC_Payments_API_Payment_Intention $intent */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort + $intent = $request->send(); + $client_secret = $intent->get_client_secret(); + $status = $intent->get_status(); + $charge = $intent->get_charge(); + $charge_id = $charge ? $charge->get_id() : null; + $currency = $intent->get_currency(); + $payment_method_id = $intent->get_payment_method_id(); + $payment_method_details = $charge ? $charge->get_payment_method_details() : []; + $payment_method_type = $this->get_payment_method_type_from_payment_details( $payment_method_details ); + $error = $intent->get_last_payment_error(); + + // This check applies to payment intents only due to two reasons: + // (1) metadata is missed for setup intents. See https://github.com/Automattic/woocommerce-payments/issues/6575. + // (2) most issues so far affect only payment intents. + $intent_metadata = is_array( $intent->get_metadata() ) ? $intent->get_metadata() : []; + $this->validate_order_id_received_vs_intent_meta_order_id( $order, $intent_metadata ); + } else { + $request = Get_Setup_Intention::create( $intent_id ); + /** @var WC_Payments_API_Setup_Intention $intent */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort + $intent = $request->send(); + $client_secret = $intent->get_client_secret(); + $status = $intent->get_status(); + $charge_id = ''; + $charge = null; + $currency = $order->get_currency(); + $payment_method_id = $intent->get_payment_method_id(); + $payment_method_details = false; + $payment_method_type = $intent->get_payment_method_type(); + $error = $intent->get_last_setup_error(); + } + + if ( ! empty( $error ) ) { + Logger::log( 'Error when processing payment: ' . $error['message'] ); + throw new Process_Payment_Exception( + __( "We're not able to process this payment. Please try again later.", 'woocommerce-payments' ), + 'upe_payment_intent_error' + ); + } else { + $payment_method = $this->get_selected_payment_method( $payment_method_type ); + if ( ! $payment_method ) { + return; + } + + if ( $save_payment_method && $payment_method->is_reusable() ) { + try { + $token = $payment_method->get_payment_token_for_user( $user, $payment_method_id ); + $this->add_token_to_order( $order, $token ); + } catch ( Exception $e ) { + // If saving the token fails, log the error message but catch the error to avoid crashing the checkout flow. + Logger::log( 'Error when saving payment method: ' . $e->getMessage() ); + } + } + + $this->order_service->attach_intent_info_to_order( $order, $intent_id, $status, $payment_method_id, $customer_id, $charge_id, $currency ); + $this->attach_exchange_info_to_order( $order, $charge_id ); + if ( Intent_Status::SUCCEEDED === $status ) { + $this->duplicate_payment_prevention_service->remove_session_processing_order( $order->get_id() ); + } + $this->order_service->update_order_status_from_intent( $order, $intent ); + $this->set_payment_method_title_for_order( $order, $payment_method_type, $payment_method_details ); + $this->order_service->attach_transaction_fee_to_order( $order, $charge ); + + if ( Intent_Status::REQUIRES_ACTION === $status ) { + // I don't think this case should be possible, but just in case... + $next_action = $intent->get_next_action(); + if ( isset( $next_action['type'] ) && 'redirect_to_url' === $next_action['type'] && ! empty( $next_action['redirect_to_url']['url'] ) ) { + wp_safe_redirect( $next_action['redirect_to_url']['url'] ); + exit; + } else { + $redirect_url = sprintf( + '#wcpay-confirm-%s:%s:%s:%s', + $payment_needed ? 'pi' : 'si', + $order_id, + WC_Payments_Utils::encrypt_client_secret( $this->account->get_stripe_account_id(), $client_secret ), + wp_create_nonce( 'wcpay_update_order_status_nonce' ) + ); + wp_safe_redirect( $redirect_url ); + exit; + } + } + } + } catch ( Exception $e ) { + Logger::log( 'Error: ' . $e->getMessage() ); + + $is_order_id_mismatched_exception = + is_a( $e, Process_Payment_Exception::class ) + && self::PROCESS_REDIRECT_ORDER_MISMATCH_ERROR_CODE === $e->get_error_code(); + + // If the order ID mismatched exception is thrown, do not mark the order as failed. + // Because the outcome of the payment intent is for another order, not for the order processed here. + if ( ! $is_order_id_mismatched_exception ) { + // Confirm our needed variables are set before using them due to there could be a server issue during the get_intent process. + $status = $status ?? null; + $charge_id = $charge_id ?? null; + + /* translators: localized exception message */ + $message = sprintf( __( 'UPE payment failed: %s', 'woocommerce-payments' ), $e->getMessage() ); + $this->order_service->mark_payment_failed( $order, $intent_id, $status, $charge_id, $message ); + } + + wc_add_notice( WC_Payments_Utils::get_filtered_error_message( $e ), 'error' ); + + $redirect_url = wc_get_checkout_url(); + if ( $is_order_id_mismatched_exception ) { + $redirect_url = add_query_arg( self::PROCESS_REDIRECT_ORDER_MISMATCH_ERROR_CODE, 'yes', $redirect_url ); + } + wp_safe_redirect( $redirect_url ); + exit; + } + } + + /** + * Mandate must be shown and acknowledged by customer before deferred intent UPE payment can be processed. + * This applies to SEPA and Link payment methods. + * https://stripe.com/docs/payments/finalize-payments-on-the-server + * + * @return boolean True if mandate must be shown and acknowledged by customer before deferred intent UPE payment can be processed, false otherwise. + */ + public function is_mandate_data_required() { + $is_stripe_link_enabled = Payment_Method::CARD === $this->get_selected_stripe_payment_type_id() && in_array( Payment_Method::LINK, $this->get_upe_enabled_payment_method_ids(), true ); + $is_sepa_debit_payment = Payment_Method::SEPA === $this->get_selected_stripe_payment_type_id(); + + return $is_stripe_link_enabled || $is_sepa_debit_payment; } /** @@ -1621,15 +2034,26 @@ private function get_mandate_data() { } /** - * By default this function does not do anything. But it can be overriden by child classes. - * It is used to set a formatted readable payment method title for order, + * Set formatted readable payment method title for order, * using payment method details from accompanying charge. * - * @param WC_Order $order WC Order being processed. + * @param \WC_Order $order WC Order being processed. * @param string $payment_method_type Stripe payment method key. * @param array|bool $payment_method_details Array of payment method details from charge or false. */ public function set_payment_method_title_for_order( $order, $payment_method_type, $payment_method_details ) { + $payment_method = $this->get_selected_payment_method( $payment_method_type ); + if ( ! $payment_method ) { + return; + } + + $payment_method_title = $payment_method->get_title( $payment_method_details ); + + $payment_gateway = in_array( $payment_method->get_id(), [ Payment_Method::CARD, Payment_Method::LINK ], true ) ? self::GATEWAY_ID : self::GATEWAY_ID . '_' . $payment_method_type; + + $order->set_payment_method( $payment_gateway ); + $order->set_payment_method_title( $payment_method_title ); + $order->save(); } /** @@ -3434,25 +3858,71 @@ public function update_cached_account_data( $property, $data ) { /** * Returns the Stripe payment type of the selected payment method. * - * @return string[] + * @return string */ public function get_selected_stripe_payment_type_id() { - return [ - 'card', - ]; + return $this->stripe_id; } + /** * Returns the list of enabled payment method types that will function with the current checkout. * * @param string $order_id optional Order ID. * @param bool $force_currency_check optional Whether the currency check is required even if is_admin(). + * * @return string[] */ public function get_payment_method_ids_enabled_at_checkout( $order_id = null, $force_currency_check = false ) { - return [ - 'card', - ]; + $automatic_capture = empty( $this->get_option( 'manual_capture' ) ) || $this->get_option( 'manual_capture' ) === 'no'; + if ( $automatic_capture ) { + $upe_enabled_payment_methods = $this->get_upe_enabled_payment_method_ids(); + } else { + $upe_enabled_payment_methods = array_intersect( $this->get_upe_enabled_payment_method_ids(), [ Payment_Method::CARD, Payment_Method::LINK ] ); + } + if ( is_wc_endpoint_url( 'order-pay' ) ) { + $force_currency_check = true; + } + + $enabled_payment_methods = []; + $active_payment_methods = $this->get_upe_enabled_payment_method_statuses(); + + foreach ( $upe_enabled_payment_methods as $payment_method_id ) { + $payment_method_capability_key = $this->payment_method_capability_key_map[ $payment_method_id ] ?? 'undefined_capability_key'; + if ( isset( $this->payment_methods[ $payment_method_id ] ) ) { + // When creating a payment intent, we need to ensure the currency is matching + // with the payment methods which are sent with the payment intent request, otherwise + // Stripe returns an error. + + // force_currency_check = 0 is_admin = 0 currency_is_checked = 1. + // force_currency_check = 0 is_admin = 1 currency_is_checked = 0. + // force_currency_check = 1 is_admin = 0 currency_is_checked = 1. + // force_currency_check = 1 is_admin = 1 currency_is_checked = 1. + + $skip_currency_check = ! $force_currency_check && is_admin(); + $processing_payment_method = $this->payment_methods[ $payment_method_id ]; + if ( $processing_payment_method->is_enabled_at_checkout( $this->get_account_country() ) && ( $skip_currency_check || $processing_payment_method->is_currency_valid( $this->get_account_domestic_currency(), $order_id ) ) ) { + $status = $active_payment_methods[ $payment_method_capability_key ]['status'] ?? null; + if ( 'active' === $status ) { + $enabled_payment_methods[] = $payment_method_id; + } + } + } + } + + // if credit card payment method is not enabled, we don't use stripe link. + if ( + ! in_array( CC_Payment_Method::PAYMENT_METHOD_STRIPE_ID, $enabled_payment_methods, true ) && + in_array( Link_Payment_Method::PAYMENT_METHOD_STRIPE_ID, $enabled_payment_methods, true ) ) { + $enabled_payment_methods = array_filter( + $enabled_payment_methods, + static function( $method ) { + return Link_Payment_Method::PAYMENT_METHOD_STRIPE_ID !== $method; + } + ); + } + + return $enabled_payment_methods; } /** @@ -3463,7 +3933,10 @@ public function get_payment_method_ids_enabled_at_checkout( $order_id = null, $f * @return string[] */ public function get_payment_method_ids_enabled_at_checkout_filtered_by_fees( $order_id = null, $force_currency_check = false ) { - return $this->get_payment_method_ids_enabled_at_checkout( $order_id, $force_currency_check ); + $enabled_payment_methods = $this->get_payment_method_ids_enabled_at_checkout( $order_id, $force_currency_check ); + $methods_with_fees = array_keys( $this->account->get_fees() ); + + return array_values( array_intersect( $enabled_payment_methods, $methods_with_fees ) ); } /** @@ -3473,11 +3946,79 @@ public function get_payment_method_ids_enabled_at_checkout_filtered_by_fees( $or * @return string[] */ public function get_upe_available_payment_methods() { - return [ - 'card', - ]; + $available_methods = [ 'card' ]; + + $available_methods[] = Becs_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + $available_methods[] = Bancontact_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + $available_methods[] = Eps_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + $available_methods[] = Giropay_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + $available_methods[] = Ideal_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + $available_methods[] = Sofort_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + $available_methods[] = Sepa_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + $available_methods[] = P24_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + $available_methods[] = Link_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + $available_methods[] = Affirm_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + $available_methods[] = Afterpay_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + $available_methods[] = Klarna_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + + $available_methods = array_values( + apply_filters( + 'wcpay_upe_available_payment_methods', + $available_methods + ) + ); + + $methods_with_fees = array_keys( $this->account->get_fees() ); + + return array_values( array_intersect( $available_methods, $methods_with_fees ) ); + } + + /** + * Handle AJAX request for saving UPE appearance value to transient. + * + * @throws Exception - If nonce or setup intent is invalid. + */ + public function save_upe_appearance_ajax() { + try { + $is_nonce_valid = check_ajax_referer( 'wcpay_save_upe_appearance_nonce', false, false ); + if ( ! $is_nonce_valid ) { + throw new Exception( + __( 'Unable to update UPE appearance values at this time.', 'woocommerce-payments' ) + ); + } + + $is_blocks_checkout = isset( $_POST['is_blocks_checkout'] ) ? rest_sanitize_boolean( wc_clean( wp_unslash( $_POST['is_blocks_checkout'] ) ) ) : false; + $appearance = isset( $_POST['appearance'] ) ? json_decode( wc_clean( wp_unslash( $_POST['appearance'] ) ) ) : null; + + $appearance_transient = $is_blocks_checkout ? self::WC_BLOCKS_UPE_APPEARANCE_TRANSIENT : self::UPE_APPEARANCE_TRANSIENT; + + if ( null !== $appearance ) { + set_transient( $appearance_transient, $appearance, DAY_IN_SECONDS ); + } + + wp_send_json_success( $appearance, 200 ); + } catch ( Exception $e ) { + // Send back error so it can be displayed to the customer. + wp_send_json_error( + [ + 'error' => [ + 'message' => WC_Payments_Utils::get_filtered_error_message( $e ), + ], + ], + WC_Payments_Utils::get_filtered_error_status_code( $e ), + ); + } } + /** + * Clear the saved UPE appearance transient value. + */ + public function clear_upe_appearance_transient() { + delete_transient( self::UPE_APPEARANCE_TRANSIENT ); + delete_transient( self::WC_BLOCKS_UPE_APPEARANCE_TRANSIENT ); + } + + /** * Text provided to users during onboarding setup. * @@ -3513,6 +4054,81 @@ protected function should_bump_rate_limiter( string $error_code ): bool { return in_array( $error_code, [ 'card_declined', 'incorrect_number', 'incorrect_cvc' ], true ); } + /** + * Returns boolean for whether payment gateway supports saved payments. + * + * @return bool True, if gateway supports saved payments. False, otherwise. + */ + public function should_support_saved_payments() { + return $this->is_enabled_for_saved_payments( $this->stripe_id ); + } + + /** + * Returns true when viewing payment methods page. + * + * @return bool + */ + private function is_payment_methods_page() { + global $wp; + + $page_id = wc_get_page_id( 'myaccount' ); + + return ( $page_id && is_page( $page_id ) && ( isset( $wp->query_vars['payment-methods'] ) ) ); + } + + /** + * Verifies that the proper intent is used to process the order. + * + * @param WC_Order $order The order object based on the order_id received from the request. + * @param string $intent_id_from_request The intent ID received from the request. + * + * @return bool True if the proper intent is used to process the order, false otherwise. + */ + public function is_proper_intent_used_with_order( $order, $intent_id_from_request ) { + $intent_id_attached_to_order = $this->order_service->get_intent_id_for_order( $order ); + if ( ! hash_equals( $intent_id_attached_to_order, $intent_id_from_request ) ) { + Logger::error( + sprintf( + 'Intent ID mismatch. Received in request: %1$s. Attached to order: %2$s. Order ID: %3$d', + $intent_id_from_request, + $intent_id_attached_to_order, + $order->get_id() + ) + ); + return false; + } + return true; + } + + /** + * True if the request contains the values that indicates a redirection after a successful setup intent creation. + * + * @return bool + */ + public function is_setup_intent_success_creation_redirection() { + return ! empty( $_GET['setup_intent_client_secret'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ! empty( $_GET['setup_intent'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ! empty( $_GET['redirect_status'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended + 'succeeded' === $_GET['redirect_status']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + /** + * Function to be used with array_filter + * to filter UPE payment methods that support saved payments + * + * @param string $payment_method_id Stripe payment method. + * + * @return bool + */ + public function is_enabled_for_saved_payments( $payment_method_id ) { + $payment_method = $this->get_selected_payment_method( $payment_method_id ); + if ( ! $payment_method ) { + return false; + } + return $payment_method->is_reusable() + && ( is_admin() || $payment_method->is_currency_valid( $this->get_account_domestic_currency() ) ); + } + /** * Move the email field to the top of the Checkout page. * @@ -3557,6 +4173,44 @@ public function append_stripelink_button( $field, $key, $args, $value ) { return $field; } + /** + * Get selected UPE payment methods. + * + * @param string $selected_upe_payment_type Selected payment methods. + * @param array $enabled_payment_methods Enabled payment methods. + * + * @return array + */ + protected function get_selected_upe_payment_methods( string $selected_upe_payment_type, array $enabled_payment_methods ) { + $payment_methods = []; + if ( '' !== $selected_upe_payment_type ) { + // Only update the payment_method_types if we have a reference to the payment type the customer selected. + $payment_methods[] = $selected_upe_payment_type; + + if ( CC_Payment_Method::PAYMENT_METHOD_STRIPE_ID === $selected_upe_payment_type ) { + $is_link_enabled = in_array( + Link_Payment_Method::PAYMENT_METHOD_STRIPE_ID, + $enabled_payment_methods, + true + ); + if ( $is_link_enabled ) { + $payment_methods[] = Link_Payment_Method::PAYMENT_METHOD_STRIPE_ID; + } + } + } + return $payment_methods; + } + + /** + * Gets UPE_Payment_Method instance from ID. + * + * @param string $payment_method_type Stripe payment method type ID. + * @return UPE_Payment_Method|false UPE payment method instance. + */ + public function get_selected_payment_method( $payment_method_type ) { + return WC_Payments::get_payment_method_by_id( $payment_method_type ); + } + /** * Returns the URL of the configuration screen for this gateway, for use in internal links. * @@ -3566,6 +4220,73 @@ public static function get_settings_url() { return WC_Payments_Admin_Settings::get_settings_url(); } + /** + * Return the payment method type from the payment method details. + * + * @param array $payment_method_details Payment method details. + * @return string|null Payment method type or nothing. + */ + private function get_payment_method_type_from_payment_details( $payment_method_details ) { + return $payment_method_details['type'] ?? null; + } + + + /** + * This function wraps WC_Payments::get_payment_method_map, useful for unit testing. + * + * @return array Array of UPE_Payment_Method instances. + */ + public function wc_payments_get_payment_method_map() { + return WC_Payments::get_payment_method_map(); + } + + /** + * Returns the payment methods for this gateway. + * + * @return array|UPE_Payment_Method[] + */ + public function get_payment_methods() { + return $this->payment_methods; + } + + /** + * Returns the UPE payment method for the gateway. + * + * @return UPE_Payment_Method + */ + public function get_payment_method() { + return $this->payment_method; + } + + /** + * Returns Stripe payment method type ID. + * + * @return string + */ + public function get_stripe_id() { + return $this->stripe_id; + } + + /** + * This function wraps WC_Payments::get_payment_gateway_by_id, useful for unit testing. + * + * @param string $payment_method_id Stripe payment method type ID. + * @return false|WC_Payment_Gateway_WCPay Matching UPE Payment Gateway instance. + */ + public function wc_payments_get_payment_gateway_by_id( $payment_method_id ) { + return WC_Payments::get_payment_gateway_by_id( $payment_method_id ); + } + + /** + * This function wraps WC_Payments::get_payment_method_by_id, useful for unit testing. + * + * @param string $payment_method_id Stripe payment method type ID. + * @return false|UPE_Payment_Method Matching UPE Payment Method instance. + */ + public function wc_payments_get_payment_method_by_id( $payment_method_id ) { + return WC_Payments::get_payment_method_by_id( $payment_method_id ); + } + /** * Get the right method description if WooPay is eligible. * @@ -3714,10 +4435,44 @@ private function upe_needs_redirection( $payment_methods ) { return 1 === count( $payment_methods ) && 'card' !== $payment_methods[0]; } + /** + * Handles the shipping requirement for Afterpay payments. + * + * This method extracts the shipping and billing data from the order and sets the appropriate + * shipping data for the Afterpay payment request. If neither shipping nor billing data is valid + * for shipping, an exception is thrown. + * + * @param WC_Order $order The order object containing shipping and billing information. + * @param Create_And_Confirm_Intention $request The Afterpay payment request object to set shipping data on. + * + * @throws Invalid_Address_Exception If neither shipping nor billing address is valid for Afterpay payments. + * @return void + */ + private function handle_afterpay_shipping_requirement( WC_Order $order, Create_And_Confirm_Intention $request ): void { + $check_if_usable = function( array $address ): bool { + return $address['country'] && $address['state'] && $address['city'] && $address['postal_code'] && $address['line1']; + }; + + $shipping_data = $this->order_service->get_shipping_data_from_order( $order ); + if ( $check_if_usable( $shipping_data['address'] ) ) { + $request->set_shipping( $shipping_data ); + return; + } + + $billing_data = $this->order_service->get_billing_data_from_order( $order ); + if ( $check_if_usable( $billing_data['address'] ) ) { + $request->set_shipping( $billing_data ); + return; + } + + throw new Invalid_Address_Exception( __( 'A valid shipping address is required for Afterpay payments.', 'woocommerce-payments' ) ); + } + + /** * Modifies the create intent parameters when processing a payment. * - * Currently used by child UPE_Payment_Gateway to add required shipping information for Afterpay. + * If the selected Stripe payment type is AFTERPAY, it updates the shipping data in the request. * * @param Create_And_Confirm_Intention $request The request object for creating and confirming intention. * @param Payment_Information $payment_information The payment information object. @@ -3725,7 +4480,9 @@ private function upe_needs_redirection( $payment_methods ) { * * @return void */ - protected function modify_create_intent_parameters_when_processing_payment( Create_And_Confirm_Intention $request, Payment_Information $payment_information, WC_Order $order ) { - // Do nothing. + protected function modify_create_intent_parameters_when_processing_payment( Create_And_Confirm_Intention $request, Payment_Information $payment_information, WC_Order $order ): void { + if ( Payment_Method::AFTERPAY === $this->get_selected_stripe_payment_type_id() ) { + $this->handle_afterpay_shipping_requirement( $order, $request ); + } } } diff --git a/includes/class-wc-payments-checkout.php b/includes/class-wc-payments-checkout.php index 89f8654f716..4826252e7ec 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -19,7 +19,7 @@ use WC_Payments_Features; use WCPay\Constants\Payment_Method; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; -use WCPay\Payment_Methods\UPE_Payment_Gateway; +use WC_Payment_Gateway_WCPay; use WCPay\WooPay\WooPay_Utilities; use WCPay\Payment_Methods\UPE_Payment_Method; @@ -32,7 +32,7 @@ class WC_Payments_Checkout { /** * WC Payments Gateway. * - * @var UPE_Payment_Gateway + * @var WC_Payment_Gateway_WCPay */ protected $gateway; @@ -67,14 +67,14 @@ class WC_Payments_Checkout { /** * Construct. * - * @param UPE_Payment_Gateway $gateway WC Payment Gateway. + * @param WC_Payment_Gateway_WCPay $gateway WC Payment Gateway. * @param WooPay_Utilities $woopay_util WooPay Utilities. * @param WC_Payments_Account $account WC Payments Account. * @param WC_Payments_Customer_Service $customer_service WC Payments Customer Service. * @param WC_Payments_Fraud_Service $fraud_service Fraud service instance. */ public function __construct( - UPE_Payment_Gateway $gateway, + WC_Payment_Gateway_WCPay $gateway, WooPay_Utilities $woopay_util, WC_Payments_Account $account, WC_Payments_Customer_Service $customer_service, @@ -170,7 +170,7 @@ public function get_payment_fields_js_config() { WC_Checkout::instance(); // The registered card gateway is more reliable than $this->gateway, but if it isn't available for any reason, fall back to the gateway provided to this checkout class. - $gateway = WC_Payments::get_registered_card_gateway() ?? $this->gateway; + $gateway = WC_Payments::get_gateway() ?? $this->gateway; $js_config = [ 'publishableKey' => $this->account->get_publishable_key( WC_Payments::mode()->is_test() ), @@ -214,12 +214,12 @@ public function get_payment_fields_js_config() { $payment_fields['accountDescriptor'] = $this->gateway->get_account_statement_descriptor(); $payment_fields['addPaymentReturnURL'] = wc_get_account_endpoint_url( 'payment-methods' ); - $payment_fields['gatewayId'] = UPE_Payment_Gateway::GATEWAY_ID; + $payment_fields['gatewayId'] = WC_Payment_Gateway_WCPay::GATEWAY_ID; $payment_fields['isCheckout'] = is_checkout(); $payment_fields['paymentMethodsConfig'] = $this->get_enabled_payment_method_config(); $payment_fields['testMode'] = WC_Payments::mode()->is_test(); - $payment_fields['upeAppearance'] = get_transient( UPE_Payment_Gateway::UPE_APPEARANCE_TRANSIENT ); - $payment_fields['wcBlocksUPEAppearance'] = get_transient( UPE_Payment_Gateway::WC_BLOCKS_UPE_APPEARANCE_TRANSIENT ); + $payment_fields['upeAppearance'] = get_transient( WC_Payment_Gateway_WCPay::UPE_APPEARANCE_TRANSIENT ); + $payment_fields['wcBlocksUPEAppearance'] = get_transient( WC_Payment_Gateway_WCPay::WC_BLOCKS_UPE_APPEARANCE_TRANSIENT ); $payment_fields['cartContainsSubscription'] = $this->gateway->is_subscription_item_in_cart(); $payment_fields['currency'] = get_woocommerce_currency(); $cart_total = ( WC()->cart ? WC()->cart->get_total( '' ) : 0 ); @@ -260,7 +260,7 @@ public function get_payment_fields_js_config() { $payment_fields['orderReturnURL'] = esc_url_raw( add_query_arg( [ - 'wc_payment_method' => UPE_Payment_Gateway::GATEWAY_ID, + 'wc_payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID, '_wpnonce' => wp_create_nonce( 'wcpay_process_redirect_order_nonce' ), ], $this->gateway->get_return_url( $order ) diff --git a/includes/class-wc-payments-payment-method-messaging-element.php b/includes/class-wc-payments-payment-method-messaging-element.php index 6fe6006188f..3d951d13cc5 100644 --- a/includes/class-wc-payments-payment-method-messaging-element.php +++ b/includes/class-wc-payments-payment-method-messaging-element.php @@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } -use WCPay\Payment_Methods\UPE_Payment_Gateway; +use WC_Payment_Gateway_WCPay; /** * WC_Payments_Payment_Method_Messaging_Element class. */ @@ -24,15 +24,15 @@ class WC_Payments_Payment_Method_Messaging_Element { /** * WC_Payments_Gateway instance to get information about the enabled payment methods. * - * @var UPE_Payment_Gateway + * @var WC_Payment_Gateway_WCPay */ private $gateway; /** * WC_Payments_Payment_Method_Messaging_Element constructor * - * @param WC_Payments_Account $account Account instance. - * @param UPE_Payment_Gateway $gateway Gateway instance. + * @param WC_Payments_Account $account Account instance. + * @param WC_Payment_Gateway_WCPay $gateway Gateway instance. * @return void */ public function __construct( WC_Payments_Account $account, $gateway ) { diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index f171f9b07d6..0677eaa5a1b 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -12,7 +12,6 @@ use WCPay\Core\Mode; use WCPay\Core\Server\Request; use WCPay\Migrations\Allowed_Payment_Request_Button_Types_Update; -use WCPay\Payment_Methods\CC_Payment_Gateway; use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Payment_Methods\Bancontact_Payment_Method; use WCPay\Payment_Methods\Becs_Payment_Method; @@ -21,7 +20,6 @@ use WCPay\Payment_Methods\P24_Payment_Method; use WCPay\Payment_Methods\Sepa_Payment_Method; use WCPay\Payment_Methods\Sofort_Payment_Method; -use WCPay\Payment_Methods\UPE_Payment_Gateway; use WCPay\Payment_Methods\Ideal_Payment_Method; use WCPay\Payment_Methods\Eps_Payment_Method; use WCPay\Payment_Methods\UPE_Payment_Method; @@ -51,24 +49,9 @@ class WC_Payments { /** * Main payment gateway controller instance, created in init function. * - * @var WC_Payment_Gateway_WCPay|UPE_Payment_Gateway - */ - private static $card_gateway; - - /** - * Instance of WC_Payment_Gateway_WCPay to register as payment gateway. - * * @var WC_Payment_Gateway_WCPay */ - private static $legacy_card_gateway; - - /** - * Copy of either $card_gateway or $legacy_card_gateway, - * depending on which gateway is registered as main CC gateway. - * - * @var WC_Payment_Gateway_WCPay|UPE_Payment_Gateway - */ - private static $registered_card_gateway; + private static $card_gateway; /** * Instance of WC_Payments_API_Client, created in init function. @@ -211,25 +194,18 @@ class WC_Payments { private static $webhook_processing_service; /** - * Maps all availabled Stripe payment method IDs to UPE Payment Method instances. + * Maps all availabled Stripe payment method IDs to Payment Method instances. * * @var array */ - private static $upe_payment_method_map = []; + private static $payment_method_map = []; /** - * Maps all availabled Stripe payment method IDs to UPE Payment Gateway instances. + * Maps all availabled Stripe payment method IDs to Payment Gateway instances. * * @var array */ - private static $upe_payment_gateway_map = []; - - /** - * Map to store all the available split upe checkouts - * - * @var array - */ - private static $upe_checkout_map = []; + private static $payment_gateway_map = []; /** * Instance of WC_Payments_Webhook_Reliability_Service, created in init function @@ -405,7 +381,6 @@ public static function init() { include_once __DIR__ . '/class-wc-payment-gateway-wcpay.php'; include_once __DIR__ . '/class-wc-payments-checkout.php'; include_once __DIR__ . '/payment-methods/class-cc-payment-gateway.php'; - include_once __DIR__ . '/payment-methods/class-upe-payment-gateway.php'; include_once __DIR__ . '/payment-methods/class-upe-payment-method.php'; include_once __DIR__ . '/payment-methods/class-cc-payment-method.php'; include_once __DIR__ . '/payment-methods/class-bancontact-payment-method.php'; @@ -523,8 +498,6 @@ public static function init() { self::$incentives_service->init_hooks(); self::$compatibility_service->init_hooks(); - self::$legacy_card_gateway = new CC_Payment_Gateway( self::$api_client, self::$account, self::$customer_service, self::$token_service, self::$action_scheduler_service, self::$failed_transaction_rate_limiter, self::$order_service, self::$duplicate_payment_prevention_service, self::$localization_service, self::$fraud_service ); - $payment_method_classes = [ CC_Payment_Method::class, Bancontact_Payment_Method::class, @@ -547,16 +520,16 @@ public static function init() { $payment_methods[ $payment_method->get_id() ] = $payment_method; } foreach ( $payment_methods as $payment_method ) { - self::$upe_payment_method_map[ $payment_method->get_id() ] = $payment_method; + self::$payment_method_map[ $payment_method->get_id() ] = $payment_method; - $split_gateway = new UPE_Payment_Gateway( self::$api_client, self::$account, self::$customer_service, self::$token_service, self::$action_scheduler_service, $payment_method, $payment_methods, self::$failed_transaction_rate_limiter, self::$order_service, self::$duplicate_payment_prevention_service, self::$localization_service, self::$fraud_service ); + $split_gateway = new WC_Payment_Gateway_WCPay( self::$api_client, self::$account, self::$customer_service, self::$token_service, self::$action_scheduler_service, $payment_method, $payment_methods, self::$failed_transaction_rate_limiter, self::$order_service, self::$duplicate_payment_prevention_service, self::$localization_service, self::$fraud_service ); // Card gateway hooks are registered once below. if ( 'card' !== $payment_method->get_id() ) { $split_gateway->init_hooks(); } - self::$upe_payment_gateway_map[ $payment_method->get_id() ] = $split_gateway; + self::$payment_gateway_map[ $payment_method->get_id() ] = $split_gateway; } self::$card_gateway = self::get_payment_gateway_by_id( 'card' ); @@ -765,23 +738,21 @@ public static function register_gateway( $gateways ) { self::get_gateway()->update_option( 'upe_enabled_payment_method_ids', $payment_methods ); } - self::$registered_card_gateway = self::$card_gateway; - - $gateways[] = self::$registered_card_gateway; - $all_upe_gateways = []; + $gateways[] = self::$card_gateway; + $all_gateways = []; $reusable_methods = []; foreach ( $payment_methods as $payment_method_id ) { if ( 'card' === $payment_method_id || 'link' === $payment_method_id ) { continue; } - $upe_gateway = self::get_payment_gateway_by_id( $payment_method_id ); - $upe_payment_method = self::get_payment_method_by_id( $payment_method_id ); + $gateway = self::get_payment_gateway_by_id( $payment_method_id ); + $payment_method = self::get_payment_method_by_id( $payment_method_id ); - if ( $upe_payment_method->is_reusable() ) { - $reusable_methods[] = $upe_gateway; + if ( $payment_method->is_reusable() ) { + $reusable_methods[] = $gateway; } - $all_upe_gateways[] = $upe_gateway; + $all_gateways[] = $gateway; } @@ -789,25 +760,7 @@ public static function register_gateway( $gateways ) { return array_merge( $gateways, $reusable_methods ); } - return array_merge( $gateways, $all_upe_gateways ); - } - - /** - * Returns main CC gateway registered for WCPay. - * - * @return WC_Payment_Gateway_WCPay|UPE_Payment_Gateway - */ - public static function get_registered_card_gateway() { - return self::$registered_card_gateway; - } - - /** - * Sets registered card gateway instance. - * - * @param WC_Payment_Gateway_WCPay|UPE_Payment_Gateway $gateway Gateway instance. - */ - public static function set_registered_card_gateway( $gateway ) { - self::$registered_card_gateway = $gateway; + return array_merge( $gateways, $all_gateways ); } /** @@ -816,7 +769,7 @@ public static function set_registered_card_gateway( $gateway ) { * Remove all WCPay gateways except CC one. */ public static function hide_gateways_on_settings_page() { - $default_gateway = self::get_registered_card_gateway(); + $default_gateway = self::get_gateway(); foreach ( WC()->payment_gateways->payment_gateways as $index => $payment_gateway ) { if ( $payment_gateway instanceof WC_Payment_Gateway_WCPay && $payment_gateway !== $default_gateway ) { unset( WC()->payment_gateways->payment_gateways[ $index ] ); @@ -1153,26 +1106,26 @@ public static function register_script_with_dependencies( string $handler, strin * Returns payment method instance by Stripe ID. * * @param string $payment_method_id Stripe payment method type ID. - * @return false|UPE_Payment_Method Matching UPE Payment Method instance. + * @return false|UPE_Payment_Method Matching Payment Method instance. */ public static function get_payment_method_by_id( $payment_method_id ) { - if ( ! isset( self::$upe_payment_method_map[ $payment_method_id ] ) ) { + if ( ! isset( self::$payment_method_map[ $payment_method_id ] ) ) { return false; } - return self::$upe_payment_method_map[ $payment_method_id ]; + return self::$payment_method_map[ $payment_method_id ]; } /** * Returns payment gateway instance by Stripe ID. * * @param string $payment_method_id Stripe payment method type ID. - * @return false|UPE_Payment_Gateway Matching UPE Payment Gateway instance. + * @return false|WC_Payment_Gateway_WCPay Matching Payment Gateway instance. */ public static function get_payment_gateway_by_id( $payment_method_id ) { - if ( ! isset( self::$upe_payment_gateway_map[ $payment_method_id ] ) ) { + if ( ! isset( self::$payment_gateway_map[ $payment_method_id ] ) ) { return false; } - return self::$upe_payment_gateway_map[ $payment_method_id ]; + return self::$payment_gateway_map[ $payment_method_id ]; } /** @@ -1181,13 +1134,13 @@ public static function get_payment_gateway_by_id( $payment_method_id ) { * @return array */ public static function get_payment_method_map() { - return self::$upe_payment_method_map; + return self::$payment_method_map; } /** * Returns the WC_Payment_Gateway_WCPay instance * - * @return WC_Payment_Gateway_WCPay|UPE_Payment_Gateway gateway instance + * @return WC_Payment_Gateway_WCPay gateway instance */ public static function get_gateway() { return self::$card_gateway; @@ -1223,7 +1176,7 @@ public static function set_database_cache( Database_Cache $database_cache ) { /** * Sets the card gateway instance. * - * @param WC_Payment_Gateway_WCPay|UPE_Payment_Gateway $gateway The card gateway instance.. + * @param WC_Payment_Gateway_WCPay $gateway The card gateway instance.. */ public static function set_gateway( $gateway ) { self::$card_gateway = $gateway; diff --git a/includes/payment-methods/class-upe-payment-gateway.php b/includes/payment-methods/class-upe-payment-gateway.php deleted file mode 100644 index eff6d3ad1d8..00000000000 --- a/includes/payment-methods/class-upe-payment-gateway.php +++ /dev/null @@ -1,1138 +0,0 @@ -title = $payment_method->get_title(); - $this->method_description = __( 'Payments made simple, with no monthly fees - designed exclusively for WooCommerce stores. Accept credit cards, debit cards, and other popular payment methods.', 'woocommerce-payments' ); - $this->description = ''; - $this->checkout_title = __( 'Popular payment methods', 'woocommerce-payments' ); - $this->payment_methods = $payment_methods; - - $this->stripe_id = $payment_method->get_id(); - $this->payment_method = $payment_method; - $this->icon = $payment_method->get_icon(); - - if ( 'card' !== $this->stripe_id ) { - $this->id = self::GATEWAY_ID . '_' . $this->stripe_id; - $this->method_title = "WooPayments ($this->title)"; - } - } - - /** - * Displays HTML tags for WC payment gateway radio button content. - */ - public function display_gateway_html() { - ?> - - get_selected_stripe_payment_type_id() ); - do_action( 'wc_payments_add_upe_payment_fields' ); - } - - /** - * Update payment intent for completed checkout and return redirect URL for Stripe to confirm payment. - * - * @param int $order_id Order ID to process the payment for. - * - * @return array|null An array with result of payment and redirect URL, or nothing. - * @throws Exception Error processing the payment. - * @throws Order_Not_Found_Exception - */ - public function process_payment( $order_id ) { - $order = wc_get_order( $order_id ); - - if ( 20 < strlen( $order->get_billing_phone() ) ) { - throw new Process_Payment_Exception( - __( 'Invalid phone number.', 'woocommerce-payments' ), - 'invalid_phone_number' - ); - } - $payment_intent_id = isset( $_POST['wc_payment_intent_id'] ) ? wc_clean( wp_unslash( $_POST['wc_payment_intent_id'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing - $amount = $order->get_total(); - $currency = $order->get_currency(); - $converted_amount = WC_Payments_Utils::prepare_amount( $amount, $currency ); - $payment_needed = 0 < $converted_amount; - $payment_type = $this->is_payment_recurring( $order_id ) ? Payment_Type::RECURRING() : Payment_Type::SINGLE(); - $save_payment_method = $payment_type->equals( Payment_Type::RECURRING() ) || ! empty( $_POST[ 'wc-' . $this->id . '-new-payment-method' ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing - $payment_country = ! empty( $_POST['wcpay_payment_country'] ) ? wc_clean( wp_unslash( $_POST['wcpay_payment_country'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Missing - - if ( $payment_intent_id ) { - list( $user, $customer_id ) = $this->manage_customer_details_for_order( $order ); - - if ( $payment_needed ) { - // Check if session exists before instantiating Fraud_Prevention_Service. - if ( WC()->session ) { - $fraud_prevention_service = Fraud_Prevention_Service::get_instance(); - // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - if ( $fraud_prevention_service->is_enabled() && ! $fraud_prevention_service->verify_token( $_POST['wcpay-fraud-prevention-token'] ?? null ) ) { - throw new Process_Payment_Exception( - __( "We're not able to process this payment. Please refresh the page and try again.", 'woocommerce-payments' ), - 'fraud_prevention_enabled' - ); - } - } - - if ( $this->failed_transaction_rate_limiter->is_limited() ) { - // Throwing an exception instead of adding an error notice - // makes the error notice show up both in the regular and block checkout. - throw new Exception( __( 'Your payment was not processed.', 'woocommerce-payments' ) ); - } - - // Try catching the error without reaching the API. - $minimum_amount = WC_Payments_Utils::get_cached_minimum_amount( $currency ); - if ( $minimum_amount > $converted_amount ) { - $exception = new Amount_Too_Small_Exception( 'Amount too small', $minimum_amount, $currency, 400 ); - throw new Exception( WC_Payments_Utils::get_filtered_error_message( $exception ) ); - } - - $check_session_order = $this->duplicate_payment_prevention_service->check_against_session_processing_order( $order ); - if ( is_array( $check_session_order ) ) { - return $check_session_order; - } - $this->duplicate_payment_prevention_service->maybe_update_session_processing_order( $order_id ); - - $check_existing_intention = $this->duplicate_payment_prevention_service->check_payment_intent_attached_to_order_succeeded( $order ); - if ( is_array( $check_existing_intention ) ) { - return $check_existing_intention; - } - - // @toDo: This is now not used? - $additional_api_parameters = $this->get_mandate_params_for_order( $order ); - - try { - $payment_methods = $this->get_selected_upe_payment_methods( $this->stripe_id, $this->get_payment_method_ids_enabled_at_checkout( null, true ) ?? [] ); - - $request = Update_Intention::create( $payment_intent_id ); - $request->set_currency_code( strtolower( $currency ) ); - $request->set_amount( WC_Payments_Utils::prepare_amount( $amount, $currency ) ); - $request->set_metadata( $this->get_metadata_from_order( $order, $payment_type ) ); - $request->set_level3( $this->get_level3_data_from_order( $order ) ); - $request->set_payment_method_types( $payment_methods ); - $request->set_hook_args( $order, $payment_intent_id ); - if ( $payment_country ) { - $request->set_payment_country( $payment_country ); - } - if ( true === $save_payment_method ) { - $request->setup_future_usage(); - } - if ( $customer_id ) { - $request->set_customer( $customer_id ); - } - $payment_method_options = $this->get_mandate_params_for_order( $order ); - if ( $payment_method_options ) { - $request->setup_future_usage(); - $request->set_payment_method_options( $payment_method_options ); - } - $updated_payment_intent = $request->send(); - } catch ( Amount_Too_Small_Exception $e ) { - // This code would only be reached if the cache has already expired. - throw new Exception( WC_Payments_Utils::get_filtered_error_message( $e ) ); - } catch ( API_Exception $e ) { - if ( 'wcpay_blocked_by_fraud_rule' === $e->get_error_code() ) { - $this->order_service->mark_order_blocked_for_fraud( $order, $payment_intent_id, Intent_Status::CANCELED ); - } - throw $e; - } - - $intent_id = $updated_payment_intent->get_id(); - $intent_status = $updated_payment_intent->get_status(); - $payment_method = $updated_payment_intent->get_payment_method_id(); - $charge = $updated_payment_intent->get_charge(); - $payment_method_details = $charge ? $charge->get_payment_method_details() : []; - $payment_method_type = $this->get_payment_method_type_from_payment_details( $payment_method_details ); - $charge_id = $charge ? $charge->get_id() : null; - - /** - * Attach the intent and exchange info to the order before doing the redirect, just in case the redirect - * either does not complete properly, or the Stripe webhook which processes a successful order hits before - * the redirect completes. - */ - $this->order_service->attach_intent_info_to_order( $order, $intent_id, $intent_status, $payment_method, $customer_id, $charge_id, $currency ); - $this->attach_exchange_info_to_order( $order, $charge_id ); - $this->set_payment_method_title_for_order( $order, $payment_method_type, $payment_method_details ); - if ( Intent_Status::SUCCEEDED === $intent_status ) { - $this->duplicate_payment_prevention_service->remove_session_processing_order( $order->get_id() ); - } - $this->order_service->update_order_status_from_intent( $order, $updated_payment_intent ); - - $last_payment_error_code = $updated_payment_intent->get_last_payment_error()['code'] ?? ''; - if ( $this->should_bump_rate_limiter( $last_payment_error_code ) ) { - // UPE method gives us the error of the previous payment attempt, so we use that for the Rate Limiter. - $this->failed_transaction_rate_limiter->bump(); - } - } - } else { - return $this->parent_process_payment( $order_id ); - } - - return [ // nosemgrep: audit.php.wp.security.xss.query-arg -- The output of add_query_arg is being escaped. - 'result' => 'success', - 'payment_needed' => $payment_needed, - 'redirect_url' => wp_sanitize_redirect( - esc_url_raw( - add_query_arg( - [ - 'wc_payment_method' => self::GATEWAY_ID, - '_wpnonce' => wp_create_nonce( 'wcpay_process_redirect_order_nonce' ), - 'save_payment_method' => $save_payment_method ? 'yes' : 'no', - ], - $this->get_return_url( $order ) - ) - ) - ), - ]; - } - - /** - * Returns true when viewing payment methods page. - * - * @return bool - */ - private function is_payment_methods_page() { - global $wp; - - $page_id = wc_get_page_id( 'myaccount' ); - - return ( $page_id && is_page( $page_id ) && ( isset( $wp->query_vars['payment-methods'] ) ) ); - } - - /** - * Get selected UPE payment methods. - * - * @param string $selected_upe_payment_type Selected payment methods. - * @param array $enabled_payment_methods Enabled payment methods. - * - * @return array - */ - protected function get_selected_upe_payment_methods( string $selected_upe_payment_type, array $enabled_payment_methods ) { - $payment_methods = []; - if ( '' !== $selected_upe_payment_type ) { - // Only update the payment_method_types if we have a reference to the payment type the customer selected. - $payment_methods[] = $selected_upe_payment_type; - - if ( CC_Payment_Method::PAYMENT_METHOD_STRIPE_ID === $selected_upe_payment_type ) { - $is_link_enabled = in_array( - Link_Payment_Method::PAYMENT_METHOD_STRIPE_ID, - $enabled_payment_methods, - true - ); - if ( $is_link_enabled ) { - $payment_methods[] = Link_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - } - } - } - return $payment_methods; - } - /** - * Check for a redirect payment method on order received page or setup intent on payment methods page. - */ - public function maybe_process_upe_redirect() { - if ( $this->is_payment_methods_page() ) { - // If a payment method was added using UPE, we need to clear the cache and notify the user. - if ( $this->is_setup_intent_success_creation_redirection() ) { - wc_add_notice( __( 'Payment method successfully added.', 'woocommerce-payments' ) ); - $user = wp_get_current_user(); - $this->customer_service->clear_cached_payment_methods_for_user( $user->ID ); - } - return; - } - - if ( ! is_order_received_page() ) { - return; - } - - $payment_method = isset( $_GET['wc_payment_method'] ) ? wc_clean( wp_unslash( $_GET['wc_payment_method'] ) ) : ''; - if ( self::GATEWAY_ID !== $payment_method ) { - return; - } - - $is_nonce_valid = check_admin_referer( 'wcpay_process_redirect_order_nonce' ); - if ( ! $is_nonce_valid || empty( $_GET['wc_payment_method'] ) ) { - return; - } - - if ( ! empty( $_GET['payment_intent_client_secret'] ) ) { - $intent_id_from_request = isset( $_GET['payment_intent'] ) ? wc_clean( wp_unslash( $_GET['payment_intent'] ) ) : ''; - } elseif ( ! empty( $_GET['setup_intent_client_secret'] ) ) { - $intent_id_from_request = isset( $_GET['setup_intent'] ) ? wc_clean( wp_unslash( $_GET['setup_intent'] ) ) : ''; - } else { - return; - } - - $order_id = absint( get_query_var( 'order-received' ) ); - $order_key_from_request = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; - $save_payment_method = isset( $_GET['save_payment_method'] ) ? 'yes' === wc_clean( wp_unslash( $_GET['save_payment_method'] ) ) : false; - - if ( empty( $intent_id_from_request ) || empty( $order_id ) || empty( $order_key_from_request ) ) { - return; - } - - $order = wc_get_order( $order_id ); - - if ( ! is_a( $order, 'WC_Order' ) ) { - // the ID of non-existing order was passed in. - return; - } - - if ( $order->get_order_key() !== $order_key_from_request ) { - // Valid return url should have matching order key. - return; - } - - // Perform additional checks for non-zero-amount. For zero-amount orders, we can't compare intents because they are not attached to the order at this stage. - // Once https://github.com/Automattic/woocommerce-payments/issues/6575 is closed, this check can be applied for zero-amount orders as well. - if ( $order->get_total() > 0 && ! $this->is_proper_intent_used_with_order( $order, $intent_id_from_request ) ) { - return; - } - - $this->process_redirect_payment( $order, $intent_id_from_request, $save_payment_method ); - } - - /** - * Processes redirect payments. - * - * @param WC_Order $order The order being processed. - * @param string $intent_id The Stripe setup/payment intent ID for the order payment. - * @param bool $save_payment_method Boolean representing whether payment method for order should be saved. - * - * @throws Process_Payment_Exception When the payment intent has an error. - */ - public function process_redirect_payment( $order, $intent_id, $save_payment_method ) { - try { - $order_id = $order->get_id(); - if ( $order->has_status( - [ - Order_Status::PROCESSING, - Order_Status::COMPLETED, - Order_Status::ON_HOLD, - ] - ) ) { - return; - } - - Logger::log( "Begin processing UPE redirect payment for order {$order_id} for the amount of {$order->get_total()}" ); - - // Get user/customer for order. - list( $user, $customer_id ) = $this->manage_customer_details_for_order( $order ); - - $payment_needed = 0 < $order->get_total(); - - // Get payment intent to confirm status. - if ( $payment_needed ) { - $request = Get_Intention::create( $intent_id ); - $request->set_hook_args( $order ); - /** @var WC_Payments_API_Payment_Intention $intent */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort - $intent = $request->send(); - $client_secret = $intent->get_client_secret(); - $status = $intent->get_status(); - $charge = $intent->get_charge(); - $charge_id = $charge ? $charge->get_id() : null; - $currency = $intent->get_currency(); - $payment_method_id = $intent->get_payment_method_id(); - $payment_method_details = $charge ? $charge->get_payment_method_details() : []; - $payment_method_type = $this->get_payment_method_type_from_payment_details( $payment_method_details ); - $error = $intent->get_last_payment_error(); - - // This check applies to payment intents only due to two reasons: - // (1) metadata is missed for setup intents. See https://github.com/Automattic/woocommerce-payments/issues/6575. - // (2) most issues so far affect only payment intents. - $intent_metadata = is_array( $intent->get_metadata() ) ? $intent->get_metadata() : []; - $this->validate_order_id_received_vs_intent_meta_order_id( $order, $intent_metadata ); - } else { - $request = Get_Setup_Intention::create( $intent_id ); - /** @var WC_Payments_API_Setup_Intention $intent */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort - $intent = $request->send(); - $client_secret = $intent->get_client_secret(); - $status = $intent->get_status(); - $charge_id = ''; - $charge = null; - $currency = $order->get_currency(); - $payment_method_id = $intent->get_payment_method_id(); - $payment_method_details = false; - $payment_method_type = $intent->get_payment_method_type(); - $error = $intent->get_last_setup_error(); - } - - if ( ! empty( $error ) ) { - Logger::log( 'Error when processing payment: ' . $error['message'] ); - throw new Process_Payment_Exception( - __( "We're not able to process this payment. Please try again later.", 'woocommerce-payments' ), - 'upe_payment_intent_error' - ); - } else { - $payment_method = $this->get_selected_payment_method( $payment_method_type ); - if ( ! $payment_method ) { - return; - } - - if ( $save_payment_method && $payment_method->is_reusable() ) { - try { - $token = $payment_method->get_payment_token_for_user( $user, $payment_method_id ); - $this->add_token_to_order( $order, $token ); - } catch ( Exception $e ) { - // If saving the token fails, log the error message but catch the error to avoid crashing the checkout flow. - Logger::log( 'Error when saving payment method: ' . $e->getMessage() ); - } - } - - $this->order_service->attach_intent_info_to_order( $order, $intent_id, $status, $payment_method_id, $customer_id, $charge_id, $currency ); - $this->attach_exchange_info_to_order( $order, $charge_id ); - if ( Intent_Status::SUCCEEDED === $status ) { - $this->duplicate_payment_prevention_service->remove_session_processing_order( $order->get_id() ); - } - $this->order_service->update_order_status_from_intent( $order, $intent ); - $this->set_payment_method_title_for_order( $order, $payment_method_type, $payment_method_details ); - $this->order_service->attach_transaction_fee_to_order( $order, $charge ); - - if ( Intent_Status::REQUIRES_ACTION === $status ) { - // I don't think this case should be possible, but just in case... - $next_action = $intent->get_next_action(); - if ( isset( $next_action['type'] ) && 'redirect_to_url' === $next_action['type'] && ! empty( $next_action['redirect_to_url']['url'] ) ) { - wp_safe_redirect( $next_action['redirect_to_url']['url'] ); - exit; - } else { - $redirect_url = sprintf( - '#wcpay-confirm-%s:%s:%s:%s', - $payment_needed ? 'pi' : 'si', - $order_id, - WC_Payments_Utils::encrypt_client_secret( $this->account->get_stripe_account_id(), $client_secret ), - wp_create_nonce( 'wcpay_update_order_status_nonce' ) - ); - wp_safe_redirect( $redirect_url ); - exit; - } - } - } - } catch ( Exception $e ) { - Logger::log( 'Error: ' . $e->getMessage() ); - - $is_order_id_mismatched_exception = - is_a( $e, Process_Payment_Exception::class ) - && self::PROCESS_REDIRECT_ORDER_MISMATCH_ERROR_CODE === $e->get_error_code(); - - // If the order ID mismatched exception is thrown, do not mark the order as failed. - // Because the outcome of the payment intent is for another order, not for the order processed here. - if ( ! $is_order_id_mismatched_exception ) { - // Confirm our needed variables are set before using them due to there could be a server issue during the get_intent process. - $status = $status ?? null; - $charge_id = $charge_id ?? null; - - /* translators: localized exception message */ - $message = sprintf( __( 'UPE payment failed: %s', 'woocommerce-payments' ), $e->getMessage() ); - $this->order_service->mark_payment_failed( $order, $intent_id, $status, $charge_id, $message ); - } - - wc_add_notice( WC_Payments_Utils::get_filtered_error_message( $e ), 'error' ); - - $redirect_url = wc_get_checkout_url(); - if ( $is_order_id_mismatched_exception ) { - $redirect_url = add_query_arg( self::PROCESS_REDIRECT_ORDER_MISMATCH_ERROR_CODE, 'yes', $redirect_url ); - } - wp_safe_redirect( $redirect_url ); - exit; - } - } - - /** - * Verifies that the proper intent is used to process the order. - * - * @param WC_Order $order The order object based on the order_id received from the request. - * @param string $intent_id_from_request The intent ID received from the request. - * - * @return bool True if the proper intent is used to process the order, false otherwise. - */ - public function is_proper_intent_used_with_order( $order, $intent_id_from_request ) { - $intent_id_attached_to_order = $this->order_service->get_intent_id_for_order( $order ); - if ( ! hash_equals( $intent_id_attached_to_order, $intent_id_from_request ) ) { - Logger::error( - sprintf( - 'Intent ID mismatch. Received in request: %1$s. Attached to order: %2$s. Order ID: %3$d', - $intent_id_from_request, - $intent_id_attached_to_order, - $order->get_id() - ) - ); - return false; - } - return true; - } - - /** - * Generates the configuration values, needed for UPE payment fields. - * - * @deprecated 5.0.0 - * - * @return array - */ - public function get_payment_fields_js_config() { - wc_deprecated_function( __FUNCTION__, '5.0.0', 'WC_Payments_Checkout::get_payment_fields_js_config' ); - return WC_Payments::get_wc_payments_checkout()->get_payment_fields_js_config(); - } - - /** - * True if the request contains the values that indicates a redirection after a successful setup intent creation. - * - * @return bool - */ - public function is_setup_intent_success_creation_redirection() { - return ! empty( $_GET['setup_intent_client_secret'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended - ! empty( $_GET['setup_intent'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended - ! empty( $_GET['redirect_status'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended - 'succeeded' === $_GET['redirect_status']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended - } - - /** - * Adds a token to current user from a setup intent id. - * - * @param string $setup_intent_id ID of the setup intent. - * @param WP_User $user User to add token to. - * - * @return WC_Payment_Token_CC|WC_Payment_Token_WCPay_SEPA|null The added token. - */ - public function create_token_from_setup_intent( $setup_intent_id, $user ) { - try { - $setup_intent_request = Get_Setup_Intention::create( $setup_intent_id ); - /** @var WC_Payments_API_Setup_Intention $setup_intent */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort - $setup_intent = $setup_intent_request->send(); - - $payment_method_id = $setup_intent->get_payment_method_id(); - // TODO: When adding SEPA and Sofort, we will need a new API call to get the payment method and from there get the type. - // Leaving 'card' as a hardcoded value for now to avoid the extra API call. - // $payment_method = $this->payment_methods['card'];// Maybe this should be enforced. - $payment_method = $this->payment_method; - - return $payment_method->get_payment_token_for_user( $user, $payment_method_id ); - } catch ( Exception $e ) { - wc_add_notice( WC_Payments_Utils::get_filtered_error_message( $e ), 'error', [ 'icon' => 'error' ] ); - Logger::log( 'Error when adding payment method: ' . $e->getMessage() ); - } - } - - /** - * Mandate must be shown and acknowledged by customer before deferred intent UPE payment can be processed. - * This applies to SEPA and Link payment methods. - * https://stripe.com/docs/payments/finalize-payments-on-the-server - * - * @return boolean True if mandate must be shown and acknowledged by customer before deferred intent UPE payment can be processed, false otherwise. - */ - public function is_mandate_data_required() { - $is_stripe_link_enabled = Payment_Method::CARD === $this->get_selected_stripe_payment_type_id() && in_array( Payment_Method::LINK, $this->get_upe_enabled_payment_method_ids(), true ); - $is_sepa_debit_payment = Payment_Method::SEPA === $this->get_selected_stripe_payment_type_id(); - - return $is_stripe_link_enabled || $is_sepa_debit_payment; - } - - /** - * Returns the Stripe payment type of the selected payment method. - * - * @return string - */ - public function get_selected_stripe_payment_type_id() { - return $this->stripe_id; - } - - /** - * Set formatted readable payment method title for order, - * using payment method details from accompanying charge. - * - * @param \WC_Order $order WC Order being processed. - * @param string $payment_method_type Stripe payment method key. - * @param array|bool $payment_method_details Array of payment method details from charge or false. - */ - public function set_payment_method_title_for_order( $order, $payment_method_type, $payment_method_details ) { - $payment_method = $this->get_selected_payment_method( $payment_method_type ); - if ( ! $payment_method ) { - return; - } - - $payment_method_title = $payment_method->get_title( $payment_method_details ); - - $payment_gateway = in_array( $payment_method->get_id(), [ Payment_Method::CARD, Payment_Method::LINK ], true ) ? self::GATEWAY_ID : self::GATEWAY_ID . '_' . $payment_method_type; - - $order->set_payment_method( $payment_gateway ); - $order->set_payment_method_title( $payment_method_title ); - $order->save(); - } - - /** - * Returns the list of enabled payment method types that will function with the current checkout. - * - * @param string $order_id optional Order ID. - * @param bool $force_currency_check optional Whether the currency check is required even if is_admin(). - * - * @return string[] - */ - public function get_payment_method_ids_enabled_at_checkout( $order_id = null, $force_currency_check = false ) { - $automatic_capture = empty( $this->get_option( 'manual_capture' ) ) || $this->get_option( 'manual_capture' ) === 'no'; - if ( $automatic_capture ) { - $upe_enabled_payment_methods = $this->get_upe_enabled_payment_method_ids(); - } else { - $upe_enabled_payment_methods = array_intersect( $this->get_upe_enabled_payment_method_ids(), [ Payment_Method::CARD, Payment_Method::LINK ] ); - } - if ( is_wc_endpoint_url( 'order-pay' ) ) { - $force_currency_check = true; - } - - $enabled_payment_methods = []; - $active_payment_methods = $this->get_upe_enabled_payment_method_statuses(); - - foreach ( $upe_enabled_payment_methods as $payment_method_id ) { - $payment_method_capability_key = $this->payment_method_capability_key_map[ $payment_method_id ] ?? 'undefined_capability_key'; - if ( isset( $this->payment_methods[ $payment_method_id ] ) ) { - // When creating a payment intent, we need to ensure the currency is matching - // with the payment methods which are sent with the payment intent request, otherwise - // Stripe returns an error. - - // force_currency_check = 0 is_admin = 0 currency_is_checked = 1. - // force_currency_check = 0 is_admin = 1 currency_is_checked = 0. - // force_currency_check = 1 is_admin = 0 currency_is_checked = 1. - // force_currency_check = 1 is_admin = 1 currency_is_checked = 1. - - $skip_currency_check = ! $force_currency_check && is_admin(); - $processing_payment_method = $this->payment_methods[ $payment_method_id ]; - if ( $processing_payment_method->is_enabled_at_checkout( $this->get_account_country() ) && ( $skip_currency_check || $processing_payment_method->is_currency_valid( $this->get_account_domestic_currency(), $order_id ) ) ) { - $status = $active_payment_methods[ $payment_method_capability_key ]['status'] ?? null; - if ( 'active' === $status ) { - $enabled_payment_methods[] = $payment_method_id; - } - } - } - } - - // if credit card payment method is not enabled, we don't use stripe link. - if ( - ! in_array( CC_Payment_Method::PAYMENT_METHOD_STRIPE_ID, $enabled_payment_methods, true ) && - in_array( Link_Payment_Method::PAYMENT_METHOD_STRIPE_ID, $enabled_payment_methods, true ) ) { - $enabled_payment_methods = array_filter( - $enabled_payment_methods, - static function( $method ) { - return Link_Payment_Method::PAYMENT_METHOD_STRIPE_ID !== $method; - } - ); - } - - return $enabled_payment_methods; - } - - /** - * Returns the list of enabled payment method types that will function with the current checkout filtered by fees. - * - * @param string $order_id optional Order ID. - * @param bool $force_currency_check optional Whether the currency check is required even if is_admin(). - * - * @return string[] - */ - public function get_payment_method_ids_enabled_at_checkout_filtered_by_fees( $order_id = null, $force_currency_check = false ) { - $enabled_payment_methods = $this->get_payment_method_ids_enabled_at_checkout( $order_id, $force_currency_check ); - $methods_with_fees = array_keys( $this->account->get_fees() ); - - return array_values( array_intersect( $enabled_payment_methods, $methods_with_fees ) ); - } - - /** - * Returns the list of available payment method types for UPE. - * Filtering out those without configured fees, this will prevent a payment method not supported by the Stripe account's country from being returned. - * Note that we are not taking into account capabilities, which are taken into account when managing payment methods in settings. - * See https://stripe.com/docs/stripe-js/payment-element#web-create-payment-intent for a complete list. - * - * @return string[] - */ - public function get_upe_available_payment_methods() { - $available_methods = parent::get_upe_available_payment_methods(); - - $available_methods[] = Becs_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - $available_methods[] = Bancontact_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - $available_methods[] = Eps_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - $available_methods[] = Giropay_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - $available_methods[] = Ideal_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - $available_methods[] = Sofort_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - $available_methods[] = Sepa_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - $available_methods[] = P24_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - $available_methods[] = Link_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - $available_methods[] = Affirm_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - $available_methods[] = Afterpay_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - $available_methods[] = Klarna_Payment_Method::PAYMENT_METHOD_STRIPE_ID; - - $available_methods = array_values( - apply_filters( - 'wcpay_upe_available_payment_methods', - $available_methods - ) - ); - - $methods_with_fees = array_keys( $this->account->get_fees() ); - - return array_values( array_intersect( $available_methods, $methods_with_fees ) ); - } - - /** - * Handle AJAX request for saving UPE appearance value to transient. - * - * @throws Exception - If nonce or setup intent is invalid. - */ - public function save_upe_appearance_ajax() { - try { - $is_nonce_valid = check_ajax_referer( 'wcpay_save_upe_appearance_nonce', false, false ); - if ( ! $is_nonce_valid ) { - throw new Exception( - __( 'Unable to update UPE appearance values at this time.', 'woocommerce-payments' ) - ); - } - - $is_blocks_checkout = isset( $_POST['is_blocks_checkout'] ) ? rest_sanitize_boolean( wc_clean( wp_unslash( $_POST['is_blocks_checkout'] ) ) ) : false; - $appearance = isset( $_POST['appearance'] ) ? json_decode( wc_clean( wp_unslash( $_POST['appearance'] ) ) ) : null; - - $appearance_transient = $is_blocks_checkout ? self::WC_BLOCKS_UPE_APPEARANCE_TRANSIENT : self::UPE_APPEARANCE_TRANSIENT; - - if ( null !== $appearance ) { - set_transient( $appearance_transient, $appearance, DAY_IN_SECONDS ); - } - - wp_send_json_success( $appearance, 200 ); - } catch ( Exception $e ) { - // Send back error so it can be displayed to the customer. - wp_send_json_error( - [ - 'error' => [ - 'message' => WC_Payments_Utils::get_filtered_error_message( $e ), - ], - ], - WC_Payments_Utils::get_filtered_error_status_code( $e ), - ); - } - } - - /** - * Clear the saved UPE appearance transient value. - */ - public function clear_upe_appearance_transient() { - delete_transient( self::UPE_APPEARANCE_TRANSIENT ); - delete_transient( self::WC_BLOCKS_UPE_APPEARANCE_TRANSIENT ); - } - - /** - * Validate order_id received from the request vs value saved in the intent metadata. - * Throw an exception if they're not matched. - * - * @param WC_Order $order The received order to process. - * @param array $intent_metadata The metadata of attached intent to the order. - * - * @return void - * @throws Process_Payment_Exception - */ - private function validate_order_id_received_vs_intent_meta_order_id( WC_Order $order, array $intent_metadata ): void { - $intent_meta_order_id_raw = $intent_metadata['order_id'] ?? ''; - $intent_meta_order_id = is_numeric( $intent_meta_order_id_raw ) ? intval( $intent_meta_order_id_raw ) : 0; - - if ( $order->get_id() !== $intent_meta_order_id ) { - Logger::error( - sprintf( - 'UPE Process Redirect Payment - Order ID mismatched. Received: %1$d. Intent Metadata Value: %2$d', - $order->get_id(), - $intent_meta_order_id - ) - ); - - throw new Process_Payment_Exception( - __( "We're not able to process this payment due to the order ID mismatch. Please try again later.", 'woocommerce-payments' ), - self::PROCESS_REDIRECT_ORDER_MISMATCH_ERROR_CODE - ); - } - } - - /** - * Gets payment method settings to pass to client scripts - * - * @deprecated 5.0.0 - * - * @return array - */ - private function get_enabled_payment_method_config() { - wc_deprecated_function( __FUNCTION__, '5.0.0', 'WC_Payments_Checkout::get_enabled_payment_method_config' ); - return WC_Payments::get_wc_payments_checkout()->get_enabled_payment_method_config(); - } - - /** - * Function to be used with array_filter - * to filter UPE payment methods that support saved payments - * - * @param string $payment_method_id Stripe payment method. - * - * @return bool - */ - public function is_enabled_for_saved_payments( $payment_method_id ) { - $payment_method = $this->get_selected_payment_method( $payment_method_id ); - if ( ! $payment_method ) { - return false; - } - return $payment_method->is_reusable() - && ( is_admin() || $payment_method->is_currency_valid( $this->get_account_domestic_currency() ) ); - } - - /** - * Returns boolean for whether payment gateway supports saved payments. - * - * @return bool True, if gateway supports saved payments. False, otherwise. - */ - public function should_support_saved_payments() { - return $this->is_enabled_for_saved_payments( $this->stripe_id ); - } - - /** - * Whether we should use the platform account to initialize Stripe on the checkout page. - * - * @return bool Result of the WCPay gateway checks if the card payment method is used, false otherwise. - */ - public function should_use_stripe_platform_on_checkout_page() { - if ( 'card' === $this->stripe_id ) { - return parent::should_use_stripe_platform_on_checkout_page(); - } - return false; - } - - /** - * This method is used by WooCommerce Core's WC_Payment_Gateways::get_available_payment_gateways() to filter out gateways. - * - * The availability decision includes an additional business rule that checks if the payment method is enabled at checkout - * via the is_enabled_at_checkout method. This method provides crucial information, among others, to determine if the gateway - * is reusable in case there's a subcription in the cart. - * - * @return bool Whether the gateway is enabled and ready to accept payments. - */ - public function is_available() { - $processing_payment_method = $this->payment_methods[ $this->payment_method->get_id() ]; - if ( ! $processing_payment_method->is_enabled_at_checkout( $this->get_account_country() ) ) { - return false; - } - return parent::is_available(); - } - - /** - * Log UPE Payment Errors on Checkout. - * - * @throws Exception If nonce is not present or invalid or charge ID is empty or order not found. - */ - public function log_payment_error_ajax() { - try { - $is_nonce_valid = check_ajax_referer( 'wcpay_log_payment_error_nonce', false, false ); - if ( ! $is_nonce_valid ) { - throw new Exception( 'Invalid request.' ); - } - - $charge_id = isset( $_POST['charge_id'] ) ? wc_clean( wp_unslash( $_POST['charge_id'] ) ) : ''; - if ( empty( $charge_id ) ) { - throw new Exception( 'Charge ID cannot be empty.' ); - } - - // Get charge data from WCPay Server. - $request = Get_Charge::create( $charge_id ); - $request->set_hook_args( $charge_id ); - $charge_data = $request->send(); - $order_id = $charge_data['metadata']['order_id']; - - // Validate Order ID and proceed with logging errors and updating order status. - $order = wc_get_order( $order_id ); - if ( ! $order ) { - throw new Exception( 'Order not found. Unable to log error.' ); - } - - $intent_id = $charge_data['payment_intent'] ?? $order->get_meta( '_intent_id' ); - - $request = Get_Intention::create( $intent_id ); - $request->set_hook_args( $order ); - $intent = $request->send(); - - $intent_status = $intent->get_status(); - $error_message = esc_html( rtrim( $charge_data['failure_message'], '.' ) ); - - $this->order_service->mark_payment_failed( $order, $intent_id, $intent_status, $charge_id, $error_message ); - - wp_send_json_success(); - } catch ( Exception $e ) { - wp_send_json_error( - [ - 'error' => [ - 'message' => WC_Payments_Utils::get_filtered_error_message( $e ), - ], - ], - WC_Payments_Utils::get_filtered_error_status_code( $e ), - ); - } - } - - /** - * This function wraps WC_Payments::get_payment_method_map, useful for unit testing. - * - * @return array Array of UPE_Payment_Method instances. - */ - public function wc_payments_get_payment_method_map() { - return WC_Payments::get_payment_method_map(); - } - - /** - * Returns the checkout tile. - * - * @return string Checkout title. - */ - public function get_checkout_title() { - return $this->checkout_title; - } - - /** - * Returns the payment methods for this gateway. - * - * @return array|UPE_Payment_Method[] - */ - public function get_payment_methods() { - return $this->payment_methods; - } - - /** - * Returns the UPE payment method for the gateway. - * - * @return UPE_Payment_Method - */ - public function get_payment_method() { - return $this->payment_method; - } - - /** - * Returns Stripe payment method type ID. - * - * @return string - */ - public function get_stripe_id() { - return $this->stripe_id; - } - - /** - * Return the payment method type from the payment method details. - * - * @param array $payment_method_details Payment method details. - * @return string|null Payment method type or nothing. - */ - private function get_payment_method_type_from_payment_details( $payment_method_details ) { - return $payment_method_details['type'] ?? null; - } - - /** - * This function wraps WC_Payments::get_payment_gateway_by_id, useful for unit testing. - * - * @param string $payment_method_id Stripe payment method type ID. - * @return false|UPE_Payment_Gateway Matching UPE Payment Gateway instance. - */ - public function wc_payments_get_payment_gateway_by_id( $payment_method_id ) { - return WC_Payments::get_payment_gateway_by_id( $payment_method_id ); - } - - /** - * This function wraps WC_Payments::get_payment_method_by_id, useful for unit testing. - * - * @param string $payment_method_id Stripe payment method type ID. - * @return false|UPE_Payment_Method Matching UPE Payment Method instance. - */ - public function wc_payments_get_payment_method_by_id( $payment_method_id ) { - return WC_Payments::get_payment_method_by_id( $payment_method_id ); - } - - /** - * Handles the shipping requirement for Afterpay payments. - * - * This method extracts the shipping and billing data from the order and sets the appropriate - * shipping data for the Afterpay payment request. If neither shipping nor billing data is valid - * for shipping, an exception is thrown. - * - * @param WC_Order $order The order object containing shipping and billing information. - * @param Create_And_Confirm_Intention $request The Afterpay payment request object to set shipping data on. - * - * @throws Invalid_Address_Exception If neither shipping nor billing address is valid for Afterpay payments. - * @return void - */ - private function handle_afterpay_shipping_requirement( WC_Order $order, Create_And_Confirm_Intention $request ): void { - $check_if_usable = function( array $address ): bool { - return $address['country'] && $address['state'] && $address['city'] && $address['postal_code'] && $address['line1']; - }; - - $shipping_data = $this->order_service->get_shipping_data_from_order( $order ); - if ( $check_if_usable( $shipping_data['address'] ) ) { - $request->set_shipping( $shipping_data ); - return; - } - - $billing_data = $this->order_service->get_billing_data_from_order( $order ); - if ( $check_if_usable( $billing_data['address'] ) ) { - $request->set_shipping( $billing_data ); - return; - } - - throw new Invalid_Address_Exception( __( 'A valid shipping address is required for Afterpay payments.', 'woocommerce-payments' ) ); - } - - - /** - * Modifies the create intent parameters when processing a payment. - * - * If the selected Stripe payment type is AFTERPAY, it updates the shipping data in the request. - * - * @param Create_And_Confirm_Intention $request The request object for creating and confirming intention. - * @param Payment_Information $payment_information The payment information object. - * @param WC_Order $order The order object. - * - * @return void - */ - protected function modify_create_intent_parameters_when_processing_payment( Create_And_Confirm_Intention $request, Payment_Information $payment_information, WC_Order $order ): void { - if ( Payment_Method::AFTERPAY === $this->get_selected_stripe_payment_type_id() ) { - $this->handle_afterpay_shipping_requirement( $order, $request ); - } - } -} diff --git a/src/Internal/Payment/State/ProcessedState.php b/src/Internal/Payment/State/ProcessedState.php index 7e8629121eb..5765f96ecbf 100644 --- a/src/Internal/Payment/State/ProcessedState.php +++ b/src/Internal/Payment/State/ProcessedState.php @@ -13,7 +13,7 @@ use WCPay\Internal\Service\OrderService; use WCPay\Vendor\League\Container\Exception\ContainerException; use WCPay\Internal\Proxy\LegacyProxy; -use WCPay\Payment_Methods\UPE_Payment_Gateway; +use WC_Payment_Gateway_WCPay; /** * This state is used when payment is completed on the server, and we need to update date on the plugin side. diff --git a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php index 72d9c1854e3..8c1628a9f91 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php @@ -12,7 +12,6 @@ use WCPay\Database_Cache; use WCPay\Duplicate_Payment_Prevention_Service; use WCPay\Payment_Methods\Eps_Payment_Method; -use WCPay\Payment_Methods\UPE_Payment_Gateway; use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Payment_Methods\Bancontact_Payment_Method; use WCPay\Payment_Methods\Becs_Payment_Method; @@ -65,34 +64,6 @@ class WC_REST_Payments_Settings_Controller_Test extends WCPAY_UnitTestCase { */ private $mock_db_cache; - /** - * An array of mocked split UPE payment gateways mapped to payment method ID. - * - * @var UPE_Payment_Gateway - */ - private $mock_upe_payment_gateway; - - /** - * An array of mocked split UPE payment gateways mapped to payment method ID. - * - * @var UPE_Payment_Gateway - */ - private $mock_split_upe_payment_gateway; - - /** - * UPE system under test. - * - * @var WC_REST_Payments_Settings_Controller - */ - private $upe_controller; - - /** - * UPE system under test. - * - * @var WC_REST_Payments_Settings_Controller - */ - private $upe_split_controller; - /** * WC_Payments_Localization_Service instance. * @@ -144,20 +115,6 @@ public function set_up() { $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); $this->mock_fraud_service = $this->createMock( WC_Payments_Fraud_Service::class ); - $this->gateway = new WC_Payment_Gateway_WCPay( - $this->mock_api_client, - $this->mock_wcpay_account, - $customer_service, - $token_service, - $action_scheduler_service, - $mock_rate_limiter, - $order_service, - $mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service - ); - $this->controller = new WC_REST_Payments_Settings_Controller( $this->mock_api_client, $this->gateway, $this->mock_wcpay_account ); - $mock_payment_methods = []; $payment_method_classes = [ Becs_Payment_Method::class, @@ -184,7 +141,7 @@ public function set_up() { $mock_payment_methods[ $mock_payment_method->get_id() ] = $mock_payment_method; } - $this->mock_upe_payment_gateway = new UPE_Payment_Gateway( + $this->gateway = new WC_Payment_Gateway_WCPay( $this->mock_api_client, $this->mock_wcpay_account, $customer_service, @@ -198,25 +155,7 @@ public function set_up() { $this->mock_localization_service, $this->mock_fraud_service ); - - $this->upe_controller = new WC_REST_Payments_Settings_Controller( $this->mock_api_client, $this->mock_upe_payment_gateway, $this->mock_wcpay_account ); - - $this->mock_split_upe_payment_gateway = new UPE_Payment_Gateway( - $this->mock_api_client, - $this->mock_wcpay_account, - $customer_service, - $token_service, - $action_scheduler_service, - $mock_payment_methods['card'], - $mock_payment_methods, - $mock_rate_limiter, - $order_service, - $mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service - ); - - $this->upe_split_controller = new WC_REST_Payments_Settings_Controller( $this->mock_api_client, $this->mock_split_upe_payment_gateway, $this->mock_wcpay_account ); + $this->controller = new WC_REST_Payments_Settings_Controller( $this->mock_api_client, $this->gateway, $this->mock_wcpay_account ); $this->mock_api_client ->method( 'is_server_connected' ) @@ -267,39 +206,13 @@ public function test_get_settings_returns_enabled_payment_method_ids() { ); } - public function test_upe_get_settings_returns_available_payment_method_ids() { + public function test_get_settings_returns_available_payment_method_ids() { $this->mock_localization_service->method( 'get_country_locale_data' )->willReturn( [ 'currency_code' => 'usd', ] ); - $response = $this->upe_controller->get_settings(); - $enabled_method_ids = $response->get_data()['available_payment_method_ids']; - - $this->assertEquals( - [ - Payment_Method::CARD, - Payment_Method::BECS, - Payment_Method::BANCONTACT, - Payment_Method::EPS, - Payment_Method::GIROPAY, - Payment_Method::IDEAL, - Payment_Method::SOFORT, - Payment_Method::SEPA, - Payment_Method::P24, - Payment_Method::LINK, - ], - $enabled_method_ids - ); - } - - public function test_split_upe_get_settings_returns_available_payment_method_ids() { - $this->mock_localization_service->method( 'get_country_locale_data' )->willReturn( - [ - 'currency_code' => 'usd', - ] - ); - $response = $this->upe_split_controller->get_settings(); + $response = $this->controller->get_settings(); $enabled_method_ids = $response->get_data()['available_payment_method_ids']; $this->assertEquals( @@ -436,26 +349,15 @@ public function test_update_settings_returns_error_on_non_bool_is_wcpay_enabled_ $this->assertEquals( 400, $response->get_status() ); } - public function test_upe_update_settings_saves_enabled_payment_methods() { - $this->mock_upe_payment_gateway->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::CARD ] ); + public function test_update_settings_saves_enabled_payment_methods() { + $this->gateway->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::CARD ] ); $request = new WP_REST_Request(); $request->set_param( 'enabled_payment_method_ids', [ Payment_Method::CARD, Payment_Method::GIROPAY ] ); - $this->upe_controller->update_settings( $request ); + $this->controller->update_settings( $request ); - $this->assertEquals( [ Payment_Method::CARD, Payment_Method::GIROPAY ], $this->mock_upe_payment_gateway->get_option( 'upe_enabled_payment_method_ids' ) ); - } - - public function test_upe_split_update_settings_saves_enabled_payment_methods() { - $this->mock_split_upe_payment_gateway->update_option( 'upe_enabled_payment_method_ids', [ Payment_Method::CARD ] ); - - $request = new WP_REST_Request(); - $request->set_param( 'enabled_payment_method_ids', [ Payment_Method::CARD, Payment_Method::GIROPAY ] ); - - $this->upe_split_controller->update_settings( $request ); - - $this->assertEquals( [ Payment_Method::CARD, Payment_Method::GIROPAY ], $this->mock_split_upe_payment_gateway->get_option( 'upe_enabled_payment_method_ids' ) ); + $this->assertEquals( [ Payment_Method::CARD, Payment_Method::GIROPAY ], $this->gateway->get_option( 'upe_enabled_payment_method_ids' ) ); } public function test_update_settings_fails_if_user_cannot_manage_woocommerce() { @@ -769,7 +671,7 @@ public function deregister_wc_blocks_rest_api() { } } - public function test_upe_get_settings_card_eligible_flag(): void { + public function test_get_settings_card_eligible_flag(): void { // Enable Cash on Delivery gateway for the purpose of this test. $cod_gateway = WC()->payment_gateways()->payment_gateways()['cod']; $cod_gateway->enabled = 'yes'; @@ -780,26 +682,7 @@ public function test_upe_get_settings_card_eligible_flag(): void { ] ); - $response = $this->upe_controller->get_settings(); - - $this->assertArrayHasKey( 'is_card_present_eligible', $response->get_data() ); - $this->assertTrue( $response->get_data()['is_card_present_eligible'] ); - - // Disable Cash on Delivery gateway. - $cod_gateway->enabled = 'no'; - } - - public function test_upe_split_get_settings_card_eligible_flag(): void { - // Enable Cash on Delivery gateway for the purpose of this test. - $cod_gateway = WC()->payment_gateways()->payment_gateways()['cod']; - $cod_gateway->enabled = 'yes'; - - $this->mock_localization_service->method( 'get_country_locale_data' )->willReturn( - [ - 'currency_code' => 'usd', - ] - ); - $response = $this->upe_split_controller->get_settings(); + $response = $this->controller->get_settings(); $this->assertArrayHasKey( 'is_card_present_eligible', $response->get_data() ); $this->assertTrue( $response->get_data()['is_card_present_eligible'] ); @@ -808,7 +691,7 @@ public function test_upe_split_get_settings_card_eligible_flag(): void { $cod_gateway->enabled = 'no'; } - public function test_upe_get_settings_domestic_currency(): void { + public function test_get_settings_domestic_currency(): void { $mock_domestic_currency = 'usd'; $this->mock_localization_service->method( 'get_country_locale_data' )->willReturn( [ @@ -819,49 +702,20 @@ public function test_upe_get_settings_domestic_currency(): void { ->expects( $this->never() ) ->method( 'get_account_default_currency' ); - $response = $this->upe_controller->get_settings(); - - $this->assertArrayHasKey( 'account_domestic_currency', $response->get_data() ); - $this->assertSame( $mock_domestic_currency, $response->get_data()['account_domestic_currency'] ); - } - - public function test_upe_get_settings_domestic_currency_fallbacks_to_default_currency(): void { - $mock_domestic_currency = 'usd'; - $this->mock_localization_service->method( 'get_country_locale_data' )->willReturn( [] ); - $this->mock_wcpay_account - ->expects( $this->once() ) - ->method( 'get_account_default_currency' ) - ->willReturn( $mock_domestic_currency ); - $response = $this->upe_controller->get_settings(); - - $this->assertArrayHasKey( 'account_domestic_currency', $response->get_data() ); - $this->assertSame( $mock_domestic_currency, $response->get_data()['account_domestic_currency'] ); - } - - public function test_upe_split_get_settings_domestic_currency(): void { - $mock_domestic_currency = 'usd'; - $this->mock_localization_service->method( 'get_country_locale_data' )->willReturn( - [ - 'currency_code' => $mock_domestic_currency, - ] - ); - $this->mock_wcpay_account - ->expects( $this->never() ) - ->method( 'get_account_default_currency' ); - $response = $this->upe_split_controller->get_settings(); + $response = $this->controller->get_settings(); $this->assertArrayHasKey( 'account_domestic_currency', $response->get_data() ); $this->assertSame( $mock_domestic_currency, $response->get_data()['account_domestic_currency'] ); } - public function test_upe_split_get_settings_domestic_currency_fallbacks_to_default_currency(): void { + public function test_get_settings_domestic_currency_fallbacks_to_default_currency(): void { $mock_domestic_currency = 'usd'; $this->mock_localization_service->method( 'get_country_locale_data' )->willReturn( [] ); $this->mock_wcpay_account ->expects( $this->once() ) ->method( 'get_account_default_currency' ) ->willReturn( $mock_domestic_currency ); - $response = $this->upe_split_controller->get_settings(); + $response = $this->controller->get_settings(); $this->assertArrayHasKey( 'account_domestic_currency', $response->get_data() ); $this->assertSame( $mock_domestic_currency, $response->get_data()['account_domestic_currency'] ); diff --git a/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php b/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php index a7f6ea1a1b9..9597390a9a0 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php @@ -9,6 +9,7 @@ use WCPay\Core\Server\Request\Add_Account_Tos_Agreement; use WCPay\Database_Cache; use WCPay\Duplicate_Payment_Prevention_Service; +use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Session_Rate_Limiter; /** @@ -63,6 +64,7 @@ public function set_up() { $order_service = new WC_Payments_Order_Service( $this->createMock( WC_Payments_API_Client::class ) ); $action_scheduler_service = new WC_Payments_Action_Scheduler_Service( $mock_api_client, $order_service ); $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $mock_payment_method = $this->createMock( CC_Payment_Method::class ); $this->gateway = new WC_Payment_Gateway_WCPay( $mock_api_client, @@ -70,6 +72,8 @@ public function set_up() { $customer_service, $token_service, $action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $mock_rate_limiter, $order_service, $mock_dpps, diff --git a/tests/unit/payment-methods/test-class-upe-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-payment-gateway.php index cd41a804268..7c013e2b858 100644 --- a/tests/unit/payment-methods/test-class-upe-payment-gateway.php +++ b/tests/unit/payment-methods/test-class-upe-payment-gateway.php @@ -1,6 +1,6 @@ mock_api_client = $this->getMockBuilder( 'WC_Payments_API_Client' ) ->disableOriginalConstructor() - ->setMethods( + ->onlyMethods( [ 'get_payment_method', 'is_server_connected', @@ -224,7 +220,7 @@ public function set_up() { // Arrange: Mock WC_Payments_Customer_Service so its methods aren't called directly. $this->mock_token_service = $this->getMockBuilder( 'WC_Payments_Token_Service' ) ->disableOriginalConstructor() - ->setMethods( [ 'add_payment_method_to_user' ] ) + ->onlyMethods( [ 'add_payment_method_to_user' ] ) ->getMock(); // Arrange: Mock WC_Payments_Action_Scheduler_Service so its methods aren't called directly. @@ -257,7 +253,7 @@ public function set_up() { foreach ( $payment_method_classes as $payment_method_class ) { $mock_payment_method = $this->getMockBuilder( $payment_method_class ) ->setConstructorArgs( [ $this->mock_token_service ] ) - ->setMethods( [ 'is_subscription_item_in_cart', 'get_icon' ] ) + ->onlyMethods( [ 'is_subscription_item_in_cart', 'get_icon' ] ) ->getMock(); $this->mock_payment_methods[ $mock_payment_method->get_id() ] = $mock_payment_method; } @@ -268,7 +264,7 @@ public function set_up() { $this->mock_api_client, ] ) - ->setMethods( + ->onlyMethods( [ 'get_payment_method_id_for_order', ] @@ -281,9 +277,9 @@ public function set_up() { ->getMock(); $this->mock_payment_methods[ $this->mock_payment_method->get_id() ] = $this->mock_payment_method; - // Arrange: Mock UPE_Payment_Gateway so that some of its methods can be + // Arrange: Mock WC_Payment_Gateway_WCPay so that some of its methods can be // mocked, and their return values can be used for testing. - $this->mock_upe_gateway = $this->getMockBuilder( UPE_Payment_Gateway::class ) + $this->mock_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) ->setConstructorArgs( [ $this->mock_api_client, @@ -312,13 +308,13 @@ public function set_up() { ->getMock(); // Arrange: Set the return value of get_return_url() so it can be used in a test later. - $this->mock_upe_gateway + $this->mock_gateway ->expects( $this->any() ) ->method( 'get_return_url' ) ->will( $this->returnValue( $this->return_url ) ); - $this->mock_upe_gateway + $this->mock_gateway ->expects( $this->any() ) ->method( 'parent_process_payment' ) ->will( @@ -329,7 +325,7 @@ public function set_up() { // so that get_payment_method_from_request() does not throw error. $_POST = [ 'wcpay-payment-method' => 'pm_mock', - 'payment_method' => UPE_Payment_Gateway::GATEWAY_ID, + 'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID, ]; // Mock the level3 service to always return an empty array. @@ -358,207 +354,53 @@ public function tear_down() { wcpay_get_test_container()->reset_all_replacements(); } - public function test_process_payment_returns_correct_redirect_url() { - $order = WC_Helper_Order::create_order(); - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - $payment_intent = WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::PROCESSING ] ); - - $request = $this->mock_wcpay_request( Update_Intention::class, 1, 'pi_mock' ); - - $request->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $payment_intent ); - - $this->set_cart_contains_subscription_items( false ); - - $result = $this->mock_upe_gateway->process_payment( $order->get_id() ); - - unset( $_POST['wc_payment_intent_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing - - $this->assertEquals( 'success', $result['result'] ); - $this->assertEquals( true, $result['payment_needed'] ); - $this->assertMatchesRegularExpression( '/wc_payment_method=woocommerce_payments/', $result['redirect_url'] ); - $this->assertMatchesRegularExpression( '/save_payment_method=no/', $result['redirect_url'] ); - } - - public function test_process_subscription_payment_passes_save_payment_method() { - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - $payment_intent = WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::PROCESSING ] ); - - $request = $this->mock_wcpay_request( Update_Intention::class, 1, 'pi_mock' ); - - $request->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $payment_intent ); + public function test_process_payment_returns_correct_redirect_when_using_saved_payment() { + $order = WC_Helper_Order::create_order(); + $_POST = $this->setup_saved_payment_method(); + $intent = WC_Helper_Intention::create_intention(); - $this->mock_upe_gateway + $this->mock_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ wp_get_current_user(), 'cus_123' ] ) + ); + $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1, $intent->get_id() ) ->expects( $this->once() ) - ->method( 'is_payment_recurring' ) - ->willReturn( true ); - - $result = $this->mock_upe_gateway->process_payment( $order->get_id() ); - - unset( $_POST['wc_payment_intent_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing - - $this->assertEquals( 'success', $result['result'] ); - $this->assertEquals( true, $result['payment_needed'] ); - $this->assertMatchesRegularExpression( '/wc_payment_method=woocommerce_payments/', $result['redirect_url'] ); - $this->assertMatchesRegularExpression( '/save_payment_method=yes/', $result['redirect_url'] ); - } - - public function test_process_payment_returns_correct_redirect_when_using_saved_payment() { - $order = WC_Helper_Order::create_order(); - $_POST = $this->setup_saved_payment_method(); + ->method( 'format_response' ) + ->willReturn( $intent ); $this->set_cart_contains_subscription_items( false ); - $this->mock_upe_gateway - ->expects( $this->never() ) - ->method( 'manage_customer_details_for_order' ); - - $result = $this->mock_upe_gateway->process_payment( $order->get_id() ); + $result = $this->mock_gateway->process_payment( $order->get_id() ); $this->assertEquals( 'success', $result['result'] ); - $this->assertMatchesRegularExpression( '/key=mock_order_key/', $result['redirect'] ); + $this->assertEquals( $this->return_url, $result['redirect'] ); } public function test_process_payment_returns_correct_redirect_when_using_payment_request() { $order = WC_Helper_Order::create_order(); + $intent = WC_Helper_Intention::create_intention(); $_POST['payment_request_type'] = 'google_pay'; - $this->set_cart_contains_subscription_items( false ); - - $result = $this->mock_upe_gateway->process_payment( $order->get_id() ); - - $this->mock_upe_gateway - ->expects( $this->never() ) - ->method( 'manage_customer_details_for_order' ); - $this->assertEquals( 'success', $result['result'] ); - $this->assertMatchesRegularExpression( '/key=mock_order_key/', $result['redirect'] ); - } - - public function test_upe_process_payment_check_session_order_redirect_to_previous_order() { - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - $response = [ - 'dummy_result' => 'xyz', - ]; - - // Arrange the order is being processed. - $current_order = WC_Helper_Order::create_order(); - $current_order_id = $current_order->get_id(); - - // Arrange the DPPS to return an order from the session. - $this->mock_dpps->expects( $this->once() ) - ->method( 'check_against_session_processing_order' ) - ->with( wc_get_order( $current_order ) ) - ->willReturn( $response ); - - // Assert: no call to the server to confirm the payment. - $this->mock_wcpay_request( Update_Intention::class, 0, 'pi_XXXXX' ); - - // Act: process the order but redirect to the previous/session paid order. - $result = $this->mock_upe_gateway->process_payment( $current_order_id ); - - // Assert: the result of check_against_session_processing_order. - $this->assertSame( $response, $result ); - } - - public function test_upe_process_payment_check_session_with_failed_intent_then_order_id_saved_to_session() { - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - // Arrange the order is being processed. - $current_order = WC_Helper_Order::create_order(); - $current_order_id = $current_order->get_id(); - - // Arrange a failed intention. - $intent = WC_Helper_Intention::create_intention( [ 'status' => 'failed' ] ); - - // Assert. - $update_request = $this->mock_wcpay_request( Update_Intention::class, 1, $intent->get_id() ); - $update_request->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $intent ); - - // Arrange the DPPS not to return an order from the session. - $this->mock_dpps->expects( $this->once() ) - ->method( 'check_against_session_processing_order' ) - ->with( wc_get_order( $current_order ) ) - ->willReturn( null ); - - // Assert: maybe_update_session_processing_order takes action and its value is kept. - $this->mock_dpps->expects( $this->once() ) - ->method( 'maybe_update_session_processing_order' ) - ->with( $current_order_id ); - - // Act: process the order but redirect to the previous/session paid order. - $this->mock_upe_gateway->process_payment( $current_order_id ); - } - - public function test_upe_process_payment_check_session_and_continue_processing() { - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - // Arrange the order is being processed. - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - - // Arrange a successful intention. - $intent = WC_Helper_Intention::create_intention(); - - // Arrange the DPPS not to return an order from the session. - $this->mock_dpps->expects( $this->once() ) - ->method( 'check_against_session_processing_order' ) - ->with( wc_get_order( $order ) ) - ->willReturn( null ); - - // Assert: Order is removed from the session. - $this->mock_dpps->expects( $this->once() ) - ->method( 'remove_session_processing_order' ) - ->with( $order_id ); - - // Assert: the payment process continues. - $this->mock_wcpay_request( Update_Intention::class, 1, $intent->get_id() ) + $this->mock_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ wp_get_current_user(), 'cus_123' ] ) + ); + $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1, $intent->get_id() ) ->expects( $this->once() ) ->method( 'format_response' ) ->willReturn( $intent ); + $this->set_cart_contains_subscription_items( false ); - // Act. - $this->mock_upe_gateway->process_payment( $order_id ); - } - - public function test_upe_check_payment_intent_attached_to_order_succeeded_return_redirection() { - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - $response = [ - 'dummy_result' => 'xyz', - ]; - - // Arrange order. - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - - // Arrange the DPPS to return a prepared response. - $this->mock_dpps->expects( $this->once() ) - ->method( 'check_payment_intent_attached_to_order_succeeded' ) - ->with( wc_get_order( $order ) ) - ->willReturn( $response ); - - // Assert: no more call to the server to update the intention. - $this->mock_wcpay_request( Update_Intention::class, 0 ); - - // Act: process the order but redirect to the order. - $result = $this->mock_upe_gateway->process_payment( $order_id ); + $result = $this->mock_gateway->process_payment( $order->get_id() ); - // Assert: the result of check_intent_attached_to_order_succeeded. - $this->assertSame( $response, $result ); + $this->assertEquals( 'success', $result['result'] ); + $this->assertEquals( $this->return_url, $result['redirect'] ); } public function is_proper_intent_used_with_order_returns_false() { - $this->assertFalse( $this->mock_upe_gateway->is_proper_intent_used_with_order( WC_Helper_Order::create_order(), 'wrong_intent_id' ) ); + $this->assertFalse( $this->mock_gateway->is_proper_intent_used_with_order( WC_Helper_Order::create_order(), 'wrong_intent_id' ) ); } public function test_process_redirect_payment_intent_processing() { @@ -584,7 +426,7 @@ public function test_process_redirect_payment_intent_processing() { ] ); - $this->mock_upe_gateway->expects( $this->once() ) + $this->mock_gateway->expects( $this->once() ) ->method( 'manage_customer_details_for_order' ) ->will( $this->returnValue( [ $user, $customer_id ] ) @@ -598,7 +440,7 @@ public function test_process_redirect_payment_intent_processing() { $this->set_cart_contains_subscription_items( false ); - $this->mock_upe_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + $this->mock_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); $result_order = wc_get_order( $order_id ); $note = wc_get_order_notes( @@ -640,7 +482,7 @@ public function test_process_redirect_payment_intent_succeded() { ] ); - $this->mock_upe_gateway->expects( $this->once() ) + $this->mock_gateway->expects( $this->once() ) ->method( 'manage_customer_details_for_order' ) ->will( $this->returnValue( [ $user, $customer_id ] ) @@ -654,7 +496,7 @@ public function test_process_redirect_payment_intent_succeded() { $this->set_cart_contains_subscription_items( false ); - $this->mock_upe_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + $this->mock_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); $result_order = wc_get_order( $order_id ); @@ -674,7 +516,7 @@ public function test_validate_order_id_received_vs_intent_meta_order_id_throw_ex $this->expectExceptionMessage( "We're not able to process this payment due to the order ID mismatch. Please try again later." ); \PHPUnit_Utils::call_method( - $this->mock_upe_gateway, + $this->mock_gateway, 'validate_order_id_received_vs_intent_meta_order_id', [ $order, $intent_metadata ] ); @@ -685,7 +527,7 @@ public function test_validate_order_id_received_vs_intent_meta_order_id_returnin $intent_metadata = [ 'order_id' => (string) ( $order->get_id() ) ]; $res = \PHPUnit_Utils::call_method( - $this->mock_upe_gateway, + $this->mock_gateway, 'validate_order_id_received_vs_intent_meta_order_id', [ $order, $intent_metadata ] ); @@ -771,7 +613,7 @@ public function test_correct_payment_method_title_for_order() { ]; foreach ( $charge_payment_method_details as $i => $payment_method_details ) { - $this->mock_upe_gateway->set_payment_method_title_for_order( $order, $payment_method_details['type'], $payment_method_details ); + $this->mock_gateway->set_payment_method_title_for_order( $order, $payment_method_details['type'], $payment_method_details ); $this->assertEquals( $expected_payment_method_titles[ $i ], $order->get_payment_method_title() ); } } @@ -1069,24 +911,7 @@ public function test_create_token_from_setup_intent_adds_token() { $this->returnValue( $mock_token ) ); - $this->assertEquals( $mock_token, $this->mock_upe_gateway->create_token_from_setup_intent( $mock_setup_intent_id, $mock_user ) ); - } - - public function test_process_payment_rejects_with_cached_minimum_acount() { - $order = WC_Helper_Order::create_order(); - $order->set_currency( 'USD' ); - $order->set_total( 0.45 ); - $order->save(); - - set_transient( 'wcpay_minimum_amount_usd', '50', DAY_IN_SECONDS ); - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - // Make sure that the payment was not actually processed. - $price = wp_strip_all_tags( html_entity_decode( wc_price( 0.5, [ 'currency' => 'USD' ] ) ) ); - $message = 'The selected payment method requires a total amount of at least ' . $price . '.'; - $this->expectException( Exception::class ); - $this->expectExceptionMessage( $message ); - $this->mock_upe_gateway->process_payment( $order->get_id() ); + $this->assertEquals( $mock_token, $this->mock_gateway->create_token_from_setup_intent( $mock_setup_intent_id, $mock_user ) ); } public function test_exception_will_be_thrown_if_phone_number_is_invalid() { @@ -1095,66 +920,20 @@ public function test_exception_will_be_thrown_if_phone_number_is_invalid() { $order->save(); $this->expectException( Exception::class ); $this->expectExceptionMessage( 'Invalid phone number.' ); - $this->mock_upe_gateway->process_payment( $order->get_id() ); - } - - public function test_process_payment_caches_mimimum_amount_and_displays_error_upon_exception() { - $amount = 0.45; - $customer = 'cus_12345'; - $payment_intent_id = 'pi_mock'; - - $order = WC_Helper_Order::create_order(); - $order->set_total( $amount ); - $order->save(); - - delete_transient( 'wcpay_minimum_amount_usd' ); - - $_POST['wc_payment_intent_id'] = $payment_intent_id; - - $request = $this->mock_wcpay_request( Update_Intention::class, 1, $payment_intent_id ); - - $request->expects( $this->once() ) - ->method( 'set_amount' ) - ->with( (int) ( $amount * 100 ) ); - - $request->expects( $this->once() ) - ->method( 'set_level3' ) - ->with( - $this->callback( - function( $argument ) { - return is_array( $argument ); - } - ) - ); - - $request->expects( $this->once() ) - ->method( 'format_response' ) - ->will( $this->throwException( new Amount_Too_Small_Exception( 'Error: Amount must be at least $60 usd', 6000, 'usd', 400 ) ) ); - - $price = wp_strip_all_tags( html_entity_decode( wc_price( 60, [ 'currency' => 'USD' ] ) ) ); - $message = 'The selected payment method requires a total amount of at least ' . $price . '.'; - $this->expectException( Exception::class ); - $this->expectExceptionMessage( $message ); - - try { - $this->mock_upe_gateway->process_payment( $order->get_id() ); - } catch ( Exception $e ) { - $this->assertEquals( '6000', get_transient( 'wcpay_minimum_amount_usd' ) ); - throw $e; - } + $this->mock_gateway->process_payment( $order->get_id() ); } public function test_remove_link_payment_method_if_card_disabled() { - $this->mock_upe_gateway->settings['upe_enabled_payment_method_ids'] = [ 'link' ]; + $this->mock_gateway->settings['upe_enabled_payment_method_ids'] = [ 'link' ]; - $this->mock_upe_gateway + $this->mock_gateway ->expects( $this->once() ) ->method( 'get_upe_enabled_payment_method_statuses' ) ->will( $this->returnValue( [ 'link_payments' => [ 'status' => 'active' ] ] ) ); - $this->assertSame( $this->mock_upe_gateway->get_payment_method_ids_enabled_at_checkout(), [] ); + $this->assertSame( $this->mock_gateway->get_payment_method_ids_enabled_at_checkout(), [] ); } /** @@ -1167,7 +946,7 @@ public function test_get_upe_available_payment_methods( $payment_methods, $expec ->method( 'get_fees' ) ->willReturn( $payment_methods ); - $gateway = new UPE_Payment_Gateway( + $gateway = new WC_Payment_Gateway_WCPay( $this->mock_api_client, $mock_wcpay_account, $this->mock_customer_service, @@ -1239,7 +1018,7 @@ private function set_get_upe_enabled_payment_method_statuses_return_value( $retu ], ]; } - $this->mock_upe_gateway + $this->mock_gateway ->expects( $this->any() ) ->method( 'get_upe_enabled_payment_method_statuses' ) ->will( $this->returnValue( $return_value ) ); diff --git a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php index 54ffd9669f0..e788e467a14 100644 --- a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php +++ b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php @@ -1,6 +1,6 @@ 'pm_mock', - 'payment_method' => UPE_Payment_Gateway::GATEWAY_ID, + 'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID, ]; $get_payment_gateway_by_id_return_value_map = []; @@ -268,7 +266,7 @@ public function set_up() { ->getMock(); $this->mock_payment_methods[ $mock_payment_method->get_id() ] = $mock_payment_method; - $mock_gateway = $this->getMockBuilder( UPE_Payment_Gateway::class ) + $mock_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) ->setConstructorArgs( [ $this->mock_api_client, @@ -412,141 +410,36 @@ public function test_non_reusable_payment_method_is_not_available_when_subscript $this->assertFalse( $payment_gateway->is_available() ); } - public function test_process_payment_returns_correct_redirect_url() { - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - $payment_intent = WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::PROCESSING ] ); - - $this->set_cart_contains_subscription_items( false ); - - foreach ( $this->mock_payment_gateways as $mock_payment_gateway ) { - $this->mock_wcpay_request( Update_Intention::class, 1, $payment_intent->get_id() ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $payment_intent ); - - $result = $mock_payment_gateway->process_payment( $order->get_id() ); - $this->assertEquals( 'success', $result['result'] ); - $this->assertEquals( true, $result['payment_needed'] ); - $this->assertMatchesRegularExpression( '/wc_payment_method=woocommerce_payments/', $result['redirect_url'] ); - $this->assertMatchesRegularExpression( '/save_payment_method=no/', $result['redirect_url'] ); - } - - unset( $_POST['wc_payment_intent_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing - } - - public function test_process_payment_passes_save_payment_method_to_store() { - $mock_sepa_payment_gateway = $this->mock_payment_gateways[ Payment_Method::SEPA ]; - - $order = WC_Helper_Order::create_order(); - $gateway_id = UPE_Payment_Gateway::GATEWAY_ID . '_' . Payment_Method::SEPA; - $save_payment_param = "wc-$gateway_id-new-payment-method"; - $_POST[ $save_payment_param ] = 'yes'; - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - $payment_intent = WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::PROCESSING ] ); - - $this->mock_wcpay_request( Update_Intention::class, 1, $payment_intent->get_id() ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( - $payment_intent - ); - - $this->set_cart_contains_subscription_items( false ); - - // Test saving with SEPA. - $result = $mock_sepa_payment_gateway->process_payment( $order->get_id() ); - $this->assertEquals( 'success', $result['result'] ); - $this->assertMatchesRegularExpression( '/wc_payment_method=woocommerce_payments/', $result['redirect_url'] ); - $this->assertMatchesRegularExpression( '/save_payment_method=yes/', $result['redirect_url'] ); - - unset( $_POST[ $save_payment_param ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing - unset( $_POST['wc_payment_intent_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing - } - - public function test_process_subscription_payment_passes_save_payment_method() { + public function test_process_payment_returns_correct_redirect_when_using_saved_payment() { $mock_card_payment_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; - $mock_sepa_payment_gateway = $this->mock_payment_gateways[ Payment_Method::SEPA ]; - - $order = WC_Helper_Order::create_order(); - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - $payment_intent = WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::PROCESSING ] ); + $user = wp_get_current_user(); + $customer_id = 'cus_mock'; - // Test card. - $this->mock_wcpay_request( Update_Intention::class, 1, $payment_intent->get_id() ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( - $payment_intent + $order = WC_Helper_Order::create_order(); + $_POST = $this->setup_saved_payment_method(); + $mock_card_payment_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ $user, $customer_id ] ) ); - - $mock_card_payment_gateway - ->expects( $this->once() ) - ->method( 'is_payment_recurring' ) - ->willReturn( true ); - $result = $mock_card_payment_gateway->process_payment( $order->get_id() ); - $this->assertEquals( 'success', $result['result'] ); - $this->assertEquals( true, $result['payment_needed'] ); - $this->assertMatchesRegularExpression( '/wc_payment_method=woocommerce_payments/', $result['redirect_url'] ); - $this->assertMatchesRegularExpression( '/save_payment_method=yes/', $result['redirect_url'] ); - - // Test SEPA. - $this->mock_wcpay_request( Update_Intention::class, 1, $payment_intent->get_id() ) + $mock_card_payment_gateway->expects( $this->any() ) + ->method( 'get_upe_enabled_payment_method_ids' ) + ->will( + $this->returnValue( [ Payment_Method::CARD ] ) + ); + $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1 ) ->expects( $this->once() ) ->method( 'format_response' ) ->willReturn( - $payment_intent + WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::PROCESSING ] ) ); - $mock_sepa_payment_gateway - ->expects( $this->once() ) - ->method( 'is_payment_recurring' ) - ->willReturn( true ); - $result = $mock_sepa_payment_gateway->process_payment( $order->get_id() ); - $this->assertEquals( 'success', $result['result'] ); - $this->assertEquals( true, $result['payment_needed'] ); - $this->assertMatchesRegularExpression( '/wc_payment_method=woocommerce_payments/', $result['redirect_url'] ); - $this->assertMatchesRegularExpression( '/save_payment_method=yes/', $result['redirect_url'] ); - - unset( $_POST['wc_payment_intent_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing - } - - public function test_process_payment_returns_correct_redirect_when_using_saved_payment() { - $mock_card_payment_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; - - $order = WC_Helper_Order::create_order(); - $_POST = $this->setup_saved_payment_method(); - $this->set_cart_contains_subscription_items( false ); $result = $mock_card_payment_gateway->process_payment( $order->get_id() ); - $mock_card_payment_gateway - ->expects( $this->never() ) - ->method( 'manage_customer_details_for_order' ); $this->assertEquals( 'success', $result['result'] ); - $this->assertMatchesRegularExpression( '/key=mock_order_key/', $result['redirect'] ); - } - - public function test_process_payment_returns_correct_redirect_when_using_payment_request() { - $mock_card_payment_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; - - $order = WC_Helper_Order::create_order(); - $_POST['payment_request_type'] = 'google_pay'; - - $this->set_cart_contains_subscription_items( false ); - - $result = $mock_card_payment_gateway->process_payment( $order->get_id() ); - - $mock_card_payment_gateway - ->expects( $this->never() ) - ->method( 'manage_customer_details_for_order' ); - $this->assertEquals( 'success', $result['result'] ); - $this->assertMatchesRegularExpression( '/key=mock_order_key/', $result['redirect'] ); + $this->assertEquals( $this->return_url, $result['redirect'] ); } public function test_upe_process_payment_check_session_order_redirect_to_previous_order() { @@ -574,113 +467,6 @@ public function test_upe_process_payment_check_session_order_redirect_to_previou $this->assertSame( $response, $result ); } - public function test_upe_process_payment_check_session_with_failed_intent_then_order_id_saved_to_session() { - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - // Arrange the order is being processed. - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - - // Arrange a failed intention. - $intent = WC_Helper_Intention::create_intention( [ 'status' => 'failed' ] ); - - // Assert. - $this->mock_wcpay_request( Update_Intention::class, 1, $intent->get_id() ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $intent ); - - // Make sure the DPPS will store the order. - $this->mock_dpps->expects( $this->once() ) - ->method( 'maybe_update_session_processing_order' ) - ->with( $order_id ); - - // Act: process the order but redirect to the previous/session paid order. - $this->mock_payment_gateways[ Payment_Method::SEPA ]->process_payment( $order_id ); - } - - public function test_upe_process_payment_check_session_and_continue_processing() { - $_POST['wc_payment_intent_id'] = 'pi_mock'; - $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::SEPA ]; - - // Arrange the order is being processed. - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - - // Arrange a successful intention. - $intent = WC_Helper_Intention::create_intention(); - - $mock_upe_gateway - ->expects( $this->once() ) - ->method( 'get_payment_method_ids_enabled_at_checkout' ) - ->willReturn( [] ); - - // Arrange the DPPs not to return anything. - $this->mock_dpps->expects( $this->once() ) - ->method( 'check_against_session_processing_order' ) - ->with( wc_get_order( $order ) ) - ->willReturn( null ); - - $this->mock_wcpay_request( Update_Intention::class, 1, $intent->get_id() ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $intent ); - - // Act. - $mock_upe_gateway->process_payment( $order_id ); - } - - public function test_upe_check_payment_intent_attached_to_order_succeeded_with_invalid_intent_id_continue_process_payment() { - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - // Arrange order. - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - - // Arrange the DPPS not to return a redirect. - $this->mock_dpps->expects( $this->once() ) - ->method( 'check_payment_intent_attached_to_order_succeeded' ) - ->with( wc_get_order( $order ) ) - ->willReturn( null ); - - // Assert: the payment process continues. - $intent = WC_Helper_Intention::create_intention(); - $this->mock_wcpay_request( Update_Intention::class, 1, $intent->get_id() ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( $intent ); - - // Act: process the order. - $this->mock_payment_gateways[ Payment_Method::SEPA ]->process_payment( $order_id ); - } - - public function test_upe_check_payment_intent_attached_to_order_succeeded_return_redirection() { - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - $response = [ - 'dummy_result' => 'xyz', - ]; - - // Arrange order. - $order = WC_Helper_Order::create_order(); - $order_id = $order->get_id(); - - // Arrange the DPPS to return a redirect based on a redirect. - $this->mock_dpps->expects( $this->once() ) - ->method( 'check_payment_intent_attached_to_order_succeeded' ) - ->with( wc_get_order( $order ) ) - ->willReturn( $response ); - - // Assert: no more call to the server to update the intention. - $this->mock_wcpay_request( Update_Intention::class, 0 ); - - // Act: process the order but redirect to the order. - $result = $this->mock_payment_gateways[ Payment_Method::SEPA ]->process_payment( $order_id ); - - // Assert: the result of check_intent_attached_to_order_succeeded. - $this->assertSame( $response, $result ); - } - public function test_process_redirect_payment_intent_processing() { $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; @@ -1263,64 +1049,13 @@ public function test_create_token_from_setup_intent_adds_token() { } } - public function test_process_payment_rejects_with_cached_minimum_acount() { - - $order = WC_Helper_Order::create_order(); - $order->set_currency( 'USD' ); - $order->set_total( 0.45 ); - $order->save(); - - set_transient( 'wcpay_minimum_amount_usd', '50', DAY_IN_SECONDS ); - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - // Make sure that the payment was not actually processed. - $price = wp_strip_all_tags( html_entity_decode( wc_price( 0.5, [ 'currency' => 'USD' ] ) ) ); - $message = 'The selected payment method requires a total amount of at least ' . $price . '.'; - - foreach ( $this->mock_payment_gateways as $mock_payment_gateway ) { - $this->expectException( Exception::class ); - $this->expectExceptionMessage( $message ); - $mock_payment_gateway->process_payment( $order->get_id() ); - } - } - - public function test_process_payment_caches_mimimum_amount_and_displays_error_upon_exception() { - $order = WC_Helper_Order::create_order(); - $order->set_total( 0.45 ); - $order->save(); - - delete_transient( 'wcpay_minimum_amount_usd' ); - $_POST['wc_payment_intent_id'] = 'pi_mock'; - - $price = wp_strip_all_tags( html_entity_decode( wc_price( 60, [ 'currency' => 'USD' ] ) ) ); - $message = 'The selected payment method requires a total amount of at least ' . $price . '.'; - $this->expectException( Exception::class ); - $this->expectExceptionMessage( $message ); - - try { - foreach ( $this->mock_payment_gateways as $mock_payment_gateway ) { - $this->mock_wcpay_request( Update_Intention::class, 1, 'pi_mock' ) - ->expects( $this->once() ) - ->method( 'format_response' ) - ->will( $this->throwException( new Amount_Too_Small_Exception( 'Error: Amount must be at least $60 usd', 6000, 'usd', 400 ) ) ); - - $mock_payment_gateway->process_payment( $order->get_id() ); - - break; - } - } catch ( Exception $e ) { - $this->assertEquals( '6000', get_transient( 'wcpay_minimum_amount_usd' ) ); - throw $e; - } - } - /** * Test get_payment_method_types with regular checkout post request context. * * @return void */ public function test_get_payment_methods_with_request_context() { - $mock_upe_gateway = $this->getMockBuilder( UPE_Payment_Gateway::class ) + $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) ->setConstructorArgs( [ $this->mock_api_client, @@ -1365,7 +1100,7 @@ public function test_get_payment_methods_with_request_context() { * @return void */ public function test_get_payment_methods_without_request_context() { - $mock_upe_gateway = $this->getMockBuilder( UPE_Payment_Gateway::class ) + $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) ->setConstructorArgs( [ $this->mock_api_client, @@ -1409,7 +1144,7 @@ public function test_get_payment_methods_without_request_context() { * @return void */ public function test_get_payment_methods_without_request_context_or_token() { - $mock_upe_gateway = $this->getMockBuilder( UPE_Payment_Gateway::class ) + $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) ->setConstructorArgs( [ $this->mock_api_client, @@ -1462,7 +1197,7 @@ public function test_get_payment_methods_without_request_context_or_token() { */ public function test_get_payment_methods_from_gateway_id_upe() { WC_Helper_Order::create_order(); - $mock_upe_gateway = $this->getMockBuilder( UPE_Payment_Gateway::class ) + $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) ->setConstructorArgs( [ $this->mock_api_client, @@ -1496,7 +1231,7 @@ public function test_get_payment_methods_from_gateway_id_upe() { $this->returnValue( [ Payment_Method::CARD, Payment_Method::LINK ] ) ); - $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( UPE_Payment_Gateway::GATEWAY_ID . '_' . Payment_Method::BANCONTACT ); + $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID . '_' . Payment_Method::BANCONTACT ); $this->assertSame( [ Payment_Method::BANCONTACT ], $payment_methods ); $mock_upe_gateway->expects( $this->any() ) @@ -1508,10 +1243,10 @@ public function test_get_payment_methods_from_gateway_id_upe() { ) ); - $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( UPE_Payment_Gateway::GATEWAY_ID ); + $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID ); $this->assertSame( [ Payment_Method::CARD, Payment_Method::LINK ], $payment_methods ); - $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( UPE_Payment_Gateway::GATEWAY_ID ); + $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID ); $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); WC_Payments::set_gateway( $gateway ); diff --git a/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php index 94273521a05..aca6138622e 100644 --- a/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php +++ b/tests/unit/src/Internal/Payment/State/ProcessedStateTest.php @@ -16,7 +16,7 @@ use WCPay\Internal\Service\DuplicatePaymentPreventionService; use WCPay\Internal\Service\OrderService; use WCPay\Internal\Proxy\LegacyProxy; -use WCPay\Payment_Methods\UPE_Payment_Gateway; +use WC_Payment_Gateway_WCPay; use WCPAY_UnitTestCase; /** diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php b/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php index e80c97b1834..8cd1668986d 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php @@ -11,7 +11,7 @@ use WCPay\Duplicate_Payment_Prevention_Service; use WCPay\Session_Rate_Limiter; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; -use WCPay\Internal\Service\OrderService; +use WCPay\Payment_Methods\CC_Payment_Method; /** * WC_Payment_Gateway_WCPay unit tests. @@ -133,7 +133,8 @@ public function set_up() { $this->mock_order_service = $this->createMock( WC_Payments_Order_Service::class ); - $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $mock_payment_method = $this->createMock( CC_Payment_Method::class ); // Arrange: Mock WC_Payment_Gateway_WCPay so that some of its methods can be // mocked, and their return values can be used for testing. @@ -145,6 +146,8 @@ public function set_up() { $this->mock_customer_service, $this->mock_token_service, $this->mock_action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $this->mock_rate_limiter, $this->mock_order_service, $mock_dpps, diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php index da7ed2798db..1ce7132f420 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php @@ -6,19 +6,17 @@ */ use WCPay\Core\Server\Request\Create_And_Confirm_Intention; -use WCPay\Core\Server\Request\WooPay_Create_And_Confirm_Intention; use WCPay\Core\Server\Request\Create_And_Confirm_Setup_Intention; use WCPay\Core\Server\Request\Get_Charge; -use WCPay\Core\Server\Response; use WCPay\Constants\Order_Status; use WCPay\Constants\Intent_Status; -use WCPay\Core\Server\Request\Get_Intention; -use WCPay\Core\Server\Request\Update_Intention; use WCPay\Duplicate_Payment_Prevention_Service; use WCPay\Exceptions\API_Exception; use WCPay\Exceptions\Connection_Exception; use WCPay\Session_Rate_Limiter; use WCPay\Constants\Payment_Method; +use WCPay\Payment_Methods\CC_Payment_Method; + // Need to use WC_Mock_Data_Store. require_once dirname( __FILE__ ) . '/helpers/class-wc-mock-wc-data-store.php'; @@ -149,7 +147,8 @@ public function set_up() { $this->mock_order_service = $this->createMock( WC_Payments_Order_Service::class ); - $this->mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $this->mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $mock_payment_method = $this->createMock( CC_Payment_Method::class ); // Arrange: Mock WC_Payment_Gateway_WCPay so that some of its methods can be // mocked, and their return values can be used for testing. @@ -161,6 +160,8 @@ public function set_up() { $this->mock_customer_service, $this->mock_token_service, $this->mock_action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $this->mock_rate_limiter, $this->mock_order_service, $this->mock_dpps, diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php b/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php index 79c11b5fe1b..7350621aedc 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php @@ -13,6 +13,7 @@ use WCPay\Core\Server\Response; use WCPay\Duplicate_Payment_Prevention_Service; use WCPay\Exceptions\API_Exception; +use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Session_Rate_Limiter; // Need to use WC_Mock_Data_Store. @@ -89,6 +90,7 @@ public function set_up() { $this->mock_rate_limiter = $this->createMock( Session_Rate_Limiter::class ); $this->mock_order_service = $this->createMock( WC_Payments_Order_Service::class ); $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $mock_payment_method = $this->createMock( CC_Payment_Method::class ); $this->wcpay_gateway = new WC_Payment_Gateway_WCPay( $this->mock_api_client, @@ -96,6 +98,8 @@ public function set_up() { $this->mock_customer_service, $this->mock_token_service, $this->mock_action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $this->mock_rate_limiter, $this->mock_order_service, $mock_dpps, diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-payment-method-order-note.php b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-payment-method-order-note.php index 73be48c5cbc..9715d61a193 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-payment-method-order-note.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-payment-method-order-note.php @@ -6,7 +6,7 @@ */ use WCPay\Duplicate_Payment_Prevention_Service; -use WCPay\Exceptions\API_Exception; +use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Session_Rate_Limiter; /** @@ -96,6 +96,9 @@ public function set_up() { ->getMock(); $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); + $this->mock_wcpay_account + ->method( 'get_account_default_currency' ) + ->willReturn( 'USD' ); $this->mock_customer_service = $this->getMockBuilder( 'WC_Payments_Customer_Service' ) ->disableOriginalConstructor() @@ -119,12 +122,19 @@ public function set_up() { $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $mock_payment_method = $this->getMockBuilder( CC_Payment_Method::class ) + ->setConstructorArgs( [ $this->mock_token_service ] ) + ->onlyMethods( [ 'is_subscription_item_in_cart' ] ) + ->getMock(); + $this->wcpay_gateway = new \WC_Payment_Gateway_WCPay( $this->mock_api_client, $this->mock_wcpay_account, $this->mock_customer_service, $this->mock_token_service, $this->mock_action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $this->mock_session_rate_limiter, $this->mock_order_service, $mock_dpps, diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php index 0db2ec88933..168349f902d 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php @@ -7,10 +7,10 @@ use WCPay\Core\Server\Request\Create_And_Confirm_Intention; use WCPay\Core\Server\Request\Create_And_Confirm_Setup_Intention; -use WCPay\Core\Server\Response; use WCPay\Constants\Order_Status; use WCPay\Constants\Intent_Status; use WCPay\Duplicate_Payment_Prevention_Service; +use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Session_Rate_Limiter; /** @@ -122,6 +122,9 @@ public function set_up() { ->getMock(); $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); + $this->mock_wcpay_account + ->method( 'get_account_default_currency' ) + ->willReturn( 'usd' ); $this->mock_customer_service = $this->getMockBuilder( 'WC_Payments_Customer_Service' ) ->disableOriginalConstructor() @@ -139,7 +142,8 @@ public function set_up() { $this->order_service = new WC_Payments_Order_Service( $this->mock_api_client ); - $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $mock_payment_method = $this->createMock( CC_Payment_Method::class ); $this->mock_wcpay_gateway = $this->getMockBuilder( '\WC_Payment_Gateway_WCPay' ) ->setConstructorArgs( @@ -149,6 +153,8 @@ public function set_up() { $this->mock_customer_service, $this->mock_token_service, $this->mock_action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $this->mock_rate_limiter, $this->order_service, $mock_dpps, 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 9c27fda1b2d..a5b33c1581c 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php @@ -11,6 +11,7 @@ use WCPay\Exceptions\API_Exception; use WCPay\Internal\Service\Level3Service; use WCPay\Internal\Service\OrderService; +use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Session_Rate_Limiter; /** @@ -111,6 +112,9 @@ public function set_up() { ->getMock(); $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); + $this->mock_wcpay_account + ->method( 'get_account_default_currency' ) + ->willReturn( 'usd' ); $this->mock_customer_service = $this->getMockBuilder( 'WC_Payments_Customer_Service' ) ->disableOriginalConstructor() @@ -135,12 +139,19 @@ public function set_up() { $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); $this->mock_fraud_service = $this->createMock( WC_Payments_Fraud_Service::class ); + $mock_payment_method = $this->getMockBuilder( CC_Payment_Method::class ) + ->setConstructorArgs( [ $this->mock_token_service ] ) + ->onlyMethods( [ 'is_subscription_item_in_cart' ] ) + ->getMock(); + $this->wcpay_gateway = new \WC_Payment_Gateway_WCPay( $this->mock_api_client, $this->mock_wcpay_account, $this->mock_customer_service, $this->mock_token_service, $this->mock_action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $this->mock_session_rate_limiter, $this->order_service, $this->mock_dpps, @@ -148,6 +159,7 @@ public function set_up() { $this->mock_fraud_service ); $this->wcpay_gateway->init_hooks(); + WC_Payments::set_gateway( $this->wcpay_gateway ); // Mock the level3 service to always return an empty array. $mock_level3_service = $this->createMock( Level3Service::class ); @@ -801,12 +813,19 @@ public function test_adds_custom_payment_meta_input_fallback_until_subs_3_0_7() WC_Subscriptions::$version = '3.0.7'; + $mock_payment_method = $this->getMockBuilder( CC_Payment_Method::class ) + ->setConstructorArgs( [ $this->mock_token_service ] ) + ->onlyMethods( [ 'is_subscription_item_in_cart' ] ) + ->getMock(); + $payment_gateway = new \WC_Payment_Gateway_WCPay( $this->mock_api_client, $this->mock_wcpay_account, $this->mock_customer_service, $this->mock_token_service, $this->mock_action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $this->mock_session_rate_limiter, $this->order_service, $this->mock_dpps, @@ -827,6 +846,11 @@ public function test_adds_custom_payment_meta_input_fallback_until_subs_3_0_7() public function test_does_not_add_custom_payment_meta_input_fallback_for_subs_3_0_8() { remove_all_actions( 'woocommerce_admin_order_data_after_billing_address' ); + $mock_payment_method = $this->getMockBuilder( CC_Payment_Method::class ) + ->setConstructorArgs( [ $this->mock_token_service ] ) + ->onlyMethods( [ 'is_subscription_item_in_cart' ] ) + ->getMock(); + WC_Subscriptions::$version = '3.0.8'; new \WC_Payment_Gateway_WCPay( $this->mock_api_client, @@ -834,6 +858,8 @@ public function test_does_not_add_custom_payment_meta_input_fallback_for_subs_3_ $this->mock_customer_service, $this->mock_token_service, $this->mock_action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $this->mock_session_rate_limiter, $this->order_service, $this->mock_dpps, diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index 3eb69d5cfb5..02505f94d51 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -13,9 +13,7 @@ use WCPay\Core\Server\Request\Get_Charge; use WCPay\Core\Server\Request\Get_Intention; use WCPay\Core\Server\Request\Get_Setup_Intention; -use WCPay\Core\Server\Request\Update_Intention; use WCPay\Constants\Order_Status; -use WCPay\Constants\Payment_Type; use WCPay\Constants\Intent_Status; use WCPay\Duplicate_Payment_Prevention_Service; use WCPay\Exceptions\Amount_Too_Small_Exception; @@ -30,7 +28,6 @@ use WCPay\Payment_Information; use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Payment_Methods\Sepa_Payment_Method; -use WCPay\Payment_Methods\UPE_Payment_Gateway; use WCPay\WooPay\WooPay_Utilities; use WCPay\Session_Rate_Limiter; @@ -48,7 +45,7 @@ class WC_Payment_Gateway_WCPay_Test extends WCPAY_UnitTestCase { /** * System under test. * - * @var UPE_Payment_Gateway + * @var WC_Payment_Gateway_WCPay */ private $wcpay_gateway; @@ -214,7 +211,7 @@ public function set_up() { ->setMethods( [ 'is_subscription_item_in_cart' ] ) ->getMock(); - $this->wcpay_gateway = new UPE_Payment_Gateway( + $this->wcpay_gateway = new WC_Payment_Gateway_WCPay( $this->mock_api_client, $this->mock_wcpay_account, $this->mock_customer_service, @@ -2006,7 +2003,7 @@ public function test_is_in_test_mode() { * @return MockObject|WC_Payment_Gateway_WCPay */ private function get_partial_mock_for_gateway( array $methods = [] ) { - return $this->getMockBuilder( UPE_Payment_Gateway::class ) + return $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) ->setConstructorArgs( [ $this->mock_api_client, @@ -2363,7 +2360,7 @@ private function create_charge_object() { } private function create_gateway_with( $payment_method ) { - return new UPE_Payment_Gateway( + return new WC_Payment_Gateway_WCPay( $this->mock_api_client, $this->mock_wcpay_account, $this->mock_customer_service, diff --git a/tests/unit/test-class-wc-payments-checkout.php b/tests/unit/test-class-wc-payments-checkout.php index b187c4f51ce..d6273a1fcd2 100644 --- a/tests/unit/test-class-wc-payments-checkout.php +++ b/tests/unit/test-class-wc-payments-checkout.php @@ -8,7 +8,6 @@ use WCPay\WC_Payments_Checkout; use PHPUnit\Framework\MockObject\MockObject; use WCPay\Constants\Payment_Method; -use WCPay\Payment_Methods\UPE_Payment_Gateway; use WCPay\WooPay\WooPay_Utilities; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; use WCPay\Payment_Methods\Bancontact_Payment_Method; @@ -36,9 +35,9 @@ class WC_Payments_Checkout_Test extends WP_UnitTestCase { private $system_under_test; /** - * UPE_Payment_Gateway instance. + * WC_Payment_Gateway_WCPay instance. * - * @var UPE_Payment_Gateway|MockObject + * @var WC_Payment_Gateway_WCPay|MockObject */ private $mock_wcpay_gateway; @@ -81,7 +80,7 @@ class WC_Payments_Checkout_Test extends WP_UnitTestCase { /** * Default gateway. * - * @var UPE_Payment_Gateway + * @var WC_Payment_Gateway_WCPay */ private $default_gateway; @@ -89,7 +88,7 @@ public function set_up() { parent::set_up(); // Setup the gateway mock. - $this->mock_wcpay_gateway = $this->getMockBuilder( UPE_Payment_Gateway::class ) + $this->mock_wcpay_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) ->onlyMethods( [ 'get_account_domestic_currency', 'get_payment_method_ids_enabled_at_checkout', 'should_use_stripe_platform_on_checkout_page', 'should_support_saved_payments', 'is_saved_cards_enabled', 'save_payment_method_checkbox', 'get_account_statement_descriptor', 'get_icon_url', 'get_payment_method_ids_enabled_at_checkout_filtered_by_fees', 'is_subscription_item_in_cart', 'wc_payments_get_payment_method_by_id', 'display_gateway_html' ] ) ->disableOriginalConstructor() ->getMock(); @@ -119,8 +118,7 @@ public function set_up() { $this->mock_token_service = $this->createMock( WC_Payments_Token_Service::class ); // This is needed to ensure that only the mocked gateway is always used by the checkout class. - $this->default_gateway = WC_Payments::get_registered_card_gateway(); - WC_Payments::set_registered_card_gateway( $this->mock_wcpay_gateway ); + $this->default_gateway = WC_Payments::get_gateway(); WC_Payments::set_gateway( $this->mock_wcpay_gateway ); // Use a callback to suppresses the output buffering being printed to the CLI. @@ -135,7 +133,6 @@ function ( $output ) { public function tear_down() { parent::tear_down(); - WC_Payments::set_registered_card_gateway( $this->default_gateway ); WC_Payments::set_gateway( $this->default_gateway ); } diff --git a/tests/unit/test-class-wc-payments-express-checkout-button-display-handler.php b/tests/unit/test-class-wc-payments-express-checkout-button-display-handler.php index dfb059ba7b4..899e530aa67 100644 --- a/tests/unit/test-class-wc-payments-express-checkout-button-display-handler.php +++ b/tests/unit/test-class-wc-payments-express-checkout-button-display-handler.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\MockObject\MockObject; use WCPay\Duplicate_Payment_Prevention_Service; +use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Session_Rate_Limiter; use WCPay\WooPay\WooPay_Utilities; @@ -148,6 +149,7 @@ private function make_wcpay_gateway() { $mock_rate_limiter = $this->createMock( Session_Rate_Limiter::class ); $mock_order_service = $this->createMock( WC_Payments_Order_Service::class ); $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $mock_payment_method = $this->createMock( CC_Payment_Method::class ); return new WC_Payment_Gateway_WCPay( $this->mock_api_client, @@ -155,6 +157,8 @@ private function make_wcpay_gateway() { $mock_customer_service, $mock_token_service, $mock_action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $mock_rate_limiter, $mock_order_service, $mock_dpps, diff --git a/tests/unit/test-class-wc-payments-payment-request-button-handler.php b/tests/unit/test-class-wc-payments-payment-request-button-handler.php index ce1283265b4..e4d7412bf15 100644 --- a/tests/unit/test-class-wc-payments-payment-request-button-handler.php +++ b/tests/unit/test-class-wc-payments-payment-request-button-handler.php @@ -6,6 +6,7 @@ */ use WCPay\Duplicate_Payment_Prevention_Service; +use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Session_Rate_Limiter; /** @@ -198,6 +199,7 @@ private function make_wcpay_gateway() { $mock_rate_limiter = $this->createMock( Session_Rate_Limiter::class ); $mock_order_service = $this->createMock( WC_Payments_Order_Service::class ); $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $mock_payment_method = $this->createMock( CC_Payment_Method::class ); return new WC_Payment_Gateway_WCPay( $this->mock_api_client, @@ -205,6 +207,8 @@ private function make_wcpay_gateway() { $mock_customer_service, $mock_token_service, $mock_action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $mock_rate_limiter, $mock_order_service, $mock_dpps, diff --git a/tests/unit/test-class-wc-payments-woopay-button-handler.php b/tests/unit/test-class-wc-payments-woopay-button-handler.php index 78f464cadf2..161c5796101 100644 --- a/tests/unit/test-class-wc-payments-woopay-button-handler.php +++ b/tests/unit/test-class-wc-payments-woopay-button-handler.php @@ -6,6 +6,7 @@ */ use WCPay\Duplicate_Payment_Prevention_Service; +use WCPay\Payment_Methods\CC_Payment_Method; use WCPay\Session_Rate_Limiter; use WCPay\WooPay\WooPay_Utilities; @@ -127,6 +128,7 @@ private function make_wcpay_gateway() { $mock_rate_limiter = $this->createMock( Session_Rate_Limiter::class ); $mock_order_service = $this->createMock( WC_Payments_Order_Service::class ); $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + $mock_payment_method = $this->createMock( CC_Payment_Method::class ); return new WC_Payment_Gateway_WCPay( $this->mock_api_client, @@ -134,6 +136,8 @@ private function make_wcpay_gateway() { $mock_customer_service, $mock_token_service, $mock_action_scheduler_service, + $mock_payment_method, + [ 'card' => $mock_payment_method ], $mock_rate_limiter, $mock_order_service, $mock_dpps, diff --git a/tests/unit/test-class-wc-payments.php b/tests/unit/test-class-wc-payments.php index ce6aa216a1d..d5e7da12239 100644 --- a/tests/unit/test-class-wc-payments.php +++ b/tests/unit/test-class-wc-payments.php @@ -5,7 +5,6 @@ * @package WooCommerce\Payments\Tests */ -use WCPay\Payment_Methods\UPE_Payment_Gateway; use WCPay\WooPay\WooPay_Session; /** @@ -81,7 +80,7 @@ public function test_it_does_not_register_woopay_hooks_if_feature_flag_is_disabl public function test_it_skips_stripe_link_gateway_registration() { $this->mock_cache->method( 'get' )->willReturn( [ 'is_deferred_intent_creation_upe_enabled' => true ] ); - $card_gateway_mock = $this->createMock( UPE_Payment_Gateway::class ); + $card_gateway_mock = $this->createMock( WC_Payment_Gateway_WCPay::class ); $card_gateway_mock ->expects( $this->once() ) ->method( 'get_payment_method_ids_enabled_at_checkout' ) @@ -100,7 +99,7 @@ public function test_it_skips_stripe_link_gateway_registration() { $registered_gateways = WC_Payments::register_gateway( [] ); $this->assertCount( 1, $registered_gateways ); - $this->assertInstanceOf( UPE_Payment_Gateway::class, $registered_gateways[0] ); + $this->assertInstanceOf( WC_Payment_Gateway_WCPay::class, $registered_gateways[0] ); $this->assertEquals( $registered_gateways[0]->get_stripe_id(), 'card' ); } diff --git a/woocommerce-payments.php b/woocommerce-payments.php index df1a348ec90..df7afe47766 100644 --- a/woocommerce-payments.php +++ b/woocommerce-payments.php @@ -9,7 +9,7 @@ * Text Domain: woocommerce-payments * Domain Path: /languages * WC requires at least: 7.6 - * WC tested up to: 8.3.1 + * WC tested up to: 8.4.0 * Requires at least: 6.0 * Requires PHP: 7.3 * Version: 6.9.2