From f2ace167194c3fdac693974d073d429ab45602d6 Mon Sep 17 00:00:00 2001 From: Miguel Gasca Date: Tue, 23 Jan 2024 12:44:14 +0100 Subject: [PATCH 01/52] Address QIT security errors (#8060) --- .../fix-8059-qit-security-test-failed-in-develop-branch | 4 ++++ includes/class-wc-payments.php | 6 +++--- includes/multi-currency/MultiCurrency.php | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 changelog/fix-8059-qit-security-test-failed-in-develop-branch diff --git a/changelog/fix-8059-qit-security-test-failed-in-develop-branch b/changelog/fix-8059-qit-security-test-failed-in-develop-branch new file mode 100644 index 00000000000..fbcf842d935 --- /dev/null +++ b/changelog/fix-8059-qit-security-test-failed-in-develop-branch @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Comment: Fix QIT security tests errors. diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index f5046144bed..b4b64d4ea23 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -1358,7 +1358,7 @@ function wcpay_show_old_woocommerce_for_norway_notice() { 'WooPayments' ), [ - 'a1' => '', + 'a1' => '', ] ) ?> @@ -1724,10 +1724,10 @@ public static function wcpay_show_old_woocommerce_for_hungary_sweden_and_czech_r $notice, 'WooCommerce', 'WooPayments', - WC_VERSION + esc_html( WC_VERSION ) ), [ - 'a1' => '', + 'a1' => '', ] ) ?> diff --git a/includes/multi-currency/MultiCurrency.php b/includes/multi-currency/MultiCurrency.php index f1c890a656e..bf3df9ad5fd 100644 --- a/includes/multi-currency/MultiCurrency.php +++ b/includes/multi-currency/MultiCurrency.php @@ -1021,7 +1021,7 @@ public function display_geolocation_currency_update_notice() { echo \WC_Payments_Utils::esc_interpolated_html( $message, [ - 'a' => '', + 'a' => '', ] ); echo ' ' . esc_html__( 'Dismiss', 'woocommerce-payments' ) . '

'; From 3b1c603b8f75e3d51740fbe8754a6f9c102f0687 Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 23 Jan 2024 13:53:28 +0100 Subject: [PATCH 02/52] chore: remove unused generate_* functions (#8072) --- ...-remove-unused-settings-generate-functions | 5 +++ includes/class-wc-payment-gateway-wcpay.php | 44 ------------------- 2 files changed, 5 insertions(+), 44 deletions(-) create mode 100644 changelog/chore-remove-unused-settings-generate-functions diff --git a/changelog/chore-remove-unused-settings-generate-functions b/changelog/chore-remove-unused-settings-generate-functions new file mode 100644 index 00000000000..794c125c0a1 --- /dev/null +++ b/changelog/chore-remove-unused-settings-generate-functions @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: remove unused generate_* functions for settings generation + + diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 7d165269b4c..0fbbab29054 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -2340,50 +2340,6 @@ private function get_payment_method_type_for_order( $order ): string { return $payment_method_details['type'] ?? 'unknown'; } - /** - * Overrides the original method in woo's WC_Settings_API in order to conditionally render the enabled checkbox. - * - * @param string $key Field key. - * @param array $data Field data. - * - * @return string Checkbox markup or empty string. - */ - public function generate_checkbox_html( $key, $data ) { - if ( 'enabled' === $key && ! $this->is_connected() ) { - return ''; - } - - $in_dev_mode = WC_Payments::mode()->is_dev(); - - if ( 'test_mode' === $key && $in_dev_mode ) { - $data['custom_attributes']['disabled'] = 'disabled'; - $data['label'] = __( 'Sandbox mode is active so all transactions will be in test mode. This setting is only available to live accounts.', 'woocommerce-payments' ); - } - - if ( 'enable_logging' === $key && $in_dev_mode ) { - $data['custom_attributes']['disabled'] = 'disabled'; - $data['label'] = __( 'Sandbox mode is active so logging is on by default.', 'woocommerce-payments' ); - } - - return parent::generate_checkbox_html( $key, $data ); - } - - /** - * Generates markup for account statement descriptor field. - * - * @param string $key Field key. - * @param array $data Field data. - * - * @return string - */ - public function generate_account_statement_descriptor_html( $key, $data ) { - if ( ! $this->is_connected() ) { - return ''; - } - - return parent::generate_text_html( $key, $data ); - } - /** * Get option from DB or connected account. * From 48e3d729f1925bb351c822e92d72169278e7683b Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 23 Jan 2024 15:08:16 +0100 Subject: [PATCH 03/52] chore: comment on internal methods called by WC core (#8077) --- ...chore-mark-internal-methods-called-by-core | 5 + includes/class-wc-payment-gateway-wcpay.php | 102 +++++++++--------- 2 files changed, 58 insertions(+), 49 deletions(-) create mode 100644 changelog/chore-mark-internal-methods-called-by-core diff --git a/changelog/chore-mark-internal-methods-called-by-core b/changelog/chore-mark-internal-methods-called-by-core new file mode 100644 index 00000000000..3b70193a9e3 --- /dev/null +++ b/changelog/chore-mark-internal-methods-called-by-core @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: chore: adding a comment to internal methods that are called by WC core. + + diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 0fbbab29054..983539be3cf 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -675,6 +675,7 @@ public function is_connected() { /** * Checks if the account has not completed onboarding due to users abandoning the process half way. * Also used by WC Core to complete the task "Set up WooPayments". + * Called directly by WooCommerce Core. * * @return bool */ @@ -682,6 +683,58 @@ public function is_account_partially_onboarded(): bool { return $this->account->is_stripe_connected() && ! $this->account->is_details_submitted(); } + /** + * Returns the URL of the configuration screen for this gateway, for use in internal links. + * Called directly by WooCommerce Core. + * + * @return string URL of the configuration screen for this gateway + */ + public static function get_settings_url() { + return WC_Payments_Admin_Settings::get_settings_url(); + } + + /** + * Text provided to users during onboarding setup. + * Called directly by WooCommerce Core. + * + * @return string + */ + public function get_setup_help_text() { + return __( 'Next we’ll ask you to share a few details about your business to create your account.', 'woocommerce-payments' ); + } + + /** + * Get the connection URL. + * Called directly by WooCommerce Core. + * + * @return string Connection URL. + */ + public function get_connection_url() { + $account_data = $this->account->get_cached_account_data(); + + // The onboarding is finished if account_id is set. `Set up` will be shown instead of `Connect`. + if ( isset( $account_data['account_id'] ) ) { + return ''; + } + return html_entity_decode( WC_Payments_Account::get_connect_url( 'WCADMIN_PAYMENT_TASK' ) ); + } + + /** + * Add a url to the admin order page that links directly to the transactions detail view. + * Called directly by WooCommerce Core. + * + * @since 1.4.0 + * + * @param WC_Order $order The context passed into this function when the user view the order details page in WordPress admin. + * @return string + */ + public function get_transaction_url( $order ) { + $intent_id = $this->order_service->get_intent_id_for_order( $order ); + $charge_id = $this->order_service->get_charge_id_for_order( $order ); + + return WC_Payments_Utils::compose_transaction_url( $intent_id, $charge_id ); + } + /** * Returns true if the gateway needs additional configuration, false if it's ready to use. * @@ -3689,21 +3742,6 @@ public function create_setup_intent_ajax() { } } - /** - * Add a url to the admin order page that links directly to the transactions detail view. - * - * @since 1.4.0 - * - * @param WC_Order $order The context passed into this function when the user view the order details page in WordPress admin. - * @return string - */ - public function get_transaction_url( $order ) { - $intent_id = $this->order_service->get_intent_id_for_order( $order ); - $charge_id = $this->order_service->get_charge_id_for_order( $order ); - - return WC_Payments_Utils::compose_transaction_url( $intent_id, $charge_id ); - } - /** * Returns a formatted token list for a user. * @@ -3987,31 +4025,6 @@ public function clear_upe_appearance_transient() { delete_transient( self::WC_BLOCKS_UPE_APPEARANCE_TRANSIENT ); } - - /** - * Text provided to users during onboarding setup. - * - * @return string - */ - public function get_setup_help_text() { - return __( 'Next we’ll ask you to share a few details about your business to create your account.', 'woocommerce-payments' ); - } - - /** - * Get the connection URL. - * - * @return string Connection URL. - */ - public function get_connection_url() { - $account_data = $this->account->get_cached_account_data(); - - // The onboarding is finished if account_id is set. `Set up` will be shown instead of `Connect`. - if ( isset( $account_data['account_id'] ) ) { - return ''; - } - return html_entity_decode( WC_Payments_Account::get_connect_url( 'WCADMIN_PAYMENT_TASK' ) ); - } - /** * Returns true if the code returned from the API represents an error that should be rate-limited. * @@ -4180,15 +4193,6 @@ public function get_selected_payment_method( $payment_method_type ) { return WC_Payments::get_payment_method_by_id( $payment_method_type ); } - /** - * Returns the URL of the configuration screen for this gateway, for use in internal links. - * - * @return string URL of the configuration screen for this gateway - */ - public static function get_settings_url() { - return WC_Payments_Admin_Settings::get_settings_url(); - } - /** * Return the payment method type from the payment method details. * From 623acb8b3e1aa365b8782798945d993ad60c4acb Mon Sep 17 00:00:00 2001 From: Brent MacKinnon Date: Tue, 23 Jan 2024 11:11:37 -0400 Subject: [PATCH 04/52] Updating to modern Woo colours (#7837) Co-authored-by: Francesco Co-authored-by: frosso --- changelog/colour-updates | 4 ++++ .../source/images/favicon-180x180.png | Bin 2639 -> 1864 bytes .../source/images/favicon-192x192.png | Bin 2679 -> 1994 bytes .../source/images/favicon-270x270.png | Bin 3789 -> 2882 bytes docs/rest-api/source/images/favicon-32x32.png | Bin 754 -> 456 bytes docs/rest-api/source/images/logo.png | Bin 9108 -> 4263 bytes .../source/stylesheets/_lang-selector.scss | 4 ++-- .../source/stylesheets/_variables.scss | 6 +++--- 8 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 changelog/colour-updates diff --git a/changelog/colour-updates b/changelog/colour-updates new file mode 100644 index 00000000000..f493a96fb3c --- /dev/null +++ b/changelog/colour-updates @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +chore: update colors on documentation pages diff --git a/docs/rest-api/source/images/favicon-180x180.png b/docs/rest-api/source/images/favicon-180x180.png index d49c8287140e415c4ce9694ac7bccb41b0f4580a..70c56d828e3126f959e9e4a8b260ec5eef618450 100644 GIT binary patch delta 1849 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 diff --git a/docs/rest-api/source/images/favicon-192x192.png b/docs/rest-api/source/images/favicon-192x192.png index 41fdb478540601097e10848dcbf636a7a057b15f..834bba3d3b00e54e18d27326b5d7c2e0022e6fe9 100644 GIT binary patch delta 1980 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 JU+TIh{0H^6g2Mm+ literal 3789 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 diff --git a/docs/rest-api/source/images/favicon-32x32.png b/docs/rest-api/source/images/favicon-32x32.png index 9c264278f0ee44cbb846fc9f5e6bfa235c093470..177ff70357bd792f0e4aa265f8d56e09d9c3ad59 100644 GIT binary patch delta 430 zcmV;f0a5<)1;_)CB!5LvOjJcja7=zvv;6!1gj%+LRI~s8|9w%ie^j%7RI`6nv*z9K zww=_FYre|3;PLGDsEW(@_WRMnB4gj#`0*J|*9RNdc{+Oojje4RAps4ZPEu19V zG~*dCY3#@HZ^ntK2Zxx4y@fDhETysUBhlC+dOC=dika3$u;g)LTmdE{CL!PqW@8hg z8{hz-z&pl5$A1JY*SH0L0C?FaVDT(JilkM-24EX(OA;J`HR^Z*z)~abdEtC@U1uRG z!^p`j1~S%jtHTnNY3r}W#Ce*cQtM(d6dl=I0CILhKs1$Ay4(klBV6iCDo$<}Mwol? zMxqr1TmUk0OiN?0?=4CX*A?$zm~Fl?SeU{H8`y{L1}CrpP?u!k=WD+M9-rRh<9&Mm YAN8dcjR# delta 730 zcmV<00ww*(1M&rsB!2{FK}|sb0I`n?{9y$E0004VQb$4nuFf3k00042P)t-s|NsB4 zf2GExzy19ErEr^`Ws|#;w3b+k%dEwgS&Fubt-O`B@#*jR_4v@V%DR!V(Y4F+>+zjq zlK%bvnqH2gYnSWa=+wH;`uO_1l(d*zjF(!B_VD$@p}nVcoqzZ9_Wu6BpzR&augy zVv*(6;`;acx_^?g-pbn6zS8yX^YrfX{Q3L)`TNkd%h0sT;LY6G!qupCp7H4K;?dvf z+~?}v=*FeM`uF+Br@_FRxvYAk$*IHs{r$I$ukq>d`S$tBtHi^fyr*-WqimSv)#2pT z;kl5p$*RNHz|*F2o0nRP_3-qUS&Qe{kDP5 zsyBfIaG?Wp0GuXp0_v*aZrQ4Hp1DK1FT`ge|}+ zpQn|nE`OkV6M5nrQ45gAxKEjW8hGVB2_&p@m|`5_I@7IyBDtrIYJ&;_6I{Af9pCay zJ=1_iU_`_-ZUA_Ap$ut2go#_Wsn@*kUMU_8@T%V88^Gq?CGhnK1oh2wtKL>ATXI0X z0cLHxIS8z|fMlmEr~2r%0WGO`USwe1aE&&g+*3%GRA*y`4cUMSBb`(>rp67Q{X}tp zQ5nq+J{M?USey1W^btGI+=kYSFKGt?IfvE}?#TMm;yV9-__H$C53ceqLWZ#1hX4Qo M07*qoM6N<$f?61>Qvd(} diff --git a/docs/rest-api/source/images/logo.png b/docs/rest-api/source/images/logo.png index 2415244b6976504cef914070fc017bcf7e29e79f..4e1967f3772f3e03224e30ccba618cb0ded29518 100644 GIT binary patch literal 4263 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 literal 9108 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 YJWzK`kl1SZ(q@AImQ$0hlr{_cAHAGpx&QzG diff --git a/docs/rest-api/source/stylesheets/_lang-selector.scss b/docs/rest-api/source/stylesheets/_lang-selector.scss index 4b3b20268ae..6988d32149f 100644 --- a/docs/rest-api/source/stylesheets/_lang-selector.scss +++ b/docs/rest-api/source/stylesheets/_lang-selector.scss @@ -6,11 +6,11 @@ &:active, &:focus, &:hover { - border-top-color: #9b5c8f; + border-top-color: #7f54b3; } &.active { - border-top-color: #9b5c8f; + border-top-color: #7f54b3; } } } diff --git a/docs/rest-api/source/stylesheets/_variables.scss b/docs/rest-api/source/stylesheets/_variables.scss index d4b22d70bcd..7203a19c0de 100644 --- a/docs/rest-api/source/stylesheets/_variables.scss +++ b/docs/rest-api/source/stylesheets/_variables.scss @@ -26,18 +26,18 @@ $examples-bg: #393939 !default; $code-bg: #292929 !default; $code-annotation-bg: #191d1f !default; $nav-subitem-bg: #f7f7f7 !default; -$nav-active-bg: #9b5c8f !default; +$nav-active-bg: #7f54b3 !default; $nav-active-parent-bg: #f7f7f7 !default; // parent links of the current section $lang-select-border: #000 !default; $lang-select-bg: #222 !default; $lang-select-active-bg: $examples-bg !default; // feel free to change this to blue or something $lang-select-pressed-bg: #111 !default; // color of language tab bg when mouse is pressed $main-bg: #ffffff !default; -$aside-notice-bg: #9b5c8f !default; +$aside-notice-bg: #7f54b3 !default; $aside-warning-bg: #ca4949 !default; $aside-success-bg: #38a845 !default; $search-notice-bg: #c97a7e !default; -$link-color: #804877; +$link-color: #7f54b3; // TEXT COLORS //////////////////// From a30b1ac0942e81d54947ec0561a22a492e919101 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Tue, 23 Jan 2024 12:25:10 -0300 Subject: [PATCH 05/52] Revert "Revert "Displaying Clearpay instead of Afterpay for UK based stores"" (#8046) --- assets/images/payment-methods/afterpay.svg | 5 - assets/images/payment-methods/clearpay.svg | 4 + changelog/afterpay-clearpay | 4 + .../connect-account-page/payment-methods.tsx | 7 +- client/payment-methods-icons.tsx | 5 + client/payment-methods-map.tsx | 42 +- includes/class-wc-payment-gateway-wcpay.php | 21 +- includes/class-wc-payments-checkout.php | 5 +- includes/class-wc-payments.php | 4 +- .../PaymentMethodsCompatibility.php | 2 +- .../class-afterpay-payment-method.php | 29 + .../class-cc-payment-method.php | 6 +- .../class-upe-payment-method.php | 8 +- ...st-class-payment-methods-compatibility.php | 2 + .../test-class-upe-payment-gateway.php | 1029 +++++++++++++ .../test-class-upe-split-payment-gateway.php | 1290 +++++++++++++++++ .../test-class-wc-payment-gateway-wcpay.php | 30 +- .../unit/test-class-wc-payments-checkout.php | 3 + 18 files changed, 2457 insertions(+), 39 deletions(-) create mode 100644 assets/images/payment-methods/clearpay.svg create mode 100644 changelog/afterpay-clearpay create mode 100644 tests/unit/payment-methods/test-class-upe-payment-gateway.php create mode 100644 tests/unit/payment-methods/test-class-upe-split-payment-gateway.php diff --git a/assets/images/payment-methods/afterpay.svg b/assets/images/payment-methods/afterpay.svg index a769af42cdd..3795553025f 100644 --- a/assets/images/payment-methods/afterpay.svg +++ b/assets/images/payment-methods/afterpay.svg @@ -1,9 +1,5 @@ - - - - @@ -12,5 +8,4 @@ - diff --git a/assets/images/payment-methods/clearpay.svg b/assets/images/payment-methods/clearpay.svg new file mode 100644 index 00000000000..bce4db33418 --- /dev/null +++ b/assets/images/payment-methods/clearpay.svg @@ -0,0 +1,4 @@ + + + + diff --git a/changelog/afterpay-clearpay b/changelog/afterpay-clearpay new file mode 100644 index 00000000000..23a06913442 --- /dev/null +++ b/changelog/afterpay-clearpay @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Displaying Clearpay instead of Afterpay for UK based stores diff --git a/client/connect-account-page/payment-methods.tsx b/client/connect-account-page/payment-methods.tsx index 9add778d558..1930ed10616 100644 --- a/client/connect-account-page/payment-methods.tsx +++ b/client/connect-account-page/payment-methods.tsx @@ -12,6 +12,7 @@ import './style.scss'; import { AffirmIcon, AfterpayIcon, + ClearpayIcon, AmericanExpressIcon, ApplePayIcon, DinersClubIcon, @@ -38,7 +39,11 @@ const PaymentMethods: React.FC = () => { - + { 'GB' === wcpaySettings?.connect?.country ? ( + + ) : ( + + ) } & more. diff --git a/client/payment-methods-icons.tsx b/client/payment-methods-icons.tsx index 1cad55b62f2..bc29d2518f7 100644 --- a/client/payment-methods-icons.tsx +++ b/client/payment-methods-icons.tsx @@ -18,6 +18,7 @@ import IdealAsset from 'assets/images/payment-methods/ideal.svg?asset'; import BankDebitAsset from 'assets/images/payment-methods/bank-debit.svg?asset'; import AffirmAsset from 'assets/images/payment-methods/affirm.svg?asset'; import AfterpayAsset from 'assets/images/payment-methods/afterpay.svg?asset'; +import ClearpayAsset from 'assets/images/payment-methods/clearpay.svg?asset'; import JCBAsset from 'assets/images/payment-methods/jcb.svg?asset'; import KlarnaAsset from 'assets/images/payment-methods/klarna.svg?asset'; import VisaAsset from 'assets/images/cards/visa.svg?asset'; @@ -57,6 +58,10 @@ export const AfterpayIcon = iconComponent( AfterpayAsset, __( 'Afterpay', 'woocommerce-payments' ) ); +export const ClearpayIcon = iconComponent( + ClearpayAsset, + __( 'Clearpay', 'woocommerce-payments' ) +); export const AmericanExpressIcon = iconComponent( AmexAsset, __( 'American Express', 'woocommerce-payments' ) diff --git a/client/payment-methods-map.tsx b/client/payment-methods-map.tsx index d0a17f282ea..be758dd10ad 100644 --- a/client/payment-methods-map.tsx +++ b/client/payment-methods-map.tsx @@ -10,6 +10,7 @@ import { __ } from '@wordpress/i18n'; import { AffirmIcon, AfterpayIcon, + ClearpayIcon, BancontactIcon, BankDebitIcon, CreditCardIcon, @@ -23,6 +24,18 @@ import { SofortIcon, } from 'wcpay/payment-methods-icons'; +declare global { + interface Window { + wcpaySettings: { + accountStatus: { + country: string; + }; + }; + } +} + +const accountCountry = window.wcpaySettings?.accountStatus?.country || 'US'; + export interface PaymentMethodMapEntry { id: string; label: string; @@ -208,16 +221,29 @@ const PaymentMethodInformationObject: Record< }, afterpay_clearpay: { id: 'afterpay_clearpay', - label: __( 'Afterpay', 'woocommerce-payments' ), + label: + 'GB' === accountCountry + ? __( 'Clearpay', 'woocommerce-payments' ) + : __( 'Afterpay', 'woocommerce-payments' ), brandTitles: { - afterpay_clearpay: __( 'Afterpay', 'woocommerce-payments' ), + afterpay_clearpay: + 'GB' === accountCountry + ? __( 'Clearpay', 'woocommerce-payments' ) + : __( 'Afterpay', 'woocommerce-payments' ), }, - description: __( - // translators: %s is the store currency. - 'Allow customers to pay over time with Afterpay. Available to all customers paying in %s.', - 'woocommerce-payments' - ), - icon: AfterpayIcon, + description: + 'GB' === accountCountry + ? __( + // translators: %s is the store currency. + 'Allow customers to pay over time with Clearpay. Available to all customers paying in %s.', + 'woocommerce-payments' + ) + : __( + // translators: %s is the store currency. + 'Allow customers to pay over time with Afterpay. Available to all customers paying in %s.', + 'woocommerce-payments' + ), + icon: 'GB' === accountCountry ? ClearpayIcon : AfterpayIcon, currencies: [ 'USD', 'AUD', 'CAD', 'NZD', 'GBP', 'EUR' ], stripe_key: 'afterpay_clearpay_payments', allows_manual_capture: false, diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 983539be3cf..0068a32e33f 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -497,6 +497,7 @@ public function __construct( * @return void */ public function init_hooks() { + add_action( 'init', [ $this, 'maybe_update_properties_with_country' ] ); // Only add certain actions/filter if this is the main gateway (i.e. not split UPE). if ( self::GATEWAY_ID === $this->id ) { add_action( 'woocommerce_order_actions', [ $this, 'add_order_actions' ] ); @@ -526,6 +527,22 @@ public function init_hooks() { $this->maybe_init_subscriptions_hooks(); } + /** + * Updates icon and title using the account country. + * This method runs on init is not in the controller because get_account_country might + * make a request to the API if the account data is not cached. + * + * @return void + */ + public function maybe_update_properties_with_country(): void { + if ( Afterpay_Payment_Method::PAYMENT_METHOD_STRIPE_ID !== $this->stripe_id ) { + return; + } + $account_country = $this->get_account_country(); + $this->icon = $this->payment_method->get_icon( $account_country ); + $this->title = $this->payment_method->get_title( $account_country ); + } + /** * Displays HTML tags for WC payment gateway radio button content. */ @@ -2106,7 +2123,7 @@ public function set_payment_method_title_for_order( $order, $payment_method_type return; } - $payment_method_title = $payment_method->get_title( $payment_method_details ); + $payment_method_title = $payment_method->get_title( $this->get_account_country(), $payment_method_details ); $payment_gateway = in_array( $payment_method->get_id(), [ Payment_Method::CARD, Payment_Method::LINK ], true ) ? self::GATEWAY_ID : self::GATEWAY_ID . '_' . $payment_method_type; @@ -2887,7 +2904,7 @@ protected function get_deposit_delay_days( int $default_value = 7 ): int { * * @return string code of the country. */ - protected function get_account_country( string $default_value = Country_Code::UNITED_STATES ): string { + public function get_account_country( string $default_value = Country_Code::UNITED_STATES ): string { try { if ( $this->is_connected() ) { return $this->account->get_account_country() ?? $default_value; diff --git a/includes/class-wc-payments-checkout.php b/includes/class-wc-payments-checkout.php index 98a647b5bff..5ba354b3810 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -295,10 +295,11 @@ public function get_enabled_payment_method_config() { } $payment_method = $this->gateway->wc_payments_get_payment_method_by_id( $payment_method_id ); + $account_country = $this->account->get_account_country(); $settings[ $payment_method_id ] = [ 'isReusable' => $payment_method->is_reusable(), - 'title' => $payment_method->get_title(), - 'icon' => $payment_method->get_icon(), + 'title' => $payment_method->get_title( $account_country ), + 'icon' => $payment_method->get_icon( $account_country ), 'showSaveOption' => $this->should_upe_payment_method_show_save_option( $payment_method ), 'countries' => $payment_method->get_countries(), ]; diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index b4b64d4ea23..94d30cd636f 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -293,6 +293,8 @@ public static function init() { include_once __DIR__ . '/class-wc-payments-utils.php'; include_once __DIR__ . '/core/class-mode.php'; + self::$mode = new Mode(); + include_once __DIR__ . '/class-database-cache.php'; self::$database_cache = new Database_Cache(); self::$database_cache->init_hooks(); @@ -539,8 +541,6 @@ public static function init() { self::$card_gateway->init_hooks(); self::$wc_payments_checkout->init_hooks(); - self::$mode = new Mode(); - self::$webhook_processing_service = new WC_Payments_Webhook_Processing_Service( self::$api_client, self::$db_helper, self::$account, self::$remote_note_service, self::$order_service, self::$in_person_payments_receipts_service, self::get_gateway(), self::$customer_service, self::$database_cache ); self::$webhook_reliability_service = new WC_Payments_Webhook_Reliability_Service( self::$api_client, self::$action_scheduler_service, self::$webhook_processing_service ); diff --git a/includes/multi-currency/PaymentMethodsCompatibility.php b/includes/multi-currency/PaymentMethodsCompatibility.php index 8f4f0f88e8b..7f48df769d5 100644 --- a/includes/multi-currency/PaymentMethodsCompatibility.php +++ b/includes/multi-currency/PaymentMethodsCompatibility.php @@ -82,7 +82,7 @@ function ( $result, $method ) { $result[ $method ] = [ 'currencies' => $payment_method_instance->get_currencies(), - 'title' => $payment_method_instance->get_title(), + 'title' => $payment_method_instance->get_title( $this->gateway->get_account_country() ), ]; return $result; diff --git a/includes/payment-methods/class-afterpay-payment-method.php b/includes/payment-methods/class-afterpay-payment-method.php index 9984ae212b7..674f648d5e2 100644 --- a/includes/payment-methods/class-afterpay-payment-method.php +++ b/includes/payment-methods/class-afterpay-payment-method.php @@ -65,6 +65,35 @@ public function __construct( $token_service ) { ]; } + /** + * Returns payment method title. + * + * @param string|null $account_country Country of merchants account. + * @param array|false $payment_details Optional payment details from charge object. + * @return string|null + */ + public function get_title( string $account_country = null, $payment_details = false ) { + if ( 'GB' === $account_country ) { + return __( 'Clearpay', 'woocommerce-payments' ); + } + + return __( 'Afterpay', 'woocommerce-payments' ); + } + + /** + * Returns payment method icon. + * + * @param string|null $account_country Country of merchants account. + * @return string|null + */ + public function get_icon( string $account_country = null ) { + if ( 'GB' === $account_country ) { + return plugins_url( 'assets/images/payment-methods/clearpay.svg', WCPAY_PLUGIN_FILE ); + } + + return plugins_url( 'assets/images/payment-methods/afterpay.svg', WCPAY_PLUGIN_FILE ); + } + /** * Returns testing credentials to be printed at checkout in test mode. * diff --git a/includes/payment-methods/class-cc-payment-method.php b/includes/payment-methods/class-cc-payment-method.php index dfd20c3eaf6..ae7f0a485dd 100644 --- a/includes/payment-methods/class-cc-payment-method.php +++ b/includes/payment-methods/class-cc-payment-method.php @@ -33,11 +33,11 @@ public function __construct( $token_service ) { /** * Returns payment method title * - * @param array|bool $payment_details Optional payment details from charge object. - * + * @param string|null $account_country Account country. + * @param array|false $payment_details Payment details. * @return string */ - public function get_title( $payment_details = false ) { + public function get_title( string $account_country = null, $payment_details = false ) { if ( ! $payment_details ) { return $this->title; } diff --git a/includes/payment-methods/class-upe-payment-method.php b/includes/payment-methods/class-upe-payment-method.php index ddc954e719f..6f0fa965a88 100644 --- a/includes/payment-methods/class-upe-payment-method.php +++ b/includes/payment-methods/class-upe-payment-method.php @@ -113,11 +113,12 @@ public function get_id() { /** * Returns payment method title * - * @param array|bool $payment_details Optional payment details from charge object. + * @param string|null $account_country Country of merchants account. + * @param array|false $payment_details Optional payment details from charge object. * * @return string */ - public function get_title( $payment_details = false ) { + public function get_title( string $account_country = null, $payment_details = false ) { return $this->title; } @@ -224,9 +225,10 @@ abstract public function get_testing_instructions(); /** * Returns the payment method icon URL or an empty string. * + * @param string|null $account_country Optional account country. * @return string */ - public function get_icon() { + public function get_icon( string $account_country = null ) { return isset( $this->icon_url ) ? $this->icon_url : ''; } diff --git a/tests/unit/multi-currency/test-class-payment-methods-compatibility.php b/tests/unit/multi-currency/test-class-payment-methods-compatibility.php index 6a0f24eeba8..7a6e910db46 100644 --- a/tests/unit/multi-currency/test-class-payment-methods-compatibility.php +++ b/tests/unit/multi-currency/test-class-payment-methods-compatibility.php @@ -54,9 +54,11 @@ public function set_up() { ->setMethods( [ 'get_upe_enabled_payment_method_ids', + 'get_account_country', ] ) ->getMock(); + $this->gateway_mock->method( 'get_account_country' )->willReturn( 'US' ); $this->payment_methods_compatibility = new \WCPay\MultiCurrency\PaymentMethodsCompatibility( $this->multi_currency_mock, $this->gateway_mock ); $this->payment_methods_compatibility->init_hooks(); diff --git a/tests/unit/payment-methods/test-class-upe-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-payment-gateway.php new file mode 100644 index 00000000000..8260f8707b5 --- /dev/null +++ b/tests/unit/payment-methods/test-class-upe-payment-gateway.php @@ -0,0 +1,1029 @@ + 'success', + 'payment_needed' => true, + 'redirect' => 'testURL/key=mock_order_key', + ]; + + /** + * WC_Payments_Localization_Service instance. + * + * @var WC_Payments_Localization_Service + */ + private $mock_localization_service; + + /** + * Mock Fraud Service. + * + * @var WC_Payments_Fraud_Service|MockObject; + */ + private $mock_fraud_service; + + /** + * Pre-test setup + */ + public function set_up() { + parent::set_up(); + + // Arrange: Mock WC_Payments_API_Client so we can configure the + // return value of create_and_confirm_intention(). + // Note that we cannot use createStub here since it's not defined in PHPUnit 6.5. + $this->mock_api_client = $this->getMockBuilder( 'WC_Payments_API_Client' ) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'get_payment_method', + 'is_server_connected', + 'get_timeline', + ] + ) + ->getMock(); + + $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); + $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' ); + $this->mock_wcpay_account->method( 'get_account_default_currency' )->willReturn( 'USD' ); + + // Mock the main class's cache service. + $this->_cache = WC_Payments::get_database_cache(); + $this->mock_cache = $this->createMock( Database_Cache::class ); + WC_Payments::set_database_cache( $this->mock_cache ); + + $payment_methods = [ + 'link' => [ + 'base' => 0.1, + ], + ]; + + $this->mock_wcpay_account + ->expects( $this->any() ) + ->method( 'get_fees' ) + ->willReturn( $payment_methods ); + + $this->mock_woopay_utilities = $this->createMock( WooPay_Utilities::class ); + + // Arrange: Mock WC_Payments_Customer_Service so its methods aren't called directly. + $this->mock_customer_service = $this->getMockBuilder( 'WC_Payments_Customer_Service' ) + ->disableOriginalConstructor() + ->getMock(); + + // Arrange: Mock WC_Payments_Customer_Service so its methods aren't called directly. + $this->mock_token_service = $this->getMockBuilder( 'WC_Payments_Token_Service' ) + ->disableOriginalConstructor() + ->onlyMethods( [ 'add_payment_method_to_user' ] ) + ->getMock(); + + // Arrange: Mock WC_Payments_Action_Scheduler_Service so its methods aren't called directly. + $this->mock_action_scheduler_service = $this->getMockBuilder( 'WC_Payments_Action_Scheduler_Service' ) + ->disableOriginalConstructor() + ->getMock(); + + $this->mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + + $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); + $this->mock_fraud_service = $this->createMock( WC_Payments_Fraud_Service::class ); + + $this->mock_payment_methods = []; + $payment_method_classes = [ + CC_Payment_Method::class, + Giropay_Payment_Method::class, + Sofort_Payment_Method::class, + Bancontact_Payment_Method::class, + EPS_Payment_Method::class, + P24_Payment_Method::class, + Ideal_Payment_Method::class, + Sepa_Payment_Method::class, + Becs_Payment_Method::class, + Link_Payment_Method::class, + Affirm_Payment_Method::class, + Afterpay_Payment_Method::class, + ]; + + $this->mock_rate_limiter = $this->createMock( Session_Rate_Limiter::class ); + foreach ( $payment_method_classes as $payment_method_class ) { + $mock_payment_method = $this->getMockBuilder( $payment_method_class ) + ->setConstructorArgs( [ $this->mock_token_service ] ) + ->onlyMethods( [ 'is_subscription_item_in_cart', 'get_icon' ] ) + ->getMock(); + $this->mock_payment_methods[ $mock_payment_method->get_id() ] = $mock_payment_method; + } + + $this->mock_order_service = $this->getMockBuilder( WC_Payments_Order_Service::class ) + ->setConstructorArgs( + [ + $this->mock_api_client, + ] + ) + ->onlyMethods( + [ + 'get_payment_method_id_for_order', + ] + ) + ->getMock(); + + $this->mock_payment_method = $this->getMockBuilder( $payment_method_class ) + ->setConstructorArgs( [ $this->mock_token_service ] ) + ->onlyMethods( [ 'is_subscription_item_in_cart', 'get_icon' ] ) + ->getMock(); + $this->mock_payment_methods[ $this->mock_payment_method->get_id() ] = $this->mock_payment_method; + + // Arrange: Mock WC_Payment_Gateway_WCPay so that some of its methods can be + // mocked, and their return values can be used for testing. + $this->mock_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) + ->setConstructorArgs( + [ + $this->mock_api_client, + $this->mock_wcpay_account, + $this->mock_customer_service, + $this->mock_token_service, + $this->mock_action_scheduler_service, + $this->mock_payment_method, + $this->mock_payment_methods, + $this->mock_rate_limiter, + $this->mock_order_service, + $this->mock_dpps, + $this->mock_localization_service, + $this->mock_fraud_service, + ] + ) + ->setMethods( + [ + 'get_return_url', + 'manage_customer_details_for_order', + 'parent_process_payment', + 'get_upe_enabled_payment_method_statuses', + 'is_payment_recurring', + ] + ) + ->getMock(); + + // Arrange: Set the return value of get_return_url() so it can be used in a test later. + $this->mock_gateway + ->expects( $this->any() ) + ->method( 'get_return_url' ) + ->will( + $this->returnValue( $this->return_url ) + ); + $this->mock_gateway + ->expects( $this->any() ) + ->method( 'parent_process_payment' ) + ->will( + $this->returnValue( $this->mock_payment_result ) + ); + + // Arrange: Define a $_POST array which includes the payment method, + // so that get_payment_method_from_request() does not throw error. + $_POST = [ + 'wcpay-payment-method' => 'pm_mock', + 'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID, + ]; + + // Mock the level3 service to always return an empty array. + $mock_level3_service = $this->createMock( Level3Service::class ); + $mock_level3_service->expects( $this->any() ) + ->method( 'get_data_from_order' ) + ->willReturn( [] ); + wcpay_get_test_container()->replace( Level3Service::class, $mock_level3_service ); + + // Mock the order service to always return an empty array for meta. + $mock_order_service = $this->createMock( OrderService::class ); + $mock_order_service->expects( $this->any() ) + ->method( 'get_payment_metadata' ) + ->willReturn( [] ); + wcpay_get_test_container()->replace( OrderService::class, $mock_order_service ); + } + + /** + * Cleanup after tests. + * + * @return void + */ + public function tear_down() { + parent::tear_down(); + WC_Payments::set_database_cache( $this->_cache ); + wcpay_get_test_container()->reset_all_replacements(); + } + + public function test_process_payment_returns_correct_redirect_when_using_saved_payment() { + $order = WC_Helper_Order::create_order(); + $_POST = $this->setup_saved_payment_method(); + $intent = WC_Helper_Intention::create_intention(); + + $this->mock_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ wp_get_current_user(), 'cus_123' ] ) + ); + $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1, $intent->get_id() ) + ->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( $intent ); + + $this->set_cart_contains_subscription_items( false ); + + $result = $this->mock_gateway->process_payment( $order->get_id() ); + + $this->assertEquals( 'success', $result['result'] ); + $this->assertEquals( $this->return_url, $result['redirect'] ); + } + + public function test_process_payment_returns_correct_redirect_when_using_payment_request() { + $order = WC_Helper_Order::create_order(); + $intent = WC_Helper_Intention::create_intention(); + $_POST['payment_request_type'] = 'google_pay'; + + $this->mock_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ wp_get_current_user(), 'cus_123' ] ) + ); + $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1, $intent->get_id() ) + ->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( $intent ); + $this->set_cart_contains_subscription_items( false ); + + $result = $this->mock_gateway->process_payment( $order->get_id() ); + + $this->assertEquals( 'success', $result['result'] ); + $this->assertEquals( $this->return_url, $result['redirect'] ); + } + + public function is_proper_intent_used_with_order_returns_false() { + $this->assertFalse( $this->mock_gateway->is_proper_intent_used_with_order( WC_Helper_Order::create_order(), 'wrong_intent_id' ) ); + } + + public function test_process_redirect_payment_intent_processing() { + $order = WC_Helper_Order::create_order(); + $order_id = $order->get_id(); + $save_payment_method = false; + $user = wp_get_current_user(); + $intent_status = Intent_Status::PROCESSING; + $intent_metadata = [ 'order_id' => (string) $order_id ]; + $charge_id = 'ch_mock'; + $customer_id = 'cus_mock'; + $intent_id = 'pi_mock'; + $payment_method_id = 'pm_mock'; + + // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. + $order->update_meta_data( '_intent_id', $intent_id ); + $order->save(); + + $payment_intent = WC_Helper_Intention::create_intention( + [ + 'status' => $intent_status, + 'metadata' => $intent_metadata, + ] + ); + + $this->mock_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ $user, $customer_id ] ) + ); + + $request = $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->will( $this->returnValue( $payment_intent ) ); + + $this->set_cart_contains_subscription_items( false ); + + $this->mock_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + + $result_order = wc_get_order( $order_id ); + $note = wc_get_order_notes( + [ + 'order_id' => $order_id, + 'limit' => 1, + ] + )[0]; + + $this->assertStringContainsString( 'authorized', $note->content ); + $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); + $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); + $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); + $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); + $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); + $this->assertEquals( Order_Status::ON_HOLD, $result_order->get_status() ); + } + + public function test_process_redirect_payment_intent_succeded() { + $order = WC_Helper_Order::create_order(); + $order_id = $order->get_id(); + $save_payment_method = false; + $user = wp_get_current_user(); + $intent_status = Intent_Status::SUCCEEDED; + $intent_metadata = [ 'order_id' => (string) $order_id ]; + $charge_id = 'ch_mock'; + $customer_id = 'cus_mock'; + $intent_id = 'pi_mock'; + $payment_method_id = 'pm_mock'; + + // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. + $order->update_meta_data( '_intent_id', $intent_id ); + $order->save(); + + $payment_intent = WC_Helper_Intention::create_intention( + [ + 'status' => $intent_status, + 'metadata' => $intent_metadata, + ] + ); + + $this->mock_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ $user, $customer_id ] ) + ); + + $request = $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->will( $this->returnValue( $payment_intent ) ); + + $this->set_cart_contains_subscription_items( false ); + + $this->mock_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + + $result_order = wc_get_order( $order_id ); + + $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); + $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); + $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); + $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); + $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); + $this->assertEquals( Order_Status::PROCESSING, $result_order->get_status() ); + } + + public function test_validate_order_id_received_vs_intent_meta_order_id_throw_exception() { + $order = WC_Helper_Order::create_order(); + $intent_metadata = [ 'order_id' => (string) ( $order->get_id() + 100 ) ]; + + $this->expectException( Process_Payment_Exception::class ); + $this->expectExceptionMessage( "We're not able to process this payment due to the order ID mismatch. Please try again later." ); + + \PHPUnit_Utils::call_method( + $this->mock_gateway, + 'validate_order_id_received_vs_intent_meta_order_id', + [ $order, $intent_metadata ] + ); + } + + public function test_validate_order_id_received_vs_intent_meta_order_id_returning_void() { + $order = WC_Helper_Order::create_order(); + $intent_metadata = [ 'order_id' => (string) ( $order->get_id() ) ]; + + $res = \PHPUnit_Utils::call_method( + $this->mock_gateway, + 'validate_order_id_received_vs_intent_meta_order_id', + [ $order, $intent_metadata ] + ); + + $this->assertSame( null, $res ); + } + + public function test_correct_payment_method_title_for_order() { + $order = WC_Helper_Order::create_order(); + + $visa_credit_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'visa', + 'funding' => 'credit', + ], + ]; + $visa_debit_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'visa', + 'funding' => 'debit', + ], + ]; + $mastercard_credit_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'mastercard', + 'funding' => 'credit', + ], + ]; + $eps_details = [ + 'type' => 'eps', + ]; + $giropay_details = [ + 'type' => 'giropay', + ]; + $p24_details = [ + 'type' => 'p24', + ]; + $sofort_details = [ + 'type' => 'sofort', + ]; + $bancontact_details = [ + 'type' => 'bancontact', + ]; + $sepa_details = [ + 'type' => 'sepa_debit', + ]; + $ideal_details = [ + 'type' => 'ideal', + ]; + $becs_details = [ + 'type' => 'au_becs_debit', + ]; + + $charge_payment_method_details = [ + $visa_credit_details, + $visa_debit_details, + $mastercard_credit_details, + $giropay_details, + $sofort_details, + $bancontact_details, + $eps_details, + $p24_details, + $ideal_details, + $sepa_details, + $becs_details, + ]; + + $expected_payment_method_titles = [ + 'Visa credit card', + 'Visa debit card', + 'Mastercard credit card', + 'giropay', + 'Sofort', + 'Bancontact', + 'EPS', + 'Przelewy24 (P24)', + 'iDEAL', + 'SEPA Direct Debit', + 'BECS Direct Debit', + ]; + + foreach ( $charge_payment_method_details as $i => $payment_method_details ) { + $this->mock_gateway->set_payment_method_title_for_order( $order, $payment_method_details['type'], $payment_method_details ); + $this->assertEquals( $expected_payment_method_titles[ $i ], $order->get_payment_method_title() ); + } + } + + public function test_payment_methods_show_correct_default_outputs() { + $mock_token = WC_Helper_Token::create_token( 'pm_mock' ); + $this->mock_token_service->expects( $this->any() ) + ->method( 'add_payment_method_to_user' ) + ->will( + $this->returnValue( $mock_token ) + ); + + $mock_user = 'mock_user'; + $mock_payment_method_id = 'pm_mock'; + + $mock_visa_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'visa', + 'funding' => 'debit', + ], + ]; + $mock_mastercard_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'mastercard', + 'funding' => 'credit', + ], + ]; + $mock_giropay_details = [ + 'type' => 'giropay', + ]; + $mock_p24_details = [ + 'type' => 'p24', + ]; + $mock_sofort_details = [ + 'type' => 'sofort', + ]; + $mock_bancontact_details = [ + 'type' => 'bancontact', + ]; + $mock_eps_details = [ + 'type' => 'eps', + ]; + $mock_sepa_details = [ + 'type' => 'sepa_debit', + ]; + $mock_ideal_details = [ + 'type' => 'ideal', + ]; + $mock_becs_details = [ + 'type' => 'au_becs_debit', + ]; + $mock_affirm_details = [ + 'type' => 'affirm', + ]; + $mock_afterpay_details = [ + 'type' => 'afterpay_clearpay', + ]; + + $this->set_cart_contains_subscription_items( false ); + $card_method = $this->mock_payment_methods['card']; + $giropay_method = $this->mock_payment_methods['giropay']; + $p24_method = $this->mock_payment_methods['p24']; + $sofort_method = $this->mock_payment_methods['sofort']; + $bancontact_method = $this->mock_payment_methods['bancontact']; + $eps_method = $this->mock_payment_methods['eps']; + $sepa_method = $this->mock_payment_methods['sepa_debit']; + $ideal_method = $this->mock_payment_methods['ideal']; + $becs_method = $this->mock_payment_methods['au_becs_debit']; + $affirm_method = $this->mock_payment_methods['affirm']; + $afterpay_method = $this->mock_payment_methods['afterpay_clearpay']; + + $this->assertEquals( 'card', $card_method->get_id() ); + $this->assertEquals( 'Credit card / debit card', $card_method->get_title( 'US' ) ); + $this->assertEquals( 'Visa debit card', $card_method->get_title( 'US', $mock_visa_details ) ); + $this->assertEquals( 'Mastercard credit card', $card_method->get_title( 'US', $mock_mastercard_details ) ); + $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); + $this->assertTrue( $card_method->is_reusable() ); + $this->assertEquals( $mock_token, $card_method->get_payment_token_for_user( $mock_user, $mock_payment_method_id ) ); + + $this->assertEquals( 'giropay', $giropay_method->get_id() ); + $this->assertEquals( 'giropay', $giropay_method->get_title( 'US' ) ); + $this->assertEquals( 'giropay', $giropay_method->get_title( 'US', $mock_giropay_details ) ); + $this->assertTrue( $giropay_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $giropay_method->is_reusable() ); + + $this->assertEquals( 'p24', $p24_method->get_id() ); + $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title( 'US' ) ); + $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title( 'US', $mock_p24_details ) ); + $this->assertTrue( $p24_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $p24_method->is_reusable() ); + + $this->assertEquals( 'sofort', $sofort_method->get_id() ); + $this->assertEquals( 'Sofort', $sofort_method->get_title( 'US' ) ); + $this->assertEquals( 'Sofort', $sofort_method->get_title( 'US', $mock_sofort_details ) ); + $this->assertTrue( $sofort_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sofort_method->is_reusable() ); + + $this->assertEquals( 'bancontact', $bancontact_method->get_id() ); + $this->assertEquals( 'Bancontact', $bancontact_method->get_title( 'US' ) ); + $this->assertEquals( 'Bancontact', $bancontact_method->get_title( 'US', $mock_bancontact_details ) ); + $this->assertTrue( $bancontact_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $bancontact_method->is_reusable() ); + + $this->assertEquals( 'eps', $eps_method->get_id() ); + $this->assertEquals( 'EPS', $eps_method->get_title( 'US' ) ); + $this->assertEquals( 'EPS', $eps_method->get_title( 'US', $mock_eps_details ) ); + $this->assertTrue( $eps_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $eps_method->is_reusable() ); + + $this->assertEquals( 'sepa_debit', $sepa_method->get_id() ); + $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title( 'US' ) ); + $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title( 'US', $mock_sepa_details ) ); + $this->assertTrue( $sepa_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sepa_method->is_reusable() ); + + $this->assertEquals( 'ideal', $ideal_method->get_id() ); + $this->assertEquals( 'iDEAL', $ideal_method->get_title( 'US' ) ); + $this->assertEquals( 'iDEAL', $ideal_method->get_title( 'US', $mock_ideal_details ) ); + $this->assertTrue( $ideal_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $ideal_method->is_reusable() ); + + $this->assertEquals( 'au_becs_debit', $becs_method->get_id() ); + $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title( 'US' ) ); + $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title( 'US', $mock_becs_details ) ); + $this->assertTrue( $becs_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $becs_method->is_reusable() ); + + $this->assertSame( 'affirm', $affirm_method->get_id() ); + $this->assertSame( 'Affirm', $affirm_method->get_title( 'US' ) ); + $this->assertSame( 'Affirm', $affirm_method->get_title( 'US', $mock_affirm_details ) ); + $this->assertTrue( $affirm_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $affirm_method->is_reusable() ); + + $this->assertSame( 'afterpay_clearpay', $afterpay_method->get_id() ); + $this->assertSame( 'Afterpay', $afterpay_method->get_title( 'US' ) ); + $this->assertSame( 'Afterpay', $afterpay_method->get_title( 'US', $mock_afterpay_details ) ); + $this->assertTrue( $afterpay_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $afterpay_method->is_reusable() ); + $this->assertSame( 'Clearpay', $afterpay_method->get_title( 'GB' ) ); + $this->assertSame( 'Clearpay', $afterpay_method->get_title( 'GB', $mock_afterpay_details ) ); + $this->assertTrue( $afterpay_method->is_enabled_at_checkout( 'GB' ) ); + } + + public function test_only_reusabled_payment_methods_enabled_with_subscription_item_present() { + $this->set_cart_contains_subscription_items( true ); + + $card_method = $this->mock_payment_methods['card']; + $giropay_method = $this->mock_payment_methods['giropay']; + $sofort_method = $this->mock_payment_methods['sofort']; + $bancontact_method = $this->mock_payment_methods['bancontact']; + $eps_method = $this->mock_payment_methods['eps']; + $sepa_method = $this->mock_payment_methods['sepa_debit']; + $p24_method = $this->mock_payment_methods['p24']; + $ideal_method = $this->mock_payment_methods['ideal']; + $becs_method = $this->mock_payment_methods['au_becs_debit']; + $affirm_method = $this->mock_payment_methods['affirm']; + $afterpay_method = $this->mock_payment_methods['afterpay_clearpay']; + + $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $giropay_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sofort_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $bancontact_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $eps_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sepa_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $p24_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $ideal_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $becs_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $affirm_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $afterpay_method->is_enabled_at_checkout( 'US' ) ); + } + + public function test_only_valid_payment_methods_returned_for_currency() { + $card_method = $this->mock_payment_methods['card']; + $giropay_method = $this->mock_payment_methods['giropay']; + $sofort_method = $this->mock_payment_methods['sofort']; + $bancontact_method = $this->mock_payment_methods['bancontact']; + $eps_method = $this->mock_payment_methods['eps']; + $sepa_method = $this->mock_payment_methods['sepa_debit']; + $p24_method = $this->mock_payment_methods['p24']; + $ideal_method = $this->mock_payment_methods['ideal']; + $becs_method = $this->mock_payment_methods['au_becs_debit']; + $affirm_method = $this->mock_payment_methods['affirm']; + $afterpay_method = $this->mock_payment_methods['afterpay_clearpay']; + + WC_Helper_Site_Currency::$mock_site_currency = 'EUR'; + + $account_domestic_currency = 'USD'; + $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $giropay_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $sofort_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $eps_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $sepa_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $p24_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $ideal_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); + // BNPLs can accept only domestic payments. + $this->assertFalse( $affirm_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); + + WC_Helper_Site_Currency::$mock_site_currency = 'USD'; + + $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $giropay_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $sofort_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $eps_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $sepa_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $p24_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $ideal_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $affirm_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); + + WC_Helper_Site_Currency::$mock_site_currency = 'AUD'; + $this->assertTrue( $becs_method->is_currency_valid( $account_domestic_currency ) ); + + // BNPLs can accept only domestic payments. + WC_Helper_Site_Currency::$mock_site_currency = 'USD'; + $account_domestic_currency = 'CAD'; + $this->assertFalse( $affirm_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); + + WC_Helper_Site_Currency::$mock_site_currency = ''; + } + + public function test_payment_method_compares_correct_currency() { + $card_method = $this->mock_payment_methods['card']; + $giropay_method = $this->mock_payment_methods['giropay']; + $sofort_method = $this->mock_payment_methods['sofort']; + $bancontact_method = $this->mock_payment_methods['bancontact']; + $eps_method = $this->mock_payment_methods['eps']; + $sepa_method = $this->mock_payment_methods['sepa_debit']; + $p24_method = $this->mock_payment_methods['p24']; + $ideal_method = $this->mock_payment_methods['ideal']; + $becs_method = $this->mock_payment_methods['au_becs_debit']; + $affirm_method = $this->mock_payment_methods['affirm']; + $afterpay_method = $this->mock_payment_methods['afterpay_clearpay']; + + WC_Helper_Site_Currency::$mock_site_currency = 'EUR'; + $account_domestic_currency = 'USD'; + + $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $giropay_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $sofort_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $eps_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $sepa_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $p24_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $ideal_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); + + global $wp; + $order = WC_Helper_Order::create_order(); + $order_id = $order->get_id(); + $wp->query_vars = [ 'order-pay' => strval( $order_id ) ]; + $order->set_currency( 'USD' ); + + $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $giropay_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $sofort_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $eps_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $sepa_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $p24_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $ideal_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $affirm_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $afterpay_method->is_currency_valid( $account_domestic_currency ) ); + + $wp->query_vars = []; + } + + public function test_create_token_from_setup_intent_adds_token() { + $mock_token = WC_Helper_Token::create_token( 'pm_mock' ); + $mock_setup_intent_id = 'si_mock'; + $mock_user = wp_get_current_user(); + + $request = $this->mock_wcpay_request( Get_Setup_Intention::class, 1, $mock_setup_intent_id ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( + WC_Helper_Intention::create_setup_intention( + [ + 'id' => $mock_setup_intent_id, + 'payment_method' => 'pm_mock', + ] + ) + ); + + $this->mock_token_service->expects( $this->once() ) + ->method( 'add_payment_method_to_user' ) + ->with( 'pm_mock', $mock_user ) + ->will( + $this->returnValue( $mock_token ) + ); + + $this->assertEquals( $mock_token, $this->mock_gateway->create_token_from_setup_intent( $mock_setup_intent_id, $mock_user ) ); + } + + public function test_exception_will_be_thrown_if_phone_number_is_invalid() { + $order = WC_Helper_Order::create_order(); + $order->set_billing_phone( '+1123456789123456789123' ); + $order->save(); + $this->expectException( Exception::class ); + $this->expectExceptionMessage( 'Invalid phone number.' ); + $this->mock_gateway->process_payment( $order->get_id() ); + } + + public function test_remove_link_payment_method_if_card_disabled() { + $this->mock_gateway->settings['upe_enabled_payment_method_ids'] = [ 'link' ]; + + $this->mock_gateway + ->expects( $this->once() ) + ->method( 'get_upe_enabled_payment_method_statuses' ) + ->will( + $this->returnValue( [ 'link_payments' => [ 'status' => 'active' ] ] ) + ); + + $this->assertSame( $this->mock_gateway->get_payment_method_ids_enabled_at_checkout(), [] ); + } + + /** + * @dataProvider available_payment_methods_provider + */ + public function test_get_upe_available_payment_methods( $payment_methods, $expected_result ) { + $mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); + $mock_wcpay_account + ->expects( $this->any() ) + ->method( 'get_fees' ) + ->willReturn( $payment_methods ); + + $gateway = new WC_Payment_Gateway_WCPay( + $this->mock_api_client, + $mock_wcpay_account, + $this->mock_customer_service, + $this->mock_token_service, + $this->mock_action_scheduler_service, + $this->mock_payment_method, + $this->mock_payment_methods, + $this->mock_rate_limiter, + $this->mock_order_service, + $this->mock_dpps, + $this->mock_localization_service, + $this->mock_fraud_service + ); + + $this->assertEquals( $expected_result, $gateway->get_upe_available_payment_methods() ); + } + + public function available_payment_methods_provider() { + return [ + 'card only' => [ + [ 'card' => [ 'base' => 0.1 ] ], + [ 'card' ], + ], + 'no match with fees' => [ + [ 'some_other_payment_method' => [ 'base' => 0.1 ] ], + [], + ], + 'multiple matches with fees' => [ + [ + 'card' => [ 'base' => 0.1 ], + 'bancontact' => [ 'base' => 0.2 ], + ], + [ 'card', 'bancontact' ], + ], + 'no fees no methods' => [ + [], + [], + ], + ]; + } + + /** + * Helper function to mock subscriptions for internal UPE payment methods. + */ + private function set_cart_contains_subscription_items( $cart_contains_subscriptions ) { + foreach ( $this->mock_payment_methods as $mock_payment_method ) { + $mock_payment_method->expects( $this->any() ) + ->method( 'is_subscription_item_in_cart' ) + ->will( + $this->returnValue( $cart_contains_subscriptions ) + ); + } + } + + private function setup_saved_payment_method() { + $token = WC_Helper_Token::create_token( 'pm_mock' ); + + return [ + 'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID, + 'wc-' . WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token' => (string) $token->get_id(), + ]; + } + + private function set_get_upe_enabled_payment_method_statuses_return_value( $return_value = null ) { + if ( null === $return_value ) { + $return_value = [ + 'card_payments' => [ + 'status' => 'active', + ], + ]; + } + $this->mock_gateway + ->expects( $this->any() ) + ->method( 'get_upe_enabled_payment_method_statuses' ) + ->will( $this->returnValue( $return_value ) ); + } +} diff --git a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php new file mode 100644 index 00000000000..1e3162dead3 --- /dev/null +++ b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php @@ -0,0 +1,1290 @@ + 'success', + 'payment_needed' => true, + 'redirect' => 'testURL/key=mock_order_key', + ]; + + /** + * WC_Payments_Localization_Service instance. + * + * @var WC_Payments_Localization_Service + */ + private $mock_localization_service; + + /** + * Mock Fraud Service. + * + * @var WC_Payments_Fraud_Service|MockObject + */ + private $mock_fraud_service; + + /** + * Mapping for payment ID to payment method. + * + * @var array + */ + private $payment_method_classes = [ + Payment_Method::CARD => CC_Payment_Method::class, + Payment_Method::GIROPAY => Giropay_Payment_Method::class, + Payment_Method::SOFORT => Sofort_Payment_Method::class, + Payment_Method::BANCONTACT => Bancontact_Payment_Method::class, + Payment_Method::EPS => EPS_Payment_Method::class, + Payment_Method::P24 => P24_Payment_Method::class, + Payment_Method::IDEAL => Ideal_Payment_Method::class, + Payment_Method::SEPA => Sepa_Payment_Method::class, + Payment_Method::BECS => Becs_Payment_Method::class, + Payment_Method::LINK => Link_Payment_Method::class, + ]; + + /** + * Pre-test setup + */ + public function set_up() { + parent::set_up(); + + $this->mock_payment_gateways = []; + $this->mock_payment_methods = []; + + // Mock the main class's cache service. + $this->_cache = WC_Payments::get_database_cache(); + $this->mock_cache = $this->createMock( Database_Cache::class ); + WC_Payments::set_database_cache( $this->mock_cache ); + + // Arrange: Mock WC_Payments_API_Client so we can configure the + // return value of create_and_confirm_intention(). + // Note that we cannot use createStub here since it's not defined in PHPUnit 6.5. + $this->mock_api_client = $this->getMockBuilder( 'WC_Payments_API_Client' ) + ->disableOriginalConstructor() + ->setMethods( + [ + 'create_intention', + 'create_setup_intention', + 'update_intention', + 'get_intent', + 'get_payment_method', + 'is_server_connected', + 'get_charge', + 'get_timeline', + ] + ) + ->getMock(); + + $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); + $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' ); + $this->mock_wcpay_account->method( 'get_account_default_currency' )->willReturn( 'USD' ); + + $payment_methods = [ + 'link' => [ + 'base' => 0.1, + ], + ]; + + $this->mock_wcpay_account + ->expects( $this->any() ) + ->method( 'get_fees' ) + ->willReturn( $payment_methods ); + + $this->mock_woopay_utilities = $this->createMock( WooPay_Utilities::class ); + + // Arrange: Mock WC_Payments_Customer_Service so its methods aren't called directly. + $this->mock_customer_service = $this->getMockBuilder( 'WC_Payments_Customer_Service' ) + ->disableOriginalConstructor() + ->getMock(); + + // Arrange: Mock WC_Payments_Customer_Service so its methods aren't called directly. + $this->mock_token_service = $this->getMockBuilder( 'WC_Payments_Token_Service' ) + ->disableOriginalConstructor() + ->setMethods( [ 'add_payment_method_to_user' ] ) + ->getMock(); + + // Arrange: Mock WC_Payments_Action_Scheduler_Service so its methods aren't called directly. + $this->mock_action_scheduler_service = $this->getMockBuilder( 'WC_Payments_Action_Scheduler_Service' ) + ->disableOriginalConstructor() + ->getMock(); + + $this->mock_rate_limiter = $this->createMock( Session_Rate_Limiter::class ); + $this->order_service = new WC_Payments_Order_Service( $this->mock_api_client ); + + $this->mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); + + $this->mock_localization_service = $this->createMock( WC_Payments_Localization_Service::class ); + $this->mock_fraud_service = $this->createMock( WC_Payments_Fraud_Service::class ); + + // Arrange: Define a $_POST array which includes the payment method, + // so that get_payment_method_from_request() does not throw error. + $_POST = [ + 'wcpay-payment-method' => 'pm_mock', + 'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID, + ]; + + $get_payment_gateway_by_id_return_value_map = []; + + foreach ( $this->payment_method_classes as $payment_method_id => $payment_method_class ) { + $mock_payment_method = $this->getMockBuilder( $payment_method_class ) + ->setConstructorArgs( [ $this->mock_token_service ] ) + ->setMethods( [ 'is_subscription_item_in_cart', 'get_icon' ] ) + ->getMock(); + $this->mock_payment_methods[ $mock_payment_method->get_id() ] = $mock_payment_method; + + $mock_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) + ->setConstructorArgs( + [ + $this->mock_api_client, + $this->mock_wcpay_account, + $this->mock_customer_service, + $this->mock_token_service, + $this->mock_action_scheduler_service, + $mock_payment_method, + $this->mock_payment_methods, + $this->mock_rate_limiter, + $this->order_service, + $this->mock_dpps, + $this->mock_localization_service, + $this->mock_fraud_service, + ] + ) + ->setMethods( + [ + 'get_return_url', + 'manage_customer_details_for_order', + 'parent_process_payment', + 'get_upe_enabled_payment_method_statuses', + 'is_payment_recurring', + 'get_payment_method_ids_enabled_at_checkout', + 'wc_payments_get_payment_gateway_by_id', + 'get_selected_payment_method', + 'get_upe_enabled_payment_method_ids', + ] + ) + ->getMock(); + + // Arrange: Set the return value of get_return_url() so it can be used in a test later. + $mock_gateway + ->expects( $this->any() ) + ->method( 'get_return_url' ) + ->will( + $this->returnValue( $this->return_url ) + ); + $mock_gateway + ->expects( $this->any() ) + ->method( 'parent_process_payment' ) + ->will( + $this->returnValue( $this->mock_payment_result ) + ); + + $this->mock_payment_gateways[ $payment_method_id ] = $mock_gateway; + + $get_payment_gateway_by_id_return_value_map[] = [ $payment_method_id, $mock_gateway ]; + + WC_Helper_Site_Currency::$mock_site_currency = ''; + } + + foreach ( $this->mock_payment_gateways as $id => $mock_gateway ) { + $mock_gateway->expects( $this->any() ) + ->method( 'wc_payments_get_payment_gateway_by_id' ) + ->will( + $this->returnValueMap( $get_payment_gateway_by_id_return_value_map ) + ); + } + + // Mock the level3 service to always return an empty array. + $mock_level3_service = $this->createMock( Level3Service::class ); + $mock_level3_service->expects( $this->any() ) + ->method( 'get_data_from_order' ) + ->willReturn( [] ); + wcpay_get_test_container()->replace( Level3Service::class, $mock_level3_service ); + + // Mock the order service to always return an empty array for meta. + $mock_order_service = $this->createMock( OrderService::class ); + $mock_order_service->expects( $this->any() ) + ->method( 'get_payment_metadata' ) + ->willReturn( [] ); + wcpay_get_test_container()->replace( OrderService::class, $mock_order_service ); + } + + /** + * Cleanup after tests. + * + * @return void + */ + public function tear_down() { + parent::tear_down(); + WC_Payments::set_database_cache( $this->_cache ); + wcpay_get_test_container()->reset_all_replacements(); + } + + /** + * Test the UI
container that will hold the payment method. + * + * @return void + */ + public function test_display_gateway_html_for_multiple_gateways() { + foreach ( $this->mock_payment_gateways as $payment_method_id => $mock_payment_gateway ) { + /** + * This tests each payment method output separately without concatenating the output + * into 1 single buffer. Each iteration has 1 assertion. + */ + ob_start(); + $mock_payment_gateway->display_gateway_html(); + $actual_output = ob_get_contents(); + ob_end_clean(); + + $this->assertStringContainsString( '
', $actual_output ); + } + } + + public function test_should_not_use_stripe_platform_on_checkout_page_for_upe() { + $payment_gateway = $this->mock_payment_gateways[ Payment_Method::SEPA ]; + $this->assertFalse( $payment_gateway->should_use_stripe_platform_on_checkout_page() ); + } + + public function test_link_payment_method_requires_mandate_data() { + $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; + + $mock_upe_gateway + ->expects( $this->once() ) + ->method( 'get_upe_enabled_payment_method_ids' ) + ->will( + $this->returnValue( [ 'link' ] ) + ); + + $this->assertTrue( $mock_upe_gateway->is_mandate_data_required() ); + } + + public function test_sepa_debit_payment_method_requires_mandate_data() { + $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::SEPA ]; + $this->assertTrue( $mock_upe_gateway->is_mandate_data_required() ); + } + + public function test_non_required_mandate_data() { + $mock_gateway_not_requiring_mandate_data = $this->mock_payment_gateways[ Payment_Method::GIROPAY ]; + $this->assertFalse( $mock_gateway_not_requiring_mandate_data->is_mandate_data_required() ); + } + + public function test_non_reusable_payment_method_is_not_available_when_subscription_is_in_cart() { + $non_reusable_payment_method = Payment_Method::BANCONTACT; + $payment_gateway = $this->mock_payment_gateways[ $non_reusable_payment_method ]; + + $this->set_cart_contains_subscription_items( true ); + + $this->assertFalse( $payment_gateway->is_available() ); + } + + public function test_process_payment_returns_correct_redirect_when_using_saved_payment() { + $mock_card_payment_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; + $user = wp_get_current_user(); + $customer_id = 'cus_mock'; + + $order = WC_Helper_Order::create_order(); + $_POST = $this->setup_saved_payment_method(); + $mock_card_payment_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ $user, $customer_id ] ) + ); + $mock_card_payment_gateway->expects( $this->any() ) + ->method( 'get_upe_enabled_payment_method_ids' ) + ->will( + $this->returnValue( [ Payment_Method::CARD ] ) + ); + $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1 ) + ->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( + WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::PROCESSING ] ) + ); + + $this->set_cart_contains_subscription_items( false ); + + $result = $mock_card_payment_gateway->process_payment( $order->get_id() ); + + $this->assertEquals( 'success', $result['result'] ); + $this->assertEquals( $this->return_url, $result['redirect'] ); + } + + public function test_upe_process_payment_check_session_order_redirect_to_previous_order() { + $_POST['wc_payment_intent_id'] = 'pi_mock'; + $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::SEPA ]; + + $response = [ + 'dummy_result' => 'xyz', + ]; + + // Arrange the order is being processed. + $order = WC_Helper_Order::create_order(); + $order_id = $order->get_id(); + + // Arrange the DPPs to return a redirect. + $this->mock_dpps->expects( $this->once() ) + ->method( 'check_against_session_processing_order' ) + ->with( wc_get_order( $order ) ) + ->willReturn( $response ); + + // Act: process the order but redirect to the previous/session paid order. + $result = $mock_upe_gateway->process_payment( $order_id ); + + // Assert: the result of check_against_session_processing_order. + $this->assertSame( $response, $result ); + } + + public function test_process_redirect_payment_intent_processing() { + + $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; + $order = WC_Helper_Order::create_order(); + + $order_id = $order->get_id(); + $save_payment_method = false; + $user = wp_get_current_user(); + $intent_status = Intent_Status::PROCESSING; + $intent_metadata = [ 'order_id' => (string) $order_id ]; + $charge_id = 'ch_mock'; + $customer_id = 'cus_mock'; + $intent_id = 'pi_mock'; + $payment_method_id = 'pm_mock'; + + // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. + $order->update_meta_data( '_intent_id', $intent_id ); + $order->save(); + + $card_method = $this->mock_payment_methods['card']; + + $payment_intent = WC_Helper_Intention::create_intention( + [ + 'status' => $intent_status, + 'metadata' => $intent_metadata, + ] + ); + + $mock_upe_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ $user, $customer_id ] ) + ); + + $mock_upe_gateway->expects( $this->any() ) + ->method( 'get_selected_payment_method' ) + ->willReturn( $card_method ); + + $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ) + ->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( $payment_intent ); + + $this->set_cart_contains_subscription_items( false ); + + $mock_upe_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + + $result_order = wc_get_order( $order_id ); + $note = wc_get_order_notes( + [ + 'order_id' => $order_id, + 'limit' => 1, + ] + )[0]; + + $this->assertStringContainsString( 'authorized', $note->content ); + $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); + $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); + $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); + $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); + $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); + $this->assertEquals( Order_Status::ON_HOLD, $result_order->get_status() ); + } + + public function test_process_redirect_payment_intent_succeded() { + + $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; + $order = WC_Helper_Order::create_order(); + + $order_id = $order->get_id(); + $save_payment_method = false; + $user = wp_get_current_user(); + $intent_status = Intent_Status::SUCCEEDED; + $intent_metadata = [ 'order_id' => (string) $order_id ]; + $charge_id = 'ch_mock'; + $customer_id = 'cus_mock'; + $intent_id = 'pi_mock'; + $payment_method_id = 'pm_mock'; + + // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. + $order->update_meta_data( '_intent_id', $intent_id ); + $order->save(); + + $card_method = $this->mock_payment_methods['card']; + + $payment_intent = WC_Helper_Intention::create_intention( + [ + 'status' => $intent_status, + 'metadata' => $intent_metadata, + ] + ); + + $mock_upe_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ $user, $customer_id ] ) + ); + + $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ) + ->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( $payment_intent ); + + $mock_upe_gateway->expects( $this->any() ) + ->method( 'get_selected_payment_method' ) + ->willReturn( $card_method ); + + $this->set_cart_contains_subscription_items( false ); + + $mock_upe_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + + $result_order = wc_get_order( $order_id ); + + $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); + $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); + $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); + $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); + $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); + $this->assertEquals( Order_Status::PROCESSING, $result_order->get_status() ); + } + + public function is_proper_intent_used_with_order_returns_false() { + $this->assertFalse( $this->mock_upe_gateway->is_proper_intent_used_with_order( WC_Helper_Order::create_order(), 'wrong_intent_id' ) ); + } + + public function test_process_redirect_setup_intent_succeded() { + + $order = WC_Helper_Order::create_order(); + $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; + + $order_id = $order->get_id(); + $save_payment_method = true; + $user = wp_get_current_user(); + $intent_status = Intent_Status::SUCCEEDED; + $client_secret = 'cs_mock'; + $customer_id = 'cus_mock'; + $intent_id = 'si_mock'; + $payment_method_id = 'pm_mock'; + $token = WC_Helper_Token::create_token( $payment_method_id ); + + // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. + $order->update_meta_data( '_intent_id', $intent_id ); + $order->save(); + + $card_method = $this->mock_payment_methods['card']; + + $order->set_shipping_total( 0 ); + $order->set_shipping_tax( 0 ); + $order->set_cart_tax( 0 ); + $order->set_total( 0 ); + $order->save(); + + $setup_intent = WC_Helper_Intention::create_setup_intention( + [ + 'id' => $intent_id, + 'client_secret' => $client_secret, + 'status' => $intent_status, + 'payment_method' => $payment_method_id, + 'payment_method_options' => [ + 'card' => [ + 'request_three_d_secure' => 'automatic', + ], + ], + 'last_setup_error' => [], + ] + ); + + $mock_upe_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ $user, $customer_id ] ) + ); + + $request = $this->mock_wcpay_request( Get_Setup_Intention::class, 1, $intent_id ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( $setup_intent ); + + $this->mock_token_service->expects( $this->once() ) + ->method( 'add_payment_method_to_user' ) + ->will( + $this->returnValue( $token ) + ); + + $mock_upe_gateway->expects( $this->any() ) + ->method( 'get_selected_payment_method' ) + ->willReturn( $card_method ); + + $this->set_cart_contains_subscription_items( true ); + + $mock_upe_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + + $result_order = wc_get_order( $order_id ); + + $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); + $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); + $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); + $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); + $this->assertEquals( Order_Status::PROCESSING, $result_order->get_status() ); + $this->assertEquals( 1, count( $result_order->get_payment_tokens() ) ); + } + + public function test_process_redirect_payment_save_payment_token() { + + $mock_upe_gateway = $this->mock_payment_gateways[ Payment_Method::CARD ]; + + $order = WC_Helper_Order::create_order(); + $order_id = $order->get_id(); + $save_payment_method = true; + $user = wp_get_current_user(); + $intent_status = Intent_Status::PROCESSING; + $intent_metadata = [ 'order_id' => (string) $order_id ]; + $charge_id = 'ch_mock'; + $customer_id = 'cus_mock'; + $intent_id = 'pi_mock'; + $payment_method_id = 'pm_mock'; + $token = WC_Helper_Token::create_token( $payment_method_id ); + + // Supply the order with the intent id so that it can be retrieved during the redirect payment processing. + $order->update_meta_data( '_intent_id', $intent_id ); + $order->save(); + + $card_method = $this->mock_payment_methods['card']; + + $payment_intent = WC_Helper_Intention::create_intention( + [ + 'status' => $intent_status, + 'metadata' => $intent_metadata, + ] + ); + + $mock_upe_gateway->expects( $this->once() ) + ->method( 'manage_customer_details_for_order' ) + ->will( + $this->returnValue( [ $user, $customer_id ] ) + ); + + $this->mock_wcpay_request( Get_Intention::class, 1, $intent_id ) + ->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( $payment_intent ); + + $this->mock_token_service->expects( $this->once() ) + ->method( 'add_payment_method_to_user' ) + ->will( + $this->returnValue( $token ) + ); + + $mock_upe_gateway->expects( $this->any() ) + ->method( 'get_selected_payment_method' ) + ->willReturn( $card_method ); + + $this->set_cart_contains_subscription_items( false ); + + $mock_upe_gateway->process_redirect_payment( $order, $intent_id, $save_payment_method ); + + $result_order = wc_get_order( $order_id ); + $note = wc_get_order_notes( + [ + 'order_id' => $order_id, + 'limit' => 1, + ] + )[0]; + + $this->assertStringContainsString( 'authorized', $note->content ); + $this->assertEquals( $intent_id, $result_order->get_meta( '_intent_id', true ) ); + $this->assertEquals( $charge_id, $result_order->get_meta( '_charge_id', true ) ); + $this->assertEquals( $intent_status, $result_order->get_meta( '_intention_status', true ) ); + $this->assertEquals( $payment_method_id, $result_order->get_meta( '_payment_method_id', true ) ); + $this->assertEquals( $customer_id, $result_order->get_meta( '_stripe_customer_id', true ) ); + $this->assertEquals( Order_Status::ON_HOLD, $result_order->get_status() ); + $this->assertEquals( 1, count( $result_order->get_payment_tokens() ) ); + } + + public function test_correct_payment_method_title_for_order() { + $order = WC_Helper_Order::create_order(); + + $visa_credit_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'visa', + 'funding' => 'credit', + ], + ]; + $visa_debit_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'visa', + 'funding' => 'debit', + ], + ]; + $mastercard_credit_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'mastercard', + 'funding' => 'credit', + ], + ]; + $eps_details = [ + 'type' => 'eps', + ]; + $giropay_details = [ + 'type' => 'giropay', + ]; + $p24_details = [ + 'type' => 'p24', + ]; + $sofort_details = [ + 'type' => 'sofort', + ]; + $bancontact_details = [ + 'type' => 'bancontact', + ]; + $sepa_details = [ + 'type' => 'sepa_debit', + ]; + $ideal_details = [ + 'type' => 'ideal', + ]; + $becs_details = [ + 'type' => 'au_becs_debit', + ]; + + $charge_payment_method_details = [ + $visa_credit_details, + $visa_debit_details, + $mastercard_credit_details, + $giropay_details, + $sofort_details, + $bancontact_details, + $eps_details, + $p24_details, + $ideal_details, + $sepa_details, + $becs_details, + ]; + + $expected_payment_method_titles = [ + 'Visa credit card', + 'Visa debit card', + 'Mastercard credit card', + 'giropay', + 'Sofort', + 'Bancontact', + 'EPS', + 'Przelewy24 (P24)', + 'iDEAL', + 'SEPA Direct Debit', + 'BECS Direct Debit', + ]; + + foreach ( $charge_payment_method_details as $i => $payment_method_details ) { + $payment_method_id = $payment_method_details['type']; + $mock_upe_gateway = $this->mock_payment_gateways[ $payment_method_id ]; + $payment_method = $this->mock_payment_methods[ $payment_method_id ]; + $mock_upe_gateway->expects( $this->any() ) + ->method( 'get_selected_payment_method' ) + ->willReturn( $payment_method ); + $mock_upe_gateway->set_payment_method_title_for_order( $order, $payment_method_id, $payment_method_details ); + $this->assertEquals( $expected_payment_method_titles[ $i ], $order->get_payment_method_title() ); + } + } + + public function test_payment_methods_show_correct_default_outputs() { + $mock_token = WC_Helper_Token::create_token( 'pm_mock' ); + $this->mock_token_service->expects( $this->any() ) + ->method( 'add_payment_method_to_user' ) + ->will( + $this->returnValue( $mock_token ) + ); + + $mock_user = 'mock_user'; + $mock_payment_method_id = 'pm_mock'; + + $mock_visa_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'visa', + 'funding' => 'debit', + ], + ]; + $mock_mastercard_details = [ + 'type' => 'card', + 'card' => [ + 'network' => 'mastercard', + 'funding' => 'credit', + ], + ]; + $mock_giropay_details = [ + 'type' => 'giropay', + ]; + $mock_p24_details = [ + 'type' => 'p24', + ]; + $mock_sofort_details = [ + 'type' => 'sofort', + ]; + $mock_bancontact_details = [ + 'type' => 'bancontact', + ]; + $mock_eps_details = [ + 'type' => 'eps', + ]; + $mock_sepa_details = [ + 'type' => 'sepa_debit', + ]; + $mock_ideal_details = [ + 'type' => 'ideal', + ]; + $mock_becs_details = [ + 'type' => 'au_becs_debit', + ]; + + $this->set_cart_contains_subscription_items( false ); + $card_method = $this->mock_payment_methods['card']; + $giropay_method = $this->mock_payment_methods['giropay']; + $p24_method = $this->mock_payment_methods['p24']; + $sofort_method = $this->mock_payment_methods['sofort']; + $bancontact_method = $this->mock_payment_methods['bancontact']; + $eps_method = $this->mock_payment_methods['eps']; + $sepa_method = $this->mock_payment_methods['sepa_debit']; + $ideal_method = $this->mock_payment_methods['ideal']; + $becs_method = $this->mock_payment_methods['au_becs_debit']; + + $this->assertEquals( 'card', $card_method->get_id() ); + $this->assertEquals( 'Credit card / debit card', $card_method->get_title( 'US' ) ); + $this->assertEquals( 'Visa debit card', $card_method->get_title( 'US', $mock_visa_details ) ); + $this->assertEquals( 'Mastercard credit card', $card_method->get_title( 'US', $mock_mastercard_details ) ); + $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); + $this->assertTrue( $card_method->is_reusable() ); + $this->assertEquals( $mock_token, $card_method->get_payment_token_for_user( $mock_user, $mock_payment_method_id ) ); + + $this->assertEquals( 'giropay', $giropay_method->get_id() ); + $this->assertEquals( 'giropay', $giropay_method->get_title( 'US' ) ); + $this->assertEquals( 'giropay', $giropay_method->get_title( 'US', $mock_giropay_details ) ); + $this->assertTrue( $giropay_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $giropay_method->is_reusable() ); + + $this->assertEquals( 'p24', $p24_method->get_id() ); + $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title( 'US' ) ); + $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title( 'US', $mock_p24_details ) ); + $this->assertTrue( $p24_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $p24_method->is_reusable() ); + + $this->assertEquals( 'sofort', $sofort_method->get_id() ); + $this->assertEquals( 'Sofort', $sofort_method->get_title( 'US' ) ); + $this->assertEquals( 'Sofort', $sofort_method->get_title( 'US', $mock_sofort_details ) ); + $this->assertTrue( $sofort_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sofort_method->is_reusable() ); + + $this->assertEquals( 'bancontact', $bancontact_method->get_id() ); + $this->assertEquals( 'Bancontact', $bancontact_method->get_title( 'US' ) ); + $this->assertEquals( 'Bancontact', $bancontact_method->get_title( 'US', $mock_bancontact_details ) ); + $this->assertTrue( $bancontact_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $bancontact_method->is_reusable() ); + + $this->assertEquals( 'eps', $eps_method->get_id() ); + $this->assertEquals( 'EPS', $eps_method->get_title( 'US' ) ); + $this->assertEquals( 'EPS', $eps_method->get_title( 'US', $mock_eps_details ) ); + $this->assertTrue( $eps_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $eps_method->is_reusable() ); + + $this->assertEquals( 'sepa_debit', $sepa_method->get_id() ); + $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title( 'US' ) ); + $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title( 'US', $mock_sepa_details ) ); + $this->assertTrue( $sepa_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sepa_method->is_reusable() ); + + $this->assertEquals( 'ideal', $ideal_method->get_id() ); + $this->assertEquals( 'iDEAL', $ideal_method->get_title( 'US' ) ); + $this->assertEquals( 'iDEAL', $ideal_method->get_title( 'US', $mock_ideal_details ) ); + $this->assertTrue( $ideal_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $ideal_method->is_reusable() ); + + $this->assertEquals( 'au_becs_debit', $becs_method->get_id() ); + $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title( 'US' ) ); + $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title( 'US', $mock_becs_details ) ); + $this->assertTrue( $becs_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $becs_method->is_reusable() ); + } + + public function test_only_reusabled_payment_methods_enabled_with_subscription_item_present() { + // Setup $this->mock_payment_methods. + + $this->set_cart_contains_subscription_items( true ); + + $card_method = $this->mock_payment_methods['card']; + $giropay_method = $this->mock_payment_methods['giropay']; + $sofort_method = $this->mock_payment_methods['sofort']; + $bancontact_method = $this->mock_payment_methods['bancontact']; + $eps_method = $this->mock_payment_methods['eps']; + $sepa_method = $this->mock_payment_methods['sepa_debit']; + $p24_method = $this->mock_payment_methods['p24']; + $ideal_method = $this->mock_payment_methods['ideal']; + $becs_method = $this->mock_payment_methods['au_becs_debit']; + + $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $giropay_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sofort_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $bancontact_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $eps_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $sepa_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $p24_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $ideal_method->is_enabled_at_checkout( 'US' ) ); + $this->assertFalse( $becs_method->is_enabled_at_checkout( 'US' ) ); + } + + public function test_only_valid_payment_methods_returned_for_currency() { + // Setup $this->mock_payment_methods. + + $card_method = $this->mock_payment_methods['card']; + $giropay_method = $this->mock_payment_methods['giropay']; + $sofort_method = $this->mock_payment_methods['sofort']; + $bancontact_method = $this->mock_payment_methods['bancontact']; + $eps_method = $this->mock_payment_methods['eps']; + $sepa_method = $this->mock_payment_methods['sepa_debit']; + $p24_method = $this->mock_payment_methods['p24']; + $ideal_method = $this->mock_payment_methods['ideal']; + $becs_method = $this->mock_payment_methods['au_becs_debit']; + + WC_Helper_Site_Currency::$mock_site_currency = 'EUR'; + $account_domestic_currency = 'USD'; + + $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $giropay_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $sofort_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $eps_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $sepa_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $p24_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertTrue( $ideal_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); + + WC_Helper_Site_Currency::$mock_site_currency = 'USD'; + + $this->assertTrue( $card_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $giropay_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $sofort_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $bancontact_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $eps_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $sepa_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $p24_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $ideal_method->is_currency_valid( $account_domestic_currency ) ); + $this->assertFalse( $becs_method->is_currency_valid( $account_domestic_currency ) ); + + WC_Helper_Site_Currency::$mock_site_currency = 'AUD'; + $this->assertTrue( $becs_method->is_currency_valid( $account_domestic_currency ) ); + + WC_Helper_Site_Currency::$mock_site_currency = ''; + } + + public function test_create_token_from_setup_intent_adds_token() { + + $mock_token = WC_Helper_Token::create_token( 'pm_mock' ); + $mock_setup_intent_id = 'si_mock'; + $mock_user = wp_get_current_user(); + + $this->mock_token_service + ->method( 'add_payment_method_to_user' ) + ->with( 'pm_mock', $mock_user ) + ->will( + $this->returnValue( $mock_token ) + ); + + foreach ( $this->mock_payment_gateways as $mock_upe_gateway ) { + $request = $this->mock_wcpay_request( Get_Setup_Intention::class, 1, $mock_setup_intent_id ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( + WC_Helper_Intention::create_setup_intention( + [ + 'id' => $mock_setup_intent_id, + 'payment_method' => 'pm_mock', + ] + ) + ); + $this->assertEquals( $mock_token, $mock_upe_gateway->create_token_from_setup_intent( $mock_setup_intent_id, $mock_user ) ); + } + } + + /** + * Test get_payment_method_types with regular checkout post request context. + * + * @return void + */ + public function test_get_payment_methods_with_request_context() { + $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) + ->setConstructorArgs( + [ + $this->mock_api_client, + $this->mock_wcpay_account, + $this->mock_customer_service, + $this->mock_token_service, + $this->mock_action_scheduler_service, + $this->mock_payment_methods[ Payment_Method::CARD ], + $this->mock_payment_methods, + $this->mock_rate_limiter, + $this->order_service, + $this->mock_dpps, + $this->mock_localization_service, + $this->mock_fraud_service, + ] + ) + ->setMethods( [ 'get_payment_methods_from_gateway_id' ] ) + ->getMock(); + + $order = WC_Helper_Order::create_order(); + $payment_information = new Payment_Information( 'pm_mock', $order ); + + $_POST['payment_method'] = 'woocommerce_payments'; + + $mock_upe_gateway->expects( $this->once() ) + ->method( 'get_payment_methods_from_gateway_id' ) + ->with( 'woocommerce_payments' ) + ->will( + $this->returnValue( [ Payment_Method::CARD ] ) + ); + + $payment_methods = $mock_upe_gateway->get_payment_method_types( $payment_information ); + + $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); + + unset( $_POST['payment_method'] ); // phpcs:ignore WordPress.Security.NonceVerification + } + + /** + * Test get_payment_method_types without post request context. + * + * @return void + */ + public function test_get_payment_methods_without_request_context() { + $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) + ->setConstructorArgs( + [ + $this->mock_api_client, + $this->mock_wcpay_account, + $this->mock_customer_service, + $this->mock_token_service, + $this->mock_action_scheduler_service, + $this->mock_payment_methods[ Payment_Method::CARD ], + $this->mock_payment_methods, + $this->mock_rate_limiter, + $this->order_service, + $this->mock_dpps, + $this->mock_localization_service, + $this->mock_fraud_service, + ] + ) + ->setMethods( [ 'get_payment_methods_from_gateway_id' ] ) + ->getMock(); + + $token = WC_Helper_Token::create_token( 'pm_mock' ); + $order = WC_Helper_Order::create_order(); + $payment_information = new Payment_Information( 'pm_mock', $order, null, $token ); + + unset( $_POST['payment_method'] ); // phpcs:ignore WordPress.Security.NonceVerification + + $mock_upe_gateway->expects( $this->once() ) + ->method( 'get_payment_methods_from_gateway_id' ) + ->with( $token->get_gateway_id(), $order->get_id() ) + ->will( + $this->returnValue( [ Payment_Method::CARD ] ) + ); + + $payment_methods = $mock_upe_gateway->get_payment_method_types( $payment_information ); + + $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); + } + + /** + * Test get_payment_method_types without post request context or saved token. + * + * @return void + */ + public function test_get_payment_methods_without_request_context_or_token() { + $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) + ->setConstructorArgs( + [ + $this->mock_api_client, + $this->mock_wcpay_account, + $this->mock_customer_service, + $this->mock_token_service, + $this->mock_action_scheduler_service, + $this->mock_payment_methods[ Payment_Method::CARD ], + $this->mock_payment_methods, + $this->mock_rate_limiter, + $this->order_service, + $this->mock_dpps, + $this->mock_localization_service, + $this->mock_fraud_service, + ] + ) + ->setMethods( + [ + 'get_payment_methods_from_gateway_id', + 'get_payment_method_ids_enabled_at_checkout', + ] + ) + ->getMock(); + + $payment_information = new Payment_Information( 'pm_mock' ); + + unset( $_POST['payment_method'] ); // phpcs:ignore WordPress.Security.NonceVerification + + $gateway = WC_Payments::get_gateway(); + WC_Payments::set_gateway( $mock_upe_gateway ); + + $mock_upe_gateway->expects( $this->never() ) + ->method( 'get_payment_methods_from_gateway_id' ); + + $mock_upe_gateway->expects( $this->once() ) + ->method( 'get_payment_method_ids_enabled_at_checkout' ) + ->willReturn( [ Payment_Method::CARD ] ); + + $payment_methods = $mock_upe_gateway->get_payment_method_types( $payment_information ); + + $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); + + WC_Payments::set_gateway( $gateway ); + } + + /** + * Test get_payment_methods_from_gateway_id function with UPE enabled. + * + * @return void + */ + public function test_get_payment_methods_from_gateway_id_upe() { + WC_Helper_Order::create_order(); + $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) + ->setConstructorArgs( + [ + $this->mock_api_client, + $this->mock_wcpay_account, + $this->mock_customer_service, + $this->mock_token_service, + $this->mock_action_scheduler_service, + $this->mock_payment_methods[ Payment_Method::CARD ], + $this->mock_payment_methods, + $this->mock_rate_limiter, + $this->order_service, + $this->mock_dpps, + $this->mock_localization_service, + $this->mock_fraud_service, + ] + ) + ->onlyMethods( + [ + 'get_upe_enabled_payment_method_ids', + 'get_payment_method_ids_enabled_at_checkout', + ] + ) + ->getMock(); + + $gateway = WC_Payments::get_gateway(); + WC_Payments::set_gateway( $mock_upe_gateway ); + + $mock_upe_gateway->expects( $this->any() ) + ->method( 'get_upe_enabled_payment_method_ids' ) + ->will( + $this->returnValue( [ Payment_Method::CARD, Payment_Method::LINK ] ) + ); + + $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID . '_' . Payment_Method::BANCONTACT ); + $this->assertSame( [ Payment_Method::BANCONTACT ], $payment_methods ); + + $mock_upe_gateway->expects( $this->any() ) + ->method( 'get_payment_method_ids_enabled_at_checkout' ) + ->will( + $this->onConsecutiveCalls( + [ Payment_Method::CARD, Payment_Method::LINK ], + [ Payment_Method::CARD ] + ) + ); + + $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID ); + $this->assertSame( [ Payment_Method::CARD, Payment_Method::LINK ], $payment_methods ); + + $payment_methods = $mock_upe_gateway->get_payment_methods_from_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID ); + $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); + + WC_Payments::set_gateway( $gateway ); + } + + /** + * Helper function to mock subscriptions for internal UPE payment methods. + */ + private function set_cart_contains_subscription_items( $cart_contains_subscriptions ) { + foreach ( $this->mock_payment_methods as $mock_payment_method ) { + $mock_payment_method->expects( $this->any() ) + ->method( 'is_subscription_item_in_cart' ) + ->will( + $this->returnValue( $cart_contains_subscriptions ) + ); + } + } + + private function setup_saved_payment_method() { + $token = WC_Helper_Token::create_token( 'pm_mock' ); + + return [ + 'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID, + 'wc-' . WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token' => (string) $token->get_id(), + ]; + } + + private function set_get_upe_enabled_payment_method_statuses_return_value( $mock_payment_gateway, $return_value = null ) { + if ( null === $return_value ) { + $return_value = [ + 'card_payments' => [ + 'status' => 'active', + ], + ]; + } + $mock_payment_gateway + ->expects( $this->any() ) + ->method( 'get_upe_enabled_payment_method_statuses' ) + ->will( $this->returnValue( $return_value ) ); + } +} diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index 62dd5c2f83a..f59ec8e6746 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -573,71 +573,77 @@ public function test_payment_methods_show_correct_default_outputs() { $this->assertEquals( 'card', $card_method->get_id() ); $this->assertEquals( 'Credit card / debit card', $card_method->get_title() ); - $this->assertEquals( 'Visa debit card', $card_method->get_title( $mock_visa_details ) ); - $this->assertEquals( 'Mastercard credit card', $card_method->get_title( $mock_mastercard_details ) ); + $this->assertEquals( 'Visa debit card', $card_method->get_title( 'US', $mock_visa_details ) ); + $this->assertEquals( 'Mastercard credit card', $card_method->get_title( 'US', $mock_mastercard_details ) ); $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); $this->assertTrue( $card_method->is_reusable() ); $this->assertEquals( $mock_token, $card_method->get_payment_token_for_user( $mock_user, $mock_payment_method_id ) ); $this->assertEquals( 'giropay', $giropay_method->get_id() ); $this->assertEquals( 'giropay', $giropay_method->get_title() ); - $this->assertEquals( 'giropay', $giropay_method->get_title( $mock_giropay_details ) ); + $this->assertEquals( 'giropay', $giropay_method->get_title( 'US', $mock_giropay_details ) ); $this->assertTrue( $giropay_method->is_enabled_at_checkout( 'US' ) ); $this->assertFalse( $giropay_method->is_reusable() ); $this->assertEquals( 'p24', $p24_method->get_id() ); $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title() ); - $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title( $mock_p24_details ) ); + $this->assertEquals( 'Przelewy24 (P24)', $p24_method->get_title( 'US', $mock_p24_details ) ); $this->assertTrue( $p24_method->is_enabled_at_checkout( 'US' ) ); $this->assertFalse( $p24_method->is_reusable() ); $this->assertEquals( 'sofort', $sofort_method->get_id() ); $this->assertEquals( 'Sofort', $sofort_method->get_title() ); - $this->assertEquals( 'Sofort', $sofort_method->get_title( $mock_sofort_details ) ); + $this->assertEquals( 'Sofort', $sofort_method->get_title( 'US', $mock_sofort_details ) ); $this->assertTrue( $sofort_method->is_enabled_at_checkout( 'US' ) ); $this->assertFalse( $sofort_method->is_reusable() ); $this->assertEquals( 'bancontact', $bancontact_method->get_id() ); $this->assertEquals( 'Bancontact', $bancontact_method->get_title() ); - $this->assertEquals( 'Bancontact', $bancontact_method->get_title( $mock_bancontact_details ) ); + $this->assertEquals( 'Bancontact', $bancontact_method->get_title( 'US', $mock_bancontact_details ) ); $this->assertTrue( $bancontact_method->is_enabled_at_checkout( 'US' ) ); $this->assertFalse( $bancontact_method->is_reusable() ); $this->assertEquals( 'eps', $eps_method->get_id() ); $this->assertEquals( 'EPS', $eps_method->get_title() ); - $this->assertEquals( 'EPS', $eps_method->get_title( $mock_eps_details ) ); + $this->assertEquals( 'EPS', $eps_method->get_title( 'US', $mock_eps_details ) ); $this->assertTrue( $eps_method->is_enabled_at_checkout( 'US' ) ); $this->assertFalse( $eps_method->is_reusable() ); $this->assertEquals( 'sepa_debit', $sepa_method->get_id() ); $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title() ); - $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title( $mock_sepa_details ) ); + $this->assertEquals( 'SEPA Direct Debit', $sepa_method->get_title( 'US', $mock_sepa_details ) ); $this->assertTrue( $sepa_method->is_enabled_at_checkout( 'US' ) ); $this->assertFalse( $sepa_method->is_reusable() ); $this->assertEquals( 'ideal', $ideal_method->get_id() ); $this->assertEquals( 'iDEAL', $ideal_method->get_title() ); - $this->assertEquals( 'iDEAL', $ideal_method->get_title( $mock_ideal_details ) ); + $this->assertEquals( 'iDEAL', $ideal_method->get_title( 'US', $mock_ideal_details ) ); $this->assertTrue( $ideal_method->is_enabled_at_checkout( 'US' ) ); $this->assertFalse( $ideal_method->is_reusable() ); $this->assertEquals( 'au_becs_debit', $becs_method->get_id() ); $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title() ); - $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title( $mock_becs_details ) ); + $this->assertEquals( 'BECS Direct Debit', $becs_method->get_title( 'US', $mock_becs_details ) ); $this->assertTrue( $becs_method->is_enabled_at_checkout( 'US' ) ); $this->assertFalse( $becs_method->is_reusable() ); $this->assertSame( 'affirm', $affirm_method->get_id() ); $this->assertSame( 'Affirm', $affirm_method->get_title() ); - $this->assertSame( 'Affirm', $affirm_method->get_title( $mock_affirm_details ) ); + $this->assertSame( 'Affirm', $affirm_method->get_title( 'US', $mock_affirm_details ) ); $this->assertTrue( $affirm_method->is_enabled_at_checkout( 'US' ) ); $this->assertFalse( $affirm_method->is_reusable() ); $this->assertSame( 'afterpay_clearpay', $afterpay_method->get_id() ); $this->assertSame( 'Afterpay', $afterpay_method->get_title() ); - $this->assertSame( 'Afterpay', $afterpay_method->get_title( $mock_afterpay_details ) ); + $this->assertSame( 'Afterpay', $afterpay_method->get_title( 'US', $mock_afterpay_details ) ); $this->assertTrue( $afterpay_method->is_enabled_at_checkout( 'US' ) ); $this->assertFalse( $afterpay_method->is_reusable() ); + + $this->assertSame( 'afterpay_clearpay', $afterpay_method->get_id() ); + $this->assertSame( 'Clearpay', $afterpay_method->get_title( 'GB' ) ); + $this->assertSame( 'Clearpay', $afterpay_method->get_title( 'GB', $mock_afterpay_details ) ); + $this->assertTrue( $afterpay_method->is_enabled_at_checkout( 'GB' ) ); + $this->assertFalse( $afterpay_method->is_reusable() ); } public function test_only_reusabled_payment_methods_enabled_with_subscription_item_present() { diff --git a/tests/unit/test-class-wc-payments-checkout.php b/tests/unit/test-class-wc-payments-checkout.php index d6273a1fcd2..72a7688187d 100644 --- a/tests/unit/test-class-wc-payments-checkout.php +++ b/tests/unit/test-class-wc-payments-checkout.php @@ -103,6 +103,9 @@ public function set_up() { ->disableOriginalConstructor() ->getMock(); $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); + $this->mock_wcpay_account + ->method( 'get_account_country' ) + ->willReturn( 'US' ); $this->mock_customer_service = $this->createMock( WC_Payments_Customer_Service::class ); $this->mock_fraud_service = $this->createMock( WC_Payments_Fraud_Service::class ); From 2f2fa96934c0100f98a0cb9bba41c1202de96bff Mon Sep 17 00:00:00 2001 From: Rafael Zaleski Date: Tue, 23 Jan 2024 15:40:56 -0300 Subject: [PATCH 06/52] Add blog ID validation to WooPay session (#8045) --- changelog/fix-woopay-2401-validate-session | 4 ++ .../woopay-express-checkout-button.js | 46 +++++++++++++------ includes/woopay/class-woopay-session.php | 8 ++++ 3 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 changelog/fix-woopay-2401-validate-session diff --git a/changelog/fix-woopay-2401-validate-session b/changelog/fix-woopay-2401-validate-session new file mode 100644 index 00000000000..b385b3dbadf --- /dev/null +++ b/changelog/fix-woopay-2401-validate-session @@ -0,0 +1,4 @@ +Significance: patch +Type: add + +Enhance WooPay session validation diff --git a/client/checkout/woopay/express-button/woopay-express-checkout-button.js b/client/checkout/woopay/express-button/woopay-express-checkout-button.js index 24413105304..ddd4dd1e65f 100644 --- a/client/checkout/woopay/express-button/woopay-express-checkout-button.js +++ b/client/checkout/woopay/express-button/woopay-express-checkout-button.js @@ -285,13 +285,22 @@ export const WoopayExpressCheckoutButton = ( { } ) .then( ( response ) => { - iframe.contentWindow.postMessage( - { - action: 'setPreemptiveSessionData', - value: response, - }, - getConfig( 'woopayHost' ) - ); + if ( + response?.blog_id && + response?.data?.session + ) { + iframe.contentWindow.postMessage( + { + action: 'setPreemptiveSessionData', + value: response, + }, + getConfig( 'woopayHost' ) + ); + } else { + // Set button's default onClick handle to use modal checkout flow. + initWoopayRef.current = onClickFallback; + throw new Error( response?.data ); + } } ) .catch( () => { const errorMessage = __( @@ -317,13 +326,22 @@ export const WoopayExpressCheckoutButton = ( { } ) .then( ( response ) => { - iframe.contentWindow.postMessage( - { - action: 'setPreemptiveSessionData', - value: response, - }, - getConfig( 'woopayHost' ) - ); + if ( + response?.blog_id && + response?.data?.session + ) { + iframe.contentWindow.postMessage( + { + action: 'setPreemptiveSessionData', + value: response, + }, + getConfig( 'woopayHost' ) + ); + } else { + // Set button's default onClick handle to use modal checkout flow. + initWoopayRef.current = onClickFallback; + throw new Error( response?.data ); + } } ) ?.catch( () => { const errorMessage = __( diff --git a/includes/woopay/class-woopay-session.php b/includes/woopay/class-woopay-session.php index 42a5ec09c67..b36624163a5 100644 --- a/includes/woopay/class-woopay-session.php +++ b/includes/woopay/class-woopay-session.php @@ -543,6 +543,14 @@ public static function ajax_get_woopay_session() { ); } + $blog_id = Jetpack_Options::get_option('id'); + if ( empty( $blog_id ) ) { + wp_send_json_error( + __( 'Could not determine the blog ID.', 'woocommerce-payments' ), + 503 + ); + } + wp_send_json( self::get_frontend_init_session_request() ); } From e9e5be3a627103ec7be901861c17cbc12e963ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ismael=20Mart=C3=ADn=20Alabarce?= Date: Wed, 24 Jan 2024 11:42:33 +0100 Subject: [PATCH 07/52] Update confetti animation (#8083) Co-authored-by: Daniel Mallory --- changelog/update-confetti-animation | 5 ++ .../components/confetti-animation/index.tsx | 65 +++++++++++-------- 2 files changed, 44 insertions(+), 26 deletions(-) create mode 100644 changelog/update-confetti-animation diff --git a/changelog/update-confetti-animation b/changelog/update-confetti-animation new file mode 100644 index 00000000000..195e2ce1c4d --- /dev/null +++ b/changelog/update-confetti-animation @@ -0,0 +1,5 @@ +Significance: patch +Type: update +Comment: Confetti animation update. + + diff --git a/client/components/confetti-animation/index.tsx b/client/components/confetti-animation/index.tsx index 32c2d971112..9331f5b8e46 100644 --- a/client/components/confetti-animation/index.tsx +++ b/client/components/confetti-animation/index.tsx @@ -6,38 +6,51 @@ import confetti from 'canvas-confetti'; const defaultColors = [ '#889BF2', '#C3CDF9', '#6079ED' ]; +const randomInRange = ( min: number, max: number ): number => + Math.floor( Math.random() * ( max - min ) + min ); + +// Use a rectangle instead of an square on supported browsers. +const rectangle = + typeof Path2D === 'function' && typeof DOMMatrix === 'function' + ? confetti.shapeFromPath( { + path: 'M0,0 L2,0 L2,1 L0,1 Z', + } ) + : 'square'; + +// Adjust particle amount based on screen size. +const particleLength = ( window.innerWidth + window.innerHeight ) / 50; + const fireConfetti = ( colors: string[] ) => { const defaults = { - origin: { y: 0.3 }, spread: 360, + particleCount: 1, + startVelocity: 0, zIndex: 1000000, - colors, }; - const rectangle = confetti.shapeFromPath( { - path: 'M0,0 L2,0 L2,1 L0,1 Z', - } ); - - confetti( { - ...defaults, - particleCount: 20, - shapes: [ rectangle ], - scalar: 4, - startVelocity: 60, - } ); - confetti( { - ...defaults, - particleCount: 20, - shapes: [ rectangle ], - scalar: 2, - startVelocity: 40, - } ); - confetti( { - ...defaults, - particleCount: 40, - shapes: [ 'circle' ], - startVelocity: 20, - } ); + for ( let i = 0; i < particleLength; i++ ) { + confetti( { + ...defaults, + colors: [ colors[ randomInRange( 0, colors.length ) ] ], + origin: { + x: Math.random(), + y: Math.random() * 0.999 - 0.2, + }, + drift: randomInRange( -2, 2 ), + shapes: [ 'circle' ], + } ); + confetti( { + ...defaults, + colors: [ colors[ randomInRange( 0, colors.length ) ] ], + origin: { + x: Math.random(), + y: Math.random() * 0.999 - 0.2, + }, + shapes: [ rectangle ], + drift: randomInRange( -2, 2 ), + scalar: randomInRange( 2, 4 ), + } ); + } }; interface Props { From fbecab2f70351937023003858983a6a368748546 Mon Sep 17 00:00:00 2001 From: Rafael Zaleski Date: Wed, 24 Jan 2024 11:11:21 -0300 Subject: [PATCH 08/52] Add E2E tests for merchant on-boarding (#7955) Co-authored-by: Brian Borman <68524302+bborman22@users.noreply.github.com> Co-authored-by: Achyuth Ajoy --- .github/actions/e2e/run-log-tests/action.yml | 1 + changelog/e2e-7347-merchant-on-boarding | 4 + .../tasks/add-currencies-task/index.js | 9 +- .../test/__snapshots__/index.test.js.snap | 6 + .../enabled-currencies-list/modal-checkbox.js | 3 +- package.json | 2 +- .../config/jest-puppeteer-headless.config.js | 17 + ...t-admin-multi-currency-on-boarding.spec.js | 362 ++++++++++++++++++ ...erchant-admin-multi-currency-setup.spec.js | 1 + tests/e2e/utils/flows.js | 31 ++ tests/e2e/utils/helpers.js | 18 + 11 files changed, 450 insertions(+), 4 deletions(-) create mode 100644 changelog/e2e-7347-merchant-on-boarding create mode 100644 tests/e2e/config/jest-puppeteer-headless.config.js create mode 100644 tests/e2e/specs/wcpay/merchant/merchant-admin-multi-currency-on-boarding.spec.js diff --git a/.github/actions/e2e/run-log-tests/action.yml b/.github/actions/e2e/run-log-tests/action.yml index 71e8d883865..142e085d0fe 100644 --- a/.github/actions/e2e/run-log-tests/action.yml +++ b/.github/actions/e2e/run-log-tests/action.yml @@ -39,6 +39,7 @@ runs: with: name: wp(${{ env.E2E_WP_VERSION }})-wc(${{ env.E2E_WC_VERSION }})-${{ env.E2E_GROUP }}-${{ env.E2E_BRANCH }} path: | + screenshots tests/e2e/screenshots tests/e2e/docker/wordpress/wp-content/debug.log ${{ env.E2E_RESULT_FILEPATH }} diff --git a/changelog/e2e-7347-merchant-on-boarding b/changelog/e2e-7347-merchant-on-boarding new file mode 100644 index 00000000000..e1de02a0b10 --- /dev/null +++ b/changelog/e2e-7347-merchant-on-boarding @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +E2E test - Merchant facing multi-currency on-boarding screen. diff --git a/client/multi-currency-setup/tasks/add-currencies-task/index.js b/client/multi-currency-setup/tasks/add-currencies-task/index.js index 335b6b78985..1795edaa6a0 100644 --- a/client/multi-currency-setup/tasks/add-currencies-task/index.js +++ b/client/multi-currency-setup/tasks/add-currencies-task/index.js @@ -173,13 +173,14 @@ const AddCurrenciesTask = () => { ); } ); - const displayCurrencyCheckbox = ( code ) => + const displayCurrencyCheckbox = ( code, testId = '' ) => availableCurrencyCodes.length && ( ); @@ -284,7 +285,11 @@ const AddCurrenciesTask = () => { { visibleRecommendedCurrencyCodes.map( - displayCurrencyCheckbox + ( code ) => + displayCurrencyCheckbox( + code, + 'recommended-currency' + ) ) }
  • { const handleChange = useCallback( ( enabled ) => { @@ -20,7 +21,7 @@ const EnabledCurrenciesModalCheckbox = ( { ); return ( -
  • +
  • `.theme[data-slug="${ themeSlug }"]`; +const ACTIVATE_THEME_BUTTON_SELECTOR = ( themeSlug ) => + `${ THEME_SELECTOR( themeSlug ) } .button.activate`; +const MULTI_CURRENCY_TOGGLE_SELECTOR = "[data-testid='multi-currency-toggle']"; +const RECOMMENDED_CURRENCY_LIST_SELECTOR = + 'li[data-testid="recommended-currency"]'; +const CURRENCY_NOT_IN_RECOMMENDED_LIST_SELECTOR = + 'li.enabled-currency-checkbox:not([data-testid="recommended-currency"])'; +const ENABLED_CURRENCY_LIST_SELECTOR = 'li.enabled-currency-checkbox'; +const GEO_CURRENCY_SWITCH_CHECKBOX_SELECTOR = + 'input[data-testid="enable_auto_currency"]'; +const PREVIEW_STORE_BTN_SELECTOR = '.multi-currency-setup-preview-button'; +const PREVIEW_STORE_IFRAME_SELECTOR = + '.multi-currency-store-settings-preview-iframe'; +const SUBMIT_STEP_BTN_SELECTOR = + '.add-currencies-task.is-active .task-collapsible-body.is-active > button.is-primary'; +const STOREFRONT_SWITCH_CHECKBOX_SELECTOR = + 'input[data-testid="enable_storefront_switcher"]'; + +let wasMulticurrencyEnabled; + +const goToThemesPage = async () => { + await page.goto( `${ WP_ADMIN_DASHBOARD }themes.php`, { + waitUntil: 'networkidle0', + } ); +}; + +const activateTheme = async ( themeSlug ) => { + await goToThemesPage(); + + // Check if the theme is already active. + const isActive = await page.evaluate( ( selector ) => { + const themeElement = document.querySelector( selector ); + return themeElement && themeElement.classList.contains( 'active' ); + }, THEME_SELECTOR( themeSlug ) ); + + // Activate the theme if it's not already active. + if ( ! isActive ) { + await page.click( ACTIVATE_THEME_BUTTON_SELECTOR( themeSlug ) ); + await page.waitForNavigation( { waitUntil: 'networkidle0' } ); + } +}; + +const goToOnboardingPage = async () => { + await page.goto( + `${ WP_ADMIN_DASHBOARD }admin.php?page=wc-admin&path=%2Fpayments%2Fmulti-currency-setup`, + { + waitUntil: 'networkidle0', + } + ); + await uiLoaded(); +}; + +const goToNextOnboardingStep = async () => { + await page.click( SUBMIT_STEP_BTN_SELECTOR ); +}; + +describe( 'Merchant On-boarding', () => { + let activeThemeSlug; + + beforeAll( async () => { + await merchant.login(); + // Get initial multi-currency feature status. + await merchantWCP.openWCPSettings(); + await page.waitForSelector( MULTI_CURRENCY_TOGGLE_SELECTOR ); + wasMulticurrencyEnabled = await page.evaluate( ( selector ) => { + const checkbox = document.querySelector( selector ); + return checkbox ? checkbox.checked : false; + }, MULTI_CURRENCY_TOGGLE_SELECTOR ); + await merchantWCP.activateMulticurrency(); + + await goToThemesPage(); + + // Get current theme slug. + activeThemeSlug = await page.evaluate( () => { + const theme = document.querySelector( '.theme.active' ); + return theme ? theme.getAttribute( 'data-slug' ) : ''; + } ); + } ); + + afterAll( async () => { + // Restore original theme. + await activateTheme( activeThemeSlug ); + + // Disable multi-currency if it was not initially enabled. + if ( ! wasMulticurrencyEnabled ) { + await merchant.login(); + await merchantWCP.deactivateMulticurrency(); + } + await merchant.logout(); + } ); + + describe( 'Currency Selection and Management', () => { + beforeAll( async () => { + await merchantWCP.disableAllEnabledCurrencies(); + } ); + + beforeEach( async () => { + await goToOnboardingPage(); + } ); + + it( 'Should disable the submit button when no currencies are selected', async () => { + await takeScreenshot( 'merchant-on-boarding-multicurrency-screen' ); + await setCheckboxState( + `${ ENABLED_CURRENCY_LIST_SELECTOR } .components-checkbox-control__input`, + false + ); + + await page.waitFor( 1000 ); + + const button = await page.$( SUBMIT_STEP_BTN_SELECTOR ); + expect( button ).not.toBeNull(); + + const isDisabled = await page.evaluate( + ( btn ) => btn.disabled, + button + ); + + expect( isDisabled ).toBeTruthy(); + } ); + + it( 'Should allow multiple currencies to be selectable', async () => { + await page.waitForSelector( + CURRENCY_NOT_IN_RECOMMENDED_LIST_SELECTOR, + { + timeout: 3000, + } + ); + + // Ensure the checkbox within the list item is present and not disabled. + const checkbox = await page.$( + `${ CURRENCY_NOT_IN_RECOMMENDED_LIST_SELECTOR } input[type="checkbox"]` + ); + expect( checkbox ).not.toBeNull(); + const isDisabled = await ( + await checkbox.getProperty( 'disabled' ) + ).jsonValue(); + expect( isDisabled ).toBe( false ); + + // Click the checkbox to select the currency and verify it's checked. + await checkbox.click(); + + const isChecked = await ( + await checkbox.getProperty( 'checked' ) + ).jsonValue(); + expect( isChecked ).toBe( true ); + } ); + + it( 'Should exclude already enabled currencies from the currency screen', async () => { + await merchantWCP.addCurrency( 'GBP' ); + + await goToOnboardingPage(); + + await page.waitForSelector( ENABLED_CURRENCY_LIST_SELECTOR, { + timeout: 3000, + } ); + + // Get the list of currencies as text + const currencies = await page.$$eval( + ENABLED_CURRENCY_LIST_SELECTOR, + ( items ) => items.map( ( item ) => item.textContent.trim() ) + ); + + expect( currencies ).not.toContain( 'GBP' ); + + await merchantWCP.removeCurrency( 'GBP' ); + } ); + + it( 'Should display some suggested currencies at the beginning of the list', async () => { + await page.waitForSelector( RECOMMENDED_CURRENCY_LIST_SELECTOR, { + timeout: 3000, + } ); + + // Get the list of recommended currencies + const recommendedCurrencies = await page.$$eval( + RECOMMENDED_CURRENCY_LIST_SELECTOR, + ( items ) => + items.map( ( item ) => ( { + code: item + .querySelector( 'input' ) + .getAttribute( 'code' ), + name: item + .querySelector( + 'span.enabled-currency-checkbox__code' + ) + .textContent.trim(), + } ) ) + ); + + expect( recommendedCurrencies.length ).toBeGreaterThan( 0 ); + } ); + + it( 'Should ensure selected currencies are enabled after submitting the form', async () => { + const testCurrencies = [ 'GBP', 'EUR', 'CAD', 'AUD' ]; + const addCurrenciesContentSelector = + '.add-currencies-task__content'; + const currencyCheckboxSelector = `${ addCurrenciesContentSelector } li input[type="checkbox"]`; + + await page.waitForSelector( addCurrenciesContentSelector, { + timeout: 3000, + } ); + + // Select the currencies + for ( const currency of testCurrencies ) { + await setCheckboxState( + `${ currencyCheckboxSelector }[code="${ currency }"]`, + true + ); + } + + // Submit the form. + await goToNextOnboardingStep(); + + await merchantWCP.openMultiCurrency(); + + // Ensure the currencies are enabled. + for ( const currency of testCurrencies ) { + const selector = `li.enabled-currency.${ currency.toLowerCase() }`; + await page.waitForSelector( selector, { timeout: 10000 } ); + const element = await page.$( selector ); + + expect( element ).not.toBeNull(); + } + } ); + } ); + + describe( 'Geolocation Features', () => { + beforeAll( async () => { + await merchantWCP.disableAllEnabledCurrencies(); + } ); + + beforeEach( async () => { + await goToOnboardingPage(); + } ); + + it( 'Should offer currency switch by geolocation', async () => { + await goToNextOnboardingStep(); + + const checkbox = await page.$( + GEO_CURRENCY_SWITCH_CHECKBOX_SELECTOR + ); + + // Check if exists and not disabled. + expect( checkbox ).not.toBeNull(); + const isDisabled = await ( + await checkbox.getProperty( 'disabled' ) + ).jsonValue(); + expect( isDisabled ).toBe( false ); + + // Click the checkbox to select it. + await page.click( GEO_CURRENCY_SWITCH_CHECKBOX_SELECTOR ); + + // Check if the checkbox is selected. + const isChecked = await ( + await checkbox.getProperty( 'checked' ) + ).jsonValue(); + expect( isChecked ).toBe( true ); + } ); + + it( 'Should preview currency switch by geolocation correctly with USD and GBP', async () => { + page.setViewport( { width: 1280, height: 1280 } ); // To take a better screenshot of the iframe preview. + + await goToNextOnboardingStep(); + + await takeScreenshot( + 'merchant-on-boarding-multicurrency-screen-2' + ); + + // Enable feature. + await setCheckboxState( + GEO_CURRENCY_SWITCH_CHECKBOX_SELECTOR, + true + ); + + // Click preview button. + await page.click( PREVIEW_STORE_BTN_SELECTOR ); + + await page.waitForSelector( PREVIEW_STORE_IFRAME_SELECTOR, { + timeout: 3000, + } ); + + const iframeElement = await page.$( PREVIEW_STORE_IFRAME_SELECTOR ); + const iframe = await iframeElement.contentFrame(); + + await iframe.waitForSelector( '.woocommerce-store-notice', { + timeout: 3000, + } ); + + await takeScreenshot( + 'merchant-on-boarding-multicurrency-geolocation-switcher-preview' + ); + + const noticeText = await iframe.$eval( + '.woocommerce-store-notice', + ( element ) => element.innerText + ); + expect( noticeText ).toContain( + // eslint-disable-next-line max-len + "We noticed you're visiting from United Kingdom (UK). We've updated our prices to Pound sterling for your shopping convenience." + ); + } ); + } ); + + describe( 'Currency Switcher Widget', () => { + it( 'Should offer the currency switcher widget while Storefront theme is active', async () => { + await activateTheme( 'storefront' ); + + await goToOnboardingPage(); + await goToNextOnboardingStep(); + + const checkbox = await page.$( + STOREFRONT_SWITCH_CHECKBOX_SELECTOR + ); + + // Check if exists and not disabled. + expect( checkbox ).not.toBeNull(); + const isDisabled = await ( + await checkbox.getProperty( 'disabled' ) + ).jsonValue(); + expect( isDisabled ).toBe( false ); + + // Click the checkbox to select it. + await page.click( STOREFRONT_SWITCH_CHECKBOX_SELECTOR ); + + // Check if the checkbox is selected. + const isChecked = await ( + await checkbox.getProperty( 'checked' ) + ).jsonValue(); + expect( isChecked ).toBe( true ); + } ); + + it( 'Should not offer the currency switcher widget when an unsupported theme is active', async () => { + await activateTheme( 'twentytwentyfour' ); + + await goToOnboardingPage(); + await goToNextOnboardingStep(); + + const checkbox = await page.$( + STOREFRONT_SWITCH_CHECKBOX_SELECTOR + ); + + expect( checkbox ).toBeNull(); + + await activateTheme( 'storefront' ); + } ); + } ); +} ); diff --git a/tests/e2e/specs/wcpay/merchant/merchant-admin-multi-currency-setup.spec.js b/tests/e2e/specs/wcpay/merchant/merchant-admin-multi-currency-setup.spec.js index 51f7fc9cd67..800ce9d0e8b 100644 --- a/tests/e2e/specs/wcpay/merchant/merchant-admin-multi-currency-setup.spec.js +++ b/tests/e2e/specs/wcpay/merchant/merchant-admin-multi-currency-setup.spec.js @@ -75,6 +75,7 @@ describe( 'Merchant Multi-Currency Settings', () => { beforeAll( async () => { await merchantWCP.activateMulticurrency(); + await merchantWCP.disableAllEnabledCurrencies(); await shopperWCP.goToShopWithCurrency( 'USD' ); await shopperWCP.goToProductPageBySlug( 'beanie' ); diff --git a/tests/e2e/utils/flows.js b/tests/e2e/utils/flows.js index 3399d5b9ffe..af662b55fee 100644 --- a/tests/e2e/utils/flows.js +++ b/tests/e2e/utils/flows.js @@ -513,6 +513,11 @@ export const merchantWCP = { } }, currencyCode ); + await page.waitForSelector( + 'div.wcpay-confirmation-modal__footer button.components-button.is-primary', + { timeout: 3000 } + ); + await page.click( 'div.wcpay-confirmation-modal__footer button.components-button.is-primary', { text: 'Update selected' } @@ -532,6 +537,7 @@ export const merchantWCP = { }, removeCurrency: async ( currencyCode ) => { + await merchantWCP.openMultiCurrency(); const currencyItemSelector = `li.enabled-currency.${ currencyCode.toLowerCase() }`; await page.waitForSelector( currencyItemSelector, { timeout: 10000 } ); await page.click( @@ -732,6 +738,31 @@ export const merchantWCP = { return wasInitiallyEnabled; }, + disableAllEnabledCurrencies: async () => { + await page.goto( WCPAY_MULTI_CURRENCY, { waitUntil: 'networkidle0' } ); + + await page.waitForSelector( '.enabled-currencies-list li', { + timeout: 10000, + } ); + + // Select all delete buttons for enabled currencies. + const deleteButtons = await page.$$( + '.enabled-currency .enabled-currency__action.delete' + ); + + // Loop through each delete button and click it. + for ( const button of deleteButtons ) { + await button.click(); + + await page.waitForSelector( '.components-snackbar', { + text: 'Enabled currencies updated.', + timeout: 10000, + } ); + + await page.waitFor( 1000 ); + } + }, + editCurrency: async ( currencyCode ) => { await merchantWCP.openMultiCurrency(); diff --git a/tests/e2e/utils/helpers.js b/tests/e2e/utils/helpers.js index ca829481a27..2d9b0e0932b 100644 --- a/tests/e2e/utils/helpers.js +++ b/tests/e2e/utils/helpers.js @@ -80,3 +80,21 @@ export const getProductPriceFromProductPage = async () => { return price; }; + +/** + * Sets the state of all checkboxes matching the specified selector. + * + * @param {string} selector The selector to use to find checkboxes. + * @param {boolean} desiredState The desired state of the checkboxes. + */ +export const setCheckboxState = async ( selector, desiredState ) => { + const checkboxes = await page.$$( selector ); + for ( const checkbox of checkboxes ) { + const isChecked = await ( + await checkbox.getProperty( 'checked' ) + ).jsonValue(); + if ( isChecked !== desiredState ) { + await checkbox.click(); + } + } +}; From 44ab94f7f77f4eebac1f8dd61e30975e58c25ff3 Mon Sep 17 00:00:00 2001 From: Hector Lovo Date: Wed, 24 Jan 2024 18:36:14 -0500 Subject: [PATCH 09/52] Prevent Coupon Usage Increase in a Woopay Preflight Check (#8065) Co-authored-by: Hector Lovo <> --- .../fix-2309-woopay-preflight-coupon-usage | 4 ++ includes/class-wc-payment-gateway-wcpay.php | 4 +- ...-payment-gateway-wcpay-process-payment.php | 57 ++++++++++++++++++- 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 changelog/fix-2309-woopay-preflight-coupon-usage diff --git a/changelog/fix-2309-woopay-preflight-coupon-usage b/changelog/fix-2309-woopay-preflight-coupon-usage new file mode 100644 index 00000000000..44e56d952ec --- /dev/null +++ b/changelog/fix-2309-woopay-preflight-coupon-usage @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Prevent coupon usage increase in a WooPay preflight check. diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 0068a32e33f..e96b468c8ae 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -634,7 +634,7 @@ private function get_enabled_payment_method_config() { /** * If we're in a WooPay preflight check, remove all the checkout order processed - * actions to prevent reduce available resources quantity. + * actions to prevent a quantity reduction of the available resources. * * @param mixed $response The response object. * @param mixed $handler The handler used for the response. @@ -647,6 +647,8 @@ public function remove_all_actions_on_preflight_check( $response, $handler, $req if ( ! empty( $payment_data['is-woopay-preflight-check'] ) ) { remove_all_actions( 'woocommerce_store_api_checkout_update_order_meta' ); remove_all_actions( 'woocommerce_store_api_checkout_order_processed' ); + // Avoid increasing coupon usage count during preflight check. + remove_all_actions( 'woocommerce_order_status_pending' ); } return $response; diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php index b064ef08f90..a3038cc410c 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php @@ -702,7 +702,7 @@ public function test_failed_transaction_rate_limiter_is_limited() { } /** - * Tests that a draft order is updated to "pending" when the $_POST 'is-woopay-preflight-check` is present. + * Tests that a draft order is updated to "pending" when the $_POST 'is-woopay-preflight-check' is present. */ public function test_draft_order_is_set_to_pending_for_woopay_preflight_check_request() { $_POST['is-woopay-preflight-check'] = true; @@ -723,7 +723,60 @@ public function test_draft_order_is_set_to_pending_for_woopay_preflight_check_re } /** - * Tests that a success response and no redirect is returned when the $_POST 'is-woopay-preflight-check` is present. + * Tests that woocommerce_order_status_pending action is not called when the $_POST 'is-woopay-preflight-check' is present. + */ + public function test_woopay_preflight_request_does_not_call_woocommerce_order_status_pending() { + // Arrange: Add woocommerce_order_status_pending action to check if it's called. + $results = [ + 'has_called_woocommerce_order_status_pending' => false, + ]; + add_action( + 'woocommerce_order_status_pending', + function () use ( &$results ) { + $results['has_called_woocommerce_order_status_pending'] = true; + } + ); + + // Arrange: Add filter to change default order status to 'wc-checkout-draft'. + // Needed to avoid a default order status of 'pending'. + add_filter( + 'woocommerce_default_order_status', + function () { + return 'wc-checkout-draft'; + } + ); + + // Arrange: Create a request to simulate a woopay preflight request. + $_POST['is-woopay-preflight-check'] = true; + $request = new WP_REST_Request( 'POST', '' ); + $request->set_body_params( + [ + 'payment_data' => [ + [ + 'key' => 'is-woopay-preflight-check', + 'value' => true, + ], + ], + ] + ); + apply_filters( 'rest_request_before_callbacks', [], [], $request ); + + // Arrange: Create an order to test with. + $order_data = [ + 'status' => 'wc-checkout-draft', + 'total' => '100', + ]; + $order = wc_create_order( $order_data ); + + // Act: process payment. + $this->mock_wcpay_gateway->process_payment( $order->get_id() ); + + // Assert: woocommerce_order_status_pending was not called. + $this->assertFalse( $results['has_called_woocommerce_order_status_pending'] ); + } + + /** + * Tests that a success response and no redirect is returned when the $_POST 'is-woopay-preflight-check' is present. */ public function test_successful_result_no_redirect_for_woopay_preflight_check_request() { $_POST['is-woopay-preflight-check'] = true; From 3cae21525274f8340a92415ed36a6ce7148ec05b Mon Sep 17 00:00:00 2001 From: Rua Haszard Date: Thu, 25 Jan 2024 14:57:20 +1300 Subject: [PATCH 10/52] remove unnecessary tracks events for acceptDispute submit success/fail (#8068) Co-authored-by: Rua Haszard --- changelog/fix-remove-dispute-accept-log-tracks-events | 4 ++++ client/data/disputes/actions.js | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changelog/fix-remove-dispute-accept-log-tracks-events diff --git a/changelog/fix-remove-dispute-accept-log-tracks-events b/changelog/fix-remove-dispute-accept-log-tracks-events new file mode 100644 index 00000000000..e33d28ff524 --- /dev/null +++ b/changelog/fix-remove-dispute-accept-log-tracks-events @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Remove unnecessary tracks events for dispute accept success/error. diff --git a/client/data/disputes/actions.js b/client/data/disputes/actions.js index 3979390e3cf..e0618b24852 100644 --- a/client/data/disputes/actions.js +++ b/client/data/disputes/actions.js @@ -12,7 +12,6 @@ import { __, sprintf } from '@wordpress/i18n'; */ import { NAMESPACE, STORE_NAME } from '../constants'; import TYPES from './action-types'; -import wcpayTracks from 'tracks'; import { getPaymentIntent } from '../payment-intents/resolvers'; export function updateDispute( data ) { @@ -70,7 +69,6 @@ export function* acceptDispute( dispute ) { id, ] ); - wcpayTracks.recordEvent( 'wcpay_dispute_accept_success' ); const message = updatedDispute.order ? sprintf( /* translators: #%s is an order number, e.g. 15 */ @@ -91,7 +89,6 @@ export function* acceptDispute( dispute ) { 'There has been an error accepting the dispute. Please try again later.', 'woocommerce-payments' ); - wcpayTracks.recordEvent( 'wcpay_dispute_accept_failed' ); yield controls.dispatch( 'core/notices', 'createErrorNotice', message ); yield controls.dispatch( STORE_NAME, 'finishResolution', 'getDispute', [ id, From 0e7fda9c149c6b0fb066d78caee19becb8a8abeb Mon Sep 17 00:00:00 2001 From: Francesco Date: Thu, 25 Jan 2024 09:31:52 +0100 Subject: [PATCH 11/52] chore: remove unused customGatewayTitle (#8071) --- .../frosso-clean-unused-custom-gateway-title | 5 +++++ client/utils/checkout.js | 19 ------------------- 2 files changed, 5 insertions(+), 19 deletions(-) create mode 100644 changelog/frosso-clean-unused-custom-gateway-title diff --git a/changelog/frosso-clean-unused-custom-gateway-title b/changelog/frosso-clean-unused-custom-gateway-title new file mode 100644 index 00000000000..84a0e09048b --- /dev/null +++ b/changelog/frosso-clean-unused-custom-gateway-title @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: removed unused customGatewyTitle function. + + diff --git a/client/utils/checkout.js b/client/utils/checkout.js index 4f341cecd61..4a67666e827 100644 --- a/client/utils/checkout.js +++ b/client/utils/checkout.js @@ -37,22 +37,3 @@ export const getUPEConfig = ( name ) => { return config[ name ] || null; }; - -/** - * Forms dynamic gateway title for UPE checkout from enabled methods - * - * @param {Object} paymentMethodsConfig Object containing map of enabled UPE payment methods to settings. - * @return {string} Dynamic title string dependent on payment methods enabled. - */ -export const getCustomGatewayTitle = ( paymentMethodsConfig ) => { - const enabledPaymentMethods = Object.keys( paymentMethodsConfig ).sort(); - let label = ''; - - if ( enabledPaymentMethods.length < 2 ) { - label = paymentMethodsConfig[ enabledPaymentMethods[ 0 ] ].title; - } else { - label = getConfig( 'checkoutTitle' ); - } - - return label; -}; From ff86d88bb5f9f192a3eb3f4a7c71429eaa863ad9 Mon Sep 17 00:00:00 2001 From: Francesco Date: Thu, 25 Jan 2024 09:32:00 +0100 Subject: [PATCH 12/52] chore: remove unused checkout API functions (#8081) --- .../chore-remove-unused-checkout-api-methods | 4 ++ client/checkout/api/index.js | 45 ---------------- ...s-duplicate-payment-prevention-service.php | 10 ++-- includes/class-wc-payment-gateway-wcpay.php | 53 ------------------- includes/class-wc-payments-checkout.php | 3 -- ...s-duplicate-payment-prevention-service.php | 2 - 6 files changed, 8 insertions(+), 109 deletions(-) create mode 100644 changelog/chore-remove-unused-checkout-api-methods diff --git a/changelog/chore-remove-unused-checkout-api-methods b/changelog/chore-remove-unused-checkout-api-methods new file mode 100644 index 00000000000..bf7970a5bb0 --- /dev/null +++ b/changelog/chore-remove-unused-checkout-api-methods @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +chore: remove unused checkout API methods diff --git a/client/checkout/api/index.js b/client/checkout/api/index.js index 1c42111e6e0..e3eaac75346 100644 --- a/client/checkout/api/index.js +++ b/client/checkout/api/index.js @@ -565,49 +565,4 @@ export default class WCPayAPI { ...paymentData, } ); } - - /** - * Log Payment Errors via Ajax. - * - * @param {string} chargeId Stripe Charge ID - * @return {boolean} Returns true irrespective of result. - */ - logPaymentError( chargeId ) { - return this.request( - buildAjaxURL( getConfig( 'wcAjaxUrl' ), 'log_payment_error' ), - { - charge_id: chargeId, - _ajax_nonce: getConfig( 'logPaymentErrorNonce' ), - } - ).then( () => { - // There is not any action to take or harm caused by a failed update, so just returning true. - return true; - } ); - } - - /** - * Redirect to the order-received page for duplicate payments. - * - * @param {Object} response Response data to check if doing the redirect. - * @return {boolean} Returns true if doing the redirection. - */ - handleDuplicatePayments( { - wcpay_upe_paid_for_previous_order: previouslyPaid, - wcpay_upe_previous_successful_intent: previousSuccessfulIntent, - redirect, - } ) { - if ( redirect ) { - // Another order has the same cart content and was paid. - if ( previouslyPaid ) { - return ( window.location = redirect ); - } - - // Another intent has the equivalent successful status for the order. - if ( previousSuccessfulIntent ) { - return ( window.location = redirect ); - } - } - - return false; - } } diff --git a/includes/class-duplicate-payment-prevention-service.php b/includes/class-duplicate-payment-prevention-service.php index 71a0bfbcc10..35ac1896234 100644 --- a/includes/class-duplicate-payment-prevention-service.php +++ b/includes/class-duplicate-payment-prevention-service.php @@ -120,9 +120,8 @@ public function check_payment_intent_attached_to_order_succeeded( WC_Order $orde $return_url = $this->gateway->get_return_url( $order ); $return_url = add_query_arg( self::FLAG_PREVIOUS_SUCCESSFUL_INTENT, 'yes', $return_url ); return [ // nosemgrep: audit.php.wp.security.xss.query-arg -- https://woocommerce.github.io/code-reference/classes/WC-Payment-Gateway.html#method_get_return_url is passed in. - 'result' => 'success', - 'redirect' => $return_url, - 'wcpay_upe_previous_successful_intent' => 'yes', // This flag is needed for UPE flow. + 'result' => 'success', + 'redirect' => $return_url, ]; } @@ -178,9 +177,8 @@ public function check_against_session_processing_order( WC_Order $current_order $return_url = add_query_arg( self::FLAG_PREVIOUS_ORDER_PAID, 'yes', $return_url ); return [ // nosemgrep: audit.php.wp.security.xss.query-arg -- https://woocommerce.github.io/code-reference/classes/WC-Payment-Gateway.html#method_get_return_url is passed in. - 'result' => 'success', - 'redirect' => $return_url, - 'wcpay_upe_paid_for_previous_order' => 'yes', // This flag is needed for UPE flow. + 'result' => 'success', + 'redirect' => $return_url, ]; } diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index e96b468c8ae..8f7e09b09ac 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -899,59 +899,6 @@ public function output_payments_settings_screen() { endif; } - /** - * Log payment errors on Checkout. - * - * @throws Exception If nonce is not present or invalid or charge ID is empty or order not found. - */ - public function log_payment_error_ajax() { - try { - $is_nonce_valid = check_ajax_referer( 'wcpay_log_payment_error_nonce', false, false ); - if ( ! $is_nonce_valid ) { - throw new Exception( 'Invalid request.' ); - } - - $charge_id = isset( $_POST['charge_id'] ) ? wc_clean( wp_unslash( $_POST['charge_id'] ) ) : ''; - if ( empty( $charge_id ) ) { - throw new Exception( 'Charge ID cannot be empty.' ); - } - - // Get charge data from WCPay Server. - $request = Get_Charge::create( $charge_id ); - $request->set_hook_args( $charge_id ); - $charge_data = $request->send(); - $order_id = $charge_data['metadata']['order_id']; - - // Validate Order ID and proceed with logging errors and updating order status. - $order = wc_get_order( $order_id ); - if ( ! $order ) { - throw new Exception( 'Order not found. Unable to log error.' ); - } - - $intent_id = $charge_data['payment_intent'] ?? $order->get_meta( '_intent_id' ); - - $request = Get_Intention::create( $intent_id ); - $request->set_hook_args( $order ); - $intent = $request->send(); - - $intent_status = $intent->get_status(); - $error_message = esc_html( rtrim( $charge_data['failure_message'], '.' ) ); - - $this->order_service->mark_payment_failed( $order, $intent_id, $intent_status, $charge_id, $error_message ); - - wp_send_json_success(); - } catch ( Exception $e ) { - wp_send_json_error( - [ - 'error' => [ - 'message' => WC_Payments_Utils::get_filtered_error_message( $e ), - ], - ], - WC_Payments_Utils::get_filtered_error_status_code( $e ), - ); - } - } - /** * Displays the save to account checkbox. * diff --git a/includes/class-wc-payments-checkout.php b/includes/class-wc-payments-checkout.php index 5ba354b3810..f19f74bee7b 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -96,12 +96,10 @@ public function init_hooks() { add_action( 'wc_payments_set_gateway', [ $this, 'set_gateway' ] ); add_action( 'wc_payments_add_upe_payment_fields', [ $this, 'payment_fields' ] ); add_action( 'wp', [ $this->gateway, 'maybe_process_upe_redirect' ] ); - add_action( 'wc_ajax_wcpay_log_payment_error', [ $this->gateway, 'log_payment_error_ajax' ] ); add_action( 'wp_ajax_save_upe_appearance', [ $this->gateway, 'save_upe_appearance_ajax' ] ); add_action( 'wp_ajax_nopriv_save_upe_appearance', [ $this->gateway, 'save_upe_appearance_ajax' ] ); add_action( 'switch_theme', [ $this->gateway, 'clear_upe_appearance_transient' ] ); add_action( 'woocommerce_woocommerce_payments_updated', [ $this->gateway, 'clear_upe_appearance_transient' ] ); - add_action( 'wc_ajax_wcpay_log_payment_error', [ $this->gateway, 'log_payment_error_ajax' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts_for_zero_order_total' ], 11 ); @@ -179,7 +177,6 @@ public function get_payment_fields_js_config() { 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'wcAjaxUrl' => WC_AJAX::get_endpoint( '%%endpoint%%' ), 'createSetupIntentNonce' => wp_create_nonce( 'wcpay_create_setup_intent_nonce' ), - 'logPaymentErrorNonce' => wp_create_nonce( 'wcpay_log_payment_error_nonce' ), 'initWooPayNonce' => wp_create_nonce( 'wcpay_init_woopay_nonce' ), 'saveUPEAppearanceNonce' => wp_create_nonce( 'wcpay_save_upe_appearance_nonce' ), 'genericErrorMessage' => __( 'There was a problem processing the payment. Please check your email inbox and refresh the page to try again.', 'woocommerce-payments' ), diff --git a/tests/unit/test-class-duplicate-payment-prevention-service.php b/tests/unit/test-class-duplicate-payment-prevention-service.php index b0a3099b3b3..411f446cd40 100644 --- a/tests/unit/test-class-duplicate-payment-prevention-service.php +++ b/tests/unit/test-class-duplicate-payment-prevention-service.php @@ -77,7 +77,6 @@ public function test_check_session_order_redirect_to_previous_order() { $result = $this->service->check_against_session_processing_order( $current_order ); // Assert: the result of check_against_session_processing_order. - $this->assertSame( 'yes', $result['wcpay_upe_paid_for_previous_order'] ); $this->assertSame( 'success', $result['result'] ); $this->assertStringContainsString( $return_url, $result['redirect'] ); @@ -267,7 +266,6 @@ public function test_check_payment_intent_attached_to_order_succeeded_return_red $result = $this->service->check_payment_intent_attached_to_order_succeeded( $order ); // Assert: the result of check_intent_attached_to_order_succeeded. - $this->assertSame( 'yes', $result['wcpay_upe_previous_successful_intent'] ); $this->assertSame( 'success', $result['result'] ); $this->assertStringContainsString( $return_url, $result['redirect'] ); } From 6a6af30827f7531fe12bb627951f5ff747c15159 Mon Sep 17 00:00:00 2001 From: Francesco Date: Thu, 25 Jan 2024 09:32:14 +0100 Subject: [PATCH 13/52] chore: remove deprecated functions since 5.0.0 (#8073) --- changelog/chore-remove-deprecated-functions | 4 ++ includes/class-wc-payment-gateway-wcpay.php | 56 --------------------- 2 files changed, 4 insertions(+), 56 deletions(-) create mode 100644 changelog/chore-remove-deprecated-functions diff --git a/changelog/chore-remove-deprecated-functions b/changelog/chore-remove-deprecated-functions new file mode 100644 index 00000000000..fc0ec5ed243 --- /dev/null +++ b/changelog/chore-remove-deprecated-functions @@ -0,0 +1,4 @@ +Significance: major +Type: dev + +chore: removed deprecated functions since 5.0.0 diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 8f7e09b09ac..704e499b32b 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -619,19 +619,6 @@ private function validate_order_id_received_vs_intent_meta_order_id( WC_Order $o } } - /** - * Gets payment method settings to pass to client scripts - * - * @deprecated 5.0.0 - * - * @return array - */ - private function get_enabled_payment_method_config() { - wc_deprecated_function( __FUNCTION__, '5.0.0', 'WC_Payments_Checkout::get_enabled_payment_method_config' ); - return WC_Payments::get_wc_payments_checkout()->get_enabled_payment_method_config(); - } - - /** * If we're in a WooPay preflight check, remove all the checkout order processed * actions to prevent a quantity reduction of the available resources. @@ -3837,7 +3824,6 @@ public function get_selected_stripe_payment_type_id() { return $this->stripe_id; } - /** * Returns the list of enabled payment method types that will function with the current checkout. * @@ -4169,7 +4155,6 @@ private function get_payment_method_type_from_payment_details( $payment_method_d return $payment_method_details['type'] ?? null; } - /** * This function wraps WC_Payments::get_payment_method_map, useful for unit testing. * @@ -4296,47 +4281,6 @@ public function is_in_test_mode() { return WC_Payments::mode()->is_test(); } - /** - * Whether the current page is the WooPayments settings page. - * - * @deprecated 5.0.0 - * - * @return bool - */ - public static function is_current_page_settings() { - wc_deprecated_function( __FUNCTION__, '5.0.0', 'WC_Payments_Admin_Settings::is_current_page_settings' ); - return WC_Payments_Admin_Settings::is_current_page_settings(); - } - - /** - * Generates the configuration values, needed for payment fields. - * - * Isolated as a separate method in order to be available both - * during the classic checkout, as well as the checkout block. - * - * @deprecated use WC_Payments_Checkout::get_payment_fields_js_config instead. - * - * @return array - */ - public function get_payment_fields_js_config() { - wc_deprecated_function( __FUNCTION__, '5.0.0', 'WC_Payments_Checkout::get_payment_fields_js_config' ); - return WC_Payments::get_wc_payments_checkout()->get_payment_fields_js_config(); - } - - /** - * Prepares customer data to be used on 'Pay for Order' or 'Add Payment Method' pages. - * Customer data is retrieved from order when on Pay for Order. - * Customer data is retrieved from customer when on 'Add Payment Method'. - * - * @deprecated use WC_Payments_Customer_Service::get_prepared_customer_data() instead. - * - * @return array|null An array with customer data or nothing. - */ - public function get_prepared_customer_data() { - wc_deprecated_function( __FUNCTION__, '5.0.0', 'WC_Payments_Customer_Service::get_prepared_customer_data' ); - return WC_Payments::get_customer_service()->get_prepared_customer_data(); - } - // End: Deprecated functions. /** From 3e13b1d52e9d0be9212681d334317ed564977bbd Mon Sep 17 00:00:00 2001 From: Francesco Date: Thu, 25 Jan 2024 14:17:24 +0100 Subject: [PATCH 14/52] fix: pay-for-order compatibility with other gateways (#8089) --- .../fix-pay-for-order-compatibility-with-other-gateways | 4 ++++ client/checkout/classic/event-handlers.js | 8 ++++++++ 2 files changed, 12 insertions(+) create mode 100644 changelog/fix-pay-for-order-compatibility-with-other-gateways diff --git a/changelog/fix-pay-for-order-compatibility-with-other-gateways b/changelog/fix-pay-for-order-compatibility-with-other-gateways new file mode 100644 index 00000000000..94c79de9ce7 --- /dev/null +++ b/changelog/fix-pay-for-order-compatibility-with-other-gateways @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +fix: pay-for-order compatibility with other gateways diff --git a/client/checkout/classic/event-handlers.js b/client/checkout/classic/event-handlers.js index b6daa42875a..6e34fe52909 100644 --- a/client/checkout/classic/event-handlers.js +++ b/client/checkout/classic/event-handlers.js @@ -130,6 +130,14 @@ jQuery( function ( $ ) { } ); $payForOrderForm.on( 'submit', function () { + if ( + $payForOrderForm + .find( "input:checked[name='payment_method']" ) + .val() !== 'woocommerce_payments' + ) { + return; + } + return processPaymentIfNotUsingSavedMethod( $payForOrderForm ); } ); From e2a17324147a7be04eda8a8372f36c53906e2e94 Mon Sep 17 00:00:00 2001 From: Eric Jinks <3147296+Jinksi@users.noreply.github.com> Date: Fri, 26 Jan 2024 09:24:52 +1000 Subject: [PATCH 15/52] Update deposits REST API docs with decoupled deposit changes (#7848) Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com> --- ...te-deposits-api-docs-estimated-deposits-rm | 4 + .../source/includes/wp-api-v3/deposits.md | 105 +++--------------- 2 files changed, 21 insertions(+), 88 deletions(-) create mode 100644 changelog/fix-7847-update-deposits-api-docs-estimated-deposits-rm diff --git a/changelog/fix-7847-update-deposits-api-docs-estimated-deposits-rm b/changelog/fix-7847-update-deposits-api-docs-estimated-deposits-rm new file mode 100644 index 00000000000..cd0f0e04d8d --- /dev/null +++ b/changelog/fix-7847-update-deposits-api-docs-estimated-deposits-rm @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Update REST API documentation for deposits endpoints with changes to estimated and instant deposits diff --git a/docs/rest-api/source/includes/wp-api-v3/deposits.md b/docs/rest-api/source/includes/wp-api-v3/deposits.md index f075242ea5f..4d1d8a07607 100644 --- a/docs/rest-api/source/includes/wp-api-v3/deposits.md +++ b/docs/rest-api/source/includes/wp-api-v3/deposits.md @@ -26,7 +26,7 @@ The Deposits API endpoints provide access to an account's deposits data, includi - `date` _int_ - The arrival date of the deposit in unix timestamp milliseconds. - `type` _string_ - The type of deposit. `deposit` `withdrawal` - `amount` _int_ - The amount of the deposit. -- `status` _string_ - The status of the deposit. `paid` `pending` `in_transit` `canceled` `failed` `estimated` +- `status` _string_ - The status of the deposit. `paid` `pending` `in_transit` `canceled` `failed` - `bankAccount` _string_ - The bank account the deposit was/will be paid to. - `currency` _string_ - The currency of the deposit. E.g. `eur` - `automatic` _bool_ - Returns `true` if the payout is created by an automated schedule and `false` if it’s requested manually. @@ -51,14 +51,12 @@ Fetch an overview of account deposits for all deposit currencies. This includes - `deposit` _object_ - `last_paid` _array_ of [**Deposit**](#deposit-object) - The last deposit that has been paid for each deposit currency. - - `next_scheduled` _array_ of [**Deposit**](#deposit-object) - The next scheduled deposit for each deposit currency. - `last_manual_deposits` _array_ of [**Deposit**](#deposit-object) - Manual deposits that have been paid in the last 24 hours. - `balance` _object_ - `pending` _array_ - The pending balance for each deposit currency. - `amount` _int_ - The amount of the balance. - `currency` _string_ - The currency of the balance. E.g. `usd`. - `source_types` _object_ | _null_ - The amount of the balance from each source type, e.g. `{ "card": 12345 }` - - `deposits_count` _int_ - The number of deposits that make up the balance. - `available` _array_ - The available balance for each deposit currency. - `amount` _int_ - The amount of the balance. - `currency` _string_ - The currency of the balance. E.g. `usd`. @@ -69,7 +67,6 @@ Fetch an overview of account deposits for all deposit currencies. This includes - `fee` _int_ - The fee amount of the balance. - `fee_percentage` _int_ - The fee percentage of the balance. - `net` _int_ - The net amount of the balance. - - `transaction_ids` _array_ - The list of transaction IDs that make up the balance. - `account` _object_ - `deposits_enabled` _bool_ - Whether deposits are enabled for the account. - `deposits_blocked` _bool_ - Whether deposits are blocked for the account. @@ -118,34 +115,6 @@ curl -X GET https://example.com/wp-json/wc/v3/payments/deposits/overview-all \ "created": 1701302400 } ], - "next_scheduled": [ - { - "id": "wcpay_estimated_weekly_eur_1702598400", - "date": 1702598400000, - "type": "deposit", - "amount": 458784, - "status": "estimated", - "bankAccount": "STRIPE TEST BANK •••• 3000 (EUR)", - "currency": "eur", - "automatic": true, - "fee": 0, - "fee_percentage": 0, - "created": 1702598400 - }, - { - "id": "wcpay_estimated_weekly_usd_1701993600", - "date": 1701993600000, - "type": "deposit", - "amount": 823789, - "status": "estimated", - "bankAccount": "STRIPE TEST BANK •••• 6789 (USD)", - "currency": "usd", - "automatic": true, - "fee": 0, - "fee_percentage": 0, - "created": 1701993600 - } - ], "last_manual_deposits": [] }, "balance": { @@ -155,16 +124,14 @@ curl -X GET https://example.com/wp-json/wc/v3/payments/deposits/overview-all \ "currency": "eur", "source_types": { "card": -114696 - }, - "deposits_count": 1 + } }, { "amount": 707676, "currency": "usd", "source_types": { "card": 707676 - }, - "deposits_count": 2 + } } ], "available": [ @@ -189,11 +156,7 @@ curl -X GET https://example.com/wp-json/wc/v3/payments/deposits/overview-all \ "currency": "usd", "fee": 185, "fee_percentage": 1.5, - "net": 0, - "transaction_ids": [ - "txn_3OHyIxCIHGKp1UAi0aVyDQ5D", - "txn_3OJSuOCIHGKp1UAi1mRA2lL5" - ] + "net": 0 } ] }, @@ -226,13 +189,11 @@ Fetch an overview of account deposits for a single deposit currency. This includ ### Returns - `last_deposit` _object_ [**Deposit**](#deposit-object) | _null_- The last deposit that has been paid for the deposit currency. -- `next_deposit` _object_ [**Deposit**](#deposit-object) | _null_ - The next scheduled deposit for the deposit currency. - `balance` _object_ - `pending` _object_ - The pending balance for the deposit currency. - `amount` _int_ - The amount of the balance. - `currency` _string_ - The currency of the balance. E.g. `usd`. - `source_types` _object_ | _null_ - The amount of the balance from each source type, e.g. `{ "card": 12345 }` - - `deposits_count` _int_ - The number of deposits that make up the balance. - `available` _object_ - The available balance for the deposit currency. - `amount` _int_ - The amount of the balance. - `currency` _string_ - The currency of the balance. E.g. `usd`. @@ -243,7 +204,6 @@ Fetch an overview of account deposits for a single deposit currency. This includ - `fee` _int_ - The fee amount of the balance. - `fee_percentage` _int_ - The fee percentage of the balance. - `net` _int_ - The net amount of the balance. - - `transaction_ids` _array_ - The list of transaction IDs that make up the balance. - `account` _object_ - `deposits_disabled` _bool_ - Whether deposits are enabled for the account. - `deposits_blocked` _bool_ - Whether deposits are blocked for the account. @@ -276,19 +236,6 @@ curl -X GET https://example.com/wp-json/wc/v3/payments/deposits/overview \ "fee_percentage": 0, "created": 1701648000 }, - "next_deposit": { - "id": "wcpay_estimated_weekly_eur_1702598400", - "date": 1702598400000, - "type": "deposit", - "amount": 458784, - "status": "estimated", - "bankAccount": "STRIPE TEST BANK •••• 3000 (EUR)", - "currency": "eur", - "automatic": true, - "fee": 0, - "fee_percentage": 0, - "created": 1702598400 - }, "balance": { "available": { "amount": 573480, @@ -302,8 +249,7 @@ curl -X GET https://example.com/wp-json/wc/v3/payments/deposits/overview \ "currency": "eur", "source_types": { "card": -114696 - }, - "deposits_count": 1 + } } }, "instant_balance": { @@ -311,8 +257,7 @@ curl -X GET https://example.com/wp-json/wc/v3/payments/deposits/overview \ "currency": "usd", "fee": 0, "fee_percentage": 1.5, - "net": 0, - "transaction_ids": [] + "net": 0 }, "account": { "deposits_disabled": false, @@ -351,8 +296,8 @@ Fetch a list of deposits. - `date_before` _string_ - `date_after` _string_ - `date_between` _array_ -- `status_is` _string_ `paid` `pending` `in_transit` `canceled` `failed` `estimated` -- `status_is_not` _string_ `paid` `pending` `in_transit` `canceled` `failed` `estimated` +- `status_is` _string_ `paid` `pending` `in_transit` `canceled` `failed` +- `status_is_not` _string_ `paid` `pending` `in_transit` `canceled` `failed` - `direction` _string_ - `page` _integer_ - `pagesize` _integer_ @@ -372,19 +317,6 @@ curl -X GET https://example.com/wp-json/wc/v3/payments/deposits?sort=date \ ```json { "data": [ - { - "id": "wcpay_estimated_weekly_eur_1702598400", - "date": 1702598400000, - "type": "deposit", - "amount": 458784, - "status": "estimated", - "bankAccount": "STRIPE TEST BANK •••• 3000 (EUR)", - "currency": "eur", - "automatic": true, - "fee": 0, - "fee_percentage": 0, - "created": 1702598400 - }, { "id": "po_1OJ466CBu6Jj8nBr38JRxdNE", "date": 1701648000000, @@ -412,7 +344,7 @@ curl -X GET https://example.com/wp-json/wc/v3/payments/deposits?sort=date \ "created": 1701302400 } ], - "total_count": 3 + "total_count": 2 } ``` @@ -438,8 +370,8 @@ Useful in combination with the **List deposits** endpoint to get a summary of de - `date_before` _string_ - `date_after` _string_ - `date_between` _array_ -- `status_is` _string_ - `paid` `pending` `in_transit` `canceled` `failed` `estimated` -- `status_is_not` _string_ - `paid` `pending` `in_transit` `canceled` `failed` `estimated` +- `status_is` _string_ - `paid` `pending` `in_transit` `canceled` `failed` +- `status_is_not` _string_ - `paid` `pending` `in_transit` `canceled` `failed` ### Returns @@ -481,7 +413,7 @@ Fetches a deposit by ID. If a deposit is found for the provided ID, the response will return a [**Deposit**](#deposit-object) object. -If no deposit is found for the provided ID, the response will be an empty array. +If no deposit is found for the provided ID, the response will return a `500` status code. ```shell curl -X GET https://example.com/wp-json/wc/v3/payments/deposits/po_123abc \ @@ -522,17 +454,14 @@ Submit an instant deposit for a list of transactions. Only for eligible accounts ### Required body properties - `type`: _string_ - The type of deposit. `instant` -- `transaction_ids`: _array_ - The list of transaction IDs to deposit. +- `currency`: _string_ - The currency of the balance to deposit. E.g. `usd` ```shell curl -X POST 'https://example.com/wp-json/wc/v3/payments/deposits' \ -u consumer_key:consumer_secret --data '{ "type": "instant", - "transaction_ids": [ - "txn_3OHyIxCIHGKp1UAi0aVyDQ5D", - "txn_3OJSuOCIHGKp1UAi1mRA2lL5" - ] + "currency": "usd" }' ``` @@ -560,8 +489,8 @@ Request a CSV export of deposits matching the query. A link to the exported CSV - `date_before` _string_ - `date_after` _string_ - `date_between` _array_ -- `status_is` _string_ - `paid` `pending` `in_transit` `canceled` `failed` `estimated` -- `status_is_not` _string_ - `paid` `pending` `in_transit` `canceled` `failed` `estimated` +- `status_is` _string_ - `paid` `pending` `in_transit` `canceled` `failed` +- `status_is_not` _string_ - `paid` `pending` `in_transit` `canceled` `failed` ### Returns @@ -571,7 +500,7 @@ Request a CSV export of deposits matching the query. A link to the exported CSV curl -X POST 'https://example.com/wp-json/wc/v3/payments/deposits/download?status_is=paid' \ -u consumer_key:consumer_secret --data '{ - "user_email": "name@example.woo.com", + "user_email": "name@example.woo.com" }' ``` From 6f0f602e4b16cf1517d2cbd8aef6c81aac57207d Mon Sep 17 00:00:00 2001 From: Eric Jinks <3147296+Jinksi@users.noreply.github.com> Date: Fri, 26 Jan 2024 09:28:32 +1000 Subject: [PATCH 16/52] Remove redundant estimated deposit code (#8057) Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com> Co-authored-by: Rua Haszard --- ...0-remove-redundant-estimated-deposits-code | 5 ++++ .../account-balances/test/index.test.tsx | 1 - client/components/deposits-overview/hooks.ts | 1 - .../deposits-overview/test/index.tsx | 27 +++-------------- client/data/deposits/hooks.ts | 12 -------- client/data/deposits/selectors.js | 2 -- .../data/deposits/test/overviews.fixture.json | 29 ------------------- client/data/deposits/test/reducer.js | 1 - client/data/deposits/test/resolvers.js | 7 ++--- client/data/deposits/test/selectors.js | 3 -- client/payment-details/timeline/map-events.js | 4 +-- client/transactions/list/deposit.tsx | 6 +--- client/types/account-overview.d.ts | 2 -- .../captured-payments/foreign-card.json | 5 +--- .../captured-payments/fx-decimal.json | 5 +--- .../captured-payments/fx-foreign-card.json | 5 +--- .../captured-payments/fx-with-capped-fee.json | 5 +--- tests/fixtures/captured-payments/fx.json | 5 +--- .../captured-payments/only-base-fee.json | 5 +--- .../captured-payments/subscription.json | 5 +--- ...yments-reports-transactions-controller.php | 16 +++++----- 21 files changed, 30 insertions(+), 121 deletions(-) create mode 100644 changelog/fix-7850-remove-redundant-estimated-deposits-code diff --git a/changelog/fix-7850-remove-redundant-estimated-deposits-code b/changelog/fix-7850-remove-redundant-estimated-deposits-code new file mode 100644 index 00000000000..85a82c16321 --- /dev/null +++ b/changelog/fix-7850-remove-redundant-estimated-deposits-code @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Not user-facing: remove redundant code related to decoupling of transactions from deposits + + diff --git a/client/components/account-balances/test/index.test.tsx b/client/components/account-balances/test/index.test.tsx index a491b244747..a3bfa4ec9bd 100644 --- a/client/components/account-balances/test/index.test.tsx +++ b/client/components/account-balances/test/index.test.tsx @@ -138,7 +138,6 @@ const createMockOverview = ( fee_percentage: 0, status: 'paid', }, - nextScheduled: undefined, instant: { currency: currencyCode, amount: instantAmount, diff --git a/client/components/deposits-overview/hooks.ts b/client/components/deposits-overview/hooks.ts index 7dfb5a5a5ac..34c1ea75fee 100644 --- a/client/components/deposits-overview/hooks.ts +++ b/client/components/deposits-overview/hooks.ts @@ -11,7 +11,6 @@ interface RecentDeposits { const useRecentDeposits = ( currency?: string ): RecentDeposits => { const query = { - status_is_not: 'estimated', store_currency_is: currency, orderby: 'date', order: 'desc', diff --git a/client/components/deposits-overview/test/index.tsx b/client/components/deposits-overview/test/index.tsx index 493f01223c5..87cbf56552b 100644 --- a/client/components/deposits-overview/test/index.tsx +++ b/client/components/deposits-overview/test/index.tsx @@ -20,7 +20,7 @@ import { useDeposits, useAllDepositsOverviews, } from 'wcpay/data'; -import type { CachedDeposit, DepositStatus } from 'wcpay/types/deposits'; +import type { CachedDeposit } from 'wcpay/types/deposits'; import type * as AccountOverview from 'wcpay/types/account-overview'; jest.mock( 'wcpay/data', () => ( { @@ -87,10 +87,7 @@ const mockDeposits = [ // Creates a mock Overview object for the given currency code and balance amounts. const createMockOverview = ( - currencyCode: string, - depositAmount: number, - depositDate: number, - depositStatus: DepositStatus + currencyCode: string ): AccountOverview.Overview => { return { currency: currencyCode, @@ -117,19 +114,6 @@ const createMockOverview = ( fee_percentage: 0, status: 'paid', }, - nextScheduled: { - id: '456', - type: 'deposit', - amount: depositAmount, - automatic: true, - currency: currencyCode, - bankAccount: null, - created: Date.now(), - date: depositDate, - fee: 0, - fee_percentage: 0, - status: depositStatus, - }, instant: { currency: currencyCode, amount: 0, @@ -158,7 +142,6 @@ const createMockNewAccountOverview = ( source_types: [], }, lastPaid: undefined, - nextScheduled: undefined, instant: undefined, }; }; @@ -249,7 +232,7 @@ describe( 'Deposits Overview information', () => { } ); test( 'Component Renders', () => { - mockOverviews( [ createMockOverview( 'usd', 100, 0, 'pending' ) ] ); + mockOverviews( [ createMockOverview( 'usd' ) ] ); mockUseDeposits.mockReturnValue( { depositsCount: 0, deposits: mockDeposits, @@ -310,9 +293,7 @@ describe( 'Deposits Overview information', () => { test( 'Confirm notice renders if deposits blocked', () => { mockAccount.deposits_blocked = true; - mockOverviews( [ - createMockOverview( 'usd', 30000, 50000, 'pending' ), - ] ); + mockOverviews( [ createMockOverview( 'usd' ) ] ); mockUseDeposits.mockReturnValue( { depositsCount: 0, deposits: mockDeposits, diff --git a/client/data/deposits/hooks.ts b/client/data/deposits/hooks.ts index 7a6178050bf..3b6c25aa910 100644 --- a/client/data/deposits/hooks.ts +++ b/client/data/deposits/hooks.ts @@ -129,12 +129,6 @@ export const useDeposits = ( { status_is: statusIs, status_is_not: statusIsNot, }: Query ): CachedDeposits => { - // Temporarily default to excluding estimated deposits. - // Client components can (temporarily) opt-in by passing `status_is=estimated`. - // When we remove estimated deposits from server / APIs we can remove this default. - if ( ! statusIsNot && statusIs !== 'estimated' ) { - statusIsNot = 'estimated'; - } return useSelect( ( select ) => { const { @@ -197,12 +191,6 @@ export const useDepositsSummary = ( { status_is: statusIs, status_is_not: statusIsNot, }: Query ): DepositsSummaryCache => { - // Temporarily default to excluding estimated deposits. - // Client components can (temporarily) opt-in by passing `status_is=estimated`. - // When we remove estimated deposits from server / APIs we can remove this default. - if ( ! statusIsNot && statusIs !== 'estimated' ) { - statusIsNot = 'estimated'; - } return useSelect( ( select ) => { const { getDepositsSummary, isResolving } = select( STORE_NAME ); diff --git a/client/data/deposits/selectors.js b/client/data/deposits/selectors.js index 68b99935577..8c263237b19 100644 --- a/client/data/deposits/selectors.js +++ b/client/data/deposits/selectors.js @@ -67,7 +67,6 @@ export const getAllDepositsOverviews = ( state ) => { const groups = { lastPaid: deposit.last_paid, - nextScheduled: deposit.next_scheduled, pending: balance.pending, available: balance.available, instant: balance.instant, @@ -86,7 +85,6 @@ export const getAllDepositsOverviews = ( state ) => { currencies[ currency ] = { currency, lastPaid: undefined, - nextScheduled: undefined, pending: undefined, available: undefined, instant: undefined, diff --git a/client/data/deposits/test/overviews.fixture.json b/client/data/deposits/test/overviews.fixture.json index 775953f30cb..a9a8b25d387 100644 --- a/client/data/deposits/test/overviews.fixture.json +++ b/client/data/deposits/test/overviews.fixture.json @@ -27,34 +27,6 @@ "fee_percentage": 0, "created": 1619395200 } - ], - "next_scheduled": [ - { - "id": "wcpay_estimated_weekly_eur_1622678400", - "date": 1622678400000, - "type": "deposit", - "amount": 3343, - "status": "estimated", - "bankAccount": null, - "currency": "eur", - "automatic": true, - "fee": 0, - "fee_percentage": 0, - "created": 1622678400 - }, - { - "id": "wcpay_estimated_weekly_usd_1622678400", - "date": 1622678400000, - "type": "deposit", - "amount": 1656, - "status": "estimated", - "bankAccount": null, - "currency": "usd", - "automatic": true, - "fee": 0, - "fee_percentage": 0, - "created": 1622678400 - } ] }, "balance": { @@ -69,7 +41,6 @@ { "amount": 1656, "currency": "usd", - "deposits_count": 2, "source_types": { "card": 1656 } diff --git a/client/data/deposits/test/reducer.js b/client/data/deposits/test/reducer.js index ae7d5dec714..648843c71e4 100644 --- a/client/data/deposits/test/reducer.js +++ b/client/data/deposits/test/reducer.js @@ -26,7 +26,6 @@ describe( 'Deposits reducer tests', () => { }; const mockOverview = { last_deposit: mockDeposits[ 0 ], - next_deposit: mockDeposits[ 1 ], balance: { object: 'balance' }, deposits_schedule: { interval: 'daily' }, }; diff --git a/client/data/deposits/test/resolvers.js b/client/data/deposits/test/resolvers.js index 1bc2666428c..0d4dd1ea27a 100644 --- a/client/data/deposits/test/resolvers.js +++ b/client/data/deposits/test/resolvers.js @@ -59,14 +59,13 @@ const filterQuery = { dateAfter: '2020-04-29 23:59:59', dateBetween: [ '2020-04-28 00:00:00', '2020-04-29 23:59:59' ], statusIs: 'paid', - statusIsNot: 'estimated', + statusIsNot: 'failed', storeCurrencyIs: 'gbp', }; describe( 'getDepositsOverview resolver', () => { const successfulResponse = { last_deposit: depositsResponse.data[ 0 ], - next_deposit: depositsResponse.data[ 1 ], balance: { pending: { amount: 5500 }, available: { amount: 0 } }, deposits_schedule: { interval: 'daily' }, }; @@ -149,7 +148,7 @@ describe( 'getDeposits resolver', () => { const expectedQueryString = // eslint-disable-next-line max-len - 'page=1&pagesize=25&match=all&store_currency_is=gbp&date_before=2020-04-29%2003%3A59%3A59&date_after=2020-04-29%2004%3A00%3A00&date_between%5B0%5D=2020-04-28%2004%3A00%3A00&date_between%5B1%5D=2020-04-30%2003%3A59%3A59&status_is=paid&status_is_not=estimated'; + 'page=1&pagesize=25&match=all&store_currency_is=gbp&date_before=2020-04-29%2003%3A59%3A59&date_after=2020-04-29%2004%3A00%3A00&date_between%5B0%5D=2020-04-28%2004%3A00%3A00&date_between%5B1%5D=2020-04-30%2003%3A59%3A59&status_is=paid&status_is_not=failed'; beforeEach( () => { generator = getDeposits( query ); @@ -208,7 +207,7 @@ describe( 'getDepositsSummary resolver', () => { const query = filterQuery; const expectedQueryString = // eslint-disable-next-line max-len - 'match=all&store_currency_is=gbp&date_before=2020-04-29%2003%3A59%3A59&date_after=2020-04-29%2004%3A00%3A00&date_between%5B0%5D=2020-04-28%2004%3A00%3A00&date_between%5B1%5D=2020-04-30%2003%3A59%3A59&status_is=paid&status_is_not=estimated'; + 'match=all&store_currency_is=gbp&date_before=2020-04-29%2003%3A59%3A59&date_after=2020-04-29%2004%3A00%3A00&date_between%5B0%5D=2020-04-28%2004%3A00%3A00&date_between%5B1%5D=2020-04-30%2003%3A59%3A59&status_is=paid&status_is_not=failed'; let generator = null; beforeEach( () => { diff --git a/client/data/deposits/test/selectors.js b/client/data/deposits/test/selectors.js index cd8613a0758..f2d1646065d 100644 --- a/client/data/deposits/test/selectors.js +++ b/client/data/deposits/test/selectors.js @@ -126,7 +126,6 @@ describe( 'Deposits overview selectors', () => { overview: { data: { last_deposit: null, - next_deposit: null, balance: { object: 'balance' }, deposits_schedule: { interval: 'daily' }, }, @@ -221,13 +220,11 @@ describe( 'Deposits overviews selectors', () => { // Check the grouping checkResult( first.lastPaid, 'deposit.last_paid', first ); - checkResult( first.nextScheduled, 'deposit.next_scheduled', first ); checkResult( first.pending, 'balance.pending', first ); checkResult( first.available, 'balance.available', first ); checkResult( first.instant, 'balance.instant', first ); checkResult( second.lastPaid, 'deposit.last_paid', second ); - checkResult( second.nextScheduled, 'deposit.next_scheduled', second ); checkResult( second.pending, 'balance.pending', second ); checkResult( second.available, 'balance.available', second ); checkResult( second.instant, 'balance.instant', second ); diff --git a/client/payment-details/timeline/map-events.js b/client/payment-details/timeline/map-events.js index 49b74106f6d..dca250daff4 100644 --- a/client/payment-details/timeline/map-events.js +++ b/client/payment-details/timeline/map-events.js @@ -70,7 +70,7 @@ const getDepositTimelineItem = ( body = [] ) => { let headline = ''; - if ( event.deposit && ! event.deposit.id.includes( 'wcpay_estimated_' ) ) { + if ( event.deposit ) { headline = sprintf( isPositive ? // translators: %1$s - formatted amount, %2$s - deposit arrival date, - link to the deposit @@ -135,7 +135,7 @@ const getDepositTimelineItem = ( */ const getFinancingPaydownTimelineItem = ( event, formattedAmount, body ) => { let headline = ''; - if ( event.deposit && ! event.deposit.id.includes( 'wcpay_estimated_' ) ) { + if ( event.deposit ) { headline = sprintf( // translators: %1$s - formatted amount, %2$s - deposit arrival date, - link to the deposit __( diff --git a/client/transactions/list/deposit.tsx b/client/transactions/list/deposit.tsx index 09ceb108ca9..588f8f84142 100644 --- a/client/transactions/list/deposit.tsx +++ b/client/transactions/list/deposit.tsx @@ -24,11 +24,7 @@ interface DepositProps { } const Deposit: React.FC< DepositProps > = ( { depositId, dateAvailable } ) => { - if ( - depositId && - dateAvailable && - ! depositId.includes( 'wcpay_estimated_' ) - ) { + if ( depositId && dateAvailable ) { const depositUrl = getAdminUrl( { page: 'wc-admin', path: '/payments/deposits/details', diff --git a/client/types/account-overview.d.ts b/client/types/account-overview.d.ts index 7b02ef2a9c2..0deeab816a8 100644 --- a/client/types/account-overview.d.ts +++ b/client/types/account-overview.d.ts @@ -30,7 +30,6 @@ export interface Account { export interface Balance { amount: number; currency: string; - deposits_count?: number; source_types: Record< string, never >[]; } @@ -59,7 +58,6 @@ export interface InstantBalance { export interface Overview { currency: string; lastPaid: Deposit | undefined; - nextScheduled: Deposit | undefined; pending: Balance | undefined; available: Balance | undefined; instant: InstantBalance | undefined; diff --git a/tests/fixtures/captured-payments/foreign-card.json b/tests/fixtures/captured-payments/foreign-card.json index 9a121e3a7ed..50dc975b029 100644 --- a/tests/fixtures/captured-payments/foreign-card.json +++ b/tests/fixtures/captured-payments/foreign-card.json @@ -33,10 +33,7 @@ }, "currency": "CAD", "datetime": 1651997332, - "deposit": { - "id": "wcpay_estimated_daily_usd_1652572800", - "arrival_date": "1652572800" - }, + "deposit": null, "transaction_id": "txn_3Kx5Ae2EFxam75ai0P2BCbp0", "transaction_details": { "customer_currency": "CAD", diff --git a/tests/fixtures/captured-payments/fx-decimal.json b/tests/fixtures/captured-payments/fx-decimal.json index 77a136924f8..52d02537e81 100644 --- a/tests/fixtures/captured-payments/fx-decimal.json +++ b/tests/fixtures/captured-payments/fx-decimal.json @@ -26,10 +26,7 @@ }, "currency": "EUR", "datetime": 1651215495, - "deposit": { - "id": "wcpay_estimated_daily_usd_1651795200", - "arrival_date": "1651795200" - }, + "deposit": null, "transaction_id": "txn_3Ktnm22EFxam75ai0Sr9SR5A", "transaction_details": { "customer_currency": "EUR", diff --git a/tests/fixtures/captured-payments/fx-foreign-card.json b/tests/fixtures/captured-payments/fx-foreign-card.json index 9b26ad48fc1..846353f7e24 100644 --- a/tests/fixtures/captured-payments/fx-foreign-card.json +++ b/tests/fixtures/captured-payments/fx-foreign-card.json @@ -27,10 +27,7 @@ }, "currency": "USD", "datetime": 1651996460, - "deposit": { - "id": "wcpay_estimated_daily_usd_1652572800", - "arrival_date": "1652572800" - }, + "deposit": null, "transaction_id": "txn_3Kx4w12EFxam75ai0W5q4669", "transaction_details": { "customer_currency": "USD", diff --git a/tests/fixtures/captured-payments/fx-with-capped-fee.json b/tests/fixtures/captured-payments/fx-with-capped-fee.json index 23c3cbf1387..7d3c354ac95 100644 --- a/tests/fixtures/captured-payments/fx-with-capped-fee.json +++ b/tests/fixtures/captured-payments/fx-with-capped-fee.json @@ -35,10 +35,7 @@ }, "currency": "EUR", "datetime": 1651998676, - "deposit": { - "id": "wcpay_estimated_weekly_usd_1652659200", - "arrival_date": "1652659200" - }, + "deposit": null, "transaction_id": "txn_3Kx5WG2HDHuit9Eg0ZrUH90z", "transaction_details": { "customer_currency": "EUR", diff --git a/tests/fixtures/captured-payments/fx.json b/tests/fixtures/captured-payments/fx.json index 166deab4a86..7a4c1186d1e 100644 --- a/tests/fixtures/captured-payments/fx.json +++ b/tests/fixtures/captured-payments/fx.json @@ -27,10 +27,7 @@ }, "currency": "VND", "datetime": 1651215552, - "deposit": { - "id": "wcpay_estimated_daily_usd_1651795200", - "arrival_date": "1651795200" - }, + "deposit": null, "transaction_id": "txn_3KtnnI2EFxam75ai06EEZYXQ", "transaction_details": { "customer_currency": "VND", diff --git a/tests/fixtures/captured-payments/only-base-fee.json b/tests/fixtures/captured-payments/only-base-fee.json index 0385bd61c6f..a84920fd3b3 100644 --- a/tests/fixtures/captured-payments/only-base-fee.json +++ b/tests/fixtures/captured-payments/only-base-fee.json @@ -19,10 +19,7 @@ }, "currency": "USD", "datetime": 1651997740, - "deposit": { - "id": "wcpay_estimated_daily_usd_1652572800", - "arrival_date": "1652572800" - }, + "deposit": null, "transaction_id": "txn_3Kx5HD2EFxam75ai1FerGksO", "transaction_details": { "customer_currency": "USD", diff --git a/tests/fixtures/captured-payments/subscription.json b/tests/fixtures/captured-payments/subscription.json index a9a4d01eae4..bf58a36666f 100644 --- a/tests/fixtures/captured-payments/subscription.json +++ b/tests/fixtures/captured-payments/subscription.json @@ -34,10 +34,7 @@ }, "currency": "EUR", "datetime": 1651999249, - "deposit": { - "id": "wcpay_estimated_weekly_usd_1652659200", - "arrival_date": "1652659200" - }, + "deposit": null, "transaction_id": "txn_3Kx5fY2HDHuit9Eg0fQ402Ee", "transaction_details": { "customer_currency": "EUR", diff --git a/tests/unit/reports/test-class-wc-rest-payments-reports-transactions-controller.php b/tests/unit/reports/test-class-wc-rest-payments-reports-transactions-controller.php index 46c2b21452c..260f3c62057 100644 --- a/tests/unit/reports/test-class-wc-rest-payments-reports-transactions-controller.php +++ b/tests/unit/reports/test-class-wc-rest-payments-reports-transactions-controller.php @@ -208,7 +208,7 @@ private function get_transactions_list_from_server() { 'currency' => 'usd', 'risk_level' => 0, 'charge_id' => 'ch_3NVXQQR7Mcmd7SUg0eV2k74L', - 'deposit_id' => 'wcpay_estimated_daily_usd_1689897600', + 'deposit_id' => null, 'available_on' => '2023-07-21', 'exchange_rate' => 1.12284, 'customer_amount' => 2300, @@ -217,7 +217,7 @@ private function get_transactions_list_from_server() { 'amount_in_usd' => 2583, 'source_device' => null, 'channel' => null, - 'deposit_status' => 'estimated', + 'deposit_status' => null, 'order' => [ 'number' => '123', 'url' => 'https:\/\/wcpay.test\/wp-admin\/post.php?post=278&action=edit', @@ -241,7 +241,7 @@ private function get_transactions_list_from_server() { 'currency' => 'usd', 'risk_level' => 0, 'charge_id' => 'ch_3NVXQER7Mcmd7SUg1Mk9SsNy', - 'deposit_id' => 'wcpay_estimated_daily_usd_1689897600', + 'deposit_id' => null, 'available_on' => '2023-07-21', 'exchange_rate' => 1.12284, 'customer_amount' => 2300, @@ -250,7 +250,7 @@ private function get_transactions_list_from_server() { 'amount_in_usd' => 2583, 'source_device' => null, 'channel' => null, - 'deposit_status' => 'estimated', + 'deposit_status' => null, 'order' => [ 'number' => '275', 'url' => 'https:\/\/wcpay.test\/wp-admin\/post.php?post=275&action=edit', @@ -289,8 +289,8 @@ private function get_transactions_list() { 'order_id' => 123, 'risk_level' => 0, 'deposit_date' => '2023-07-21', - 'deposit_id' => 'wcpay_estimated_daily_usd_1689897600', - 'deposit_status' => 'estimated', + 'deposit_id' => null, + 'deposit_status' => null, ], [ 'transaction_id' => 'txn_345', @@ -315,8 +315,8 @@ private function get_transactions_list() { 'order_id' => 275, 'risk_level' => 0, 'deposit_date' => '2023-07-21', - 'deposit_id' => 'wcpay_estimated_daily_usd_1689897600', - 'deposit_status' => 'estimated', + 'deposit_id' => null, + 'deposit_status' => null, ], ]; } From 33cdb7d7dd87619a172ab4d37dbf2ea4e6bf90ce Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Fri, 26 Jan 2024 10:01:33 -0500 Subject: [PATCH 17/52] Refactor: Consolidate Express Checkout Methods into Single Helper Class (#8055) Co-authored-by: Timur Karimov --- ...s-checkout-consolidate-duplicate-functions | 4 + dev/phpcs/ruleset.xml | 2 +- ...ayments-payment-request-button-handler.php | 180 +++------------- ...lass-wc-payments-woopay-button-handler.php | 193 ++++-------------- includes/class-wc-payments.php | 10 +- ...xpress-checkout-button-display-handler.php | 6 +- ...ayments-express-checkout-button-helper.php | 157 +++++++++++++- ...xpress-checkout-button-display-handler.php | 29 ++- ...ayments-express-checkout-button-helper.php | 152 ++++++++++++++ ...ayments-payment-request-button-handler.php | 59 +++--- ...lass-wc-payments-woopay-button-handler.php | 96 ++++++--- 11 files changed, 512 insertions(+), 376 deletions(-) create mode 100644 changelog/1545-express-checkout-consolidate-duplicate-functions rename includes/{ => express-checkout}/class-wc-payments-express-checkout-button-display-handler.php (96%) create mode 100644 tests/unit/test-class-wc-payments-express-checkout-button-helper.php diff --git a/changelog/1545-express-checkout-consolidate-duplicate-functions b/changelog/1545-express-checkout-consolidate-duplicate-functions new file mode 100644 index 00000000000..2c9592be3e7 --- /dev/null +++ b/changelog/1545-express-checkout-consolidate-duplicate-functions @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Merge duplicated Payment Request and WooPay button functionality . diff --git a/dev/phpcs/ruleset.xml b/dev/phpcs/ruleset.xml index 21b9b1fa970..27f2329cbb6 100644 --- a/dev/phpcs/ruleset.xml +++ b/dev/phpcs/ruleset.xml @@ -17,7 +17,7 @@ */includes/class-wc-payments-apple-pay-registration.php - */includes/class-wc-payments-express-checkout-button-display-handler.php + */includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php */includes/class-wc-payments-customer-service.php diff --git a/includes/class-wc-payments-payment-request-button-handler.php b/includes/class-wc-payments-payment-request-button-handler.php index 135ea785bd4..8463e1aed89 100644 --- a/includes/class-wc-payments-payment-request-button-handler.php +++ b/includes/class-wc-payments-payment-request-button-handler.php @@ -23,6 +23,8 @@ * WC_Payments_Payment_Request_Button_Handler class. */ class WC_Payments_Payment_Request_Button_Handler { + const BUTTON_LOCATIONS = 'payment_request_button_locations'; + /** * WC_Payments_Account instance to get information about the account * @@ -154,7 +156,7 @@ public function set_session() { // Don't set session cookies on product pages to allow for caching when payment request // buttons are disabled. But keep cookies if there is already an active WC session in place. if ( - ! ( $this->is_product() && $this->should_show_payment_request_button() ) + ! ( $this->express_checkout_helper->is_product() && $this->should_show_payment_request_button() ) || ( isset( WC()->session ) && WC()->session->has_session() ) ) { return; @@ -182,22 +184,20 @@ public function handle_payment_request_redirect() { } /** - * Gets the button height. + * The settings for the `button` attribute - they depend on the "grouped settings" flag value. * - * @return string + * @return array */ - public function get_button_height() { - $height = $this->gateway->get_option( 'payment_request_button_size' ); - if ( 'medium' === $height ) { - return '48'; - } - - if ( 'large' === $height ) { - return '56'; - } + public function get_button_settings() { + $button_type = $this->gateway->get_option( 'payment_request_button_type' ); + $common_settings = $this->express_checkout_helper->get_common_button_settings(); + $payment_request_button_settings = [ + // Default format is en_US. + 'locale' => apply_filters( 'wcpay_payment_request_button_locale', substr( get_locale(), 0, 2 ) ), + 'branded_type' => 'default' === $button_type ? 'short' : 'long', + ]; - // for the "default"/"small" and "catch-all" scenarios. - return '40'; + return array_merge( $common_settings, $payment_request_button_settings ); } /** @@ -246,12 +246,12 @@ public function get_product_price( $product ) { * @return mixed Returns false if not on a product page, the product information otherwise. */ public function get_product_data() { - if ( ! $this->is_product() ) { + if ( ! $this->express_checkout_helper->is_product() ) { return false; } /** @var WC_Product_Variable $product */ // phpcs:ignore - $product = $this->get_product(); + $product = $this->express_checkout_helper->get_product(); $currency = get_woocommerce_currency(); if ( 'variable' === $product->get_type() || 'variable-subscription' === $product->get_type() ) { @@ -395,7 +395,7 @@ public function display_pay_for_order_page_html( $order ) { * @return mixed Returns false if on a product page, the product information otherwise. */ public function get_cart_data() { - if ( $this->is_product() ) { + if ( $this->express_checkout_helper->is_product() ) { return false; } @@ -504,47 +504,47 @@ public function should_show_payment_request_button() { } // Page not supported. - if ( ! $this->is_product() && ! $this->is_cart() && ! $this->is_checkout() ) { + if ( ! $this->express_checkout_helper->is_product() && ! $this->express_checkout_helper->is_cart() && ! $this->express_checkout_helper->is_checkout() ) { return false; } // Product page, but not available in settings. - if ( $this->is_product() && ! $this->is_available_at( 'product' ) ) { + if ( $this->express_checkout_helper->is_product() && ! $this->express_checkout_helper->is_available_at( 'product', self::BUTTON_LOCATIONS ) ) { return false; } // Checkout page, but not available in settings. - if ( $this->is_checkout() && ! $this->is_available_at( 'checkout' ) ) { + if ( $this->express_checkout_helper->is_checkout() && ! $this->express_checkout_helper->is_available_at( 'checkout', self::BUTTON_LOCATIONS ) ) { return false; } // Cart page, but not available in settings. - if ( $this->is_cart() && ! $this->is_available_at( 'cart' ) ) { + if ( $this->express_checkout_helper->is_cart() && ! $this->express_checkout_helper->is_available_at( 'cart', self::BUTTON_LOCATIONS ) ) { return false; } // Product page, but has unsupported product type. - if ( $this->is_product() && ! $this->is_product_supported() ) { + if ( $this->express_checkout_helper->is_product() && ! $this->is_product_supported() ) { Logger::log( 'Product page has unsupported product type ( Payment Request button disabled )' ); return false; } // Cart has unsupported product type. - if ( ( $this->is_checkout() || $this->is_cart() ) && ! $this->has_allowed_items_in_cart() ) { + if ( ( $this->express_checkout_helper->is_checkout() || $this->express_checkout_helper->is_cart() ) && ! $this->has_allowed_items_in_cart() ) { Logger::log( 'Items in the cart have unsupported product type ( Payment Request button disabled )' ); return false; } // Order total doesn't matter for Pay for Order page. Thus, this page should always display payment buttons. - if ( $this->is_pay_for_order_page() ) { + if ( $this->express_checkout_helper->is_pay_for_order_page() ) { return true; } // Cart total is 0 or is on product page and product price is 0. // Exclude pay-for-order pages from this check. if ( - ( ! $this->is_product() && ! $this->is_pay_for_order_page() && 0.0 === (float) WC()->cart->get_total( 'edit' ) ) || - ( $this->is_product() && 0.0 === (float) $this->get_product()->get_price() ) + ( ! $this->express_checkout_helper->is_product() && ! $this->express_checkout_helper->is_pay_for_order_page() && 0.0 === (float) WC()->cart->get_total( 'edit' ) ) || + ( $this->express_checkout_helper->is_product() && 0.0 === (float) $this->express_checkout_helper->get_product()->get_price() ) ) { Logger::log( 'Order price is 0 ( Payment Request button disabled )' ); @@ -632,12 +632,12 @@ public function has_subscription_product() { return false; } - if ( $this->is_product() ) { - $product = $this->get_product(); + if ( $this->express_checkout_helper->is_product() ) { + $product = $this->express_checkout_helper->get_product(); if ( WC_Subscriptions_Product::is_subscription( $product ) ) { return true; } - } elseif ( $this->is_checkout() || $this->is_cart() ) { + } elseif ( $this->express_checkout_helper->is_checkout() || $this->express_checkout_helper->is_cart() ) { if ( WC_Subscriptions_Cart::cart_contains_subscription() ) { return true; } @@ -646,103 +646,6 @@ public function has_subscription_product() { return false; } - /** - * Checks if this is a product page or content contains a product_page shortcode. - * - * @return boolean - */ - public function is_product() { - return is_product() || wc_post_content_has_shortcode( 'product_page' ); - } - - /** - * Checks if this is the Pay for Order page. - * - * @return boolean - */ - public function is_pay_for_order_page() { - return is_checkout() && isset( $_GET['pay_for_order'] ); // phpcs:ignore WordPress.Security.NonceVerification - } - - /** - * Checks if this is the cart page or content contains a cart block. - * - * @return boolean - */ - public function is_cart() { - return is_cart() || has_block( 'woocommerce/cart' ); - } - - /** - * Checks if this is the checkout page or content contains a cart block. - * - * @return boolean - */ - public function is_checkout() { - return is_checkout() || has_block( 'woocommerce/checkout' ); - } - - /** - * Checks if payment request is available at a given location. - * - * @param string $location Location. - * @return boolean - */ - public function is_available_at( $location ) { - $available_locations = $this->gateway->get_option( 'payment_request_button_locations' ); - if ( $available_locations && is_array( $available_locations ) ) { - return in_array( $location, $available_locations, true ); - } - - return false; - } - - /** - * Gets the context for where the button is being displayed. - * - * @return string - */ - public function get_button_context() { - if ( $this->is_product() ) { - return 'product'; - } - - if ( $this->is_cart() ) { - return 'cart'; - } - - if ( $this->is_checkout() ) { - return 'checkout'; - } - - if ( $this->is_pay_for_order_page() ) { - return 'pay_for_order'; - } - - return ''; - } - - /** - * Get product from product page or product_page shortcode. - * - * @return WC_Product|false|null Product object. - */ - public function get_product() { - global $post; - - if ( is_product() ) { - return wc_get_product( $post->ID ); - } elseif ( wc_post_content_has_shortcode( 'product_page' ) ) { - // Get id from product_page shortcode. - preg_match( '/\[product_page id="(?\d+)"\]/', $post->post_content, $shortcode_match ); - if ( isset( $shortcode_match['id'] ) ) { - return wc_get_product( $shortcode_match['id'] ); - } - } - - return null; - } - /** * Returns the login redirect URL. * @@ -796,9 +699,9 @@ public function scripts() { ], 'button' => $this->get_button_settings(), 'login_confirmation' => $this->get_login_confirmation_settings(), - 'is_product_page' => $this->is_product(), - 'button_context' => $this->get_button_context(), - 'is_pay_for_order' => $this->is_pay_for_order_page(), + 'is_product_page' => $this->express_checkout_helper->is_product(), + 'button_context' => $this->express_checkout_helper->get_button_context(), + 'is_pay_for_order' => $this->express_checkout_helper->is_pay_for_order_page(), 'has_block' => has_block( 'woocommerce/cart' ) || has_block( 'woocommerce/checkout' ), 'product' => $this->get_product_data(), 'total_label' => $this->express_checkout_helper->get_total_label(), @@ -847,7 +750,7 @@ public function display_payment_request_button_html() { * @return boolean */ private function is_product_supported() { - $product = $this->get_product(); + $product = $this->express_checkout_helper->get_product(); $is_supported = true; if ( is_null( $product ) @@ -1514,23 +1417,6 @@ public function get_option_is_apple_pay_enabled( $value ) { return $value; } - /** - * The settings for the `button` attribute - they depend on the "grouped settings" flag value. - * - * @return array - */ - public function get_button_settings() { - $button_type = $this->gateway->get_option( 'payment_request_button_type' ); - return [ - 'type' => $button_type, - 'theme' => $this->gateway->get_option( 'payment_request_button_theme' ), - 'height' => $this->get_button_height(), - // Default format is en_US. - 'locale' => apply_filters( 'wcpay_payment_request_button_locale', substr( get_locale(), 0, 2 ) ), - 'branded_type' => 'default' === $button_type ? 'short' : 'long', - ]; - } - /** * Settings array for the user authentication dialog and redirection. * diff --git a/includes/class-wc-payments-woopay-button-handler.php b/includes/class-wc-payments-woopay-button-handler.php index 5045174a7b3..4f19ffb3f57 100644 --- a/includes/class-wc-payments-woopay-button-handler.php +++ b/includes/class-wc-payments-woopay-button-handler.php @@ -22,6 +22,8 @@ * WC_Payments_WooPay_Button_Handler class. */ class WC_Payments_WooPay_Button_Handler { + const BUTTON_LOCATIONS = 'platform_checkout_button_locations'; + /** * WC_Payments_Account instance to get information about the account * @@ -43,17 +45,26 @@ class WC_Payments_WooPay_Button_Handler { */ private $woopay_utilities; + /** + * Express Checkout Helper instance. + * + * @var WC_Payments_Express_Checkout_Button_Helper + */ + private $express_checkout_helper; + /** * Initialize class actions. * - * @param WC_Payments_Account $account Account information. - * @param WC_Payment_Gateway_WCPay $gateway WCPay gateway. - * @param WooPay_Utilities $woopay_utilities WCPay gateway. + * @param WC_Payments_Account $account Account information. + * @param WC_Payment_Gateway_WCPay $gateway WCPay gateway. + * @param WooPay_Utilities $woopay_utilities WCPay gateway. + * @param WC_Payments_Express_Checkout_Button_Helper $express_checkout_helper Express checkout helper. */ - public function __construct( WC_Payments_Account $account, WC_Payment_Gateway_WCPay $gateway, WooPay_Utilities $woopay_utilities ) { - $this->account = $account; - $this->gateway = $gateway; - $this->woopay_utilities = $woopay_utilities; + public function __construct( WC_Payments_Account $account, WC_Payment_Gateway_WCPay $gateway, WooPay_Utilities $woopay_utilities, WC_Payments_Express_Checkout_Button_Helper $express_checkout_helper ) { + $this->account = $account; + $this->gateway = $gateway; + $this->woopay_utilities = $woopay_utilities; + $this->express_checkout_helper = $express_checkout_helper; } /** @@ -112,11 +123,11 @@ public function init() { } // Create WooPay button location option if it doesn't exist and enable all locations by default. - if ( ! array_key_exists( 'platform_checkout_button_locations', get_option( 'woocommerce_woocommerce_payments_settings' ) ) ) { + if ( ! array_key_exists( self::BUTTON_LOCATIONS, get_option( 'woocommerce_woocommerce_payments_settings' ) ) ) { - $all_locations = $this->gateway->form_fields['platform_checkout_button_locations']['options']; + $all_locations = $this->gateway->form_fields[ self::BUTTON_LOCATIONS ]['options']; - $this->gateway->update_option( 'platform_checkout_button_locations', array_keys( $all_locations ) ); + $this->gateway->update_option( self::BUTTON_LOCATIONS, array_keys( $all_locations ) ); WC_Payments::woopay_tracker()->woopay_locations_updated( $all_locations, array_keys( $all_locations ) ); } @@ -212,115 +223,19 @@ public function show_error_notice() { wp_die(); } - /** - * Checks if this is a product page or content contains a product_page shortcode. - * - * @return boolean - */ - public function is_product() { - return is_product() || wc_post_content_has_shortcode( 'product_page' ); - } - - /** - * Checks if this is the Pay for Order page. - * - * @return boolean - */ - public function is_pay_for_order_page() { - return is_checkout() && isset( $_GET['pay_for_order'] ); // phpcs:ignore WordPress.Security.NonceVerification - } - - /** - * Checks if this is the cart page or content contains a cart block. - * - * @return boolean - */ - public function is_cart() { - return is_cart() || has_block( 'woocommerce/cart' ); - } - - /** - * Checks if this is the checkout page or content contains a cart block. - * - * @return boolean - */ - public function is_checkout() { - return is_checkout() || has_block( 'woocommerce/checkout' ); - } - - /** - * Checks if payment request is available at a given location. - * - * @param string $location Location. - * @return boolean - */ - public function is_available_at( $location ) { - $available_locations = $this->gateway->get_option( 'platform_checkout_button_locations' ); - if ( $available_locations && is_array( $available_locations ) ) { - return in_array( $location, $available_locations, true ); - } - - return false; - } - - /** - * Gets the context for where the button is being displayed. - * - * @return string - */ - public function get_button_context() { - if ( $this->is_product() ) { - return 'product'; - } - - if ( $this->is_cart() ) { - return 'cart'; - } - - if ( $this->is_pay_for_order_page() ) { - return 'pay_for_order'; - } - - if ( $this->is_checkout() ) { - return 'checkout'; - } - - return ''; - } - /** * The settings for the `button` attribute - they depend on the "grouped settings" flag value. * * @return array */ public function get_button_settings() { - $button_type = $this->gateway->get_option( 'payment_request_button_type', 'default' ); - return [ - 'type' => $button_type, - 'theme' => $this->gateway->get_option( 'payment_request_button_theme', 'dark' ), - 'height' => $this->get_button_height(), + $common_settings = $this->express_checkout_helper->get_common_button_settings(); + $woopay_button_settings = [ 'size' => $this->gateway->get_option( 'payment_request_button_size' ), - 'context' => $this->get_button_context(), + 'context' => $this->express_checkout_helper->get_button_context(), ]; - } - - /** - * Gets the button height. - * - * @return string - */ - public function get_button_height() { - $height = $this->gateway->get_option( 'payment_request_button_size' ); - if ( 'medium' === $height ) { - return '48'; - } - if ( 'large' === $height ) { - return '56'; - } - - // for the "default" and "catch-all" scenarios. - return '40'; + return array_merge( $common_settings, $woopay_button_settings ); } /** @@ -341,7 +256,7 @@ public function should_show_woopay_button() { } // Page not supported. - if ( ! $this->is_product() && ! $this->is_cart() && ! $this->is_checkout() ) { + if ( ! $this->express_checkout_helper->is_product() && ! $this->express_checkout_helper->is_cart() && ! $this->express_checkout_helper->is_checkout() ) { return false; } @@ -351,44 +266,44 @@ public function should_show_woopay_button() { } // Product page, but not available in settings. - if ( $this->is_product() && ! $this->is_available_at( 'product' ) ) { + if ( $this->express_checkout_helper->is_product() && ! $this->express_checkout_helper->is_available_at( 'product', self::BUTTON_LOCATIONS ) ) { return false; } // Checkout page, but not available in settings. - if ( $this->is_checkout() && ! $this->is_available_at( 'checkout' ) ) { + if ( $this->express_checkout_helper->is_checkout() && ! $this->express_checkout_helper->is_available_at( 'checkout', self::BUTTON_LOCATIONS ) ) { return false; } // Cart page, but not available in settings. - if ( $this->is_cart() && ! $this->is_available_at( 'cart' ) ) { + if ( $this->express_checkout_helper->is_cart() && ! $this->express_checkout_helper->is_available_at( 'cart', self::BUTTON_LOCATIONS ) ) { return false; } // Product page, but has unsupported product type. - if ( $this->is_product() && ! $this->is_product_supported() ) { + if ( $this->express_checkout_helper->is_product() && ! $this->is_product_supported() ) { Logger::log( 'Product page has unsupported product type ( WooPay Express button disabled )' ); return false; } // Cart has unsupported product type. - if ( ( $this->is_checkout() || $this->is_cart() ) && ! $this->has_allowed_items_in_cart() ) { + if ( ( $this->express_checkout_helper->is_checkout() || $this->express_checkout_helper->is_cart() ) && ! $this->has_allowed_items_in_cart() ) { Logger::log( 'Items in the cart have unsupported product type ( WooPay Express button disabled )' ); return false; } if ( ! is_user_logged_in() ) { // On product page for a subscription product, but not logged in, making WooPay unavailable. - if ( $this->is_product() ) { + if ( $this->express_checkout_helper->is_product() ) { $current_product = wc_get_product(); - if ( $current_product && $this->is_product_subscription( $current_product ) ) { + if ( $current_product && $this->express_checkout_helper->is_product_subscription( $current_product ) ) { return false; } } // On cart or checkout page with a subscription product in cart, but not logged in, making WooPay unavailable. - if ( ( $this->is_checkout() || $this->is_cart() ) && class_exists( 'WC_Subscriptions_Cart' ) && WC_Subscriptions_Cart::cart_contains_subscription() ) { + if ( ( $this->express_checkout_helper->is_checkout() || $this->express_checkout_helper->is_cart() ) && class_exists( 'WC_Subscriptions_Cart' ) && WC_Subscriptions_Cart::cart_contains_subscription() ) { // Check cart for subscription products. return false; } @@ -419,7 +334,7 @@ public function display_woopay_button_html() { $settings = $this->get_button_settings(); ?> -
    is_product() ); ?>> +
    express_checkout_helper->is_product() ); ?>>