Plugin Directory

source: woocommerce-gateway-stripe/trunk/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php

Last change on this file was 3393884, checked in by wesleyjrosa, 10 days ago

Tagging version 10.1.0

File size: 138.7 KB
Line 
1<?php
2
3use Automattic\WooCommerce\Enums\OrderStatus;
4
5if ( ! defined( 'ABSPATH' ) ) {
6        exit;
7}
8
9/**
10* Class that handles UPE payment method.
11*
12* @extends WC_Gateway_Stripe
13*
14* @since 5.5.0
15*/
16class WC_Stripe_UPE_Payment_Gateway extends WC_Gateway_Stripe {
17
18        const ID = 'stripe';
19
20        /**
21         * Upe Available Methods
22         *
23         * @type WC_Stripe_UPE_Payment_Method[]
24         */
25        const UPE_AVAILABLE_METHODS = [
26                WC_Stripe_Payment_Methods::CARD              => WC_Stripe_UPE_Payment_Method_CC::class,
27                WC_Stripe_Payment_Methods::ACH               => WC_Stripe_UPE_Payment_Method_ACH::class,
28                WC_Stripe_Payment_Methods::ALIPAY            => WC_Stripe_UPE_Payment_Method_Alipay::class,
29                WC_Stripe_Payment_Methods::AMAZON_PAY        => WC_Stripe_UPE_Payment_Method_Amazon_Pay::class,
30                WC_Stripe_Payment_Methods::BLIK              => WC_Stripe_UPE_Payment_Method_BLIK::class,
31                WC_Stripe_Payment_Methods::GIROPAY           => WC_Stripe_UPE_Payment_Method_Giropay::class,
32                WC_Stripe_Payment_Methods::KLARNA            => WC_Stripe_UPE_Payment_Method_Klarna::class,
33                WC_Stripe_Payment_Methods::AFFIRM            => WC_Stripe_UPE_Payment_Method_Affirm::class,
34                WC_Stripe_Payment_Methods::AFTERPAY_CLEARPAY => WC_Stripe_UPE_Payment_Method_Afterpay_Clearpay::class,
35                WC_Stripe_Payment_Methods::EPS               => WC_Stripe_UPE_Payment_Method_Eps::class,
36                WC_Stripe_Payment_Methods::BANCONTACT        => WC_Stripe_UPE_Payment_Method_Bancontact::class,
37                WC_Stripe_Payment_Methods::BOLETO            => WC_Stripe_UPE_Payment_Method_Boleto::class,
38                WC_Stripe_Payment_Methods::IDEAL             => WC_Stripe_UPE_Payment_Method_Ideal::class,
39                WC_Stripe_Payment_Methods::OXXO              => WC_Stripe_UPE_Payment_Method_Oxxo::class,
40                WC_Stripe_Payment_Methods::SEPA_DEBIT        => WC_Stripe_UPE_Payment_Method_Sepa::class,
41                WC_Stripe_Payment_Methods::P24               => WC_Stripe_UPE_Payment_Method_P24::class,
42                WC_Stripe_Payment_Methods::SOFORT            => WC_Stripe_UPE_Payment_Method_Sofort::class,
43                WC_Stripe_Payment_Methods::MULTIBANCO        => WC_Stripe_UPE_Payment_Method_Multibanco::class,
44                WC_Stripe_Payment_Methods::LINK              => WC_Stripe_UPE_Payment_Method_Link::class,
45                WC_Stripe_Payment_Methods::WECHAT_PAY        => WC_Stripe_UPE_Payment_Method_Wechat_Pay::class,
46                WC_Stripe_Payment_Methods::CASHAPP_PAY       => WC_Stripe_UPE_Payment_Method_Cash_App_Pay::class,
47                WC_Stripe_Payment_Methods::ACSS_DEBIT        => WC_Stripe_UPE_Payment_Method_ACSS::class,
48                WC_Stripe_Payment_Methods::BACS_DEBIT        => WC_Stripe_UPE_Payment_Method_Bacs_Debit::class,
49                WC_Stripe_Payment_Methods::BECS_DEBIT        => WC_Stripe_UPE_Payment_Method_Becs_Debit::class,
50        ];
51
52        /**
53         * Stripe intents that are treated as successfully created.
54         *
55         * @type array
56         *
57         * @deprecated 9.1.0
58         */
59        const SUCCESSFUL_INTENT_STATUS = [ 'succeeded', 'requires_capture', 'processing' ];
60
61        /**
62         * Transient name for appearance settings.
63         *
64         * @type string
65         */
66        const APPEARANCE_TRANSIENT = 'wc_stripe_appearance';
67
68        /**
69         * Transient name for appearance settings on the block checkout.
70         *
71         * @type string
72         */
73        const BLOCKS_APPEARANCE_TRANSIENT = 'wc_stripe_blocks_appearance';
74
75        /**
76         * The default layout for the Optimized Checkout.
77         *
78         * @var string
79         */
80        public const OPTIMIZED_CHECKOUT_DEFAULT_LAYOUT = 'accordion';
81
82        /**
83         * Notices (array)
84         *
85         * @var array
86         */
87        public $notices = [];
88
89        /**
90         * Is test mode active?
91         *
92         * @var bool
93         */
94        public $testmode;
95
96        /**
97         * Alternate credit card statement name
98         *
99         * @var bool
100         */
101        public $statement_descriptor;
102
103        /**
104         * Are saved cards enabled
105         *
106         * @var bool
107         */
108        public $saved_cards;
109
110        /**
111         * Should SEPA tokens be used for other payment methods (iDEAL and Bancontact)
112         *
113         * @var bool
114         *
115         * @deprecated 10.0.0 Use `sepa_tokens_for_ideal` and `sepa_tokens_for_bancontact` instead.
116         */
117        public $sepa_tokens_for_other_methods;
118
119        /**
120         * Should SEPA tokens be used for iDEAL
121         *
122         * @var bool
123         */
124        public $sepa_tokens_for_ideal;
125
126        /**
127         * Should SEPA tokens be used for Bancontact
128         *
129         * @var bool
130         */
131        public $sepa_tokens_for_bancontact;
132
133        /**
134         * Is Single Payment Element enabled?
135         *
136         * @var bool
137         *
138         * @deprecated 9.5.0 Use `oc_enabled`.
139         */
140        public $spe_enabled;
141
142        /**
143         * Is Optimized Checkout enabled?
144         *
145         * @var bool
146         */
147        public $oc_enabled;
148
149        /**
150         * API access secret key
151         *
152         * @var string
153         */
154        public $secret_key;
155
156        /**
157         * Api access publishable key
158         *
159         * @var string
160         */
161        public $publishable_key;
162
163        /**
164         * Instance of WC_Stripe_Intent_Controller.
165         *
166         * @var WC_Stripe_Intent_Controller
167         */
168        public $intent_controller;
169
170        /**
171         * WC_Stripe_Action_Scheduler_Service instance for scheduling ActionScheduler jobs.
172         *
173         * @var WC_Stripe_Action_Scheduler_Service
174         */
175        public $action_scheduler_service;
176
177        /**
178         * Array mapping payment method string IDs to classes
179         *
180         * @var WC_Stripe_UPE_Payment_Method[]
181         */
182        public $payment_methods = [];
183
184        /**
185         * Constructor
186         */
187        public function __construct() {
188                $this->id           = self::ID;
189                $this->method_title = __( 'Stripe', 'woocommerce-gateway-stripe' );
190                /* translators: link */
191                $this->method_description = __( 'Accept debit and credit cards in 135+ currencies, methods such as SEPA, and one-touch checkout with Apple Pay.', 'woocommerce-gateway-stripe' );
192                $this->has_fields         = true;
193                $this->supports           = [
194                        'products',
195                        'refunds',
196                        'tokenization',
197                        'add_payment_method',
198                ];
199
200                $enabled_payment_methods = $this->get_upe_enabled_payment_method_ids();
201                $is_sofort_enabled       = in_array( WC_Stripe_Payment_Methods::SOFORT, $enabled_payment_methods, true );
202
203                $main_settings    = WC_Stripe_Helper::get_stripe_settings();
204                $this->oc_enabled = WC_Stripe_Feature_Flags::is_oc_available() && 'yes' === $this->get_option( 'optimized_checkout_element' );
205
206                $this->payment_methods = [];
207                foreach ( self::UPE_AVAILABLE_METHODS as $payment_method_class ) {
208                        /** Show Sofort if it's already enabled. Hide from the new merchants and keep it for the old ones who are already using this gateway, until we remove it completely.
209                         * Stripe is deprecating Sofort https://support.stripe.com/questions/sofort-is-being-deprecated-as-a-standalone-payment-method.
210                         */
211                        if ( WC_Stripe_UPE_Payment_Method_Sofort::class === $payment_method_class && ! $is_sofort_enabled ) {
212                                continue;
213                        }
214
215                        // Show giropay only on the orders page to allow refunds. It was deprecated.
216                        if ( WC_Stripe_UPE_Payment_Method_Giropay::class === $payment_method_class && ! $this->is_order_details_page() && ! $this->is_refund_request() ) {
217                                continue;
218                        }
219
220                        $payment_method                                     = new $payment_method_class();
221                        $this->payment_methods[ $payment_method->get_id() ] = $payment_method;
222                }
223
224                $this->intent_controller        = new WC_Stripe_Intent_Controller();
225                $this->action_scheduler_service = new WC_Stripe_Action_Scheduler_Service();
226
227                // Load the form fields.
228                $this->init_form_fields();
229
230                // Load the settings.
231                $this->init_settings();
232
233                // Check if subscriptions are enabled and add support for them.
234                $this->maybe_init_subscriptions();
235
236                // Check if pre-orders are enabled and add support for them.
237                $this->maybe_init_pre_orders();
238
239                $this->title                         = $this->payment_methods['card']->get_title();
240                $this->description                   = $this->payment_methods['card']->get_description();
241                $this->enabled                       = $this->get_option( 'enabled' );
242                $this->sepa_tokens_for_ideal         = 'yes' === $this->get_option( 'sepa_tokens_for_ideal' );
243                $this->sepa_tokens_for_bancontact    = 'yes' === $this->get_option( 'sepa_tokens_for_bancontact' );
244                $this->saved_cards                   = 'yes' === $this->get_option( 'saved_cards' );
245                $this->testmode                      = WC_Stripe_Mode::is_test();
246                $this->publishable_key               = ! empty( $main_settings['publishable_key'] ) ? $main_settings['publishable_key'] : '';
247                $this->secret_key                    = ! empty( $main_settings['secret_key'] ) ? $main_settings['secret_key'] : '';
248                $this->statement_descriptor          = ! empty( $main_settings['statement_descriptor'] ) ? $main_settings['statement_descriptor'] : '';
249
250                // When feature flags are enabled, title shows the count of enabled payment methods in settings page only.
251                if ( WC_Stripe_Feature_Flags::is_upe_checkout_enabled() && WC_Stripe_Feature_Flags::is_upe_preview_enabled() && isset( $_GET['page'] ) && 'wc-settings' === $_GET['page'] && isset( $_GET['tab'] ) && 'checkout' === $_GET['tab'] ) {
252                        $enabled_payment_methods_count = count( $enabled_payment_methods );
253                        $this->title                   = $enabled_payment_methods_count ?
254                                /* translators: $1. Count of enabled payment methods. */
255                                sprintf( _n( '%d payment method', '%d payment methods', $enabled_payment_methods_count, 'woocommerce-gateway-stripe' ), $enabled_payment_methods_count )
256                                : $this->method_title;
257                }
258
259                if ( $this->testmode ) {
260                        $this->publishable_key = ! empty( $main_settings['test_publishable_key'] ) ? $main_settings['test_publishable_key'] : '';
261                        $this->secret_key      = ! empty( $main_settings['test_secret_key'] ) ? $main_settings['test_secret_key'] : '';
262                }
263
264                add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, [ $this, 'process_admin_options' ] );
265
266                add_action( 'wp_footer', [ $this, 'payment_scripts' ] );
267
268                // Display the correct fees on the order page.
269                add_action( 'woocommerce_admin_order_totals_after_total', [ $this, 'display_order_fee' ] );
270                add_action( 'woocommerce_admin_order_totals_after_total', [ $this, 'display_order_payout' ], 20 );
271
272                // Needed for 3DS compatibility when checking out with PRBs..
273                // Copied from WC_Gateway_Stripe::__construct().
274                add_filter( 'woocommerce_payment_successful_result', [ $this, 'modify_successful_payment_result' ], 99999, 2 );
275
276                // Update the current request logged_in cookie after a guest user is created to avoid nonce inconsistencies.
277                add_action( 'set_logged_in_cookie', [ $this, 'set_cookie_on_current_request' ] );
278
279                add_action( 'wc_ajax_wc_stripe_save_appearance', [ $this, 'save_appearance_ajax' ] );
280
281                add_filter( 'woocommerce_saved_payment_methods_list', [ $this, 'filter_saved_payment_methods_list' ], 10, 2 );
282
283                // Add hooks for clearing appearance transients when theme is updated
284                add_action( 'customize_save_after', [ $this, 'clear_appearance_transients' ] );
285                add_action( 'save_post', [ $this, 'clear_appearance_transients_block_theme' ], 10, 2 );
286
287                // Hide action buttons for pending orders if they take a while to be confirmed.
288                add_filter( 'woocommerce_my_account_my_orders_actions', [ $this, 'filter_my_account_my_orders_actions' ], 10, 2 );
289
290                // Allow the display property in inline styles to hide payment method instructions (see `get_testing_instructions_for_optimized_checkout`)
291                // And to display notices in the admin pages with stylized action buttons
292                add_filter(
293                        'safe_style_css',
294                        function ( $styles ) {
295                                return is_array( $styles ) ? array_merge( $styles, [ 'display' ] ) : [ 'display' ];
296                        }
297                );
298
299                // Add metadata to Stripe intents for easier debugging of BNPL issues.
300                add_filter( 'wc_stripe_intent_metadata', [ $this, 'add_bnpl_debug_metadata' ], 10, 2 );
301        }
302
303        /**
304         * Returns the payment method instance for the given payment method name.
305         *
306         * @param $payment_method string The payment method name.
307         * @return WC_Stripe_UPE_Payment_Method|null The payment method instance.
308         */
309        public static function get_payment_method_instance( $payment_method ) {
310                $payment_method_class = self::UPE_AVAILABLE_METHODS[ $payment_method ] ?? null;
311                if ( ! $payment_method_class ) {
312                        return null;
313                }
314                return new $payment_method_class();
315        }
316
317        /**
318         * Returns the HTML for the bundled payment instructions when Optimized Checkout (previously known as Smart Checkout and SPE) is enabled.
319         *
320         * @return string
321         *
322         * @deprecated 10.0.0 Use `WC_Stripe_UPE_Payment_Method_OC::get_testing_instructions()` instead.
323         */
324        public static function get_testing_instructions_for_optimized_checkout() {
325                $payment_method = new WC_Stripe_UPE_Payment_Method_OC();
326                return $payment_method->get_testing_instructions();
327        }
328
329        /**
330         * Removes all saved payment methods when the setting to save cards is disabled.
331         *
332         * @param  array $list         List of payment methods passed from wc_get_customer_saved_methods_list().
333         * @param  int   $customer_id  The customer to fetch payment methods for.
334         * @return array               Filtered list of customers payment methods.
335         */
336        public function filter_saved_payment_methods_list( $list, $customer_id ) {
337                if ( ! $this->saved_cards ) {
338                        return [];
339                }
340                return $list;
341        }
342
343        /**
344         * Proceed with current request using new login session (to ensure consistent nonce).
345         *
346         * @param string $cookie New cookie value.
347         */
348        public function set_cookie_on_current_request( $cookie ) {
349                $_COOKIE[ LOGGED_IN_COOKIE ] = $cookie;
350        }
351
352        /**
353         * Hides refund through stripe when payment method does not allow refund
354         *
355         * @param WC_Order $order
356         *
357         * @return array|bool
358         */
359        public function can_refund_order( $order ) {
360                $upe_payment_type = WC_Stripe_Order_Helper::get_instance()->get_stripe_upe_payment_type( $order );
361
362                if ( ! $upe_payment_type ) {
363                        return true;
364                }
365
366                return $this->payment_methods[ $upe_payment_type ]->can_refund_via_stripe();
367        }
368
369        /**
370         * Gets the payment method's icon.
371         *
372         * @return string The icon HTML.
373         */
374        public function get_icon() {
375                $icons = WC_Stripe::get_instance()->get_main_stripe_gateway()->payment_icons();
376                return isset( $icons['cards'] ) ? apply_filters( 'woocommerce_gateway_icon', $icons['cards'], $this->id ) : parent::get_icon();
377        }
378
379        /**
380         * Initialize Gateway Settings Form Fields.
381         */
382        public function init_form_fields() {
383                $this->form_fields = require WC_STRIPE_PLUGIN_PATH . '/includes/admin/stripe-settings.php';
384                unset( $this->form_fields['inline_cc_form'] );
385                unset( $this->form_fields['title'] );
386                unset( $this->form_fields['description'] );
387        }
388
389        /**
390         * Outputs scripts used for stripe payment
391         */
392        public function payment_scripts() {
393                if (
394                        ! is_product()
395                        && ! WC_Stripe_Helper::has_cart_or_checkout_on_current_page()
396                        && ! parent::is_valid_pay_for_order_endpoint()
397                        && ! is_add_payment_method_page() ) {
398                        return;
399                }
400
401                if ( is_product() && ! WC_Stripe_Helper::should_load_scripts_on_product_page() ) {
402                        return;
403                }
404
405                if ( is_cart() && ! WC_Stripe_Helper::should_load_scripts_on_cart_page() ) {
406                        return;
407                }
408
409                // Bail if Stripe is not enabled.
410                if ( 'no' === $this->enabled ) {
411                        return;
412                }
413
414                $asset_path   = WC_STRIPE_PLUGIN_PATH . '/build/checkout_upe.asset.php';
415                $version      = WC_STRIPE_VERSION;
416                $dependencies = [];
417                if ( file_exists( $asset_path ) ) {
418                        $asset        = require $asset_path;
419                        $version      = is_array( $asset ) && isset( $asset['version'] )
420                                ? $asset['version']
421                                : $version;
422                        $dependencies = is_array( $asset ) && isset( $asset['dependencies'] )
423                                ? $asset['dependencies']
424                                : $dependencies;
425                }
426
427                wp_register_script(
428                        'stripe',
429                        'https://js.stripe.com/v3/',
430                        [],
431                        '3.0',
432                        true
433                );
434
435                wp_register_script(
436                        'wc-stripe-upe-classic',
437                        WC_STRIPE_PLUGIN_URL . '/build/upe-classic.js',
438                        array_merge( [ 'stripe', 'wc-checkout' ], $dependencies ),
439                        $version,
440                        true
441                );
442                wp_set_script_translations(
443                        'wc-stripe-upe-classic',
444                        'woocommerce-gateway-stripe'
445                );
446
447                wp_localize_script(
448                        'wc-stripe-upe-classic',
449                        'wc_stripe_upe_params',
450                        apply_filters( 'wc_stripe_upe_params', $this->javascript_params() )
451                );
452
453                wp_register_style(
454                        'wc-stripe-upe-classic',
455                        WC_STRIPE_PLUGIN_URL . '/build/upe-classic.css',
456                        [],
457                        $version
458                );
459
460                wp_enqueue_script( 'wc-stripe-upe-classic' );
461                wp_enqueue_style( 'wc-stripe-upe-classic' );
462
463                wp_register_style( 'stripelink_styles', plugins_url( 'assets/css/stripe-link.css', WC_STRIPE_MAIN_FILE ), [], WC_STRIPE_VERSION );
464                wp_enqueue_style( 'stripelink_styles' );
465        }
466
467        /**
468         * Returns the JavaScript configuration object used on the product, cart, and checkout pages.
469         *
470         * @return array  The configuration object to be loaded to JS.
471         */
472        public function javascript_params() {
473                global $wp;
474
475                $is_change_payment_method = $this->is_changing_payment_method_for_subscription();
476                $stripe_params            = [
477                        'gatewayId'    => self::ID,
478                        'title'        => $this->title,
479                        'isUPEEnabled' => true,
480                        'key'          => $this->publishable_key,
481                        'locale'       => WC_Stripe_Helper::convert_wc_locale_to_stripe_locale( get_locale() ),
482                        'apiVersion'   => WC_Stripe_API::STRIPE_API_VERSION,
483                ];
484
485                $enabled_billing_fields = [];
486                foreach ( WC()->checkout()->get_checkout_fields( 'billing' ) as $billing_field => $billing_field_options ) {
487                        if ( ! isset( $billing_field_options['enabled'] ) || $billing_field_options['enabled'] ) {
488                                $enabled_billing_fields[] = $billing_field;
489                        }
490                }
491
492                $express_checkout_helper = new WC_Stripe_Express_Checkout_Helper();
493
494                $is_signup_on_checkout_allowed = 'yes' === get_option( 'woocommerce_enable_signup_and_login_from_checkout', 'no' )
495                        || ( $this->is_subscription_item_in_cart() && 'yes' === get_option( 'woocommerce_enable_signup_from_checkout_for_subscriptions', 'no' ) );
496
497                $stripe_params['isLoggedIn']                        = is_user_logged_in();
498                $stripe_params['isSignupOnCheckoutAllowed']         = $is_signup_on_checkout_allowed;
499                $stripe_params['isCheckout']                        = ( is_checkout() || has_block( 'woocommerce/checkout' ) ) && empty( $_GET['pay_for_order'] ); // wpcs: csrf ok.
500                $stripe_params['return_url']                        = $this->get_stripe_return_url();
501                $stripe_params['ajax_url']                          = WC_AJAX::get_endpoint( '%%endpoint%%' );
502                $stripe_params['wp_ajax_url']                       = admin_url( 'admin-ajax.php' );
503                $stripe_params['theme_name']                        = get_option( 'stylesheet' );
504                $stripe_params['testMode']                          = $this->testmode;
505                $stripe_params['createPaymentIntentNonce']          = wp_create_nonce( 'wc_stripe_create_payment_intent_nonce' );
506                $stripe_params['updatePaymentIntentNonce']          = wp_create_nonce( 'wc_stripe_update_payment_intent_nonce' );
507                $stripe_params['createSetupIntentNonce']            = wp_create_nonce( 'wc_stripe_create_setup_intent_nonce' );
508                $stripe_params['createAndConfirmSetupIntentNonce']  = wp_create_nonce( 'wc_stripe_create_and_confirm_setup_intent_nonce' );
509                $stripe_params['updateFailedOrderNonce']            = wp_create_nonce( 'wc_stripe_update_failed_order_nonce' );
510                $stripe_params['paymentMethodsConfig']              = $this->get_enabled_payment_method_config();
511                $stripe_params['genericErrorMessage']               = __( 'There was a problem processing the payment. Please check your email inbox and refresh the page to try again.', 'woocommerce-gateway-stripe' );
512                $stripe_params['accountDescriptor']                 = $this->statement_descriptor;
513                $stripe_params['addPaymentReturnURL']               = wc_get_account_endpoint_url( 'payment-methods' );
514                $stripe_params['orderReceivedURL']                  = $this->get_return_url(); // $order argument is intentionally left empty as a fallback.
515                $stripe_params['enabledBillingFields']              = $enabled_billing_fields;
516                $stripe_params['cartContainsSubscription']          = $this->is_subscription_item_in_cart();
517                $stripe_params['subscriptionRequiresManualRenewal'] = WC_Stripe_Subscriptions_Helper::is_manual_renewal_required();
518                $stripe_params['subscriptionManualRenewalEnabled']  = WC_Stripe_Subscriptions_Helper::is_manual_renewal_enabled();
519                $stripe_params['forceSavePaymentMethod']            = WC_Stripe_Helper::should_force_save_payment_method();
520                $stripe_params['accountCountry']                    = WC_Stripe::get_instance()->account->get_account_country();
521                $stripe_params['isPaymentRequestEnabled']           = $express_checkout_helper->is_payment_request_enabled();
522                $stripe_params['isAmazonPayEnabled']                = $express_checkout_helper->is_amazon_pay_enabled();
523                $stripe_params['isLinkEnabled']                     = $express_checkout_helper->is_link_enabled();
524
525                // Add appearance settings.
526                $stripe_params['appearance']          = get_transient( $this->get_appearance_transient_key() );
527                $stripe_params['blocksAppearance']    = get_transient( $this->get_appearance_transient_key( true ) );
528                $stripe_params['saveAppearanceNonce'] = wp_create_nonce( 'wc_stripe_save_appearance_nonce' );
529
530                // ECE feature flag
531                $stripe_params['isECEEnabled'] = WC_Stripe_Feature_Flags::is_stripe_ece_enabled();
532
533                // Amazon Pay feature flag.
534                $stripe_params['isAmazonPayAvailable'] = WC_Stripe_Feature_Flags::is_amazon_pay_available();
535
536                // Optimized Checkout feature flag + setting.
537                $stripe_params['isOCEnabled'] = $this->oc_enabled;
538                $stripe_params['OCLayout']    = $this->get_option( 'optimized_checkout_layout', self::OPTIMIZED_CHECKOUT_DEFAULT_LAYOUT );
539
540                // Single Payment Element payment method parent configuration ID
541                $stripe_params['paymentMethodConfigurationParentId'] = WC_Stripe_Payment_Method_Configurations::get_parent_configuration_id();
542
543                // Checking for other BNPL extensions.
544                $stripe_params['hasAffirmGatewayPlugin'] = WC_Stripe_Helper::has_gateway_plugin_active( WC_Stripe_Helper::OFFICIAL_PLUGIN_ID_AFFIRM );
545                $stripe_params['hasKlarnaGatewayPlugin'] = WC_Stripe_Helper::has_gateway_plugin_active( WC_Stripe_Helper::OFFICIAL_PLUGIN_ID_KLARNA );
546
547                $cart_total = ( WC()->cart ? WC()->cart->get_total( '' ) : 0 );
548                $currency   = get_woocommerce_currency();
549
550                $stripe_params['cartTotal'] = WC_Stripe_Helper::get_stripe_amount( $cart_total, strtolower( $currency ) );
551                $stripe_params['currency']  = $currency;
552
553                if ( parent::is_valid_pay_for_order_endpoint() || $is_change_payment_method ) {
554                        $order_id = absint( get_query_var( 'order-pay' ) );
555                        $order    = wc_get_order( $order_id );
556
557                        $stripe_params['orderId'] = $order_id;
558
559                        // Make billing country available for subscriptions as well, so country-restricted payment methods can be shown.
560                        if ( is_a( $order, 'WC_Order' ) ) {
561                                $stripe_params['customerData'] = [ 'billing_country' => $order->get_billing_country() ];
562                        }
563
564                        if ( WC_Stripe_Subscriptions_Helper::is_subscriptions_enabled() && $is_change_payment_method ) {
565                                $stripe_params['isChangingPayment']   = true;
566                                $stripe_params['addPaymentReturnURL'] = wp_sanitize_redirect( esc_url_raw( home_url( add_query_arg( [] ) ) ) );
567
568                                if ( $this->is_setup_intent_success_creation_redirection() && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wpnonce'] ) ) ) ) {
569                                        $setup_intent_id                 = isset( $_GET['setup_intent'] ) ? wc_clean( wp_unslash( $_GET['setup_intent'] ) ) : '';
570                                        $token                           = $this->create_token_from_setup_intent( $setup_intent_id, wp_get_current_user() );
571                                        $stripe_params['newTokenFormId'] = '#wc-' . $token->get_gateway_id() . '-payment-token-' . $token->get_id();
572                                }
573                                return $stripe_params;
574                        }
575
576                        $stripe_params['isOrderPay'] = true;
577
578                        // Additional params for order pay page, when the order was successfully loaded.
579                        if ( is_a( $order, 'WC_Order' ) ) {
580                                $order_currency                  = $order->get_currency();
581                                $stripe_params['currency']       = $order_currency;
582                                $stripe_params['cartTotal']      = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $order_currency );
583                                $stripe_params['orderReturnURL'] = esc_url_raw(
584                                        add_query_arg(
585                                                [
586                                                        'order_id'          => $order_id,
587                                                        'wc_payment_method' => self::ID,
588                                                        '_wpnonce'          => wp_create_nonce( 'wc_stripe_process_redirect_order_nonce' ),
589                                                ],
590                                                $this->get_return_url( $order )
591                                        )
592                                );
593                        }
594                } elseif ( is_wc_endpoint_url( 'add-payment-method' ) ) {
595                        $stripe_params['cartTotal']    = 0;
596                        $stripe_params['customerData'] = [ 'billing_country' => WC()->customer->get_billing_country() ];
597                }
598
599                // Pre-orders and free trial subscriptions don't require payments.
600                $stripe_params['isPaymentNeeded'] = $this->is_payment_needed( isset( $order_id ) ? $order_id : null );
601
602                // Some saved tokens need to override the default token label on the checkout.
603                if ( has_block( 'woocommerce/checkout' ) ) {
604                        $stripe_params['tokenLabelOverrides'] = WC_Stripe_Payment_Tokens::get_token_label_overrides_for_checkout();
605                }
606
607                return array_merge( $stripe_params, WC_Stripe_Helper::get_localized_messages() );
608        }
609
610        /**
611         * Gets payment method settings to pass to client scripts
612         *
613         * @return array
614         */
615        private function get_enabled_payment_method_config() {
616                $settings = [];
617
618                $enabled_payment_methods = $this->get_upe_enabled_at_checkout_payment_method_ids();
619                $original_method_ids     = $enabled_payment_methods; // For OC, keep the original methods to control availability
620                $payment_methods         = $this->payment_methods;
621
622                // If the Optimized Checkout is enabled, we need to return just the card payment method + express methods.
623                // All payment methods are rendered inside the card container.
624                if ( $this->oc_enabled ) {
625                        $oc_method_id            = WC_Stripe_UPE_Payment_Method_OC::STRIPE_ID;
626                        $enabled_express_methods = array_intersect(
627                                $enabled_payment_methods,
628                                WC_Stripe_Payment_Methods::EXPRESS_PAYMENT_METHODS
629                        );
630                        $enabled_payment_methods          = array_merge( [ $oc_method_id ], $enabled_express_methods );
631                        $payment_methods[ $oc_method_id ] = new WC_Stripe_UPE_Payment_Method_OC();
632                }
633
634                foreach ( $enabled_payment_methods as $payment_method_id ) {
635                        $payment_method = $payment_methods[ $payment_method_id ];
636
637                        $settings[ $payment_method_id ] = [
638                                'isReusable'             => $payment_method->is_reusable(),
639                                'title'                  => $payment_method->get_title(),
640                                'description'            => $payment_method->get_description(),
641                                'testingInstructions'    => $payment_method->get_testing_instructions(),
642                                'showSaveOption'         => $this->should_upe_payment_method_show_save_option( $payment_method ),
643                                'supportsDeferredIntent' => $payment_method->supports_deferred_intent(),
644                                'countries'              => $payment_method->get_available_billing_countries(),
645                                'enabledPaymentMethods'  => $original_method_ids,
646                        ];
647                }
648
649                return $settings;
650        }
651
652        /**
653         * Returns UPE enabled payment method IDs.
654         *
655         * @return string[]
656         */
657        public function get_upe_enabled_payment_method_ids( $force_refresh = false ) {
658                return WC_Stripe_Payment_Method_Configurations::get_upe_enabled_payment_method_ids( $force_refresh );
659        }
660
661        /**
662         * Returns the list of enabled payment method types that will function with the current checkout.
663         *
664         * @param int|null $order_id
665         * @return string[]
666         */
667        public function get_upe_enabled_at_checkout_payment_method_ids( $order_id = null ) {
668                $is_automatic_capture_enabled = $this->is_automatic_capture_enabled();
669                $available_method_ids         = [];
670                $account_domestic_currency    = WC_Stripe::get_instance()->account->get_account_default_currency();
671                foreach ( $this->get_upe_enabled_payment_method_ids() as $payment_method_id ) {
672                        if ( ! isset( $this->payment_methods[ $payment_method_id ] ) ) {
673                                continue;
674                        }
675
676                        $method = $this->payment_methods[ $payment_method_id ];
677                        if ( $method->is_enabled_at_checkout( $order_id, $account_domestic_currency ) === false ) {
678                                continue;
679                        }
680
681                        if ( ! $is_automatic_capture_enabled && $method->requires_automatic_capture() ) {
682                                continue;
683                        }
684
685                        $available_method_ids[] = $payment_method_id;
686                }
687
688                return $available_method_ids;
689        }
690
691        /**
692         * Returns the list of available payment method types for UPE.
693         * See https://docs.stripe.com/payments/accept-a-payment?platform=web&ui=elements#web-create-intent for a complete list.
694         *
695         * @return string[]
696         */
697        public function get_upe_available_payment_methods() {
698                // If the payment method configurations API is not enabled, fall back to determining available payment methods
699                // based on the plugin's internal logic.
700                if ( ! WC_Stripe_Payment_Method_Configurations::is_enabled() ) {
701                        $available_payment_methods = [];
702
703                        foreach ( $this->payment_methods as $payment_method ) {
704                                if ( is_callable( [ $payment_method, 'is_available_for_account_country' ] ) && ! $payment_method->is_available_for_account_country() ) {
705                                        continue;
706                                }
707                                $available_payment_methods[] = $payment_method->get_id();
708                        }
709                        return $available_payment_methods;
710                }
711
712                return WC_Stripe_Payment_Method_Configurations::get_upe_available_payment_method_ids();
713        }
714
715        /**
716         * Updates the enabled payment methods.
717         *
718         * @param string[] $payment_method_ids_to_enable
719         */
720        public function update_enabled_payment_methods( $payment_method_ids_to_enable ) {
721                // If the payment method configurations API is not enabled, we fallback to store the enabled payment methods in the DB.
722                if ( ! WC_Stripe_Payment_Method_Configurations::is_enabled() ) {
723                        $currently_enabled_payment_method_ids      = (array) $this->get_option( 'upe_checkout_experience_accepted_payments' );
724                        $upe_checkout_experience_accepted_payments = [];
725
726                        foreach ( self::UPE_AVAILABLE_METHODS as $gateway ) {
727                                if ( in_array( $gateway::STRIPE_ID, $payment_method_ids_to_enable, true ) ) {
728                                        $upe_checkout_experience_accepted_payments[] = $gateway::STRIPE_ID;
729                                }
730                        }
731                        $this->update_option( 'upe_checkout_experience_accepted_payments', $upe_checkout_experience_accepted_payments );
732
733                        // After updating payment methods record tracks events.
734                        $newly_enabled_methods  = array_diff( $upe_checkout_experience_accepted_payments, $currently_enabled_payment_method_ids );
735                        $newly_disabled_methods = array_diff( $currently_enabled_payment_method_ids, $payment_method_ids_to_enable );
736                        WC_Stripe_Payment_Method_Configurations::record_payment_method_settings_event( $newly_enabled_methods, $newly_disabled_methods );
737
738                        return;
739                }
740
741                $payment_method_ids_to_update = array_merge(
742                        $this->get_stripe_supported_payment_methods(),
743                        [ WC_Stripe_Payment_Methods::APPLE_PAY, WC_Stripe_Payment_Methods::GOOGLE_PAY ]
744                );
745
746                WC_Stripe_Payment_Method_Configurations::update_payment_method_configuration(
747                        $payment_method_ids_to_enable,
748                        $payment_method_ids_to_update
749                );
750        }
751
752        /**
753         * Returns the list of supported payment method types for Stripe.
754         *
755         * @return string[]
756         */
757        private function get_stripe_supported_payment_methods() {
758                $supported_stripe_ids         = [];
759                $available_payment_method_ids = $this->get_upe_available_payment_methods();
760
761                // Return the list if the payment method configurations API is enabled.
762                // We don't need any additional filtering as the list is already fetched from the payment method configurations API..
763                if ( WC_Stripe_Payment_Method_Configurations::is_enabled() ) {
764                        return $available_payment_method_ids;
765                }
766
767                foreach ( self::UPE_AVAILABLE_METHODS as $gateway_class ) {
768                        $gateway = new $gateway_class();
769
770                        if (
771                                ! in_array( $gateway->get_id(), $available_payment_method_ids, true ) ||
772                                ( $gateway->get_supported_currencies() && ! in_array( get_woocommerce_currency(), $gateway->get_supported_currencies(), true ) )
773                        ) {
774                                continue;
775                        }
776
777                        $supported_stripe_ids[] = $gateway::STRIPE_ID;
778                }
779
780                return $supported_stripe_ids;
781        }
782
783        /**
784         * Renders the UPE input fields needed to get the user's payment information on the checkout page
785         */
786        public function payment_fields() {
787                try {
788                        $display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
789
790                        // Output the form HTML.
791                        ?>
792                        <?php if ( ! empty( $this->get_description() ) ) : ?>
793                                <p><?php echo wp_kses_post( $this->get_description() ); ?></p>
794                        <?php endif; ?>
795
796                        <?php
797                        if ( $this->testmode ) :
798                                if ( $this->oc_enabled ) :
799                                        echo wp_kses(
800                                                ( new WC_Stripe_UPE_Payment_Method_OC() )->get_testing_instructions(),
801                                                [
802                                                        'div' => [
803                                                                'id'    => [],
804                                                                'class' => [],
805                                                                'style' => [],
806                                                        ],
807                                                        'strong' => [],
808                                                        'a'    => [
809                                                                'href'   => [],
810                                                                'target' => [],
811                                                        ],
812                                                ]
813                                        );
814                                else :
815                                        ?>
816                                <p class="testmode-info">
817                                        <?php
818                                        printf(
819                                        /* translators: 1) HTML strong open tag 2) HTML strong closing tag 3) HTML anchor open tag 2) HTML anchor closing tag */
820                                                esc_html__( '%1$sTest mode:%2$s use the test VISA card 4242424242424242 with any expiry date and CVC. Other payment methods may redirect to a Stripe test page to authorize payment. More test card numbers are listed %3$shere%4$s.', 'woocommerce-gateway-stripe' ),
821                                                '<strong>',
822                                                '</strong>',
823                                                '<a href="https://docs.stripe.com/testing" target="_blank">',
824                                                '</a>'
825                                        );
826                                        ?>
827                                </p>
828                                        <?php
829                                endif;
830                        endif;
831                        ?>
832
833                        <?php
834                        if ( $display_tokenization ) {
835                                $this->tokenization_script();
836                                $this->saved_payment_methods();
837                        }
838                        ?>
839
840                        <fieldset id="wc-stripe-upe-form" class="wc-upe-form wc-payment-form">
841                                <div class="wc-stripe-upe-element" data-payment-method-type="<?php echo esc_attr( WC_Stripe_UPE_Payment_Method_CC::STRIPE_ID ); ?>"></div>
842                                <div id="wc-stripe-upe-errors" role="alert"></div>
843                                <input id="wc-stripe-payment-method-upe" type="hidden" name="wc-stripe-payment-method-upe" />
844                                <input id="wc_stripe_selected_upe_payment_type" type="hidden" name="wc_stripe_selected_upe_payment_type" />
845                                <input type="hidden" class="wc-stripe-is-deferred-intent" name="wc-stripe-is-deferred-intent" value="1" />
846                        </fieldset>
847                        <?php
848                        $methods_enabled_for_saved_payments = array_filter( $this->get_upe_enabled_payment_method_ids(), [ $this, 'is_enabled_for_saved_payments' ] );
849                        if ( $this->is_saved_cards_enabled() && ! empty( $methods_enabled_for_saved_payments ) ) {
850                                $force_save_payment = ( $display_tokenization && ! apply_filters( 'wc_stripe_display_save_payment_method_checkbox', $display_tokenization ) ) || is_add_payment_method_page() || WC_Stripe_Helper::should_force_save_payment_method();
851                                $this->save_payment_method_checkbox( $force_save_payment );
852                        }
853
854                        do_action( 'wc_stripe_payment_fields_' . $this->id, $this->id );
855                } catch ( Exception $e ) {
856                        // Output the error message.
857                        WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
858                        ?>
859                        <div>
860                                <?php
861                                echo esc_html__( 'An error was encountered when preparing the payment form. Please try again later.', 'woocommerce-gateway-stripe' );
862                                ?>
863                        </div>
864                        <?php
865                }
866        }
867
868        /**
869         * Process the payment for a given order.
870         *
871         * @param int   $order_id Reference.
872         * @param bool  $retry Should we retry on fail.
873         * @param bool  $force_save_source Force save the payment source.
874         * @param mixed $previous_error Any error message from previous request.
875         * @param bool  $use_order_source Whether to use the source, which should already be attached to the order.
876         *
877         * @return array|null An array with result of payment and redirect URL, or nothing.
878         */
879        public function process_payment( $order_id, $retry = true, $force_save_source = false, $previous_error = false, $use_order_source = false ) {
880                $payment_intent_id     = isset( $_POST['wc_payment_intent_id'] ) ? wc_clean( wp_unslash( $_POST['wc_payment_intent_id'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
881                $order                 = wc_get_order( $order_id );
882                $selected_payment_type = $this->get_selected_payment_method_type_from_request();
883                $save_payment_method   = $this->should_save_payment_method_from_request( $order_id, $selected_payment_type );
884
885                if ( $payment_intent_id && ! $this->payment_methods[ $selected_payment_type ]->supports_deferred_intent() ) {
886                        // Adds customer and metadata to PaymentIntent.
887                        // These parameters cannot be added upon updating the intent via the `/confirm` API.
888                        try {
889                                $this->intent_controller->update_intent( $payment_intent_id, $order_id, $save_payment_method, $selected_payment_type );
890                        } catch ( Exception $update_intent_exception ) {
891                                throw new Exception( __( "We're not able to process this payment. Please try again later.", 'woocommerce-gateway-stripe' ) );
892                        }
893                }
894
895                // Flag for using a deferred intent. To be removed.
896                // https://github.com/woocommerce/woocommerce-gateway-stripe/issues/3868
897                if ( ! empty( $_POST['wc-stripe-is-deferred-intent'] ) ) {
898                        return $this->process_payment_with_deferred_intent( $order_id );
899                }
900
901                if ( $this->maybe_change_subscription_payment_method( $order_id ) ) {
902                        return $this->process_change_subscription_payment_method( $order_id );
903                }
904
905                if ( $this->is_using_saved_payment_method() ) {
906                        return $this->process_payment_with_saved_payment_method( $order_id );
907                }
908
909                $payment_needed            = $this->is_payment_needed( $order_id );
910                $selected_upe_payment_type = ! empty( $_POST['wc_stripe_selected_upe_payment_type'] ) ? wc_clean( wp_unslash( $_POST['wc_stripe_selected_upe_payment_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
911
912                $is_short_statement_descriptor_enabled = ! empty( $this->get_option( 'is_short_statement_descriptor_enabled' ) ) && 'yes' === $this->get_option( 'is_short_statement_descriptor_enabled' );
913
914                if ( $payment_intent_id ) {
915                        if ( $payment_needed ) {
916                                $amount           = $order->get_total();
917                                $currency         = $order->get_currency();
918                                $converted_amount = WC_Stripe_Helper::get_stripe_amount( $amount, $currency );
919
920                                $request = [
921                                        'amount'      => $converted_amount,
922                                        'currency'    => $currency,
923                                        /* translators: 1) blog name 2) order number */
924                                        'description' => sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() ),
925                                ];
926
927                                // Use the dynamic + short statement descriptor if enabled and it's a card payment.
928                                if ( WC_Stripe_Payment_Methods::CARD === $selected_upe_payment_type && $is_short_statement_descriptor_enabled ) {
929                                        $request['statement_descriptor_suffix'] = WC_Stripe_Helper::get_dynamic_statement_descriptor_suffix( $order );
930                                }
931
932                                $customer = $this->get_stripe_customer_from_order( $order );
933
934                                // Update customer or create customer if customer does not exist.
935                                if ( ! $customer->get_id() ) {
936                                        $request['customer'] = $customer->create_customer();
937                                } else {
938                                        $request['customer'] = $customer->update_customer();
939                                }
940
941                                if ( '' !== $selected_upe_payment_type ) {
942                                        // Only update the payment_method_types if we have a reference to the payment type the customer selected.
943                                        $request['payment_method_types'] = [ $selected_upe_payment_type ];
944                                        if ( WC_Stripe_UPE_Payment_Method_CC::STRIPE_ID === $selected_upe_payment_type ) {
945                                                if ( in_array(
946                                                        WC_Stripe_UPE_Payment_Method_Link::STRIPE_ID,
947                                                        $this->get_upe_enabled_payment_method_ids(),
948                                                        true
949                                                ) ) {
950                                                        $request['payment_method_types'] = [
951                                                                WC_Stripe_UPE_Payment_Method_CC::STRIPE_ID,
952                                                                WC_Stripe_UPE_Payment_Method_Link::STRIPE_ID,
953                                                        ];
954                                                }
955                                        }
956                                        $this->set_payment_method_title_for_order( $order, $selected_upe_payment_type );
957                                        if ( ! $this->payment_methods[ $selected_upe_payment_type ]->is_allowed_on_country( $order->get_billing_country() ) ) {
958                                                throw new \Exception( __( 'This payment method is not available on the selected country', 'woocommerce-gateway-stripe' ) );
959                                        }
960                                }
961
962                                if ( $save_payment_method ) {
963                                        $request['setup_future_usage'] = 'off_session';
964                                }
965
966                                $request['metadata'] = $this->get_metadata_from_order( $order );
967
968                                // If order requires shipping, add the shipping address details to the payment intent request.
969                                if ( method_exists( $order, 'get_shipping_postcode' ) && ! empty( $order->get_shipping_postcode() ) ) {
970                                        $request['shipping'] = $this->get_address_data_for_payment_request( $order );
971                                }
972
973                                // Run the necessary filter to make sure mandate information is added when it's required.
974                                $request = apply_filters(
975                                        'wc_stripe_generate_create_intent_request',
976                                        $request,
977                                        $order,
978                                        null // $prepared_source parameter is not necessary for adding mandate information.
979                                );
980
981                                $order_helper = WC_Stripe_Order_Helper::get_instance();
982
983                                $order_helper->add_payment_intent_to_order( $payment_intent_id, $order );
984                                $order->update_status( OrderStatus::PENDING, __( 'Awaiting payment.', 'woocommerce-gateway-stripe' ) );
985                                $order_helper->update_stripe_upe_payment_type( $order, $selected_upe_payment_type );
986
987                                // TODO: This is a stop-gap to fix a critical issue, see
988                                // https://github.com/woocommerce/woocommerce-gateway-stripe/issues/2536. It would
989                                // be better if we removed the need for additional meta data in favor of refactoring
990                                // this part of the payment processing.
991                                $order_helper->update_stripe_upe_waiting_for_redirect( $order, true );
992
993                                $order->save();
994
995                                $this->stripe_request(
996                                        "payment_intents/$payment_intent_id",
997                                        $request,
998                                        $order
999                                );
1000                        }
1001                } else {
1002                        return parent::process_payment( $order_id, $retry, $force_save_source, $previous_error, $use_order_source );
1003                }
1004
1005                return [
1006                        'result'         => 'success',
1007                        'payment_needed' => $payment_needed,
1008                        'order_id'       => $order_id,
1009                        'redirect_url'   => wp_sanitize_redirect(
1010                                esc_url_raw(
1011                                        add_query_arg(
1012                                                [
1013                                                        'order_id'            => $order_id,
1014                                                        'wc_payment_method'   => self::ID,
1015                                                        '_wpnonce'            => wp_create_nonce( 'wc_stripe_process_redirect_order_nonce' ),
1016                                                        'save_payment_method' => $save_payment_method ? 'yes' : 'no',
1017                                                ],
1018                                                $this->get_return_url( $order )
1019                                        )
1020                                )
1021                        ),
1022                ];
1023        }
1024
1025        /**
1026         * Process the payment for an order using a deferred intent.
1027         *
1028         * @param int $order_id WC Order ID to be paid for.
1029         *
1030         * @return array An array with the result of the payment processing, and a redirect URL on success.
1031         */
1032        private function process_payment_with_deferred_intent( int $order_id ) {
1033                if ( ! empty( $_POST['wc-stripe-confirmation-token'] ) ) {
1034                        return $this->process_payment_with_confirmation_token( $order_id );
1035                }
1036
1037                return $this->process_payment_with_payment_method( $order_id );
1038        }
1039
1040        /**
1041         * Process the payment for an order that has a payment method attached.
1042         *
1043         * @param int $order_id ID of order to be processed.
1044         *
1045         * @return array An array with the result of the payment processing, and a redirect URL on success.
1046         */
1047        private function process_payment_with_payment_method( int $order_id ) {
1048                if ( $this->is_changing_payment_method_for_subscription() ) {
1049                        return $this->process_change_subscription_payment_with_deferred_intent( $order_id );
1050                }
1051
1052                $order_helper = WC_Stripe_Order_Helper::get_instance();
1053
1054                $order = wc_get_order( $order_id );
1055
1056                try {
1057                        $payment_information = $this->prepare_payment_information_from_request( $order );
1058
1059                        $this->validate_selected_payment_method_type( $payment_information, $order->get_billing_country() );
1060
1061                        // Attempt to acquire lock, bail if already locked
1062                        $is_order_payment_locked = $order_helper->lock_order_payment( $order );
1063                        if ( $is_order_payment_locked ) {
1064                                // If the request is already being processed, return an error.
1065                                return [
1066                                        'result'   => 'failure',
1067                                        'redirect' => '',
1068                                        'message'  => __( 'Your payment is already being processed. Please wait.', 'woocommerce-gateway-stripe' ),
1069                                ];
1070                        }
1071
1072                        $payment_needed                = $this->is_payment_needed( $order->get_id() );
1073                        $payment_method_id             = $payment_information['payment_method'];
1074                        $payment_method_details        = $payment_information['payment_method_details'];
1075                        $selected_payment_type         = $payment_information['selected_payment_type'];
1076                        $is_using_saved_payment_method = $payment_information['is_using_saved_payment_method'];
1077                        $upe_payment_method            = $this->payment_methods[ $selected_payment_type ] ?? null;
1078                        $response_args                 = [];
1079
1080                        if ( $this->oc_enabled && isset( $payment_method_details->type ) ) {
1081                                $upe_payment_method = self::get_payment_method_instance( $payment_method_details->type );
1082                        }
1083
1084                        // Make sure that we attach the payment method and the customer ID to the order meta data.
1085                        $this->set_payment_method_id_for_order( $order, $payment_method_id );
1086                        $this->set_customer_id_for_order( $order, $payment_information['customer'] );
1087
1088                        // Only update the payment_type if we have a reference to the payment type the customer selected.
1089                        if ( '' !== $selected_payment_type ) {
1090                                $this->set_selected_payment_type_for_order( $order, $selected_payment_type );
1091                        }
1092
1093                        // Retrieve the payment method object from Stripe.
1094                        $payment_method = $this->stripe_request( 'payment_methods/' . $payment_method_id );
1095
1096                        // Throw an exception when the payment method is a prepaid card and it's disallowed.
1097                        $this->maybe_disallow_prepaid_card( $payment_method );
1098
1099                        // Until we know other payment methods need this, let's just set for BLIK.
1100                        if ( WC_Stripe_Payment_Methods::BLIK === $selected_payment_type ) {
1101                                $payment_information['payment_method_options'] = $this->get_payment_method_options(
1102                                        $selected_payment_type,
1103                                        $order,
1104                                        $payment_method_details
1105                                );
1106                        }
1107
1108                        // Update saved payment method to include billing details.
1109                        if ( $is_using_saved_payment_method ) {
1110                                $this->update_saved_payment_method( $payment_method_id, $order );
1111                        }
1112
1113                        if ( $payment_needed ) {
1114                                // Throw an exception if the minimum order amount isn't met.
1115                                $order_helper->validate_minimum_order_amount( $order );
1116
1117                                // Create a payment intent, or update an existing one associated with the order.
1118                                $payment_intent = $this->process_payment_intent_for_order( $order, $payment_information );
1119                        } elseif ( $is_using_saved_payment_method && WC_Stripe_Payment_Methods::CASHAPP_PAY === $selected_payment_type ) {
1120                                // If the payment method is Cash App Pay, the order has no cost, and a saved payment method is used, mark the order as paid.
1121                                $this->maybe_update_source_on_subscription_order(
1122                                        $order,
1123                                        (object) [
1124                                                'payment_method' => $payment_information['payment_method'],
1125                                                'customer'       => $payment_information['customer'],
1126                                        ],
1127                                        $this->get_upe_gateway_id_for_order( $upe_payment_method )
1128                                );
1129                                $order->payment_complete();
1130
1131                                return [
1132                                        'result'   => 'success',
1133                                        'redirect' => $this->get_return_url( $order ),
1134                                ];
1135                        } else {
1136                                // Create a setup intent, or update an existing one associated with the order.
1137                                $payment_intent = $this->process_setup_intent_for_order( $order, $payment_information );
1138                        }
1139
1140                        // Handle saving the payment method in the store.
1141                        // It's already attached to the Stripe customer at this point.
1142                        if ( $payment_information['save_payment_method_to_store'] && $upe_payment_method && $upe_payment_method->get_id() === $upe_payment_method->get_retrievable_type() ) {
1143                                $this->handle_saving_payment_method(
1144                                        $order,
1145                                        $payment_method_details,
1146                                        $selected_payment_type
1147                                );
1148                        } elseif ( $is_using_saved_payment_method ) {
1149                                $this->maybe_update_source_on_subscription_order(
1150                                        $order,
1151                                        (object) [
1152                                                'payment_method' => $payment_information['payment_method'],
1153                                                'customer'       => $payment_information['customer'],
1154                                        ],
1155                                        $this->get_upe_gateway_id_for_order( $upe_payment_method )
1156                                );
1157                        }
1158
1159                        // Set the selected UPE payment method type title in the WC order.
1160                        $this->set_payment_method_title_for_order( $order, $selected_payment_type, $payment_method );
1161
1162                        // Save the preferred card brand on the order.
1163                        $this->maybe_set_preferred_card_brand_for_order( $order, $payment_method );
1164
1165                        // Updates the redirect URL and add extra meta data to the order if the payment intent requires confirmation or action.
1166                        // Note: BLIK falls into this condition, but we want to skip this logic for it because from this point on,
1167                        // the confirming action is done by the customer and the confirmation comes through webhooks.
1168                        if ( in_array( $payment_intent->status, WC_Stripe_Intent_Status::REQUIRES_CONFIRMATION_OR_ACTION_STATUSES, true )
1169                                && WC_Stripe_Payment_Methods::BLIK !== $selected_payment_type ) {
1170                                $wallet_and_voucher_methods        = array_merge( WC_Stripe_Payment_Methods::VOUCHER_PAYMENT_METHODS, WC_Stripe_Payment_Methods::WALLET_PAYMENT_METHODS );
1171                                $contains_wallet_or_voucher_method = isset( $payment_intent->payment_method_types ) && count( array_intersect( $wallet_and_voucher_methods, $payment_intent->payment_method_types ) ) !== 0;
1172                                $contains_redirect_next_action     = isset( $payment_intent->next_action->type ) && in_array( $payment_intent->next_action->type, [ 'redirect_to_url', 'alipay_handle_redirect' ], true )
1173                                        && ! empty( $payment_intent->next_action->{$payment_intent->next_action->type}->url );
1174                                if ( ! $contains_wallet_or_voucher_method && ! $contains_redirect_next_action ) {
1175                                        // Return the payment method used to process the payment so the block checkout can save the payment method.
1176                                        $response_args['payment_method'] = $payment_information['payment_method'];
1177                                }
1178
1179                                // If the order requires some action from the customer, add meta to the order to prevent it from being cancelled by WooCommerce's hold stock settings.
1180                                $order_helper->set_payment_awaiting_action( $order, false );
1181
1182                                // Prevent processing the payment intent webhooks while also processing the redirect payment (also prevents duplicate Stripe meta stored on the order).
1183                                $order_helper->update_stripe_upe_waiting_for_redirect( $order, true );
1184                                $order->save();
1185
1186                                $redirect = $this->get_redirect_url( $this->get_return_url( $order ), $payment_intent, $payment_information, $order, $payment_needed );
1187                        } else {
1188                                if ( $payment_needed ) {
1189                                        // Use the last charge within the intent to proceed.
1190                                        $charge = $this->get_latest_charge_from_intent( $payment_intent );
1191
1192                                        // Only process the response if it contains a charge object. Intents with no charge require further action like 3DS and will be processed later.
1193                                        if ( $charge ) {
1194                                                $this->process_response( $charge, $order );
1195                                        }
1196                                } elseif ( in_array( $payment_intent->status, WC_Stripe_Intent_Status::SUCCESSFUL_STATUSES, true ) ) {
1197                                        if ( ! $this->has_pre_order( $order ) ) {
1198                                                $order->payment_complete();
1199                                        } elseif ( $this->maybe_process_pre_orders( $order ) ) {
1200                                                $this->mark_order_as_pre_ordered( $order );
1201                                        }
1202                                }
1203                                $redirect = $this->get_return_url( $order );
1204                        }
1205
1206                        $order_helper->unlock_order_payment( $order );
1207
1208                        return array_merge(
1209                                [
1210                                        'result'   => 'success',
1211                                        'redirect' => $redirect,
1212                                ],
1213                                $response_args
1214                        );
1215                } catch ( WC_Stripe_Exception $e ) {
1216                        // Ensure the order is unlocked in case of an exception.
1217                        $order_helper->unlock_order_payment( $order );
1218                        return $this->handle_process_payment_error( $e, $order );
1219                }
1220        }
1221
1222        /**
1223         * Process the payment for an order that has a confirmation token attached.
1224         *
1225         * @param int $order_id ID of order to be processed.
1226         *
1227         * @return array An array with the result of the payment processing, and a redirect URL on success.
1228         */
1229        private function process_payment_with_confirmation_token( int $order_id ) {
1230                $order = wc_get_order( $order_id );
1231
1232                try {
1233                        $payment_information = $this->prepare_payment_information_from_request( $order );
1234
1235                        $this->validate_selected_payment_method_type( $payment_information, $order->get_billing_country() );
1236
1237                        $this->set_customer_id_for_order( $order, $payment_information['customer'] );
1238
1239                        $payment_needed = $this->is_payment_needed( $order->get_id() );
1240
1241                        if ( $payment_needed ) {
1242                                $payment_intent = $this->process_payment_intent_for_order( $order, $payment_information );
1243                        } else {
1244                                // TODO: add confirmation token support, if possible.
1245                                $payment_intent = $this->process_setup_intent_for_order( $order, $payment_information );
1246                        }
1247
1248                        $payment_method_id = $payment_intent->payment_method;
1249                        $this->set_payment_method_id_for_order( $order, $payment_method_id );
1250
1251                        $selected_payment_type = $payment_information['selected_payment_type'];
1252
1253                        // Retrieve the payment method object from Stripe.
1254                        $payment_method = $this->stripe_request( 'payment_methods/' . $payment_method_id );
1255
1256                        $this->set_payment_method_title_for_order( $order, $selected_payment_type, $payment_method );
1257
1258                        if ( $payment_needed ) {
1259                                // Use the last charge within the intent to proceed.
1260                                $charge = $this->get_latest_charge_from_intent( $payment_intent );
1261
1262                                if ( $charge ) {
1263                                        $this->process_response( $charge, $order );
1264                                }
1265                        }
1266
1267                        if ( $payment_information['save_payment_method_to_store'] ) {
1268                                $this->handle_saving_payment_method(
1269                                        $order,
1270                                        $payment_method,
1271                                        $selected_payment_type
1272                                );
1273                        }
1274
1275                        $return_url = $this->get_return_url( $order );
1276
1277                        return [
1278                                'result'   => 'success',
1279                                'redirect' => $return_url,
1280                        ];
1281                } catch ( WC_Stripe_Exception $e ) {
1282                        return $this->handle_process_payment_error( $e, $order );
1283                }
1284        }
1285
1286        /**
1287         * Handle errors that occur during the payment processing.
1288         *
1289         * @param WC_Stripe_Exception $e    The exception that was thrown.
1290         * @param WC_Order            $order The order that was being processed.
1291         *
1292         * @return array
1293         */
1294        private function handle_process_payment_error( WC_Stripe_Exception $e, $order ) {
1295                $error_message = sprintf(
1296                        /* translators: localized exception message */
1297                        __( 'There was an error processing the payment: %s', 'woocommerce-gateway-stripe' ),
1298                        $e->getLocalizedMessage()
1299                );
1300
1301                // If the error message is 'Invalid API Key...', we want to show a more generic error message,
1302                // as the user won't be able to do anything about it.
1303                // The log and the order note will still show the full error message for debugging purposes.
1304                if ( 0 === strpos( $e->getLocalizedMessage(), 'Invalid API Key' ) ) {
1305                        $error_message = __( "We're not able to process this payment. This may be an error on our side. Please contact us if you need any help placing your order.", 'woocommerce-gateway-stripe' );
1306                }
1307
1308                wc_add_notice( $error_message, 'error' );
1309
1310                WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
1311
1312                do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
1313
1314                $order->update_status(
1315                        OrderStatus::FAILED,
1316                        /* translators: localized exception message */
1317                        sprintf( __( 'Payment failed: %s', 'woocommerce-gateway-stripe' ), $e->getLocalizedMessage() )
1318                );
1319
1320                return [
1321                        'result'   => 'failure',
1322                        'redirect' => '',
1323                ];
1324        }
1325
1326        /**
1327         * Process payment using saved payment method.
1328         * This follows WC_Gateway_Stripe::process_payment,
1329         * but uses Payment Methods instead of Sources.
1330         *
1331         * @param int $order_id   The order ID being processed.
1332         * @param bool $can_retry Should we retry on fail.
1333         */
1334        public function process_payment_with_saved_payment_method( $order_id, $can_retry = true ) {
1335                try {
1336                        $order = wc_get_order( $order_id );
1337
1338                        if ( $this->maybe_process_pre_orders( $order_id ) ) {
1339                                return $this->process_pre_order( $order_id );
1340                        }
1341
1342                        $token = WC_Stripe_Payment_Tokens::get_token_from_request( $_POST );
1343                        if ( ! $token ) {
1344                                throw new WC_Stripe_Exception(
1345                                        sprintf(
1346                                                /* translators: %s is the order ID */
1347                                                __( "We're not able to process this payment. The saved payment method for order %s could not be found.", 'woocommerce-gateway-stripe' ),
1348                                                $order_id
1349                                        )
1350                                );
1351                        }
1352
1353                        $payment_method          = $this->stripe_request( 'payment_methods/' . $token->get_token(), [], null, 'GET' );
1354                        $prepared_payment_method = $this->prepare_payment_method( $payment_method );
1355
1356                        $this->maybe_disallow_prepaid_card( $payment_method );
1357                        $this->save_payment_method_to_order( $order, $prepared_payment_method );
1358
1359                        WC_Stripe_Logger::log( "Info: Begin processing payment with saved payment method for order $order_id for the amount of {$order->get_total()}" );
1360
1361                        // If we are retrying request, maybe intent has been saved to order.
1362                        $intent = $this->get_intent_from_order( $order );
1363
1364                        $enabled_payment_methods = array_filter( $this->get_upe_enabled_payment_method_ids(), [ $this, 'is_enabled_at_checkout' ] );
1365                        $payment_needed          = $this->is_payment_needed( $order_id );
1366
1367                        if ( $payment_needed ) {
1368                                // This will throw exception if not valid.
1369                                WC_Stripe_Order_Helper::get_instance()->validate_minimum_order_amount( $order );
1370
1371                                $request_details = $this->generate_payment_request( $order, $prepared_payment_method );
1372                                $endpoint        = false !== $intent ? "payment_intents/$intent->id" : 'payment_intents';
1373                                $request         = [
1374                                        'payment_method'       => $payment_method->id,
1375                                        'payment_method_types' => array_values( $enabled_payment_methods ),
1376                                        'amount'               => WC_Stripe_Helper::get_stripe_amount( $order->get_total() ),
1377                                        'currency'             => strtolower( $order->get_currency() ),
1378                                        'description'          => $request_details['description'],
1379                                        'metadata'             => $request_details['metadata'],
1380                                        'customer'             => $payment_method->customer,
1381                                ];
1382                                if ( false === $intent ) {
1383                                        // Only set capture_method for payment methods that support it (e.g., cards).
1384                                        // Payment methods like ACH don't support capture_method and will have it omitted from $request_details.
1385                                        if ( isset( $request_details['capture'] ) ) {
1386                                                $request['capture_method'] = ( 'true' === $request_details['capture'] ) ? 'automatic' : 'manual';
1387                                        }
1388                                        $request['confirm'] = 'true';
1389                                }
1390
1391                                // If order requires shipping, add the shipping address details to the payment intent request.
1392                                if ( method_exists( $order, 'get_shipping_postcode' ) && ! empty( $order->get_shipping_postcode() ) ) {
1393                                        $request['shipping'] = $this->get_address_data_for_payment_request( $order );
1394                                }
1395
1396                                if ( $this->has_subscription( $order_id ) ) {
1397                                        $request['setup_future_usage'] = 'off_session';
1398                                }
1399
1400                                // Run the necessary filter to make sure mandate information is added when it's required.
1401                                $request = apply_filters(
1402                                        'wc_stripe_generate_create_intent_request',
1403                                        $request,
1404                                        $order,
1405                                        null // $prepared_source parameter is not necessary for adding mandate information.
1406                                );
1407
1408                                $intent = $this->stripe_request(
1409                                        $endpoint,
1410                                        $request,
1411                                        $order
1412                                );
1413                        } else {
1414                                $endpoint = false !== $intent ? "setup_intents/$intent->id" : 'setup_intents';
1415                                $request  = [
1416                                        'payment_method'       => $payment_method->id,
1417                                        'payment_method_types' => array_values( $enabled_payment_methods ),
1418                                        'customer'             => $payment_method->customer,
1419                                ];
1420                                if ( false === $intent ) {
1421                                        $request['confirm'] = 'true';
1422
1423                                        // SEPA setup intents require mandate data.
1424                                        if ( in_array( WC_Stripe_Payment_Methods::SEPA_DEBIT, array_values( $enabled_payment_methods ), true ) ) {
1425                                                $request = WC_Stripe_Helper::add_mandate_data( $request );
1426                                        }
1427                                }
1428
1429                                $intent = $this->stripe_request( $endpoint, $request );
1430                        }
1431                        $this->save_intent_to_order( $order, $intent );
1432
1433                        if ( ! empty( $intent->error ) ) {
1434                                $this->maybe_remove_non_existent_customer( $intent->error, $order );
1435
1436                                // We want to retry (apparently).
1437                                if ( $this->is_retryable_error( $intent->error ) ) {
1438                                        return $this->retry_after_error( $intent, $order, $can_retry );
1439                                }
1440
1441                                $this->throw_localized_message( $intent, $order );
1442                        }
1443
1444                        if ( WC_Stripe_Intent_Status::REQUIRES_ACTION === $intent->status || WC_Stripe_Intent_Status::REQUIRES_CONFIRMATION === $intent->status ) {
1445                                if ( isset( $intent->next_action->type ) && 'redirect_to_url' === $intent->next_action->type && ! empty( $intent->next_action->redirect_to_url->url ) ) {
1446                                        return [
1447                                                'result'   => 'success',
1448                                                'redirect' => $intent->next_action->redirect_to_url->url,
1449                                        ];
1450                                } else {
1451                                        return [
1452                                                'result'   => 'success',
1453                                                // Include a new nonce for update_order_status to ensure the update order
1454                                                // status call works when a guest user creates an account during checkout.
1455                                                'redirect' => sprintf(
1456                                                        '#wc-stripe-confirm-%s:%s:%s:%s',
1457                                                        $payment_needed ? 'pi' : 'si',
1458                                                        $order_id,
1459                                                        $intent->client_secret,
1460                                                        wp_create_nonce( 'wc_stripe_update_order_status_nonce' )
1461                                                ),
1462                                        ];
1463                                }
1464                        }
1465
1466                        list( $payment_method_type, $payment_method_details ) = $this->get_payment_method_data_from_intent( $intent );
1467
1468                        if ( $payment_needed ) {
1469                                // Use the last charge within the intent to proceed.
1470                                $this->process_response( $this->get_latest_charge_from_intent( $intent ), $order );
1471                        } else {
1472                                $order->payment_complete();
1473                        }
1474                        $this->set_payment_method_title_for_order( $order, $payment_method_type );
1475
1476                        // Remove cart.
1477                        if ( isset( WC()->cart ) ) {
1478                                WC()->cart->empty_cart();
1479                        }
1480
1481                        // Return thank you page redirect.
1482                        return [
1483                                'result'   => 'success',
1484                                'redirect' => $this->get_return_url( $order ),
1485                        ];
1486
1487                } catch ( WC_Stripe_Exception $e ) {
1488                        wc_add_notice( $e->getLocalizedMessage(), 'error' );
1489                        WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
1490
1491                        do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
1492
1493                        /* translators: error message */
1494                        $order->update_status( OrderStatus::FAILED );
1495
1496                        return [
1497                                'result'   => 'fail',
1498                                'redirect' => '',
1499                        ];
1500                }
1501        }
1502
1503        /**
1504         * Check for a UPE redirect payment method on order received page or setup intent on payment methods page.
1505         *
1506         * @since 5.6.0
1507         * @version 5.6.0
1508         */
1509        public function maybe_process_upe_redirect() {
1510                if ( $this->is_payment_methods_page() || $this->is_changing_payment_method_for_subscription() ) {
1511                        if ( $this->is_setup_intent_success_creation_redirection() ) {
1512                                if ( isset( $_GET['redirect_status'] ) && 'succeeded' === $_GET['redirect_status'] ) {
1513                                        $user_id  = wp_get_current_user()->ID;
1514                                        $customer = new WC_Stripe_Customer( $user_id );
1515                                        $customer->clear_cache();
1516                                        wc_add_notice( __( 'Payment method successfully added.', 'woocommerce-gateway-stripe' ) );
1517
1518                                        // The newly created payment method does not inherit the customers' billing info, so we manually
1519                                        // trigger an update; in case of failure we log the error and continue because the payment method's
1520                                        // billing info will be updated when the customer makes a purchase anyway.
1521                                        try {
1522                                                $setup_intent_id = isset( $_GET['setup_intent'] ) ? wc_clean( wp_unslash( $_GET['setup_intent'] ) ) : '';
1523                                                $token           = $this->create_token_from_setup_intent( $setup_intent_id, wp_get_current_user() );
1524
1525                                                $customer_data         = WC_Stripe_Customer::map_customer_data( null, new WC_Customer( $user_id ) );
1526                                                $payment_method_object = $this->stripe_request(
1527                                                        'payment_methods/' . $token->get_token(),
1528                                                        [
1529                                                                'billing_details' => [
1530                                                                        'name'    => $customer_data['name'],
1531                                                                        'email'   => $customer_data['email'],
1532                                                                        'phone'   => $customer_data['phone'],
1533                                                                        'address' => $customer_data['address'],
1534                                                                ],
1535                                                        ]
1536                                                );
1537
1538                                                do_action( 'woocommerce_stripe_add_payment_method', $user_id, $payment_method_object );
1539                                                wp_safe_redirect( wc_get_account_endpoint_url( 'payment-methods' ) );
1540                                        } catch ( Exception $e ) {
1541                                                WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
1542                                        }
1543                                } else {
1544                                        wc_add_notice( __( 'Failed to add payment method.', 'woocommerce-gateway-stripe' ), 'error', [ 'icon' => 'error' ] );
1545                                }
1546                        }
1547                        return;
1548                }
1549
1550                if ( ! parent::is_valid_order_received_endpoint() ) {
1551                        return;
1552                }
1553
1554                $payment_method = isset( $_GET['wc_payment_method'] ) ? wc_clean( wp_unslash( $_GET['wc_payment_method'] ) ) : '';
1555                if ( self::ID !== $payment_method ) {
1556                        return;
1557                }
1558
1559                $is_nonce_valid = isset( $_GET['_wpnonce'] ) && wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wpnonce'] ) ), 'wc_stripe_process_redirect_order_nonce' );
1560                if ( ! $is_nonce_valid || empty( $_GET['wc_payment_method'] ) ) {
1561                        return;
1562                }
1563
1564                $order_id            = isset( $_GET['order_id'] ) ? absint( wc_clean( wp_unslash( $_GET['order_id'] ) ) ) : '';
1565                $save_payment_method = isset( $_GET['save_payment_method'] ) ? 'yes' === wc_clean( wp_unslash( $_GET['save_payment_method'] ) ) : false;
1566
1567                if ( ! empty( $_GET['payment_intent_client_secret'] ) ) {
1568                        $intent_id = isset( $_GET['payment_intent'] ) ? wc_clean( wp_unslash( $_GET['payment_intent'] ) ) : '';
1569                        if ( ! $this->is_order_associated_to_payment_intent( $order_id, $intent_id ) ) {
1570                                return;
1571                        }
1572                } elseif ( ! empty( $_GET['setup_intent_client_secret'] ) ) {
1573                        $intent_id = isset( $_GET['setup_intent'] ) ? wc_clean( wp_unslash( $_GET['setup_intent'] ) ) : '';
1574                        if ( ! $this->is_order_associated_to_setup_intent( $order_id, $intent_id ) ) {
1575                                return;
1576                        }
1577                } else {
1578                        return;
1579                }
1580
1581                if ( empty( $intent_id ) ) {
1582                        return;
1583                }
1584
1585                $this->process_upe_redirect_payment(
1586                        $order_id,
1587                        $intent_id,
1588                        $save_payment_method,
1589                        isset( $_GET['pay_for_order'] ) && 'yes' === $_GET['pay_for_order']
1590                );
1591        }
1592
1593        /**
1594         * Ensure the order is associated to the payment intent.
1595         *
1596         * @param int $order_id The order ID.
1597         * @param string $intent_id The payment intent ID.
1598         * @return bool
1599         */
1600        private function is_order_associated_to_payment_intent( int $order_id, string $intent_id ): bool {
1601                $order_from_payment_intent = WC_Stripe_Helper::get_order_by_intent_id( $intent_id );
1602                return $order_from_payment_intent && $order_from_payment_intent->get_id() === $order_id;
1603        }
1604
1605        /**
1606         * Ensure the order is associated to the setup intent.
1607         *
1608         * @param int $order_id The order ID.
1609         * @param string $intent_id The setup intent ID.
1610         * @return bool
1611         */
1612        private function is_order_associated_to_setup_intent( int $order_id, string $intent_id ): bool {
1613                $order = wc_get_order( $order_id );
1614                if ( ! $order ) {
1615                        return false;
1616                }
1617
1618                $intent = $this->stripe_request( 'setup_intents/' . $intent_id . '?expand[]=payment_method.billing_details' );
1619                if ( ! $intent ) {
1620                        return false;
1621                }
1622
1623                if ( ! isset( $intent->payment_method ) || ! isset( $intent->payment_method->billing_details ) ) {
1624                        return false;
1625                }
1626
1627                if ( $order->get_billing_email() !== $intent->payment_method->billing_details->email ) {
1628                        return false;
1629                }
1630
1631                return true;
1632        }
1633
1634        /**
1635         * Processes UPE redirect payments.
1636         *
1637         * @param int    $order_id The order ID being processed.
1638         * @param string $intent_id The Stripe setup/payment intent ID for the order payment.
1639         * @param bool   $save_payment_method Boolean representing whether payment method for order should be saved.
1640         * @param bool $is_pay_for_order True if processing payment from Pay for Order page. Optional.
1641         *
1642         * @since 5.5.0
1643         * @version 5.5.0
1644         */
1645        public function process_upe_redirect_payment( $order_id, $intent_id, $save_payment_method, $is_pay_for_order = false ) {
1646                $order = wc_get_order( $order_id );
1647
1648                if ( ! is_object( $order ) ) {
1649                        return;
1650                }
1651
1652                if ( $order->has_status( [ OrderStatus::PROCESSING, OrderStatus::COMPLETED, OrderStatus::ON_HOLD ] ) ) {
1653                        return;
1654                }
1655
1656                $order_helper = WC_Stripe_Order_Helper::get_instance();
1657
1658                if ( $order_helper->get_stripe_upe_redirect_processed( $order ) ) {
1659                        return;
1660                }
1661
1662                try {
1663                        // First check if the order is already being processed by another request.
1664                        $locked = $order_helper->lock_order_payment( $order );
1665                        if ( $locked ) {
1666                                WC_Stripe_Logger::log( "Skip processing UPE redirect payment for order $order_id for the amount of {$order->get_total()}, order payment is already being processed (locked)" );
1667                                return;
1668                        }
1669
1670                        WC_Stripe_Logger::log( "Begin processing UPE redirect payment for order $order_id for the amount of {$order->get_total()}" );
1671
1672                        $this->process_order_for_confirmed_intent( $order, $intent_id, $save_payment_method );
1673                } catch ( Exception $e ) {
1674                        $order_helper->unlock_order_payment( $order );
1675
1676                        WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
1677                        /* translators: localized exception message */
1678                        $order->update_status( OrderStatus::FAILED, sprintf( __( 'UPE payment failed: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
1679
1680                        wc_add_notice( $e->getMessage(), 'error' );
1681
1682                        $redirect_url = '';
1683                        if ( $is_pay_for_order ) {
1684                                $redirect_url = $order->get_checkout_payment_url();
1685                        } else {
1686                                $redirect_url = wc_get_checkout_url();
1687                        }
1688                        wp_safe_redirect( wp_sanitize_redirect( $redirect_url ) );
1689
1690                        exit;
1691                } finally {
1692                        $order_helper->unlock_order_payment( $order );
1693                }
1694        }
1695
1696        /**
1697         * Update order and maybe save payment method for an order after an intent has been created and confirmed.
1698         *
1699         * @param WC_Order $order               Order being processed.
1700         * @param string   $intent_id           Stripe setup/payment ID.
1701         * @param bool     $save_payment_method Boolean representing whether payment method for order should be saved.
1702         */
1703        public function process_order_for_confirmed_intent( $order, $intent_id, $save_payment_method ) {
1704                $payment_needed = $this->is_payment_needed( $order->get_id() );
1705
1706                // Get payment intent to confirm status.
1707                if ( $payment_needed ) {
1708                        $intent = $this->stripe_request( 'payment_intents/' . $intent_id . '?expand[]=payment_method' );
1709                        $error  = isset( $intent->last_payment_error ) ? $intent->last_payment_error : false;
1710                } else {
1711                        $intent = $this->stripe_request( 'setup_intents/' . $intent_id . '?expand[]=payment_method&expand[]=latest_attempt' );
1712                        $error  = isset( $intent->last_setup_error ) ? $intent->last_setup_error : false;
1713                }
1714
1715                if ( ! empty( $error ) ) {
1716                        WC_Stripe_Logger::log( 'Error when processing payment: ' . $error->message );
1717                        throw new WC_Stripe_Exception( __( "We're not able to process this payment. Please try again later.", 'woocommerce-gateway-stripe' ) );
1718                }
1719
1720                $order_helper = WC_Stripe_Order_Helper::get_instance();
1721
1722                // Validates the intent can be applied to the order.
1723                try {
1724                        $order_helper->validate_intent_for_order( $order, $intent );
1725                } catch ( Exception $e ) {
1726                        throw new Exception( __( "We're not able to process this payment. Please try again later.", 'woocommerce-gateway-stripe' ) );
1727                }
1728
1729                list( $payment_method_type, $payment_method_details ) = $this->get_payment_method_data_from_intent( $intent );
1730
1731                if ( ! isset( $this->payment_methods[ $payment_method_type ] ) ) {
1732                        return;
1733                }
1734                $payment_method = $this->payment_methods[ $payment_method_type ];
1735
1736                $is_pre_order = false;
1737                if ( $this->has_pre_order( $order->get_id() ) ) {
1738                        // If this is a pre-order, simply mark the order as pre-ordered and allow
1739                        // the subsequent logic to save the payment method and proceed to complete the order.
1740                        $this->mark_order_as_pre_ordered( $order->get_id() );
1741
1742                        // We require to save the payment method if the pre-order is charged upon release.
1743                        $save_payment_method = $save_payment_method || $this->has_pre_order_charged_upon_release( $order );
1744                        $is_pre_order        = true;
1745                }
1746
1747                if ( $save_payment_method && $payment_method->is_reusable() ) {
1748                        $payment_method_object = null;
1749                        if ( $payment_method->get_id() !== $payment_method->get_retrievable_type() ) {
1750                                $generated_payment_method_id = $payment_method_details[ $payment_method_type ]->generated_sepa_debit;
1751                                $payment_method_object       = $this->stripe_request( "payment_methods/$generated_payment_method_id", [], null, 'GET' );
1752
1753                                // This is our first opportunity to save the payment method for payment methods that have a different retrievable type. Save it now.
1754                                $payment_method->create_payment_token_for_user( $order->get_customer_id(), $payment_method_object );
1755                        } else {
1756                                $payment_method_object = $intent->payment_method;
1757                        }
1758
1759                        $customer                = $this->get_stripe_customer_from_order( $order );
1760                        $prepared_payment_method = $this->prepare_payment_method( $payment_method_object );
1761
1762                        $customer->clear_cache();
1763                        $this->save_payment_method_to_order( $order, $prepared_payment_method );
1764                        do_action( 'woocommerce_stripe_add_payment_method', $customer->get_user_id(), $payment_method_object );
1765                }
1766
1767                if ( ! $is_pre_order ) {
1768                        if ( $payment_needed ) {
1769                                // Use the last charge within the intent to proceed.
1770                                $this->process_response( $this->get_latest_charge_from_intent( $intent ), $order );
1771                        } else {
1772                                $order->payment_complete();
1773                        }
1774                }
1775
1776                $this->save_intent_to_order( $order, $intent );
1777                $this->set_payment_method_title_for_order( $order, $payment_method_type );
1778                $order_helper->update_stripe_upe_redirect_processed( $order, true );
1779
1780                // TODO: This is a stop-gap to fix a critical issue, see
1781                // https://github.com/woocommerce/woocommerce-gateway-stripe/issues/2536. It would
1782                // be better if we removed the need for additional meta data in favor of refactoring
1783                // this part of the payment processing.
1784                $order_helper->delete_stripe_upe_waiting_for_redirect( $order );
1785
1786                /**
1787                 * This meta is to prevent stores with short hold stock settings from cancelling orders while waiting for payment to be finalised by Stripe or the customer (i.e. completing 3DS or payment redirects).
1788                 * Now that payment is confirmed, we can remove this meta.
1789                 */
1790                $order_helper->remove_payment_awaiting_action( $order, false );
1791
1792                $order->save();
1793        }
1794
1795        /**
1796         * Converts payment method into object similar to prepared source
1797         * compatible with wc_stripe_payment_metadata and wc_stripe_generate_payment_request filters.
1798         *
1799         * @param object           $payment_method Stripe payment method object response.
1800         *
1801         * @return object
1802         */
1803        public function prepare_payment_method( $payment_method ) {
1804                return (object) [
1805                        'customer'              => $payment_method->customer,
1806                        'source'                => null,
1807                        'source_object'         => null,
1808                        'payment_method'        => $payment_method->id,
1809                        'payment_method_object' => $payment_method,
1810                ];
1811        }
1812
1813        /**
1814         * Save payment method to order.
1815         *
1816         * @param WC_Order $order For to which the source applies.
1817         * @param stdClass $payment_method Stripe Payment Method.
1818         */
1819        public function save_payment_method_to_order( $order, $payment_method ) {
1820                $order_helper = WC_Stripe_Order_Helper::get_instance();
1821                if ( $payment_method->customer ) {
1822                        $order_helper->update_stripe_customer_id( $order, $payment_method->customer );
1823                }
1824
1825                // Save the payment method id as `source_id`, because we use both `sources` and `payment_methods` APIs.
1826                $order_helper->update_stripe_source_id( $order, $payment_method->payment_method );
1827
1828                if ( is_callable( [ $order, 'save' ] ) ) {
1829                        $order->save();
1830                }
1831
1832                // Fetch the payment method ID from the payment method object.
1833                if ( isset( $this->payment_methods[ $payment_method->payment_method_object->type ] ) ) {
1834                        $payment_method_id = $this->get_upe_gateway_id_for_order( $this->payment_methods[ $payment_method->payment_method_object->type ] );
1835                }
1836
1837                $this->maybe_update_source_on_subscription_order( $order, $payment_method, $payment_method_id ?? $this->id );
1838        }
1839
1840        /**
1841         * Retries the payment process once an error occured.
1842         *
1843         * @param object   $response          The response from the Stripe API.
1844         * @param WC_Order $order             An order that is being paid for.
1845         * @param bool     $retry             A flag that indicates whether another retry should be attempted.
1846         * @param bool     $force_save_source Force save the payment source.
1847         * @param mixed    $previous_error    Any error message from previous request.
1848         * @param bool     $use_order_source  Whether to use the source, which should already be attached to the order.
1849         * @throws WC_Stripe_Exception If the payment is not accepted.
1850         * @return array|void
1851         */
1852        public function retry_after_error( $response, $order, $retry, $force_save_source = false, $previous_error = false, $use_order_source = false ) {
1853                if ( ! $retry ) {
1854                        $localized_message = __( 'Sorry, we are unable to process your payment at this time. Please retry later.', 'woocommerce-gateway-stripe' );
1855                        $order->add_order_note( $localized_message );
1856                        throw new WC_Stripe_Exception( print_r( $intent, true ), $localized_message ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.
1857                }
1858
1859                // Don't do anymore retries after this.
1860                if ( 5 <= $this->retry_interval ) {
1861                        return $this->process_payment( $order->get_id(), false, $force_save_source, $response->error, $previous_error );
1862                }
1863
1864                sleep( $this->retry_interval );
1865                ++$this->retry_interval;
1866
1867                return $this->process_payment( $order->get_id(), true, $force_save_source, $response->error, $previous_error );
1868        }
1869
1870        /**
1871         * Returns true if a payment is needed for the current cart or order.
1872         * Pre-Orders and Subscriptions may not require an upfront payment, so we need to check whether
1873         * or not the payment is necessary to decide for either a setup intent or a payment intent.
1874         *
1875         * @since 5.8.0
1876         *
1877         * @param int $order_id The order ID being processed.
1878         *
1879         * @return bool Whether a payment is necessary.
1880         */
1881        public function is_payment_needed( $order_id = null ) {
1882                $is_pay_for_order_page = parent::is_valid_pay_for_order_endpoint();
1883
1884                // Check if the cart contains a pre-order product. Ignore the cart if we're on the Pay for Order page.
1885                if ( $this->is_pre_order_item_in_cart() && ! $is_pay_for_order_page ) {
1886                        $pre_order_product = $this->get_pre_order_product_from_cart();
1887
1888                        // Only one pre-order product is allowed per cart,
1889                        // so we can return if it's charged upfront.
1890                        return $this->is_pre_order_product_charged_upfront( $pre_order_product );
1891                }
1892
1893                if ( ! empty( $order_id ) && $this->has_pre_order( $order_id ) ) {
1894                        $pre_order_product  = $this->get_pre_order_product_from_order( $order_id );
1895                        $is_charged_upfront = $this->is_pre_order_product_charged_upfront( $pre_order_product );
1896
1897                        // If the pre-order is set to charge upon release and we're on the pay for order page and the pre-order has completed status, payment is needed.
1898                        if ( ! $is_charged_upfront && $is_pay_for_order_page && $this->is_pre_order_completed( $order_id ) ) {
1899                                return true;
1900                        }
1901
1902                        return $is_charged_upfront;
1903                }
1904
1905                // Free trial subscriptions without a sign up fee, or any other type
1906                // of order with a `0` amount should fall into the logic below.
1907                $amount = is_null( WC()->cart ) ? 0 : WC()->cart->get_total( false );
1908                $order  = isset( $order_id ) ? wc_get_order( $order_id ) : null;
1909                if ( is_a( $order, 'WC_Order' ) ) {
1910                        $amount = $order->get_total();
1911                }
1912
1913                $converted_amount = WC_Stripe_Helper::get_stripe_amount( $amount, strtolower( get_woocommerce_currency() ) );
1914
1915                return 0 < $converted_amount;
1916        }
1917
1918        /**
1919         * Checks if card on Payment Method is a prepaid card.
1920         *
1921         * @since 4.0.6
1922         * @param object $payment_method
1923         * @return bool
1924         */
1925        public function is_prepaid_card( $payment_method ) {
1926                return (
1927                        $payment_method
1928                        && ( WC_Stripe_Payment_Methods::CARD === $payment_method->type )
1929                        && 'prepaid' === $payment_method->card->funding
1930                );
1931        }
1932
1933        /**
1934         * Get WC User from WC Order.
1935         *
1936         * @param WC_Order $order
1937         *
1938         * @return WP_User
1939         */
1940        public function get_user_from_order( $order ) {
1941                $user = $order->get_user();
1942                if ( false === $user ) {
1943                        $user = wp_get_current_user();
1944                }
1945                return $user;
1946        }
1947
1948        /**
1949         * Get WC Stripe Customer from WC Order.
1950         *
1951         * @param WC_Order $order
1952         *
1953         * @return WC_Stripe_Customer
1954         */
1955        public function get_stripe_customer_from_order( $order ) {
1956                $user     = $this->get_user_from_order( $order );
1957                $customer = new WC_Stripe_Customer( $user->ID );
1958
1959                return $customer;
1960        }
1961
1962        /**
1963         * Checks if gateway should be available to use.
1964         *
1965         * @since 5.6.0
1966         */
1967        public function is_available() {
1968                // The main UPE gateway represents the card payment method. So it's only available if the card payment method is enabled and available.
1969                if ( isset( $this->payment_methods['card'] ) && ( ! $this->payment_methods['card']->is_enabled() || ! $this->payment_methods['card']->is_available() ) ) {
1970                        return false;
1971                }
1972
1973                return parent::is_available();
1974        }
1975
1976        /**
1977         * Function to be used with array_filter
1978         * to filter UPE payment methods supported with current checkout
1979         *
1980         * @param string $payment_method_id Stripe payment method.
1981         *
1982         * @return bool
1983         */
1984        public function is_enabled_at_checkout( $payment_method_id ) {
1985                if ( ! isset( $this->payment_methods[ $payment_method_id ] ) ) {
1986                        return false;
1987                }
1988
1989                $account_domestic_currency = WC_Stripe::get_instance()->account->get_account_default_currency();
1990
1991                return $this->payment_methods[ $payment_method_id ]->is_enabled_at_checkout( null, $account_domestic_currency );
1992        }
1993
1994        /**
1995         * Function to be used with array_filter
1996         * to filter UPE payment methods that support saved payments
1997         *
1998         * @param string $payment_method_id Stripe payment method.
1999         *
2000         * @return bool
2001         */
2002        public function is_enabled_for_saved_payments( $payment_method_id ) {
2003                if ( ! isset( $this->payment_methods[ $payment_method_id ] ) ) {
2004                        return false;
2005                }
2006                return $this->payment_methods[ $payment_method_id ]->is_reusable();
2007        }
2008
2009        // TODO: Actually validate.
2010        public function validate_upe_checkout_experience_accepted_payments_field( $key, $value ) {
2011                return $value;
2012        }
2013
2014        /**
2015         * Checks if the setting to allow the user to save cards is enabled.
2016         *
2017         * @return bool Whether the setting to allow saved cards is enabled or not.
2018         */
2019        public function is_saved_cards_enabled() {
2020                return $this->saved_cards;
2021        }
2022
2023        /**
2024         * Checks if the setting to allow the saving of SEPA tokens for other payment methods (iDEAL and Bancontact) is enabled.
2025         *
2026         * @return bool Whether the setting to allow SEPA tokens for other payment methods is enabled.
2027         *
2028         * @deprecated 10.0.0 Use is_sepa_tokens_for_ideal_enabled() and is_sepa_tokens_for_bancontact_enabled() instead.
2029         */
2030        public function is_sepa_tokens_for_other_methods_enabled() {
2031                return $this->sepa_tokens_for_other_methods;
2032        }
2033
2034        /**
2035         * Checks if the setting to allow the saving of SEPA tokens for iDEAL is enabled.
2036         *
2037         * @return bool Whether the setting to allow SEPA tokens for iDEAL is enabled.
2038         */
2039        public function is_sepa_tokens_for_ideal_enabled() {
2040                return $this->sepa_tokens_for_ideal;
2041        }
2042
2043        /**
2044         * Checks if the setting to allow the saving of SEPA tokens for Bancontact is enabled.
2045         *
2046         * @return bool Whether the setting to allow SEPA tokens for Bancontact is enabled.
2047         */
2048        public function is_sepa_tokens_for_bancontact_enabled() {
2049                return $this->sepa_tokens_for_bancontact;
2050        }
2051
2052        /**
2053         * Checks if the Optimized Checkout setting is enabled.
2054         *
2055         * @return bool Whether the Optimized Checkout setting is enabled.
2056         */
2057        public function is_oc_enabled() {
2058                return $this->oc_enabled;
2059        }
2060
2061        /**
2062         * Set formatted readable payment method title for order,
2063         * using payment method details from accompanying charge.
2064         *
2065         * @param WC_Order      $order WC Order being processed.
2066         * @param string        $payment_method_type Stripe payment method key.
2067         * @param stdClass|bool $stripe_payment_method Stripe payment method object.
2068         *
2069         * @since 5.5.0
2070         * @version 5.5.0
2071         */
2072        public function set_payment_method_title_for_order( $order, $payment_method_type, $stripe_payment_method = false ) {
2073                $payment_methods = $this->payment_methods;
2074
2075                // Override the payment method type if the Optimized Checkout is enabled.
2076                if ( $this->oc_enabled && WC_Stripe_Payment_Methods::OC === $payment_method_type ) {
2077                        $payment_methods[ WC_Stripe_Payment_Methods::OC ] = new WC_Stripe_UPE_Payment_Method_OC();
2078                }
2079
2080                if ( ! isset( $payment_methods[ $payment_method_type ] ) ) {
2081                        return;
2082                }
2083
2084                $payment_method    = $payment_methods[ $payment_method_type ];
2085                $payment_method_id = $payment_method instanceof WC_Stripe_UPE_Payment_Method_CC ? $this->id : $payment_method->id;
2086                $is_stripe_link    = WC_Stripe_Payment_Methods::LINK === $payment_method_type ||
2087                        ( isset( $stripe_payment_method->type ) && WC_Stripe_Payment_Methods::LINK === $stripe_payment_method->type );
2088
2089                // Stripe Link uses the main gateway to process payments, however Link payments should use the title of the Link payment method.
2090                if ( $is_stripe_link && isset( $payment_methods[ WC_Stripe_Payment_Methods::LINK ] ) ) {
2091                        $payment_method_id    = $this->id;
2092                        $payment_method_title = $payment_methods[ WC_Stripe_Payment_Methods::LINK ]->get_title( $stripe_payment_method );
2093                } else {
2094                        $payment_method_title = $payment_method->get_title( $stripe_payment_method );
2095                }
2096
2097                $order->set_payment_method( $payment_method_id );
2098                $order->set_payment_method_title( $payment_method_title );
2099                $order->save();
2100
2101                // Update the subscription's purchased in this order with the payment method ID.
2102                $this->update_subscription_payment_method_from_order( $order, $this->get_upe_gateway_id_for_order( $payment_method ) );
2103        }
2104
2105        /**
2106         * This is overloading the upe checkout experience type on the settings page.
2107         *
2108         * @param string $key Field key.
2109         * @param array  $data Field data.
2110         * @return string
2111         */
2112        public function generate_upe_checkout_experience_accepted_payments_html( $key, $data ) {
2113                try {
2114                        $stripe_account = $this->stripe_request( 'account' );
2115                } catch ( WC_Stripe_Exception $e ) {
2116                        WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
2117                }
2118
2119                $stripe_capabilities = isset( $stripe_account->capabilities ) ? (array) $stripe_account->capabilities : [];
2120                $data['description'] = '<p>' . __( "Select payments available to customers at checkout. We'll only show your customers the most relevant payment methods based on their currency and location.", 'woocommerce-gateway-stripe' ) . '</p>
2121                <table class="wc_gateways widefat form-table wc-stripe-upe-method-selection" cellspacing="0" aria-describedby="wc_stripe_upe_method_selection">
2122                        <thead>
2123                                <tr>
2124                                        <th class="name wc-stripe-upe-method-selection__name">' . esc_html__( 'Method', 'woocommerce-gateway-stripe' ) . '</th>
2125                                        <th class="status wc-stripe-upe-method-selection__status">' . esc_html__( 'Enabled', 'woocommerce-gateway-stripe' ) . '</th>
2126                                        <th class="description wc-stripe-upe-method-selection__description">' . esc_html__( 'Description', 'woocommerce-gateway-stripe' ) . '</th>
2127                                </tr>
2128                        </thead>
2129                        <tbody>';
2130
2131                $is_automatic_capture_enabled = $this->is_automatic_capture_enabled();
2132
2133                foreach ( $this->payment_methods as $method_id => $method ) {
2134                        $method_enabled       = in_array( $method_id, $this->get_upe_enabled_payment_method_ids(), true ) && ( $is_automatic_capture_enabled || ! $method->requires_automatic_capture() ) ? 'enabled' : 'disabled';
2135                        $method_enabled_label = 'enabled' === $method_enabled ? __( 'enabled', 'woocommerce-gateway-stripe' ) : __( 'disabled', 'woocommerce-gateway-stripe' );
2136                        $capability_id        = WC_Stripe_Helper::get_payment_method_capability_id( $method_id );
2137                        $method_status        = isset( $stripe_capabilities[ $capability_id ] ) ? $stripe_capabilities[ $capability_id ] : 'inactive';
2138                        $subtext_messages     = $method->get_subtext_messages( $method_status );
2139                        $aria_label           = sprintf(
2140                                /* translators: $1%s payment method ID, $2%s "enabled" or "disabled" */
2141                                esc_attr__( 'The &quot;%1$s&quot; payment method is currently %2$s', 'woocommerce-gateway-stripe' ),
2142                                $method_id,
2143                                $method_enabled_label
2144                        );
2145                        $manual_capture_tip = sprintf(
2146                                /* translators: $1%s payment method label */
2147                                __( '%1$s is not available to your customers when manual capture is enabled.', 'woocommerce-gateway-stripe' ),
2148                                $method->get_label()
2149                        );
2150                        $data['description'] .= '<tr data-upe_method_id="' . $method_id . '">
2151                                        <td class="name wc-stripe-upe-method-selection__name" width="">
2152                                                ' . $method->get_label() . '
2153                                                ' . ( empty( $subtext_messages ) ? '' : '<span class="wc-payment-gateway-method-name">&nbsp;–&nbsp;' . $subtext_messages . '</span>' ) . '
2154                                        </td>
2155                                        <td class="status wc-stripe-upe-method-selection__status" width="1%">
2156                                                <a class="wc-payment-upe-method-toggle-' . $method_enabled . '" href="#">
2157                                                        <span class="woocommerce-input-toggle woocommerce-input-toggle--' . $method_enabled . '" aria-label="' . $aria_label . '">
2158                                                        ' . ( 'enabled' === $method_enabled ? __( 'Yes', 'woocommerce-gateway-stripe' ) : __( 'No', 'woocommerce-gateway-stripe' ) ) . '
2159                                                        </span>
2160                                                </a>'
2161                                                . ( ! $is_automatic_capture_enabled && $method->requires_automatic_capture() ? '<span class="tips dashicons dashicons-warning" style="margin-top: 1px; margin-right: -25px; margin-left: 5px; color: red" data-tip="' . $manual_capture_tip . '" />' : '' ) .
2162                                        '</td>
2163                                        <td class="description wc-stripe-upe-method-selection__description" width="">' . $method->get_description() . '</td>
2164                                </tr>';
2165                }
2166
2167                $data['description'] .= '</tbody>
2168                        </table>
2169                        <p><a class="button" target="_blank" href="https://dashboard.stripe.com/account/payments/settings">' . __( 'Get more payment methods', 'woocommerce-gateway-stripe' ) . '</a></p>
2170                        <span id="wc_stripe_upe_change_notice" class="hidden">' . __( 'You must save your changes.', 'woocommerce-gateway-stripe' ) . '</span>';
2171
2172                return $this->generate_title_html( $key, $data );
2173        }
2174
2175        /**
2176         * Extacts the Stripe intent's payment_method_type and payment_method_details values.
2177         *
2178         * @param $intent   Stripe's intent response.
2179         * @return string[] List with 2 values: payment_method_type and payment_method_details.
2180         */
2181        private function get_payment_method_data_from_intent( $intent ) {
2182                $payment_method_type    = '';
2183                $payment_method_details = false;
2184
2185                if ( 'payment_intent' === $intent->object ) {
2186                        $charge = $this->get_latest_charge_from_intent( $intent );
2187                        if ( ! empty( $charge ) ) {
2188                                $payment_method_details = (array) $charge->payment_method_details;
2189                                $payment_method_type    = ! empty( $payment_method_details ) ? $payment_method_details['type'] : '';
2190                        }
2191                } elseif ( 'setup_intent' === $intent->object ) {
2192                        if ( ! empty( $intent->latest_attempt ) && ! empty( $intent->latest_attempt->payment_method_details ) ) {
2193                                $payment_method_details = (array) $intent->latest_attempt->payment_method_details;
2194                                $payment_method_type    = $payment_method_details['type'];
2195                        } elseif ( ! empty( $intent->payment_method ) ) {
2196                                $payment_method_details = $intent->payment_method;
2197                                $payment_method_type    = $payment_method_details->type;
2198                        }
2199                        // Setup intents don't have details, keep the false value.
2200                }
2201
2202                return [ $payment_method_type, $payment_method_details ];
2203        }
2204
2205        /**
2206         * Prepares Stripe metadata for a given order.
2207         *
2208         * @param WC_Order $order Order being processed.
2209         *
2210         * @return array Array of keyed metadata values.
2211         */
2212        public function get_metadata_from_order( $order ) {
2213                $payment_type = $this->is_payment_recurring( $order->get_id() ) ? 'recurring' : 'single';
2214                $name         = sanitize_text_field( $order->get_billing_first_name() ) . ' ' . sanitize_text_field( $order->get_billing_last_name() );
2215                $email        = sanitize_email( $order->get_billing_email() );
2216
2217                $metadata = [
2218                        'customer_name'  => $name,
2219                        'customer_email' => $email,
2220                        'site_url'       => esc_url( get_site_url() ),
2221                        'order_id'       => $order->get_order_number(),
2222                        'order_key'      => $order->get_order_key(),
2223                        'payment_type'   => $payment_type,
2224                        'signature'      => $this->get_order_signature( $order ),
2225                        'tax_amount'     => WC_Stripe_Helper::get_stripe_amount( $order->get_total_tax(), strtolower( $order->get_currency() ) ),
2226                ];
2227
2228                return apply_filters( 'wc_stripe_intent_metadata', $metadata, $order );
2229        }
2230
2231        /**
2232         * Adds BNPL debug metadata to the metadata array.
2233         *
2234         * @return array
2235         */
2236        public function add_bnpl_debug_metadata( $metadata, $order ) {
2237                // The following parameters are used to debug BNPL display issues.
2238                $pmc_enabled = $this->get_option( 'pmc_enabled', 'null' );
2239                if ( ! is_string( $pmc_enabled ) ) {
2240                        $pmc_enabled = $pmc_enabled ? 'yes' : 'no';
2241                }
2242                return array_merge(
2243                        $metadata,
2244                        [
2245                                'is_legacy_checkout_enabled' => WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ? 'no' : 'yes',
2246                                'is_oc_enabled'              => $this->is_oc_enabled() ? 'yes' : 'no',
2247                                'pmc_enabled'                => $pmc_enabled,
2248                        ]
2249                );
2250        }
2251
2252        /**
2253         * Returns true when viewing payment methods page.
2254         *
2255         * @return bool
2256         */
2257        private function is_payment_methods_page() {
2258                global $wp;
2259
2260                $page_id = wc_get_page_id( 'myaccount' );
2261
2262                return ( $page_id && is_page( $page_id ) && ( isset( $wp->query_vars['payment-methods'] ) ) );
2263        }
2264
2265        /**
2266         * True if the request contains the values that indicates a redirection after a successful setup intent creation.
2267         *
2268         * @return bool
2269         */
2270        private function is_setup_intent_success_creation_redirection() {
2271                return ( ! empty( $_GET['setup_intent_client_secret'] ) & ! empty( $_GET['setup_intent'] ) & ! empty( $_GET['redirect_status'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
2272        }
2273
2274        /**
2275         * Adds a token to current user from a setup intent id.
2276         *
2277         * @param string  $setup_intent_id ID of the setup intent.
2278         * @param WP_User $user            User to add token to.
2279         *
2280         * @return WC_Payment_Token The added token.
2281         *
2282         * @since 5.8.0
2283         * @version 5.8.0
2284         */
2285        public function create_token_from_setup_intent( $setup_intent_id, $user ) {
2286                try {
2287                        $setup_intent = $this->stripe_request( 'setup_intents/' . $setup_intent_id . '?&expand[]=latest_attempt' );
2288                        if ( ! empty( $setup_intent->last_payment_error ) ) {
2289                                throw new WC_Stripe_Exception( __( "We're not able to add this payment method. Please try again later.", 'woocommerce-gateway-stripe' ) );
2290                        }
2291
2292                        list( $payment_method_type, $payment_method_details ) = $this->get_payment_method_data_from_intent( $setup_intent );
2293
2294                        $payment_method_id = $setup_intent->payment_method;
2295
2296                        $payment_method = null;
2297                        if ( $this->oc_enabled ) {
2298                                $payment_method_type = $payment_method_details['type'] ?? $payment_method_details->type ?? null;
2299                                if ( ! empty( $payment_method_type ) ) {
2300                                        $payment_method = self::get_payment_method_instance( $payment_method_type );
2301                                }
2302                        } else {
2303                                $payment_method = $this->payment_methods[ $payment_method_type ] ?? null;
2304                        }
2305
2306                        if ( ! $payment_method ) {
2307                                throw new WC_Stripe_Exception( __( "We're not able to add this payment method. Please try again later.", 'woocommerce-gateway-stripe' ) );
2308                        }
2309
2310                        if ( $payment_method->get_id() !== $payment_method->get_retrievable_type() ) {
2311                                $payment_method_id = $payment_method_details[ $payment_method_type ]->generated_sepa_debit;
2312                        }
2313
2314                        $payment_method_object = $this->stripe_request( 'payment_methods/' . $payment_method_id );
2315
2316                        $customer = new WC_Stripe_Customer( wp_get_current_user()->ID );
2317                        $customer->clear_cache();
2318
2319                        return $payment_method->create_payment_token_for_user( $user->ID, $payment_method_object );
2320                } catch ( Exception $e ) {
2321                        wc_add_notice( $e->getMessage(), 'error', [ 'icon' => 'error' ] );
2322                        WC_Stripe_Logger::log( 'Error when adding payment method: ' . $e->getMessage() );
2323                        return [
2324                                'result' => 'error',
2325                        ];
2326                }
2327        }
2328
2329        /**
2330         * Wrapper function to manage requests to WC_Stripe_API.
2331         *
2332         * @param string   $path   Stripe API endpoint path to query.
2333         * @param string   $params Parameters for request body.
2334         * @param WC_Order $order  WC Order for request.
2335         * @param string   $method HTTP method for request.
2336         *
2337         * @return object JSON response object.
2338         */
2339        protected function stripe_request( $path, $params = null, $order = null, $method = 'POST' ) {
2340                if ( is_null( $params ) ) {
2341                        return WC_Stripe_API::retrieve( $path );
2342                }
2343                if ( ! is_null( $order ) ) {
2344                        $level3_data = $this->get_level3_data_from_order( $order );
2345                        return WC_Stripe_API::request_with_level3_data( $params, $path, $level3_data, $order );
2346                }
2347                return WC_Stripe_API::request( $params, $path, $method );
2348        }
2349
2350        /**
2351         * Returns an array of address data to be used in a Stripe /payment_intents API request.
2352         *
2353         * Stripe docs: https://docs.stripe.com/api/payment_intents/create#create_payment_intent-shipping
2354         *
2355         * @since 7.7.0
2356         *
2357         * @param WC_Order $order Order to fetch address data from.
2358         *
2359         * @return array
2360         */
2361        private function get_address_data_for_payment_request( $order ) {
2362                return [
2363                        'name'    => trim( $order->get_shipping_first_name() . ' ' . $order->get_shipping_last_name() ),
2364                        'address' => [
2365                                'line1'       => $order->get_shipping_address_1(),
2366                                'line2'       => $order->get_shipping_address_2(),
2367                                'city'        => $order->get_shipping_city(),
2368                                'country'     => $order->get_shipping_country(),
2369                                'postal_code' => $order->get_shipping_postcode(),
2370                                'state'       => $order->get_shipping_state(),
2371                        ],
2372                ];
2373        }
2374
2375        /**
2376         * Create a payment intent for the order, or update the existing one.
2377         *
2378         * @param WC_Order $order The WC Order for which we're handling a payment intent.
2379         * @param array    $payment_information The payment information to be used for the payment intent.
2380         * @param bool     $retry Whether we should retry if this processing fails.
2381         *
2382         * @throws WC_Stripe_Exception When there's an error creating or updating the payment intent, and can't be retried.
2383         *
2384         * @return stdClass
2385         */
2386        private function process_payment_intent_for_order( WC_Order $order, array $payment_information, $retry = true ) {
2387                // Check if order already has a successful payment intent
2388                $existing_intent = $this->get_intent_from_order( $order );
2389                if ( $existing_intent && isset( $existing_intent->id ) && 'pi_' === substr( $existing_intent->id, 0, 3 ) ) {
2390                        // Fetch the latest intent data from Stripe
2391                        $intent = $this->stripe_request( 'payment_intents/' . $existing_intent->id );
2392
2393                        // If the intent is already successful, return it to prevent duplicate charges
2394                        if ( isset( $intent->status ) && in_array( $intent->status, self::SUCCESSFUL_INTENT_STATUS, true ) ) {
2395                                return $intent;
2396                        }
2397                }
2398
2399                // Check if the order has a payment intent that is compatible with the current payment method types.
2400                $payment_intent = $this->get_existing_compatible_payment_intent( $order, $payment_information['payment_method_types'] );
2401
2402                // If the payment intent is not compatible, we need to create a new one. Throws an exception on error.
2403                if ( $payment_intent ) {
2404                        // Update the existing payment intent if one exists.
2405                        $payment_intent = $this->intent_controller->update_and_confirm_payment_intent( $payment_intent, $payment_information );
2406                } else {
2407                        // Create (and confirm) a new payment intent if one doesn't exist.
2408                        $payment_intent = $this->intent_controller->create_and_confirm_payment_intent( $payment_information );
2409                }
2410
2411                // Handle an error in the payment intent.
2412                if ( ! empty( $payment_intent->error ) ) {
2413
2414                        // Add the payment intent information to the order meta
2415                        // if we were able to create one despite the error.
2416                        if ( ! empty( $payment_intent->error->payment_intent ) ) {
2417                                $this->save_intent_to_order( $order, $payment_intent->error->payment_intent );
2418                        }
2419
2420                        $has_removed_customer = $this->maybe_remove_non_existent_customer( $payment_intent->error, $order );
2421
2422                        if ( ! $this->is_retryable_error( $payment_intent->error ) || ! $retry ) {
2423                                throw new WC_Stripe_Exception(
2424                                        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
2425                                        print_r( $payment_intent, true ),
2426                                        $this->get_payment_intent_error_message( $payment_intent )
2427                                );
2428                        }
2429
2430                        // If the non existent customer was removed, we need to recreate a customer.
2431                        if ( $has_removed_customer ) {
2432                                $payment_information['customer'] = $this->get_customer_id_for_order( $order );
2433                        }
2434
2435                        // Don't do anymore retries after this.
2436                        if ( 5 <= $this->retry_interval ) {
2437                                return $this->process_payment_intent_for_order( $order, $payment_information, false );
2438                        }
2439
2440                        sleep( $this->retry_interval );
2441                        ++$this->retry_interval;
2442
2443                        return $this->process_payment_intent_for_order( $order, $payment_information, true );
2444                }
2445
2446                // Add the payment intent information to the order meta.
2447                $this->save_intent_to_order( $order, $payment_intent );
2448
2449                return $payment_intent;
2450        }
2451
2452        /**
2453         * Return specific error messages for payment intent errors.
2454         *
2455         * @param stdClass $payment_intent The payment intent object.
2456         * @return string The error message.
2457         */
2458        private function get_payment_intent_error_message( $payment_intent ) {
2459                if ( isset( $payment_intent->error->payment_intent->payment_method_types[0] ) &&
2460                        'amazon_pay' === $payment_intent->error->payment_intent->payment_method_types[0] &&
2461                        isset( $payment_intent->error->decline_code ) &&
2462                        'generic_decline' === $payment_intent->error->decline_code
2463                ) {
2464                        return __(
2465                                'Amazon Pay is not compatible for this order. Please try a different payment method.',
2466                                'woocommerce-gateway-stripe'
2467                        );
2468                }
2469
2470                // This error indicates that the saved payment method is no longer valid.
2471                // This can happen if the payment method was removed in Stripe dashboard, or if it expired.
2472                // In this case, we want to show a specific message to the user.
2473                if ( isset( $payment_intent->error->type )
2474                        && 'invalid_request_error' === $payment_intent->error->type
2475                        && isset( $payment_intent->error->message )
2476                        && str_contains( $payment_intent->error->message, self::DETACHED_PAYMENT_METHOD_ERROR_STRING )
2477                ) {
2478                        return __(
2479                                'This saved payment method is no longer valid. It might be expired, removed, or broken. Please choose a different payment method.',
2480                                'woocommerce-gateway-stripe'
2481                        );
2482                }
2483
2484                return $payment_intent->error->message;
2485        }
2486
2487        /**
2488         * Create a setup intent for the order.
2489         *
2490         * @param WC_Order $order               The WC Order for which we're handling a setup intent.
2491         * @param array    $payment_information The payment information to be used for the setup intent.
2492         *
2493         * @throws WC_Stripe_Exception When there's an error creating the setup intent.
2494         *
2495         * @return stdClass
2496         */
2497        protected function process_setup_intent_for_order( WC_Order $order, array $payment_information ) {
2498                $setup_intent = $this->intent_controller->create_and_confirm_setup_intent( $payment_information );
2499
2500                if ( ! empty( $setup_intent->error ) ) {
2501
2502                        // Add the setup intent information to the order meta, if one was created despite the error.
2503                        if ( ! empty( $setup_intent->error->payment_intent ) ) {
2504                                $this->save_intent_to_order( $order, $setup_intent->error->payment_intent );
2505                        }
2506
2507                        $this->maybe_remove_non_existent_customer( $setup_intent->error, $order );
2508
2509                        throw new WC_Stripe_Exception(
2510                                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
2511                                print_r( $setup_intent, true ),
2512                                __( 'Sorry, we are unable to process your payment at this time. Please retry later.', 'woocommerce-gateway-stripe' )
2513                        );
2514                }
2515
2516                // Add the payment intent information to the order meta.
2517                $this->save_intent_to_order( $order, $setup_intent );
2518
2519                return $setup_intent;
2520        }
2521
2522        /**
2523         * Collects the payment information needed for processing a payment intent.
2524         *
2525         * @param WC_Order $order The WC Order to be paid for.
2526         * @return array An array containing the payment information for processing a payment intent.
2527         * @throws WC_Stripe_Exception When there's an error retrieving the payment information.
2528         */
2529        protected function prepare_payment_information_from_request( WC_Order $order ) {
2530                $selected_payment_type = $this->get_selected_payment_method_type_from_request();
2531                $capture_method        = $this->is_automatic_capture_enabled() ? 'automatic' : 'manual'; // automatic | manual.
2532                $currency              = strtolower( $order->get_currency() );
2533                $amount                = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $currency );
2534                $shipping_details      = null;
2535                $token                 = false;
2536
2537                $save_payment_method_to_store  = $this->should_save_payment_method_from_request( $order->get_id(), $selected_payment_type );
2538                $is_using_saved_payment_method = $this->is_using_saved_payment_method();
2539
2540                // If order requires shipping, add the shipping address details to the payment intent request.
2541                if ( method_exists( $order, 'get_shipping_postcode' ) && ! empty( $order->get_shipping_postcode() ) ) {
2542                        $shipping_details = $this->get_address_data_for_payment_request( $order );
2543                }
2544
2545                if ( $is_using_saved_payment_method ) {
2546                        // Use the saved payment method.
2547                        $token = WC_Stripe_Payment_Tokens::get_token_from_request( $_POST );
2548
2549                        // A valid token couldn't be retrieved from the request.
2550                        if ( null === $token ) {
2551                                throw new WC_Stripe_Exception(
2552                                        'A valid payment method token could not be retrieved from the request.',
2553                                        __( "The selected payment method isn't valid.", 'woocommerce-gateway-stripe' )
2554                                );
2555                        }
2556
2557                        $payment_method_id = $token->get_token();
2558
2559                        if ( is_a( $token, 'WC_Payment_Token_SEPA' ) ) {
2560                                $selected_payment_type = WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID;
2561                        } elseif ( is_a( $token, 'WC_Payment_Token_Amazon_Pay' ) ) {
2562                                $selected_payment_type = WC_Stripe_UPE_Payment_Method_Amazon_Pay::STRIPE_ID;
2563                        }
2564                } else {
2565                        $payment_method_id = sanitize_text_field( wp_unslash( $_POST['wc-stripe-payment-method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
2566                }
2567
2568                $payment_method_details = ! empty( $payment_method_id ) ? WC_Stripe_API::get_payment_method( $payment_method_id ) : (object) [];
2569
2570                // Override the payment method type with the API value when OC is enabled
2571                if ( $this->oc_enabled ) {
2572                        $selected_payment_type = $payment_method_details->type ?? null;
2573                        $payment_method_types  = [ $selected_payment_type ];
2574                } else {
2575                        $payment_method_types = $this->get_payment_method_types_for_intent_creation(
2576                                $selected_payment_type,
2577                                $order->get_id(),
2578                                $this->get_express_payment_type_from_request()
2579                        );
2580                }
2581
2582                $payment_information = [
2583                        'amount'                        => $amount,
2584                        'currency'                      => $currency,
2585                        'customer'                      => $this->get_customer_id_for_order( $order ),
2586                        'is_using_saved_payment_method' => $is_using_saved_payment_method,
2587                        'level3'                        => $this->get_level3_data_from_order( $order ),
2588                        'metadata'                      => $this->get_metadata_from_order( $order ),
2589                        'order'                         => $order,
2590                        'payment_initiated_by'          => 'initiated_by_customer', // initiated_by_merchant | initiated_by_customer.
2591                        'selected_payment_type'         => $selected_payment_type,
2592                        'payment_method_types'          => $payment_method_types,
2593                        'shipping'                      => $shipping_details,
2594                        'token'                         => $token,
2595                        'return_url'                    => $this->get_return_url_for_redirect( $order, $save_payment_method_to_store ),
2596                        'use_stripe_sdk'                => 'true', // We want to use the SDK to handle next actions via the client payment elements. See https://docs.stripe.com/api/setup_intents/create#create_setup_intent-use_stripe_sdk
2597                        'has_subscription'              => $this->has_subscription( $order->get_id() ),
2598                        'payment_method'                => $payment_method_id,
2599                        'payment_method_details'        => $payment_method_details,
2600                        'payment_type'                  => 'single', // single | recurring.
2601                        'save_payment_method_to_store'  => $save_payment_method_to_store,
2602                        'capture_method'                => $capture_method,
2603                ];
2604
2605                if ( WC_Stripe_Payment_Methods::ACH === $selected_payment_type ) {
2606                        WC_Stripe_API::attach_payment_method_to_customer( $payment_information['customer'], $payment_method_id );
2607                }
2608
2609                // Use the dynamic + short statement descriptor if enabled and it's a card payment.
2610                $is_short_statement_descriptor_enabled = 'yes' === $this->get_option( 'is_short_statement_descriptor_enabled', 'no' );
2611                if ( WC_Stripe_Payment_Methods::CARD === $selected_payment_type && $is_short_statement_descriptor_enabled ) {
2612                        $payment_information['statement_descriptor_suffix'] = WC_Stripe_Helper::get_dynamic_statement_descriptor_suffix( $order );
2613                }
2614
2615                if ( empty( $payment_method_id ) && ! empty( $_POST['wc-stripe-confirmation-token'] ) ) {
2616                        // Add fields that are only set when using the confirmation token flow.
2617                        $payment_information = $this->prepare_payment_information_for_confirmation_token(
2618                                $payment_information,
2619                                $selected_payment_type,
2620                                $capture_method,
2621                        );
2622                } else {
2623                        // Add fields that are only set when using the payment method flow.
2624                        $payment_information = $this->prepare_payment_information_for_payment_method( $payment_information, $selected_payment_type, $order );
2625                }
2626
2627                return $payment_information;
2628        }
2629
2630        /**
2631         * Add or remove payment information fields for the confirmation token flow.
2632         *
2633         * @param array $payment_information The base payment information.
2634         * @param string $selected_payment_type The selected payment type.
2635         * @param string $capture_method The capture method to be used.
2636         * @return array The customized payment information for the confirmation token flow.
2637         */
2638        private function prepare_payment_information_for_confirmation_token( $payment_information, $selected_payment_type, $capture_method ) {
2639                // These fields should not be set when using confirmation tokens to create a payment intent.
2640                unset( $payment_information['payment_method'] );
2641                unset( $payment_information['payment_method_details'] );
2642
2643                $confirmation_token_id                     = sanitize_text_field( wp_unslash( $_POST['wc-stripe-confirmation-token'] ?? '' ) );
2644                $payment_information['confirmation_token'] = $confirmation_token_id;
2645
2646                // Some payment methods such as Amazon Pay will only accept a capture_method of 'manual'
2647                // under payment_method_options instead of at the top level.
2648                if ( 'manual' === $capture_method ) {
2649                        unset( $payment_information['capture_method'] );
2650                        $payment_information['payment_method_options'][ $selected_payment_type ]['capture_method'] = 'manual';
2651                }
2652
2653                if ( $payment_information['has_subscription'] ) {
2654                        $payment_information['payment_method_options'][ $selected_payment_type ]['setup_future_usage'] = 'off_session';
2655                }
2656
2657                return $payment_information;
2658        }
2659
2660        /**
2661         * Add or remove payment information fields for the payment method flow.
2662         *
2663         * @param array $payment_information The base payment information.
2664         * @param string $selected_payment_type The selected payment type.
2665         * @param WC_Order $order The WC Order being processed.
2666         * @return array The customized payment information for the payment method flow.
2667         */
2668        private function prepare_payment_information_for_payment_method( $payment_information, $selected_payment_type, $order ) {
2669                $payment_information['payment_method_options'] = $this->get_payment_method_options(
2670                        $selected_payment_type,
2671                        $order,
2672                        $payment_information['payment_method_details']
2673                );
2674
2675                return $payment_information;
2676        }
2677
2678        /**
2679         * Returns the payment method options for the selected payment type.
2680         *
2681         * @param string   $selected_payment_type  The selected payment type, e.g. 'klarna'
2682         * @param WC_Order $order                  The WC Order we are processing a payment for.
2683         * @param stdClass $payment_method_details The payment method details.
2684         */
2685        private function get_payment_method_options( $selected_payment_type, $order, $payment_method_details ) {
2686                $payment_method_options = [];
2687
2688                // If the Optimized Checkout is enabled, we need to use the payment method details from the request.
2689                if ( $this->oc_enabled && isset( $payment_method_details->type ) ) {
2690                        $selected_payment_type = $payment_method_details->type;
2691                }
2692
2693                // Specify the client in payment_method_options (currently, Checkout only supports a client value of "web")
2694                if ( WC_Stripe_Payment_Methods::WECHAT_PAY === $selected_payment_type ) {
2695                        $payment_method_options = [
2696                                WC_Stripe_Payment_Methods::WECHAT_PAY => [
2697                                        'client' => 'web',
2698                                ],
2699                        ];
2700                } elseif ( WC_Stripe_Payment_Methods::KLARNA === $selected_payment_type ) {
2701                        $preferred_locale = WC_Stripe_Helper::get_klarna_preferred_locale(
2702                                get_locale(),
2703                                $order->get_billing_country()
2704                        );
2705
2706                        if ( ! empty( $preferred_locale ) ) {
2707                                $payment_method_options = [
2708                                        WC_Stripe_Payment_Methods::KLARNA => [
2709                                                'preferred_locale' => $preferred_locale,
2710                                        ],
2711                                ];
2712                        }
2713                } elseif ( WC_Stripe_Payment_Methods::BLIK === $selected_payment_type ) {
2714                        $payment_method_options = [
2715                                WC_Stripe_Payment_Methods::BLIK => [
2716                                        'code' => sanitize_text_field( wp_unslash( $_POST['wc-stripe-blik-code'] ?? '' ) ),
2717                                ],
2718                        ];
2719                }
2720
2721                // Add the updated preferred credit card brand when defined
2722                $preferred_brand = $payment_method_details->card->networks->preferred ?? null;
2723                if ( isset( $preferred_brand ) ) {
2724                        $payment_method_options = [
2725                                'card' => [
2726                                        'network' => $preferred_brand,
2727                                ],
2728                        ];
2729                }
2730
2731                return $payment_method_options;
2732        }
2733
2734        /**
2735         * Conditionally stores the card brand to the order meta.
2736         *
2737         * @param WC_Order $order          The WC Order for which we're processing a payment.
2738         * @param stdClass $payment_method The payment method object.
2739         */
2740        private function maybe_set_preferred_card_brand_for_order( WC_Order $order, $payment_method ) {
2741                // Retrieve the preferred card brand for the payment method.
2742                $preferred_brand = $payment_method->card->networks->preferred ?? null;
2743                if ( WC_Stripe_Co_Branded_CC_Compatibility::is_wc_supported() && $preferred_brand ) {
2744
2745                        $order->update_meta_data( '_stripe_card_brand', $preferred_brand );
2746                        $order->save_meta_data();
2747
2748                        if ( function_exists( 'wc_admin_record_tracks_event' ) ) {
2749                                wc_admin_record_tracks_event( 'wcstripe_co_branded_cc_preferred_brand_selected', [ 'brand' => $preferred_brand ] );
2750                        }
2751                }
2752        }
2753
2754        /**
2755         * Returns whether the selected payment method should be saved.
2756         *
2757         * We want to save it for subscriptions and when the shopper chooses to,
2758         * as long as the selected payment method type is reusable.
2759         *
2760         * @param int    $order_id            The ID of the order we're processing.
2761         * @param string $payment_method_type
2762         *
2763         * @return boolean
2764         */
2765        private function should_save_payment_method_from_request( $order_id, $payment_method_type ) {
2766                // Don't save it when the type is unknown or not reusable.
2767                if (
2768                        ! isset( $this->payment_methods[ $payment_method_type ] ) ||
2769                        ! $this->payment_methods[ $payment_method_type ]->is_reusable()
2770                ) {
2771                        return false;
2772                }
2773
2774                // Don't save it if we're using a saved payment method.
2775                if ( $this->is_using_saved_payment_method() ) {
2776                        return false;
2777                }
2778
2779                // Save it when paying for a subscription and manual renewal is not required.
2780                if ( $this->has_subscription( $order_id ) ) {
2781                        return ! WC_Stripe_Subscriptions_Helper::is_manual_renewal_required();
2782                }
2783
2784                // Unless it's paying for a subscription, don't save it when saving payment methods is disabled.
2785                if ( ! $this->is_saved_cards_enabled() ) {
2786                        return false;
2787                }
2788
2789                // Save the payment method when forced by the filter.
2790                if ( WC_Stripe_Helper::should_force_save_payment_method( false, $order_id ) ) {
2791                        return true;
2792                }
2793
2794                // For card/stripe, the request arg is `wc-stripe-new-payment-method` and for our reusable APMs (i.e. bancontact) it's `wc-stripe_bancontact-new-payment-method`.
2795                $save_payment_method_request_arg = sprintf( 'wc-stripe%s-new-payment-method', WC_Stripe_Payment_Methods::CARD !== $payment_method_type ? '_' . $payment_method_type : '' );
2796
2797                // Don't save it if we don't have the data from the checkout checkbox for saving a payment method.
2798                if ( ! isset( $_POST[ $save_payment_method_request_arg ] ) ) {
2799                        return false;
2800                }
2801
2802                // Save it when the checkout checkbox for saving a payment method was checked off.
2803                $save_payment_method = wc_clean( wp_unslash( $_POST[ $save_payment_method_request_arg ] ) );
2804
2805                // Its value is 'true' for classic and '1' for block.
2806                if ( in_array( $save_payment_method, [ 'true', '1' ], true ) ) {
2807                        return true;
2808                }
2809
2810                return false;
2811        }
2812
2813        /**
2814         * Gets the selected payment method type from the request and normalizes its slug for internal use.
2815         *
2816         * @return string
2817         */
2818        private function get_selected_payment_method_type_from_request() {
2819                // phpcs:ignore WordPress.Security.NonceVerification.Missing
2820                if ( ! isset( $_POST['payment_method'] ) ) {
2821                        return '';
2822                }
2823
2824                $payment_method_type = sanitize_text_field( wp_unslash( $_POST['payment_method'] ) );
2825                if ( substr( $payment_method_type, 0, 6 ) !== 'stripe' ) {
2826                        return '';
2827                }
2828
2829                // Amazon Pay is available as an express checkout method only, for now.
2830                // To prevent WooCommerce from rendering it as a standard payment method in checkout, we make
2831                // WC_Stripe_UPE_Payment_Method_Amazon_Pay::is_available() return false.
2832                // We set the payment method to 'amazon_pay' here, instead of earlier (i.e. passing
2833                // 'stripe_amazon_pay' in the POST request) to avoid WooCommerce rejecting the order for
2834                // having an "unavailable" payment method type.
2835                if ( WC_Stripe_Payment_Methods::AMAZON_PAY === $this->get_express_payment_type_from_request() ) {
2836                        return WC_Stripe_Payment_Methods::AMAZON_PAY;
2837                }
2838
2839                return substr( $payment_method_type, 0, 7 ) === 'stripe_' ? substr( $payment_method_type, 7 ) : 'card';
2840        }
2841
2842        /**
2843         * Gets the express payment type, e.g. google_pay, apple_pay, from the request,
2844         *   if applicable.
2845         *
2846         * @return string|null
2847         */
2848        private function get_express_payment_type_from_request() {
2849                if ( ! isset( $_POST['express_payment_type'] ) ) {
2850                        return null;
2851                }
2852
2853                return sanitize_text_field( wp_unslash( $_POST['express_payment_type'] ) );
2854        }
2855
2856        /**
2857         * Save the selected payment method information to the order and as a payment token for the user.
2858         *
2859         * @param WC_Order $order                  The WC order for which we're saving the payment method.
2860         * @param stdClass $payment_method_object  The payment method object retrieved from Stripe.
2861         * @param string   $payment_method_type    The payment method type, like `card`, `sepa_debit`, etc.
2862         */
2863        protected function handle_saving_payment_method( WC_Order $order, $payment_method_object, string $payment_method_type ) {
2864                $user     = $this->get_user_from_order( $order );
2865                $customer = new WC_Stripe_Customer( $user->ID );
2866                $customer->clear_cache();
2867
2868                // If the payment method object is a Link payment method, use Link as the payment method type.
2869                if ( isset( $payment_method_object->type ) && WC_Stripe_Payment_Methods::LINK === $payment_method_object->type ) {
2870                        $payment_method_type     = WC_Stripe_Payment_Methods::LINK;
2871                        $payment_method_instance = $this->get_payment_method_instance( $payment_method_type );
2872                } elseif ( $this->oc_enabled && isset( $payment_method_object->type ) ) {
2873                        // When OC is enabled, use the payment method type from the payment method object
2874                        $payment_method_type     = $payment_method_object->type;
2875                        $payment_method_instance = $this->get_payment_method_instance( $payment_method_type );
2876                } else {
2877                        $payment_method_instance = $this->payment_methods[ $payment_method_type ];
2878                }
2879
2880                // Searches for an existing duplicate token to update.
2881                $found_token = WC_Stripe_Payment_Tokens::get_duplicate_token( $payment_method_object, $customer->get_user_id(), $this->id );
2882
2883                if ( $found_token ) {
2884                        // Update the token with the new payment method ID.
2885                        $payment_method_instance->update_payment_token( $found_token, $payment_method_object->id );
2886                } else {
2887                        // Create a payment token for the user in the store.
2888                        $payment_method_instance->create_payment_token_for_user( $user->ID, $payment_method_object );
2889                }
2890
2891                // Add the payment method information to the order.
2892                $prepared_payment_method_object = $this->prepare_payment_method( $payment_method_object );
2893
2894                // If the customer ID is missing from the Payment Method, Stripe haven't attached it to the customer yet. This occurs for Cash App for example.
2895                // Fallback to the order's customer ID.
2896                if ( empty( $prepared_payment_method_object->customer ) ) {
2897                        $prepared_payment_method_object->customer = $this->get_stripe_customer_id( $order );
2898                }
2899
2900                $this->maybe_update_source_on_subscription_order( $order, $prepared_payment_method_object, $this->get_upe_gateway_id_for_order( $payment_method_instance ) );
2901
2902                do_action( 'woocommerce_stripe_add_payment_method', $user->ID, $payment_method_object );
2903        }
2904
2905        /**
2906         * Set the payment metadata for payment method id.
2907         *
2908         * @param WC_Order $order The order.
2909         * @param string   $payment_method_id The value to be set.
2910         */
2911        public function set_payment_method_id_for_order( WC_Order $order, string $payment_method_id ) {
2912                // Save the payment method id as `source_id`, because we use both `sources` and `payment_methods` APIs.
2913                WC_Stripe_Order_Helper::get_instance()->update_stripe_source_id( $order, $payment_method_id );
2914                $order->save_meta_data();
2915        }
2916
2917        /**
2918         * Set the payment metadata for payment method id for subscription.
2919         *
2920         * @param WC_Subscription $order The order.
2921         * @param string   $payment_method_id The value to be set.
2922         */
2923        public function set_payment_method_id_for_subscription( $subscription, string $payment_method_id ) {
2924                $subscription->update_meta_data( '_stripe_source_id', $payment_method_id );
2925                $subscription->save_meta_data();
2926        }
2927
2928        /**
2929         * Set the payment metadata for customer id.
2930         *
2931         * Set to public so it can be called from confirm_change_payment_from_setup_intent_ajax()
2932         *
2933         * @param WC_Order $order The order.
2934         * @param string   $customer_id The value to be set.
2935         */
2936        public function set_customer_id_for_order( WC_Order $order, string $customer_id ) {
2937                WC_Stripe_Order_Helper::get_instance()->update_stripe_customer_id( $order, $customer_id );
2938                $order->save_meta_data();
2939        }
2940
2941        /**
2942         * Set the payment metadata for customer id for subscription.
2943         *
2944         * Set to public so it can be called from confirm_change_payment_from_setup_intent_ajax()
2945         *
2946         * @param WC_Subscription $subscription The subscription.
2947         * @param string          $customer_id The value to be set.
2948         */
2949        public function set_customer_id_for_subscription( $subscription, string $customer_id ) {
2950                $subscription->update_meta_data( '_stripe_customer_id', $customer_id );
2951                $subscription->save_meta_data();
2952        }
2953
2954        /**
2955         * Set the payment metadata for the selected payment type.
2956         *
2957         * @param WC_Order $order                 The order for which we're setting the selected payment type.
2958         * @param string   $selected_payment_type The selected payment type.
2959         */
2960        private function set_selected_payment_type_for_order( WC_Order $order, string $selected_payment_type ) {
2961                WC_Stripe_Order_Helper::get_instance()->update_stripe_upe_payment_type( $order, $selected_payment_type );
2962                $order->save_meta_data();
2963        }
2964        /**
2965         * Gets the Stripe customer ID associated with an order, creates one if none is associated.
2966         *
2967         * @param WC_Order $order The WC order from which to get the Stripe customer.
2968         * @return string The Stripe customer ID.
2969         */
2970        private function get_customer_id_for_order( WC_Order $order ): string {
2971
2972                // Get the user/customer from the order.
2973                $customer_id = $this->get_stripe_customer_id( $order );
2974                if ( ! empty( $customer_id ) ) {
2975                        return $customer_id;
2976                }
2977
2978                // Update customer or create customer if one does not exist.
2979                $user     = $this->get_user_from_order( $order );
2980                $customer = new WC_Stripe_Customer( $user->ID );
2981
2982                $current_context = $this->is_valid_pay_for_order_endpoint() ? WC_Stripe_Customer::CUSTOMER_CONTEXT_PAY_FOR_ORDER : null;
2983
2984                // Pass the order object so we can retrieve billing details
2985                // in payment flows where it is not present in the request.
2986                $args = [ 'order' => $order ];
2987                return $customer->update_or_create_customer( $args, $current_context );
2988        }
2989
2990        /**
2991         * Throws an exception when the given payment method type is not valid.
2992         *
2993         * @param array  $payment_information Payment information to process the payment.
2994         * @param string $billing_country     Order billing country.
2995         *
2996         * @throws WC_Stripe_Exception When the payment method type is not allowed in the given country.
2997         */
2998        protected function validate_selected_payment_method_type( $payment_information, $billing_country ) {
2999                $invalid_method_message = __( 'The selected payment method type is invalid.', 'woocommerce-gateway-stripe' );
3000
3001                // No payment method type was provided.
3002                if ( empty( $payment_information['selected_payment_type'] ) ) {
3003                        throw new WC_Stripe_Exception( 'No payment method type selected.', $invalid_method_message );
3004                }
3005
3006                $payment_method_type = $payment_information['selected_payment_type'];
3007
3008                // The provided payment method type is not among the available payment method types.
3009                if ( ! isset( $this->payment_methods[ $payment_method_type ] ) ) {
3010                        throw new WC_Stripe_Exception(
3011                                sprintf(
3012                                        'The selected payment method type is not within the available payment methods.%1$sSelected payment method type: %2$s. Available payment methods: %3$s',
3013                                        PHP_EOL,
3014                                        $payment_method_type,
3015                                        implode( ', ', array_keys( $this->payment_methods ) )
3016                                ),
3017                                $invalid_method_message
3018                        );
3019                }
3020
3021                // The selected payment method is allowed in the billing country.
3022                if ( ! $this->payment_methods[ $payment_method_type ]->is_allowed_on_country( $billing_country ) ) {
3023                        throw new WC_Stripe_Exception(
3024                                sprintf( 'The payment method type "%1$s" is not available in %2$s.', $payment_method_type, $billing_country ),
3025                                __( 'This payment method type is not available in the selected country.', 'woocommerce-gateway-stripe' )
3026                        );
3027                }
3028        }
3029
3030        /**
3031         * Add a new Stripe payment method via the My Account > Payment methods page.
3032         *
3033         * This function is called by @see WC_Form_Handler::add_payment_method_action().
3034         *
3035         * @return array
3036         */
3037        public function add_payment_method() {
3038                try {
3039                        if ( ! is_user_logged_in() ) {
3040                                throw new WC_Stripe_Exception( 'No logged-in user found.' );
3041                        }
3042
3043                        // phpcs:ignore WordPress.Security.NonceVerification.Missing
3044                        if ( ! isset( $_POST['wc-stripe-setup-intent'] ) ) {
3045                                throw new WC_Stripe_Exception( 'Stripe setup intent is missing.' );
3046                        }
3047
3048                        $user            = wp_get_current_user();
3049                        $setup_intent_id = wc_clean( wp_unslash( $_POST['wc-stripe-setup-intent'] ) );
3050                        $setup_intent    = $this->stripe_request( 'setup_intents/' . $setup_intent_id );
3051
3052                        if ( ! empty( $setup_intent->last_payment_error ) ) {
3053                                throw new WC_Stripe_Exception( sprintf( 'Error fetching the setup intent (ID %s) from Stripe: %s.', $setup_intent_id, ! empty( $setup_intent->last_payment_error->message ) ? $setup_intent->last_payment_error->message : 'Unknown error' ) );
3054                        }
3055
3056                        $payment_method_id     = $setup_intent->payment_method;
3057                        $payment_method_object = $this->stripe_request( 'payment_methods/' . $payment_method_id );
3058
3059                        $payment_method = $this->payment_methods[ $payment_method_object->type ];
3060
3061                        $customer = new WC_Stripe_Customer( $user->ID );
3062                        $customer->clear_cache();
3063
3064                        // Check if a token with the same payment method details exist. If so, just updates the payment method ID and return.
3065                        $found_token = WC_Stripe_Payment_Tokens::get_duplicate_token( $payment_method_object, $user->ID, $this->id );
3066
3067                        // If we have a token found, update it and return.
3068                        if ( $found_token ) {
3069                                $token = $payment_method->update_payment_token( $found_token, $payment_method_object->id );
3070                        } else {
3071                                // Create a new token if not.
3072                                $token = $payment_method->create_payment_token_for_user( $user->ID, $payment_method_object );
3073                        }
3074
3075                        if ( ! is_a( $token, 'WC_Payment_Token' ) ) {
3076                                throw new WC_Stripe_Exception( sprintf( 'New payment token is not an instance of WC_Payment_Token. Token: %s.', print_r( $token, true ) ) );
3077                        }
3078
3079                        do_action( 'woocommerce_stripe_add_payment_method', $user->ID, $payment_method_object );
3080
3081                        return [
3082                                'result'   => 'success',
3083                                'redirect' => wc_get_endpoint_url( 'payment-methods' ),
3084                        ];
3085                } catch ( WC_Stripe_Exception $e ) {
3086                        WC_Stripe_Logger::log( sprintf( 'Add payment method error: %s', $e->getMessage() ) );
3087                        return [ 'result' => 'failure' ];
3088                }
3089        }
3090
3091        /**
3092         * Returns a URL to process UPE redirect payments.
3093         *
3094         * @param WC_Order $order               The WC Order to be paid for.
3095         * @param bool     $save_payment_method Whether to save the payment method for future use.
3096         *
3097         * @return string
3098         */
3099        private function get_return_url_for_redirect( $order, $save_payment_method ) {
3100                return wp_sanitize_redirect(
3101                        esc_url_raw(
3102                                add_query_arg(
3103                                        [
3104                                                'order_id'            => $order->get_id(),
3105                                                'wc_payment_method'   => self::ID,
3106                                                '_wpnonce'            => wp_create_nonce( 'wc_stripe_process_redirect_order_nonce' ),
3107                                                'save_payment_method' => $save_payment_method ? 'yes' : 'no',
3108                                                'pay_for_order'       => parent::is_valid_pay_for_order_endpoint() ? 'yes' : 'no',
3109                                        ],
3110                                        $this->get_return_url( $order )
3111                                )
3112                        )
3113                );
3114        }
3115
3116        /**
3117         * Retrieves the (possible) existing payment intent for an order and payment method types.
3118         *
3119         * @param WC_Order $order The order.
3120         * @param array    $payment_method_types The payment method types.
3121         *
3122         * @return object|null
3123         *
3124         * @throws WC_Stripe_Exception
3125         */
3126        private function get_existing_compatible_payment_intent( $order, $payment_method_types ) {
3127                // Reload the order to make sure we have the latest data.
3128                $order  = wc_get_order( $order->get_id() );
3129                $intent = $this->get_intent_from_order( $order );
3130                if ( ! $intent ) {
3131                        return null;
3132                }
3133
3134                // If the payment method types match, we can reuse the payment intent.
3135                if ( count( array_intersect( $intent->payment_method_types, $payment_method_types ) ) !== count( $payment_method_types ) ) {
3136                        return null;
3137                }
3138
3139                // Check if the order total matches the existing intent amount.
3140                $order_total = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $order->get_currency() );
3141                if ( $order_total !== $intent->amount ) {
3142                        return null;
3143                }
3144
3145                // Check if the status of the intent still allows update.
3146                if ( in_array( $intent->status, [ WC_Stripe_Intent_Status::CANCELED, WC_Stripe_Intent_Status::SUCCEEDED ], true ) ) {
3147                        return null;
3148                }
3149
3150                // If the intent requires confirmation to show voucher on checkout (i.e. Boleto or oxxo or multibanco ) or requires action (i.e. need to show a 3DS confirmation card or handle the UPE redirect), don't reuse the intent
3151                if ( in_array( $intent->status, [ WC_Stripe_Intent_Status::REQUIRES_CONFIRMATION, WC_Stripe_Intent_Status::REQUIRES_ACTION ], true ) ) {
3152                        return null;
3153                }
3154
3155                // Cash App Pay intents with a "requires payment method" status cannot be reused. See https://docs.stripe.com/payments/cash-app-pay/accept-a-payment?web-or-mobile=web&payments-ui-type=direct-api#failed-payments
3156                if ( in_array( WC_Stripe_Payment_Methods::CASHAPP_PAY, $intent->payment_method_types ) && WC_Stripe_Intent_Status::REQUIRES_PAYMENT_METHOD === $intent->status ) {
3157                        return null;
3158                }
3159
3160                return $intent;
3161        }
3162
3163        /**
3164         * Returns the payment method types for the intent creation request, given the selected payment type.
3165         *
3166         * @param string $selected_payment_type The payment type the shopper selected, if any.
3167         * @param int    $order_id              ID of the WC order we're handling.
3168         * @param string|null $express_payment_type  The express payment type, if any.
3169         *
3170         * @return array
3171         */
3172        private function get_payment_method_types_for_intent_creation(
3173                string $selected_payment_type,
3174                int $order_id,
3175                ?string $express_payment_type = null
3176        ): array {
3177                // If the shopper didn't select a payment type, return all the enabled ones.
3178                if ( '' === $selected_payment_type ) {
3179                        return $this->get_upe_enabled_at_checkout_payment_method_ids( $order_id );
3180                }
3181
3182                // Check if this is for an express payment
3183                if ( ! empty( $express_payment_type ) ) {
3184                        switch ( $express_payment_type ) {
3185                                case WC_Stripe_UPE_Payment_Method_Link::STRIPE_ID:
3186                                        return [ WC_Stripe_UPE_Payment_Method_CC::STRIPE_ID, WC_Stripe_UPE_Payment_Method_Link::STRIPE_ID ];
3187                                case WC_Stripe_Payment_Methods::AMAZON_PAY:
3188                                        return [ WC_Stripe_Payment_Methods::AMAZON_PAY ];
3189                                case WC_Stripe_Payment_Methods::GOOGLE_PAY:
3190                                case WC_Stripe_Payment_Methods::APPLE_PAY:
3191                                default:
3192                                        return [ WC_Stripe_UPE_Payment_Method_CC::STRIPE_ID ];
3193                        }
3194                }
3195
3196                // If the "card" type was selected and Link is enabled, include Link in the types,
3197                // to support paying with cards stored in Link.
3198                if (
3199                        WC_Stripe_UPE_Payment_Method_CC::STRIPE_ID === $selected_payment_type &&
3200                        in_array(
3201                                WC_Stripe_UPE_Payment_Method_Link::STRIPE_ID,
3202                                $this->get_upe_enabled_payment_method_ids(),
3203                                true
3204                        )
3205                ) {
3206                        return [
3207                                WC_Stripe_UPE_Payment_Method_CC::STRIPE_ID,
3208                                WC_Stripe_UPE_Payment_Method_Link::STRIPE_ID,
3209                        ];
3210                }
3211
3212                // Otherwise, return the selected payment method type.
3213                return [ $selected_payment_type ];
3214        }
3215
3216        /**
3217         * Checks if the save option for a payment method should be displayed or not.
3218         *
3219         * @param WC_Stripe_UPE_Payment_Method $payment_method UPE Payment Method instance.
3220         * @return bool - True if the payment method is reusable and the saved cards feature is enabled for the gateway and there is no subscription item in the cart, false otherwise.
3221         */
3222        private function should_upe_payment_method_show_save_option( $payment_method ) {
3223                if ( $payment_method->is_reusable() ) {
3224                        // If a subscription in the cart, it will be saved by default so no need to show the option.
3225                        // If force save payment method is true, no need to show the option.
3226                        return $this->is_saved_cards_enabled() && ! $this->is_subscription_item_in_cart() && ! $this->is_pre_order_charged_upon_release_in_cart() && ! WC_Stripe_Helper::should_force_save_payment_method();
3227                }
3228
3229                return false;
3230        }
3231
3232        /**
3233         * Determines the gateway ID to set as the subscription order's payment method.
3234         *
3235         * Some UPE payment methods use different gateway IDs to process their payments. eg Bancontact uses SEPA tokens, cards use 'stripe' etc.
3236         * This function will return the correct gateway ID which should be recorded on the subscription so that the correct payment method is used to process future payments.
3237         *
3238         * @param WC_Stripe_UPE_Payment_Method $payment_method The UPE payment method instance.
3239         * @return string The gateway ID to set on the subscription/order.
3240         */
3241        protected function get_upe_gateway_id_for_order( $payment_method ) {
3242                $token_gateway_type = $payment_method->get_retrievable_type();
3243
3244                if ( WC_Stripe_Payment_Methods::CARD === $token_gateway_type ||
3245                        WC_Stripe_Payment_Methods::LINK === $token_gateway_type ) {
3246                        return $this->id;
3247                }
3248
3249                return $this->payment_methods[ $token_gateway_type ]->id;
3250        }
3251
3252        /**
3253         * Fetches the appearance settings for Stripe Elements from the transient cache.
3254         *
3255         * Include the theme name in the transient key because getAppearance (found in client/styles/upe/index.js) will generate different
3256         * appearance rules based on the active theme, so we need to cache the appearance settings per theme.
3257         *
3258         * @param bool $is_block_checkout Whether the appearance settings are for the block checkout.
3259         *
3260         * @return array|null The appearance settings.
3261         */
3262        private function get_appearance_transient_key( $is_block_checkout = false ) {
3263                return ( $is_block_checkout ? self::BLOCKS_APPEARANCE_TRANSIENT : self::APPEARANCE_TRANSIENT ) . '_' . get_option( 'stylesheet' );
3264        }
3265
3266        /**
3267         * Checks if the current page is the order details page.
3268         *
3269         * @return bool Whether the current page is the order details page.
3270         */
3271        private function is_order_details_page() {
3272                $query_params = wp_unslash( $_GET ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
3273                if ( WC_Stripe_Woo_Compat_Utils::is_custom_orders_table_enabled() ) { // If custom order tables are enabled, we need to check the page query param.
3274                        return isset( $query_params['page'] ) && 'wc-orders' === $query_params['page'] && isset( $query_params['id'] );
3275                }
3276
3277                // If custom order tables are not enabled, we need to check the post type and action query params.
3278                $is_shop_order_post_type = isset( $query_params['post'] ) && 'shop_order' === get_post_type( $query_params['post'] );
3279                return isset( $query_params['action'] ) && 'edit' === $query_params['action'] && $is_shop_order_post_type;
3280        }
3281
3282        /**
3283         * Checks if this is a refund request.
3284         *
3285         * @return bool Whether this is a refund request.
3286         */
3287        private function is_refund_request() {
3288                return isset( $_POST['action'] ) && 'woocommerce_refund_line_items' === $_POST['action']; // phpcs:ignore WordPress.Security.NonceVerification.Missing
3289        }
3290
3291        /**
3292         * Depending on the payment method used to process the payment, we may need to redirect the user to a URL for further processing.
3293         *
3294         * - Voucher payments (Boleto or Oxxo or Multibanco) respond with a hash URL so the client JS code can recognize the response, pull out the necessary args and handle the displaying of the voucher.
3295         * - Wallet payments (CashApp or WeChat) respond with a hash URL so the client JS code can recognize the response, pull out the necessary args and handle the displaying of the modal.
3296         * - Other payment methods like Giropay, iDEAL, Alipay etc require a redirect to a URL provided by Stripe.
3297         * - 3DS Card payments return a hash URL so the client JS code can recognize the response, pull out the necessary PI args and display the 3DS confirmation modal.
3298         *
3299         * @param $return_url string The return URL.
3300         * @param $payment_intent object The payment intent object.
3301         * @param $payment_information array The payment information.
3302         * @param $order WC_Order The order.
3303         * @param $payment_needed bool Whether payment is needed.
3304         * @return string The redirect URL.
3305         */
3306        protected function get_redirect_url( $return_url, $payment_intent, $payment_information, $order, $payment_needed ) {
3307                $selected_payment_type = $this->oc_enabled ? $payment_information['payment_method_details']->type : $payment_information['selected_payment_type'];
3308                if ( isset( $payment_intent->payment_method_types ) && count( array_intersect( WC_Stripe_Payment_Methods::VOUCHER_PAYMENT_METHODS, $payment_intent->payment_method_types ) ) !== 0 ) {
3309                        // For Voucher payment method types (Boleto/Oxxo/Multibanco), redirect the customer to a URL hash formatted #wc-stripe-voucher-{order_id}:{payment_method_type}:{client_secret}:{redirect_url} to confirm the intent which also displays the voucher.
3310                        return sprintf(
3311                                '#wc-stripe-voucher-%s:%s:%s:%s',
3312                                $order->get_id(),
3313                                $selected_payment_type,
3314                                $payment_intent->client_secret,
3315                                rawurlencode( $return_url )
3316                        );
3317                } elseif ( isset( $payment_intent->payment_method_types ) && count( array_intersect( WC_Stripe_Payment_Methods::WALLET_PAYMENT_METHODS, $payment_intent->payment_method_types ) ) !== 0 ) {
3318                        // For Wallet payment method types (CashApp/WeChat Pay), redirect the customer to a URL hash formatted #wc-stripe-wallet-{order_id}:{payment_method_type}:{payment_intent_type}:{client_secret}:{redirect_url} to confirm the intent which also displays the modal.
3319                        return sprintf(
3320                                '#wc-stripe-wallet-%s:%s:%s:%s:%s:%s',
3321                                $order->get_id(),
3322                                $selected_payment_type,
3323                                $payment_intent->object,
3324                                $payment_intent->client_secret,
3325                                rawurlencode( $return_url ),
3326                                wp_create_nonce( 'wc_stripe_update_order_status_nonce' )
3327                        );
3328                } elseif ( isset( $payment_intent->next_action->type ) && in_array( $payment_intent->next_action->type, [ 'redirect_to_url', 'alipay_handle_redirect' ], true ) && ! empty( $payment_intent->next_action->{$payment_intent->next_action->type}->url ) ) {
3329                        return $payment_intent->next_action->{$payment_intent->next_action->type}->url;
3330                }
3331
3332                return sprintf(
3333                        '#wc-stripe-confirm-%s:%s:%s:%s',
3334                        $payment_needed ? 'pi' : 'si',
3335                        $order->get_id(),
3336                        $payment_intent->client_secret,
3337                        wp_create_nonce( 'wc_stripe_update_order_status_nonce' )
3338                );
3339        }
3340
3341        /**
3342         * Saves the default appearance settings to a transient cache.
3343         *
3344         * Individual appearance settings are saved for both block and shortcode checkout and also against each theme so that changing the theme will use different transient setting.
3345         */
3346        public function save_appearance_ajax() {
3347                try {
3348                        $is_nonce_valid = check_ajax_referer( 'wc_stripe_save_appearance_nonce', false, false );
3349
3350                        if ( ! $is_nonce_valid ) {
3351                                throw new WC_Stripe_Exception( 'Invalid nonce saving appearance.', __( 'Unable to update Stripe Elements appearance values at this time.', 'woocommerce-gateway-stripe' ) );
3352                        }
3353
3354                        $is_block_checkout = isset( $_POST['is_block_checkout'] ) ? wc_string_to_bool( wc_clean( wp_unslash( $_POST['is_block_checkout'] ) ) ) : false;
3355                        $appearance        = isset( $_POST['appearance'] ) ? json_decode( wc_clean( wp_unslash( $_POST['appearance'] ) ) ) : null;
3356
3357                        if ( null !== $appearance ) {
3358                                set_transient( $this->get_appearance_transient_key( $is_block_checkout ), $appearance, DAY_IN_SECONDS );
3359                        }
3360
3361                        wp_send_json_success( $appearance, 200 );
3362                } catch ( WC_Stripe_Exception $e ) {
3363                        WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
3364                        wp_send_json_error(
3365                                [
3366                                        'error' => [
3367                                                'message' => $e->getLocalizedMessage(),
3368                                        ],
3369                                ]
3370                        );
3371                }
3372        }
3373
3374        /**
3375         * Clears the appearance transients when a Block theme is updated or customized.
3376         * This ensures the UPE appearance is regenerated with the new theme colors.
3377         */
3378        public function clear_appearance_transients_block_theme( $post_id, $post ) {
3379                if ( in_array( $post->post_type, [ 'wp_global_styles' ], true ) ) {
3380                        delete_transient( $this->get_appearance_transient_key( true ) );
3381                }
3382        }
3383
3384        /**
3385         * Clears the appearance transients when a classic theme is updated or customized.
3386         * This ensures the UPE appearance is regenerated with the new theme colors.
3387         */
3388        public function clear_appearance_transients() {
3389                delete_transient( $this->get_appearance_transient_key() );
3390        }
3391
3392        /**
3393         * Hide "Pay" and "Cancel" action buttons for pending orders if they take a while to be confirmed.
3394         *
3395         * @param $actions array An array with the default actions.
3396         * @param $order WC_Order The order.
3397         * @return array
3398         */
3399        public function filter_my_account_my_orders_actions( $actions, $order ) {
3400                $methods_with_delayed_confirmation = [
3401                        WC_Stripe_Payment_Methods::BACS_DEBIT_LABEL,
3402                ];
3403                if ( is_order_received_page() && in_array( $order->get_payment_method_title(), $methods_with_delayed_confirmation, true ) && $order->has_status( OrderStatus::PENDING ) ) {
3404                        unset( $actions['pay'], $actions['cancel'] );
3405                }
3406                return $actions;
3407        }
3408
3409        /**
3410         * Checks if Google Pay and Apple Pay (ECE) are enabled.
3411         *
3412         * Overrides WC_Gateway_Stripe::is_payment_request_enabled().
3413         *
3414         * @return bool
3415         */
3416        public function is_payment_request_enabled() {
3417                // If the payment method configurations API is not enabled, we fallback to the enabled payment methods stored in the DB.
3418                if ( ! WC_Stripe_Payment_Method_Configurations::is_enabled() ) {
3419                        return parent::is_payment_request_enabled();
3420                }
3421
3422                $enabled_payment_method_ids = $this->get_upe_enabled_payment_method_ids();
3423
3424                // Apple Pay and Google Pay settings are currently unified in wp-admin.
3425                // However, they are managed separately within the Stripe dashboard.
3426                // Until we move to separate settings in wp-admin, both payment methods will be
3427                // considered enabled if either is enabled in Stripe.
3428                return in_array( WC_Stripe_Payment_Methods::APPLE_PAY, $enabled_payment_method_ids, true ) ||
3429                        in_array( WC_Stripe_Payment_Methods::GOOGLE_PAY, $enabled_payment_method_ids, true );
3430        }
3431}
Note: See TracBrowser for help on using the repository browser.