diff --git a/changelog.txt b/changelog.txt index acdd013efe5..b0951f1cd02 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ *** WooPayments Changelog *** += 7.8.1 - 2024-06-25 = +* Fix - Fix "Dispute not loaded" error that was affecting responding to disputes. + = 7.8.0 - 2024-06-19 = * Add - Add a feedback survey modal upon deactivation. * Add - Add new select component to be used for reporting filters, e.g. Payments overview currency select diff --git a/changelog/add-8951-support-style-settings-with-ece-buttons b/changelog/add-8951-support-style-settings-with-ece-buttons new file mode 100644 index 00000000000..acb0ba496fd --- /dev/null +++ b/changelog/add-8951-support-style-settings-with-ece-buttons @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Support style settings for ECE buttons diff --git a/changelog/fix-token-retrieval-per-payment-method b/changelog/fix-token-retrieval-per-payment-method new file mode 100644 index 00000000000..1ec9a65c246 --- /dev/null +++ b/changelog/fix-token-retrieval-per-payment-method @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Retrieve saved tokens only relevant for the specific payment gateway. diff --git a/changelog/revert-8901-update-route-param-validation b/changelog/revert-8901-update-route-param-validation new file mode 100644 index 00000000000..77bc7eaeee5 --- /dev/null +++ b/changelog/revert-8901-update-route-param-validation @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix "Dispute not loaded" error that was affecting responding to disputes. diff --git a/changelog/update-speedup-phpunit-tests b/changelog/update-speedup-phpunit-tests new file mode 100644 index 00000000000..db651399352 --- /dev/null +++ b/changelog/update-speedup-phpunit-tests @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: These changes only affect PHPUnit tests. + + diff --git a/client/express-checkout/blocks/components/express-checkout-container.js b/client/express-checkout/blocks/components/express-checkout-container.js index 9285227bf63..931e2a6223f 100644 --- a/client/express-checkout/blocks/components/express-checkout-container.js +++ b/client/express-checkout/blocks/components/express-checkout-container.js @@ -2,6 +2,7 @@ * External dependencies */ import { Elements } from '@stripe/react-stripe-js'; +import { getExpressCheckoutButtonAppearance } from 'wcpay/express-checkout/utils'; /** * Internal dependencies @@ -16,6 +17,7 @@ const ExpressCheckoutContainer = ( props ) => { paymentMethodCreation: 'manual', amount: billing.cartTotal.value, currency: billing.currency.code.toLowerCase(), + appearance: getExpressCheckoutButtonAppearance(), }; return ( diff --git a/client/express-checkout/blocks/hooks/use-express-checkout.js b/client/express-checkout/blocks/hooks/use-express-checkout.js index e01c5eaf5bc..ee5f7be6ed4 100644 --- a/client/express-checkout/blocks/hooks/use-express-checkout.js +++ b/client/express-checkout/blocks/hooks/use-express-checkout.js @@ -5,7 +5,10 @@ */ import { useCallback } from '@wordpress/element'; import { useStripe, useElements } from '@stripe/react-stripe-js'; -import { normalizeLineItems } from 'wcpay/express-checkout/utils'; +import { + getExpressCheckoutButtonStyleSettings, + normalizeLineItems, +} from 'wcpay/express-checkout/utils'; import { onConfirmHandler } from 'wcpay/express-checkout/event-handlers'; export const useExpressCheckout = ( { @@ -19,17 +22,7 @@ export const useExpressCheckout = ( { const stripe = useStripe(); const elements = useElements(); - const buttonOptions = { - paymentMethods: { - applePay: 'always', - googlePay: 'always', - link: 'auto', - }, - buttonType: { - googlePay: wcpayExpressCheckoutParams.button.type, - applePay: wcpayExpressCheckoutParams.button.type, - }, - }; + const buttonOptions = getExpressCheckoutButtonStyleSettings(); const onCancel = () => { onClose(); diff --git a/client/express-checkout/index.js b/client/express-checkout/index.js index abcf1102b46..45fea02f042 100644 --- a/client/express-checkout/index.js +++ b/client/express-checkout/index.js @@ -6,7 +6,12 @@ import { __ } from '@wordpress/i18n'; */ import WCPayAPI from '../checkout/api'; import '../checkout/express-checkout-buttons.scss'; -import { getExpressCheckoutData, normalizeLineItems } from './utils/index'; +import { + getExpressCheckoutButtonAppearance, + getExpressCheckoutButtonStyleSettings, + getExpressCheckoutData, + normalizeLineItems, +} from './utils/index'; import { onConfirmHandler, shippingAddressChangeHandler, @@ -232,14 +237,13 @@ jQuery( ( $ ) => { amount: options?.total, currency: options?.currency, paymentMethodCreation: 'manual', + appearance: getExpressCheckoutButtonAppearance(), } ); - const eceButton = wcpayECE.createButton( elements, { - buttonType: { - googlePay: getExpressCheckoutData( 'button' ).type, - applePay: getExpressCheckoutData( 'button' ).type, - }, - } ); + const eceButton = wcpayECE.createButton( + elements, + getExpressCheckoutButtonStyleSettings() + ); wcpayECE.showButton( eceButton ); diff --git a/client/express-checkout/utils/index.ts b/client/express-checkout/utils/index.ts index b10d80b0960..abe139becec 100644 --- a/client/express-checkout/utils/index.ts +++ b/client/express-checkout/utils/index.ts @@ -87,8 +87,10 @@ declare global { } } -export const getExpressCheckoutData = ( - key: keyof WCPayExpressCheckoutParams +export const getExpressCheckoutData = < + K extends keyof WCPayExpressCheckoutParams +>( + key: K ) => { if ( window.wcpayExpressCheckoutParams ) { return window.wcpayExpressCheckoutParams?.[ key ]; @@ -108,3 +110,78 @@ export const getErrorMessageFromNotice = ( notice: string ) => { div.innerHTML = notice.trim(); return div.firstChild ? div.firstChild.textContent : ''; }; + +/** + * Returns the appearance settings for the Express Checkout buttons. + * Currently only configures border radius for the buttons. + */ +export const getExpressCheckoutButtonAppearance = () => { + return { + // variables: { borderRadius: '99999px' }, + }; +}; + +/** + * Returns the style settings for the Express Checkout buttons. + */ +export const getExpressCheckoutButtonStyleSettings = () => { + const buttonSettings = getExpressCheckoutData( 'button' ); + + const mapWooPaymentsThemeToButtonTheme = ( + buttonType: string, + theme: string + ) => { + switch ( theme ) { + case 'dark': + return 'black'; + case 'light': + return 'white'; + case 'light-outline': + if ( buttonType === 'googlePay' ) { + return 'white'; + } + + return 'white-outline'; + default: + return 'black'; + } + }; + + const googlePayType = + buttonSettings?.type === 'default' + ? 'plain' + : buttonSettings?.type ?? 'buy'; + + const applePayType = + buttonSettings?.type === 'default' + ? 'plain' + : buttonSettings?.type ?? 'plain'; + + return { + paymentMethods: { + applePay: 'always', + googlePay: 'always', + link: 'auto', + }, + layout: { overflow: 'never' }, + buttonTheme: { + googlePay: mapWooPaymentsThemeToButtonTheme( + 'googlePay', + buttonSettings?.theme ?? 'black' + ), + applePay: mapWooPaymentsThemeToButtonTheme( + 'applePay', + buttonSettings?.theme ?? 'black' + ), + }, + buttonType: { + googlePay: googlePayType, + applePay: applePayType, + }, + // Allowed height must be 40px to 55px. + buttonHeight: Math.min( + Math.max( parseInt( buttonSettings?.height ?? '48', 10 ), 40 ), + 55 + ), + }; +}; diff --git a/includes/admin/class-wc-rest-payments-authorizations-controller.php b/includes/admin/class-wc-rest-payments-authorizations-controller.php index a35f3fc85e3..1b2993e3c71 100644 --- a/includes/admin/class-wc-rest-payments-authorizations-controller.php +++ b/includes/admin/class-wc-rest-payments-authorizations-controller.php @@ -46,7 +46,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<payment_intent_id>(ch|pi|py)_[A-Za-z0-9]+)', + '/' . $this->rest_base . '/(?P<payment_intent_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_authorization' ], diff --git a/includes/admin/class-wc-rest-payments-charges-controller.php b/includes/admin/class-wc-rest-payments-charges-controller.php index 2297e3bd13a..34422cac647 100644 --- a/includes/admin/class-wc-rest-payments-charges-controller.php +++ b/includes/admin/class-wc-rest-payments-charges-controller.php @@ -28,7 +28,7 @@ class WC_REST_Payments_Charges_Controller extends WC_Payments_REST_Controller { public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<charge_id>ch_[A-Za-z0-9]+)', + '/' . $this->rest_base . '/(?P<charge_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_charge' ], @@ -37,7 +37,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/order/(?P<order_id>[A-Za-z0-9_\-]+)', + '/' . $this->rest_base . '/order/(?P<order_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'generate_charge_from_order' ], diff --git a/includes/admin/class-wc-rest-payments-customer-controller.php b/includes/admin/class-wc-rest-payments-customer-controller.php index 5ccce5c99d6..c0f43a97003 100644 --- a/includes/admin/class-wc-rest-payments-customer-controller.php +++ b/includes/admin/class-wc-rest-payments-customer-controller.php @@ -49,7 +49,7 @@ public function __construct( public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<customer_id>[A-Za-z0-9_\-]+)/payment_methods', + '/' . $this->rest_base . '/(?P<customer_id>\w+)/payment_methods', [ [ 'methods' => WP_REST_Server::READABLE, diff --git a/includes/admin/class-wc-rest-payments-deposits-controller.php b/includes/admin/class-wc-rest-payments-deposits-controller.php index da3b3c1afbc..1b5c808a314 100644 --- a/includes/admin/class-wc-rest-payments-deposits-controller.php +++ b/includes/admin/class-wc-rest-payments-deposits-controller.php @@ -55,7 +55,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<deposit_id>[A-Za-z0-9_\-]+)', + '/' . $this->rest_base . '/(?P<deposit_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_deposit' ], diff --git a/includes/admin/class-wc-rest-payments-disputes-controller.php b/includes/admin/class-wc-rest-payments-disputes-controller.php index 4dacf45ee5c..64e1e478a21 100644 --- a/includes/admin/class-wc-rest-payments-disputes-controller.php +++ b/includes/admin/class-wc-rest-payments-disputes-controller.php @@ -54,7 +54,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<dispute_id>(dp|dispute)_[A-Za-z0-9]+)', + '/' . $this->rest_base . '/(?P<dispute_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_dispute' ], @@ -63,7 +63,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<dispute_id>(dp|dispute)_[A-Za-z0-9]+)', + '/' . $this->rest_base . '/(?P<dispute_id>\w+)', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'update_dispute' ], @@ -72,7 +72,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<dispute_id>(dp|dispute)_[A-Za-z0-9]+)/close', + '/' . $this->rest_base . '/(?P<dispute_id>\w+)/close', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'close_dispute' ], diff --git a/includes/admin/class-wc-rest-payments-files-controller.php b/includes/admin/class-wc-rest-payments-files-controller.php index 6720b52b959..e66eedefffd 100644 --- a/includes/admin/class-wc-rest-payments-files-controller.php +++ b/includes/admin/class-wc-rest-payments-files-controller.php @@ -35,7 +35,7 @@ public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<file_id>[A-Za-z0-9_\-]+)/details', + '/' . $this->rest_base . '/(?P<file_id>\w+)/details', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_file_detail' ], @@ -45,7 +45,7 @@ public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<file_id>[A-Za-z0-9_\-]+)/content', + '/' . $this->rest_base . '/(?P<file_id>\w+)/content', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_file_content' ], @@ -55,7 +55,7 @@ public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<file_id>[A-Za-z0-9_\-]+)', + '/' . $this->rest_base . '/(?P<file_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_file' ], diff --git a/includes/admin/class-wc-rest-payments-fraud-outcomes-controller.php b/includes/admin/class-wc-rest-payments-fraud-outcomes-controller.php index 199e62255cf..7d0feb06024 100644 --- a/includes/admin/class-wc-rest-payments-fraud-outcomes-controller.php +++ b/includes/admin/class-wc-rest-payments-fraud-outcomes-controller.php @@ -25,7 +25,7 @@ class WC_REST_Payments_Fraud_Outcomes_Controller extends WC_Payments_REST_Contro public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<id>[A-Za-z0-9_\-]+)/latest', + '/' . $this->rest_base . '/(?P<id>\w+)/latest', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_latest_fraud_outcome' ], diff --git a/includes/admin/class-wc-rest-payments-orders-controller.php b/includes/admin/class-wc-rest-payments-orders-controller.php index f79e0a5fcfd..04c86f54197 100644 --- a/includes/admin/class-wc-rest-payments-orders-controller.php +++ b/includes/admin/class-wc-rest-payments-orders-controller.php @@ -68,7 +68,7 @@ public function __construct( WC_Payments_API_Client $api_client, WC_Payment_Gate public function register_routes() { register_rest_route( $this->namespace, - $this->rest_base . '/(?P<order_id>[A-Za-z0-9_\-]+)/capture_terminal_payment', + $this->rest_base . '/(?P<order_id>\w+)/capture_terminal_payment', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'capture_terminal_payment' ], @@ -82,7 +82,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - $this->rest_base . '/(?P<order_id>[A-Za-z0-9_\-]+)/capture_authorization', + $this->rest_base . '/(?P<order_id>\w+)/capture_authorization', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'capture_authorization' ], @@ -96,7 +96,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - $this->rest_base . '/(?P<order_id>[A-Za-z0-9_\-]+)/cancel_authorization', + $this->rest_base . '/(?P<order_id>\w+)/cancel_authorization', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'cancel_authorization' ], @@ -110,7 +110,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - $this->rest_base . '/(?P<order_id>[A-Za-z0-9_\-]+)/create_terminal_intent', + $this->rest_base . '/(?P<order_id>\w+)/create_terminal_intent', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'create_terminal_intent' ], diff --git a/includes/admin/class-wc-rest-payments-payment-intents-controller.php b/includes/admin/class-wc-rest-payments-payment-intents-controller.php index 9ba1a39fa67..670d15a3089 100644 --- a/includes/admin/class-wc-rest-payments-payment-intents-controller.php +++ b/includes/admin/class-wc-rest-payments-payment-intents-controller.php @@ -32,7 +32,7 @@ class WC_REST_Payments_Payment_Intents_Controller extends WC_Payments_REST_Contr public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<payment_intent_id>(ch|pi|py)_[A-Za-z0-9]+)', + '/' . $this->rest_base . '/(?P<payment_intent_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_payment_intent' ], diff --git a/includes/admin/class-wc-rest-payments-reader-controller.php b/includes/admin/class-wc-rest-payments-reader-controller.php index 569e49bc9ed..fa6fa5192e0 100644 --- a/includes/admin/class-wc-rest-payments-reader-controller.php +++ b/includes/admin/class-wc-rest-payments-reader-controller.php @@ -114,7 +114,7 @@ public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/charges/(?P<transaction_id>[A-Za-z0-9_\-]+)', + '/' . $this->rest_base . '/charges/(?P<transaction_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_summary' ], @@ -132,7 +132,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/receipts/(?P<payment_intent_id>(ch|pi|py)_[A-Za-z0-9]+)', + '/' . $this->rest_base . '/receipts/(?P<payment_intent_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'generate_print_receipt' ], diff --git a/includes/admin/class-wc-rest-payments-terminal-locations-controller.php b/includes/admin/class-wc-rest-payments-terminal-locations-controller.php index 90062ef3b8c..c0f0f7754ee 100644 --- a/includes/admin/class-wc-rest-payments-terminal-locations-controller.php +++ b/includes/admin/class-wc-rest-payments-terminal-locations-controller.php @@ -38,7 +38,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<location_id>[A-Za-z0-9_\-]+)', + '/' . $this->rest_base . '/(?P<location_id>\w+)', [ 'methods' => WP_REST_Server::DELETABLE, 'callback' => [ $this, 'delete_location' ], @@ -47,7 +47,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<location_id>[A-Za-z0-9_\-]+)', + '/' . $this->rest_base . '/(?P<location_id>\w+)', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'update_location' ], @@ -66,7 +66,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<location_id>[A-Za-z0-9_\-]+)', + '/' . $this->rest_base . '/(?P<location_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_location' ], diff --git a/includes/admin/class-wc-rest-payments-timeline-controller.php b/includes/admin/class-wc-rest-payments-timeline-controller.php index 23a1c4136d1..c2c0f5ac87a 100644 --- a/includes/admin/class-wc-rest-payments-timeline-controller.php +++ b/includes/admin/class-wc-rest-payments-timeline-controller.php @@ -24,7 +24,7 @@ class WC_REST_Payments_Timeline_Controller extends WC_Payments_REST_Controller { public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<intention_id>(ch|pi|py)_[A-Za-z0-9]+)', + '/' . $this->rest_base . '/(?P<intention_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_timeline' ], diff --git a/includes/admin/class-wc-rest-payments-transactions-controller.php b/includes/admin/class-wc-rest-payments-transactions-controller.php index 5c72a32ed0e..c3a783943b8 100644 --- a/includes/admin/class-wc-rest-payments-transactions-controller.php +++ b/includes/admin/class-wc-rest-payments-transactions-controller.php @@ -100,7 +100,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<transaction_id>[A-Za-z0-9_\-]+)', + '/' . $this->rest_base . '/(?P<transaction_id>\w+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_transaction' ], diff --git a/includes/class-wc-payments-token-service.php b/includes/class-wc-payments-token-service.php index 6a8f9e21d0a..0eb810bce75 100644 --- a/includes/class-wc-payments-token-service.php +++ b/includes/class-wc-payments-token-service.php @@ -163,15 +163,7 @@ public function woocommerce_get_customer_payment_tokens( $tokens, $user_id, $gat } } - $retrievable_payment_method_types = [ Payment_Method::CARD ]; - - if ( in_array( Payment_Method::SEPA, WC_Payments::get_gateway()->get_upe_enabled_payment_method_ids(), true ) ) { - $retrievable_payment_method_types[] = Payment_Method::SEPA; - } - - if ( in_array( Payment_Method::LINK, WC_Payments::get_gateway()->get_upe_enabled_payment_method_ids(), true ) ) { - $retrievable_payment_method_types[] = Payment_Method::LINK; - } + $retrievable_payment_method_types = $this->get_retrievable_payment_method_types( $gateway_id ); $payment_methods = []; @@ -213,6 +205,77 @@ public function woocommerce_get_customer_payment_tokens( $tokens, $user_id, $gat return $tokens; } + /** + * Retrieves the payment method types for which tokens should be retrieved. + * + * This function determines the appropriate payment method types based on the provided gateway ID. + * - If a gateway ID is provided, it retrieves the payment methods specific to that gateway to prevent duplication of saved tokens under incorrect payment methods during checkout. + * - If no gateway ID is provided, it retrieves the default payment methods to fetch all saved tokens, e.g., for the Blocks checkout or My Account page. + * + * @param string|null $gateway_id The optional ID of the gateway. + * @return array The list of retrievable payment method types. + */ + private function get_retrievable_payment_method_types( $gateway_id = null ) { + if ( empty( $gateway_id ) ) { + return $this->get_all_retrievable_payment_types(); + } else { + return $this->get_gateway_specific_retrievable_payment_types( $gateway_id ); + } + } + + /** + * Returns all the enabled retrievable payment method types. + * + * @return array Enabled retrievable payment method types. + */ + private function get_all_retrievable_payment_types() { + $types = [ Payment_Method::CARD ]; + + if ( $this->is_payment_method_enabled( Payment_Method::SEPA ) ) { + $types[] = Payment_Method::SEPA; + } + + if ( $this->is_payment_method_enabled( Payment_Method::LINK ) ) { + $types[] = Payment_Method::LINK; + } + + return $types; + } + /** + * Returns retrievable payment method types for a given gateway. + * + * @param string $gateway_id The ID of the gateway. + * @return array Retrievable payment method types for the specified gateway. + */ + private function get_gateway_specific_retrievable_payment_types( $gateway_id ) { + $types = []; + + foreach ( self::REUSABLE_GATEWAYS_BY_PAYMENT_METHOD as $payment_method => $gateway ) { + if ( $gateway !== $gateway_id ) { + continue; + } + + // Stripe Link is part of the card gateway, so we need to check separately if Link is enabled. + if ( Payment_Method::LINK === $payment_method && ! $this->is_payment_method_enabled( Payment_Method::LINK ) ) { + continue; + } + + $types[] = $payment_method; + } + + return $types; + } + + /** + * Checks if a payment method is enabled. + * + * @param string $payment_method The payment method to check. + * @return bool True if the payment method is enabled, false otherwise. + */ + private function is_payment_method_enabled( $payment_method ) { + return in_array( $payment_method, WC_Payments::get_gateway()->get_upe_enabled_payment_method_ids(), true ); + } + /** * Delete token from Stripe. * diff --git a/includes/reports/class-wc-rest-payments-reports-authorizations-controller.php b/includes/reports/class-wc-rest-payments-reports-authorizations-controller.php index 20cba8bc6cf..0834d078eeb 100644 --- a/includes/reports/class-wc-rest-payments-reports-authorizations-controller.php +++ b/includes/reports/class-wc-rest-payments-reports-authorizations-controller.php @@ -40,7 +40,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<id>[A-Za-z0-9_\-]+)', + '/' . $this->rest_base . '/(?P<id>\w+)', [ [ 'methods' => WP_REST_Server::READABLE, diff --git a/includes/reports/class-wc-rest-payments-reports-transactions-controller.php b/includes/reports/class-wc-rest-payments-reports-transactions-controller.php index 6e130400af2..1e38d6cd746 100644 --- a/includes/reports/class-wc-rest-payments-reports-transactions-controller.php +++ b/includes/reports/class-wc-rest-payments-reports-transactions-controller.php @@ -39,7 +39,7 @@ public function register_routes() { ); register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P<id>[A-Za-z0-9_\-]+)', + '/' . $this->rest_base . '/(?P<id>\w+)', [ [ 'methods' => WP_REST_Server::READABLE, diff --git a/includes/wc-payment-api/class-wc-payments-api-client.php b/includes/wc-payment-api/class-wc-payments-api-client.php index 4127e620606..626137a8458 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -518,17 +518,8 @@ public function get_disputes( array $filters = [] ) { * * @param string $dispute_id id of requested dispute. * @return array dispute object. - * @throws API_Exception - Exception thrown in case route validation fails. */ public function get_dispute( $dispute_id ) { - if ( ! preg_match( '/(dp|dispute)_[A-Za-z0-9]+/', $dispute_id ) ) { - throw new API_Exception( - __( 'Route param validation failed.', 'woocommerce-payments' ), - 'wcpay_route_validation_failure', - 400 - ); - } - $dispute = $this->request( [], self::DISPUTES_API . '/' . $dispute_id, self::GET ); if ( is_wp_error( $dispute ) ) { @@ -735,17 +726,8 @@ public function create_token( $request ) { * @return array * * @throws Exception - Exception thrown on request failure. - * @throws API_Exception - Exception thrown in case route validation fails. */ public function get_timeline( $id ) { - if ( ! preg_match( '/(ch|pi|py)_[A-Za-z0-9]+/', $id ) ) { - throw new API_Exception( - __( 'Route param validation failed.', 'woocommerce-payments' ), - 'wcpay_route_validation_failure', - 400 - ); - } - $timeline = $this->request( [], self::TIMELINE_API . '/' . $id, self::GET ); $has_fraud_outcome_event = false; @@ -1187,14 +1169,6 @@ public function update_charge( string $charge_id, array $data = [] ) { * @throws API_Exception */ public function get_charge( string $charge_id ) { - if ( ! preg_match( '/(ch|pi|py)_[A-Za-z0-9]+/', $charge_id ) ) { - throw new API_Exception( - __( 'Route param validation failed.', 'woocommerce-payments' ), - 'wcpay_route_validation_failure', - 400 - ); - } - return $this->request( [], self::CHARGES_API . '/' . $charge_id, diff --git a/package-lock.json b/package-lock.json index 60e9f7f8a8a..c02183691cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "woocommerce-payments", - "version": "7.8.0", + "version": "7.8.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "woocommerce-payments", - "version": "7.8.0", + "version": "7.8.1", "hasInstallScript": true, "license": "GPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index 5c001ab691d..ae3c8b791f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-payments", - "version": "7.8.0", + "version": "7.8.1", "main": "webpack.config.js", "author": "Automattic", "license": "GPL-3.0-or-later", diff --git a/readme.txt b/readme.txt index f0756550b7f..45f5ac455d7 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: woocommerce payments, apple pay, credit card, google pay, payment, payment Requires at least: 6.0 Tested up to: 6.5 Requires PHP: 7.3 -Stable tag: 7.8.0 +Stable tag: 7.8.1 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -94,6 +94,10 @@ Please note that our support for the checkout block is still experimental and th == Changelog == += 7.8.1 - 2024-06-25 = +* Fix - Fix "Dispute not loaded" error that was affecting responding to disputes. + + = 7.8.0 - 2024-06-19 = * Add - Add a feedback survey modal upon deactivation. * Add - Add new select component to be used for reporting filters, e.g. Payments overview currency select diff --git a/tests/WCPAY_UnitTestCase.php b/tests/WCPAY_UnitTestCase.php index 21094b0c23c..4470710b519 100644 --- a/tests/WCPAY_UnitTestCase.php +++ b/tests/WCPAY_UnitTestCase.php @@ -15,6 +15,68 @@ * Class WP_UnitTestCase */ class WCPAY_UnitTestCase extends WP_UnitTestCase { + public function set_up() { + parent::set_up(); + + // Use a priority of 9 to ensure that these filters will allow tests that want to mock external requests + // to hook in with the regular 10 priority and do their thing. + // But by default we will intercept all external requests. + add_filter( 'pre_http_request', [ $this, 'filter_intercept_external_requests' ], 9, 3 ); + add_filter( 'woocommerce_get_geolocation', [ $this, 'filter_mock_wc_geolocation' ], 9, 2 ); + } + + public function tear_down() { + remove_filter( 'pre_http_request', [ $this, 'filter_intercept_external_requests' ], 9, 3 ); + remove_filter( 'woocommerce_get_geolocation', [ $this, 'filter_mock_wc_geolocation' ], 9, 2 ); + + parent::tear_down(); + } + + /** + * Intercept external requests and return a service unavailable response. + * + * This way we don't allow relying on external services for tests (fragile and slow tests) and force those tests + * that care about a response to mock the request response. + * + * @see WP_Http::request() + * + * @param false|array|WP_Error $response A preemptive return value of an HTTP request. Default false. + * @param array $parsed_args HTTP request arguments. + * @param string $url The request URL. + * + * @return array + */ + public function filter_intercept_external_requests( $response, $parsed_args, $url ) { + // Return a service unavailable response. + return [ + 'body' => '', + 'response' => [ + 'code' => WP_Http::SERVICE_UNAVAILABLE, + ], + 'headers' => [], + 'cookies' => [], + 'http_response' => null, + ]; + } + + /** + * Intercept geolocation requests and return mock data. + * + * @param array $geolocation + * @param string $ip_address + * + * @return array + */ + public function filter_mock_wc_geolocation( $geolocation, $ip_address ) { + $ip_geolocation_test_data = json_decode( file_get_contents( __DIR__ . '/unit/test-data/ip-geolocation.json' ), true ); + + if ( ! empty( $ip_geolocation_test_data[ $ip_address ] ) ) { + $geolocation = array_merge( $geolocation, $ip_geolocation_test_data[ $ip_address ] ); + } + + return $geolocation; + } + protected function is_wpcom() { return defined( 'IS_WPCOM' ) && IS_WPCOM; } diff --git a/tests/unit/test-class-wc-payments-token-service.php b/tests/unit/test-class-wc-payments-token-service.php index f9465769f89..4835cf32b09 100644 --- a/tests/unit/test-class-wc-payments-token-service.php +++ b/tests/unit/test-class-wc-payments-token-service.php @@ -489,17 +489,23 @@ public function test_woocommerce_get_customer_payment_tokens_multiple_tokens_mul $gateway->settings['upe_enabled_payment_method_ids'] = $payment_methods; // Array keys should match the database ID of the token. - $tokens = [ + $card_tokens = [ 1 => $this->generate_card_token( 'pm_111', 1 ), 2 => $this->generate_card_token( 'pm_222', 2 ), + ]; + $sepa_tokens = [ 3 => $this->generate_sepa_token( 'pm_333', 3 ), 4 => $this->generate_sepa_token( 'pm_444', 4 ), + ]; + $stripe_link_tokens = [ 5 => $this->generate_link_token( 'pm_555', 5 ), 6 => $this->generate_link_token( 'pm_666', 6 ), ]; + $all_saved_tokens = $card_tokens + $sepa_tokens + $stripe_link_tokens; + $this->mock_customer_service - ->expects( $this->once() ) + ->expects( $this->exactly( 2 ) ) ->method( 'get_customer_id_by_user_id' ) ->willReturn( $customer_id ); @@ -509,7 +515,6 @@ public function test_woocommerce_get_customer_payment_tokens_multiple_tokens_mul ->method( 'get_payment_methods_for_customer' ) ->withConsecutive( [ $customer_id, Payment_Method::CARD ], - [ $customer_id, Payment_Method::SEPA ], [ $customer_id, Payment_Method::LINK ] ) ->willReturnOnConsecutiveCalls( @@ -517,20 +522,27 @@ public function test_woocommerce_get_customer_payment_tokens_multiple_tokens_mul $this->generate_card_pm_response( 'pm_111' ), $this->generate_card_pm_response( 'pm_222' ), ], - [ - $this->generate_sepa_pm_response( 'pm_333' ), - $this->generate_sepa_pm_response( 'pm_444' ), - ], [ $this->generate_link_pm_response( 'pm_555' ), $this->generate_link_pm_response( 'pm_666' ), + ], + [ + $this->generate_sepa_pm_response( 'pm_333' ), + $this->generate_sepa_pm_response( 'pm_444' ), ] ); - $result = $this->token_service->woocommerce_get_customer_payment_tokens( $tokens, 1, 'woocommerce_payments' ); + $card_and_link_result = $this->token_service->woocommerce_get_customer_payment_tokens( $all_saved_tokens, 1, WC_Payment_Gateway_WCPay::GATEWAY_ID ); + $sepa_result = $this->token_service->woocommerce_get_customer_payment_tokens( $all_saved_tokens, 1, WC_Payment_Gateway_WCPay::GATEWAY_ID . '_' . Payment_Method::SEPA ); + $this->assertSame( - array_keys( $tokens ), - array_keys( $result ) + array_keys( $card_tokens + $stripe_link_tokens ), + array_keys( $card_and_link_result ) + ); + + $this->assertSame( + array_keys( $sepa_tokens ), + array_keys( $sepa_result ) ); } @@ -656,51 +668,6 @@ public function test_woocommerce_get_customer_payment_tokens_not_added_twice_for $this->assertEquals( 'pm_444', $result_tokens[3]->get_token() ); } - public function test_woocommerce_get_customer_payment_tokens_not_added_from_different_gateway() { - $this->mock_cache->method( 'get' )->willReturn( [ 'is_deferred_intent_creation_upe_enabled' => true ] ); - $gateway_id = WC_Payment_Gateway_WCPay::GATEWAY_ID; - $tokens = []; - $payment_methods = [ Payment_Method::CARD, Payment_Method::SEPA ]; - - $gateway = WC_Payments::get_gateway(); - $gateway->settings['upe_enabled_payment_method_ids'] = $payment_methods; - - $this->mock_customer_service - ->expects( $this->any() ) - ->method( 'get_customer_id_by_user_id' ) - ->willReturn( 'cus_12345' ); - - $this->mock_customer_service - ->expects( $this->exactly( 2 ) ) - ->method( 'get_payment_methods_for_customer' ) - ->withConsecutive( - [ 'cus_12345', Payment_Method::CARD ], - [ 'cus_12345', Payment_Method::SEPA ] - ) - ->willReturnOnConsecutiveCalls( - [ - $this->generate_card_pm_response( 'pm_mock0' ), - $this->generate_card_pm_response( 'pm_222' ), - ], - [ - $this->generate_sepa_pm_response( 'other_gateway_pm_111' ), - $this->generate_sepa_pm_response( 'other_gateway_pm_222' ), - $this->generate_sepa_pm_response( 'other_gateway_pm_333' ), - $this->generate_sepa_pm_response( 'other_gateway_pm_444' ), - $this->generate_sepa_pm_response( 'other_gateway_pm_555' ), - ] - ); - - $result = $this->token_service->woocommerce_get_customer_payment_tokens( $tokens, 1, $gateway_id ); - $result_tokens = array_values( $result ); - - $this->assertEquals( 2, count( $result_tokens ) ); - $this->assertEquals( $gateway_id, $result_tokens[0]->get_gateway_id() ); - $this->assertEquals( $gateway_id, $result_tokens[1]->get_gateway_id() ); - $this->assertEquals( 'pm_mock0', $result_tokens[0]->get_token() ); - $this->assertEquals( 'pm_222', $result_tokens[1]->get_token() ); - } - public function test_woocommerce_get_customer_payment_tokens_payment_methods_only_for_retrievable_types() { $enabled_upe_payment_methods = [ Payment_Method::CARD, @@ -716,7 +683,6 @@ public function test_woocommerce_get_customer_payment_tokens_payment_methods_onl $gateway = WC_Payments::get_gateway(); $gateway->settings['upe_enabled_payment_method_ids'] = $enabled_upe_payment_methods; $tokens = []; - $gateway_id = 'woocommerce_payments'; $customer_id = 'cus_12345'; $this->mock_customer_service @@ -729,22 +695,23 @@ public function test_woocommerce_get_customer_payment_tokens_payment_methods_onl ->method( 'get_payment_methods_for_customer' ) ->withConsecutive( [ $customer_id, Payment_Method::CARD ], + [ $customer_id, Payment_Method::LINK ], [ $customer_id, Payment_Method::SEPA ], - [ $customer_id, Payment_Method::LINK ] ) ->willReturnOnConsecutiveCalls( [ $this->generate_card_pm_response( 'pm_mock0' ), ], [ - $this->generate_sepa_pm_response( 'pm_mock_2' ), + $this->generate_link_pm_response( 'pm_mock_3' ), ], [ - $this->generate_link_pm_response( 'pm_mock_3' ), - ] + $this->generate_sepa_pm_response( 'pm_mock_2' ), + ], ); - $this->token_service->woocommerce_get_customer_payment_tokens( $tokens, 1, $gateway_id ); + $this->token_service->woocommerce_get_customer_payment_tokens( $tokens, 1, WC_Payment_Gateway_WCPay::GATEWAY_ID ); + $this->token_service->woocommerce_get_customer_payment_tokens( $tokens, 1, WC_Payment_Gateway_WCPay::GATEWAY_ID . '_' . Payment_Method::SEPA ); } /** diff --git a/tests/unit/test-data/ip-geolocation.json b/tests/unit/test-data/ip-geolocation.json new file mode 100644 index 00000000000..69313efa0d6 --- /dev/null +++ b/tests/unit/test-data/ip-geolocation.json @@ -0,0 +1,11 @@ +{ + "206.71.50.230": { + "country": "US" + }, + "187.34.8.193": { + "country": "BR" + }, + "127.0.0.1": { + "country": "US" + } +} diff --git a/tests/unit/woopay/test-class-woopay-utilities.php b/tests/unit/woopay/test-class-woopay-utilities.php index 13a8bcb5f01..72f7d30e480 100644 --- a/tests/unit/woopay/test-class-woopay-utilities.php +++ b/tests/unit/woopay/test-class-woopay-utilities.php @@ -30,7 +30,7 @@ public function tear_down() { /** * Data provider for test_should_enable_woopay. * - * @return boolean + * @return array */ public function should_enable_woopay_data_provider() { return [ @@ -61,9 +61,11 @@ public function test_should_enable_woopay( $woopay_eligible, $gateway_woopay_ena } /** - * Data provider for test_should_enable_woopay. + * Data provider for test_is_country_available. + * + * @see test-data/ip_geolocation.json * - * @return boolean + * @return array */ public function is_country_available_data_provider() { return [ @@ -84,7 +86,7 @@ public function test_is_country_available( $ip_address, $expected ) { WC_Payments::mode()->live(); $woopay_utilities = new WooPay_Utilities(); - $actual = $woopay_utilities->is_country_available( $this->gateway_mock ); + $actual = $woopay_utilities->is_country_available(); $this->assertSame( $expected, $actual ); } @@ -92,7 +94,7 @@ public function test_is_country_available_in_test_mode_return_true() { WC_Payments::mode()->test(); $woopay_utilities = new WooPay_Utilities(); - $actual = $woopay_utilities->is_country_available( $this->gateway_mock ); + $actual = $woopay_utilities->is_country_available(); $this->assertSame( true, $actual ); } diff --git a/woocommerce-payments.php b/woocommerce-payments.php index f49664ad999..ac10ffab61c 100644 --- a/woocommerce-payments.php +++ b/woocommerce-payments.php @@ -11,7 +11,7 @@ * WC tested up to: 8.9.3 * Requires at least: 6.0 * Requires PHP: 7.3 - * Version: 7.8.0 + * Version: 7.8.1 * Requires Plugins: woocommerce * * @package WooCommerce\Payments